fmu-manipulation-toolbox 1.8__py3-none-any.whl → 1.8.2__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 (25) hide show
  1. fmu_manipulation_toolbox/__version__.py +1 -1
  2. fmu_manipulation_toolbox/assembly.py +241 -127
  3. fmu_manipulation_toolbox/cli.py +16 -7
  4. fmu_manipulation_toolbox/fmu_container.py +69 -30
  5. fmu_manipulation_toolbox/fmu_operations.py +2 -3
  6. fmu_manipulation_toolbox/gui.py +356 -103
  7. fmu_manipulation_toolbox/gui_style.py +129 -0
  8. fmu_manipulation_toolbox/resources/container.png +0 -0
  9. fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
  10. fmu_manipulation_toolbox/resources/fmu.png +0 -0
  11. fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
  12. fmu_manipulation_toolbox/resources/icon_fmu.png +0 -0
  13. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  14. fmu_manipulation_toolbox/resources/mask.png +0 -0
  15. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  16. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  17. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  18. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  19. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  20. {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/METADATA +8 -3
  21. {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/RECORD +25 -21
  22. {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/WHEEL +1 -1
  23. {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/LICENSE.txt +0 -0
  24. {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/entry_points.txt +0 -0
  25. {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- 'V1.8'
1
+ 'V1.8.2'
@@ -7,7 +7,7 @@ import uuid
7
7
  import xml.parsers.expat
8
8
  import zipfile
9
9
 
10
- from .fmu_container import FMUContainer, FMUContainerError
10
+ from .fmu_container import FMUContainer
11
11
 
12
12
  logger = logging.getLogger("fmu_manipulation_toolbox")
13
13
 
@@ -23,28 +23,44 @@ class Port:
23
23
  def __eq__(self, other):
24
24
  return str(self) == str(other)
25
25
 
26
+ def __str__(self):
27
+ return f"{self.fmu_name}/{self.port_name}"
28
+
26
29
 
27
30
  class Connection:
28
31
  def __init__(self, from_port: Port, to_port: Port):
29
32
  self.from_port = from_port
30
33
  self.to_port = to_port
31
34
 
35
+ def __str__(self):
36
+ return f"{self.from_port} -> {self.to_port}"
37
+
32
38
 
33
39
  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):
40
+ def __init__(self, name: Optional[str], step_size: float = None, mt=False, profiling=False,
41
+ auto_link=True, auto_input=True, auto_output=True, auto_parameter=False, auto_local=False):
36
42
  self.name = name
37
- self.step_size = step_size
43
+ if step_size:
44
+ try:
45
+ self.step_size = float(step_size)
46
+ except ValueError:
47
+ logger.warning(f"Step size '{step_size}' is incorrect format.")
48
+ self.step_size = None
49
+ else:
50
+ self.step_size = None
38
51
  self.mt = mt
39
52
  self.profiling = profiling
40
53
  self.auto_link = auto_link
41
54
  self.auto_input = auto_input
42
55
  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] = {}
56
+ self.auto_parameter = auto_parameter
57
+ self.auto_local = auto_local
58
+
59
+ self.parent: Optional[AssemblyNode] = None
60
+ self.children: Dict[str, AssemblyNode] = {} # sub-containers
61
+ self.fmu_names_list: List[str] = [] # FMUs contained at this level (ordered list)
62
+ self.input_ports: Dict[Port, str] = {} # value is input port name, key is the source
63
+ self.output_ports: Dict[Port, str] = {} # value is output port name, key is the origin
48
64
  self.start_values: Dict[Port, str] = {}
49
65
  self.drop_ports: List[Port] = []
50
66
  self.links: List[Connection] = []
@@ -53,11 +69,20 @@ class AssemblyNode:
53
69
  if sub_node.name is None:
54
70
  sub_node.name = str(uuid.uuid4())+".fmu"
55
71
 
56
- self.fmu_names_list.add(sub_node.name)
57
- self.children.append(sub_node)
72
+ if sub_node.parent is not None:
73
+ raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already parented.")
74
+
75
+ if sub_node.name in self.children:
76
+ raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already child of {self.name}")
77
+
78
+ sub_node.parent = self
79
+ if sub_node.name not in self.fmu_names_list:
80
+ self.fmu_names_list.append(sub_node.name)
81
+ self.children[sub_node.name] = sub_node
58
82
 
59
83
  def add_fmu(self, fmu_name: str):
60
- self.fmu_names_list.add(fmu_name)
84
+ if fmu_name not in self.fmu_names_list:
85
+ self.fmu_names_list.append(fmu_name)
61
86
 
62
87
  def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
63
88
  self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
@@ -76,12 +101,13 @@ class AssemblyNode:
76
101
  self.start_values[Port(fmu_filename, port_name)] = value
77
102
 
78
103
  def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
79
- for node in self.children:
104
+ for node in self.children.values():
80
105
  node.make_fmu(fmu_directory, debug=debug)
81
106
 
82
- container = FMUContainer(self.name, fmu_directory, description_pathname=description_pathname)
107
+ identifier = str(Path(self.name).stem)
108
+ container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname)
83
109
 
84
- for fmu_name in sorted(self.fmu_names_list):
110
+ for fmu_name in self.fmu_names_list:
85
111
  container.get_fmu(fmu_name)
86
112
 
87
113
  for port, source in self.input_ports.items():
@@ -102,14 +128,78 @@ class AssemblyNode:
102
128
 
103
129
  container.add_implicit_rule(auto_input=self.auto_input,
104
130
  auto_output=self.auto_output,
105
- auto_link=self.auto_link)
131
+ auto_link=self.auto_link,
132
+ auto_parameter=self.auto_parameter,
133
+ auto_local=self.auto_local)
106
134
 
107
135
  container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
108
136
 
109
- for node in self.children:
137
+ for node in self.children.values():
110
138
  logger.info(f"Deleting transient FMU Container '{node.name}'")
111
139
  (fmu_directory / node.name).unlink()
112
140
 
141
+ def get_final_from(self, port: Port) -> Port:
142
+ if port in self.input_ports:
143
+ ancestor = Port(self.name, self.input_ports[port])
144
+ if self.parent:
145
+ return self.parent.get_final_from(ancestor) # input port
146
+ else:
147
+ return ancestor # TOPLEVEL input port
148
+ elif port.fmu_name in self.fmu_names_list:
149
+ if port.fmu_name in self.children:
150
+ child = self.children[port.fmu_name]
151
+ ancestors = [key for key, val in child.output_ports.items() if val == port.port_name]
152
+ if len(ancestors) == 1:
153
+ return child.get_final_from(ancestors[0]) # child output port
154
+ else:
155
+ return port # embedded FMU
156
+
157
+ raise AssemblyError(f"{self.name}: Port {port} is not connected upstream.")
158
+
159
+ def get_final_to(self, port: Port) -> Port:
160
+ if port in self.output_ports:
161
+ successor = Port(self.name, self.output_ports[port])
162
+ if self.parent:
163
+ return self.parent.get_final_to(successor) # Output port
164
+ else:
165
+ return successor # TOLEVEL output port
166
+ elif port.fmu_name in self.fmu_names_list:
167
+ if port.fmu_name in self.children:
168
+ child = self.children[port.fmu_name]
169
+ successors = [key for key, val in child.input_ports.items() if val == port.port_name]
170
+ if len(successors) == 1:
171
+ return child.get_final_to(successors[0]) # Child input port
172
+ else:
173
+ return port # embedded FMU
174
+
175
+ raise AssemblyError(f"Node {self.name}: Port {port} is not connected downstream.")
176
+
177
+ def get_fmu_connections(self, fmu_name: str) -> List[Connection]:
178
+ connections = []
179
+ if fmu_name not in self.fmu_names_list:
180
+ raise AssemblyError(f"Internal Error: FMU {fmu_name} is not embedded by {self.name}.")
181
+ for link in self.links:
182
+ if link.from_port.fmu_name == fmu_name:
183
+ connections.append(Connection(link.from_port, self.get_final_to(link.to_port)))
184
+ elif link.to_port.fmu_name == fmu_name:
185
+ connections.append(Connection(self.get_final_from(link.from_port), link.to_port))
186
+
187
+ for to_port, input_port_name in self.input_ports.items():
188
+ if to_port.fmu_name == fmu_name:
189
+ if self.parent:
190
+ connections.append(Connection(self.parent.get_final_from(Port(self.name, input_port_name)), to_port))
191
+ else:
192
+ connections.append(Connection(Port(self.name, input_port_name), to_port))
193
+
194
+ for from_port, output_port_name in self.output_ports.items():
195
+ if from_port.fmu_name == fmu_name:
196
+ if self.parent:
197
+ connections.append(Connection(from_port, self.parent.get_final_to(Port(self.name, output_port_name))))
198
+ else:
199
+ connections.append(Connection(from_port, Port(self.name, output_port_name))) ###HERE
200
+
201
+ return connections
202
+
113
203
 
114
204
  class AssemblyError(Exception):
115
205
  def __init__(self, reason: str):
@@ -121,13 +211,16 @@ class AssemblyError(Exception):
121
211
 
122
212
  class Assembly:
123
213
  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 = "."):
214
+ auto_output=True, mt=False, profiling=False, fmu_directory: Path = Path("."), auto_parameter=False,
215
+ auto_local=False):
125
216
  self.filename = Path(filename)
126
217
  self.default_auto_input = auto_input
127
218
  self.debug = debug
128
219
  self.default_auto_output = auto_output
129
220
  self.default_step_size = step_size
130
221
  self.default_auto_link = auto_link
222
+ self.default_auto_parameter = auto_parameter
223
+ self.default_auto_local = auto_local
131
224
  self.default_mt = mt
132
225
  self.default_profiling = profiling
133
226
  self.fmu_directory = fmu_directory
@@ -135,9 +228,10 @@ class Assembly:
135
228
  self.transient_dirnames: Set[Path] = set()
136
229
 
137
230
  if not fmu_directory.is_dir():
138
- raise FMUContainerError(f"FMU directory is not valid: '{fmu_directory}'")
231
+ raise AssemblyError(f"FMU directory is not valid: '{fmu_directory}'")
139
232
 
140
- self.description_pathname = fmu_directory / self.filename # For inclusion in FMU
233
+ self.input_pathname = fmu_directory / self.filename
234
+ self.description_pathname = self.input_pathname # For inclusion in FMU
141
235
  self.root = None
142
236
  self.read()
143
237
 
@@ -169,7 +263,7 @@ class Assembly:
169
263
  elif self.filename.suffix == ".csv":
170
264
  self.read_csv()
171
265
  else:
172
- raise FMUContainerError(f"Not supported file format '{self.filename}")
266
+ raise AssemblyError(f"Not supported file format '{self.filename}")
173
267
 
174
268
  def write(self, filename: str):
175
269
  if filename.endswith(".csv"):
@@ -183,11 +277,12 @@ class Assembly:
183
277
  name = str(self.filename.with_suffix(".fmu"))
184
278
  self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
185
279
  mt=self.default_mt, profiling=self.default_profiling,
186
- auto_input=self.default_auto_input, auto_output=self.default_auto_output)
280
+ auto_input=self.default_auto_input, auto_output=self.default_auto_output,
281
+ auto_parameter=self.default_auto_parameter, auto_local=self.default_auto_local)
187
282
 
188
- with open(self.description_pathname) as file:
283
+ with open(self.input_pathname) as file:
189
284
  reader = csv.reader(file, delimiter=';')
190
- self.check_csv_headers(reader)
285
+ self._check_csv_headers(reader)
191
286
  for i, row in enumerate(reader):
192
287
  if not row or row[0][0] == '#': # skip blank line of comment
193
288
  continue
@@ -198,36 +293,19 @@ class Assembly:
198
293
  logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
199
294
  continue
200
295
 
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.")
296
+ try:
297
+ self._read_csv_rule(self.root, rule.upper(),
298
+ from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
299
+ except AssemblyError as e:
300
+ logger.error(f"Line #{i+2}: {e}. Line skipped.")
301
+ continue
215
302
 
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")
303
+ @staticmethod
304
+ def _check_csv_headers(reader):
305
+ headers = next(reader)
306
+ headers_lowered = [h.lower() for h in headers]
307
+ if not headers_lowered == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
308
+ raise AssemblyError("Header (1st line of the file) is not well formatted.")
231
309
 
232
310
  @staticmethod
233
311
  def _read_csv_rule(node: AssemblyNode, rule: str, from_fmu_filename: str, from_port_name: str,
@@ -264,114 +342,150 @@ class Assembly:
264
342
  raise AssemblyError("Missing START ports information.")
265
343
 
266
344
  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()
345
+ else:
346
+ raise AssemblyError(f"unexpected rule '{rule}'. Line skipped.")
268
347
 
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.")
348
+ def write_csv(self, filename: Union[str, Path]):
349
+ if self.root.children:
350
+ raise AssemblyError("This assembly is not flat. Cannot export to CSV file.")
351
+
352
+ with open(self.fmu_directory / filename, "wt") as outfile:
353
+ outfile.write("rule;from_fmu;from_port;to_fmu;to_port\n")
354
+ for fmu in self.root.fmu_names_list:
355
+ outfile.write(f"FMU;{fmu};;;\n")
356
+ for port, source in self.root.input_ports.items():
357
+ outfile.write(f"INPUT;;{source};{port.fmu_name};{port.port_name}\n")
358
+ for port, target in self.root.output_ports.items():
359
+ outfile.write(f"OUTPUT;{port.fmu_name};{port.port_name};;{target}\n")
360
+ for link in self.root.links:
361
+ outfile.write(f"LINK;{link.from_port.fmu_name};{link.from_port.port_name};"
362
+ f"{link.to_port.fmu_name};{link.to_port.port_name}\n")
363
+ for port, value in self.root.start_values.items():
364
+ outfile.write(f"START;{port.fmu_name};{port.port_name};{value};\n")
365
+ for port in self.root.drop_ports:
366
+ outfile.write(f"DROP;{port.fmu_name};{port.port_name};;\n")
274
367
 
275
368
  def read_json(self):
276
- with open(self.description_pathname) as file:
369
+ with open(self.input_pathname) as file:
277
370
  try:
278
371
  data = json.load(file)
279
372
  except json.decoder.JSONDecodeError as e:
280
- raise FMUContainerError(f"Cannot read json: {e}")
281
- self.root = self.json_decode_node(data)
373
+ raise AssemblyError(f"Cannot read json: {e}")
374
+ self.root = self._json_decode_node(data)
282
375
  if not self.root.name:
283
- self.root.name = str(self.filename.with_suffix(".fmu"))
376
+ self.root.name = str(self.filename.with_suffix(".fmu").name)
377
+
378
+ def _json_decode_node(self, data: Dict) -> AssemblyNode:
379
+ name = data.get("name", None) # 1
380
+ mt = data.get("mt", self.default_mt) # 2
381
+ profiling = data.get("profiling", self.default_profiling) # 3
382
+ auto_link = data.get("auto_link", self.default_auto_link) # 4
383
+ auto_input = data.get("auto_input", self.default_auto_input) # 5
384
+ auto_output = data.get("auto_output", self.default_auto_output) # 6
385
+ auto_parameter = data.get("auto_parameter", self.default_auto_parameter) # 6b
386
+ auto_local = data.get("auto_local", self.default_auto_local) # 6c
387
+ step_size = data.get("step_size", self.default_step_size) # 7
388
+
389
+ node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
390
+ auto_input=auto_input, auto_output=auto_output, auto_parameter=auto_parameter,
391
+ auto_local=auto_local)
392
+
393
+ for key, value in data.items():
394
+ if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling',
395
+ 'auto_parameter', 'auto_local'):
396
+ continue # Already read
397
+
398
+ elif key == "container": # 8
399
+ if not isinstance(value, list):
400
+ raise AssemblyError("JSON: 'container' keyword should define a list.")
401
+ for sub_data in value:
402
+ node.add_sub_node(self._json_decode_node(sub_data))
403
+
404
+ elif key == "fmu": # 9
405
+ if not isinstance(value, list):
406
+ raise AssemblyError("JSON: 'fmu' keyword should define a list.")
407
+ for fmu in value:
408
+ node.add_fmu(fmu)
409
+
410
+ elif key == "input": # 10
411
+ self._json_decode_keyword('input', value, node.add_input)
412
+
413
+ elif key == "output": # 11
414
+ self._json_decode_keyword('output', value, node.add_output)
415
+
416
+ elif key == "link": # 12
417
+ self._json_decode_keyword('link', value, node.add_link)
418
+
419
+ elif key == "start": # 13
420
+ self._json_decode_keyword('start', value, node.add_start_value)
421
+
422
+ elif key == "drop": # 14
423
+ self._json_decode_keyword('drop', value, node.add_drop_port)
424
+
425
+ else:
426
+ logger.error(f"JSON: unexpected keyword {key}. Skipped.")
427
+
428
+ return node
429
+
430
+ @staticmethod
431
+ def _json_decode_keyword(keyword: str, value, function):
432
+ if not isinstance(value, list):
433
+ raise AssemblyError(f"JSON: '{keyword}' keyword should define a list.")
434
+ for line in value:
435
+ if not isinstance(line, list):
436
+ raise AssemblyError(f"JSON: unexpected '{keyword}' value: {line}.")
437
+ try:
438
+ function(*line)
439
+ except TypeError:
440
+ raise AssemblyError(f"JSON: '{keyword}' value does not contain right number of fields: {line}.")
284
441
 
285
442
  def write_json(self, filename: Union[str, Path]):
286
443
  with open(self.fmu_directory / filename, "wt") as file:
287
- data = self.json_encode_node(self.root)
444
+ data = self._json_encode_node(self.root)
288
445
  json.dump(data, file, indent=2)
289
446
 
290
- def json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
447
+ def _json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
291
448
  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
449
+ json_node["name"] = node.name # 1
450
+ json_node["mt"] = node.mt # 2
451
+ json_node["profiling"] = node.profiling # 3
452
+ json_node["auto_link"] = node.auto_link # 4
453
+ json_node["auto_input"] = node.auto_input # 5
454
+ json_node["auto_output"] = node.auto_output # 6
455
+ json_node["auto_parameter"] = node.auto_parameter # 6b
456
+ json_node["auto_local"] = node.auto_local # 6c
457
+
296
458
  if node.step_size:
297
- json_node["step_size"] = node.step_size
459
+ json_node["step_size"] = node.step_size # 7
298
460
 
299
461
  if node.children:
300
- json_node["container"] = [self.json_encode_node(child) for child in node.children]
462
+ json_node["container"] = [self._json_encode_node(child) for child in node.children.values()] # 8
301
463
 
302
464
  if node.fmu_names_list:
303
- json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)]
465
+ json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] # 9
304
466
 
305
467
  if node.input_ports:
306
- json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"]
468
+ json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"] # 10
307
469
  for port, source in node.input_ports.items()]
308
470
 
309
471
  if node.output_ports:
310
- json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"]
472
+ json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"] # 11
311
473
  for port, target in node.output_ports.items()]
312
474
 
313
475
  if node.links:
314
- json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}",
476
+ json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}", # 12
315
477
  f"{link.to_port.fmu_name}", f"{link.to_port.port_name}"]
316
478
  for link in node.links]
317
479
 
318
480
  if node.start_values:
319
- json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value]
481
+ json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value] # 13
320
482
  for port, value in node.start_values.items()]
321
483
 
322
484
  if node.drop_ports:
323
- json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports]
485
+ json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports] # 14
324
486
 
325
487
  return json_node
326
488
 
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
489
  def read_ssp(self):
376
490
  logger.warning("This feature is ALPHA stage.")
377
491
 
@@ -388,11 +502,11 @@ class Assembly:
388
502
  mt=self.default_mt, profiling=self.default_profiling,
389
503
  auto_input=self.default_auto_input, auto_output=self.default_auto_output)
390
504
  self.root = sdd.parse(self.description_pathname)
505
+ self.root.name = str(self.filename.with_suffix(".fmu"))
391
506
 
392
507
  def make_fmu(self, dump_json=False):
393
508
  if dump_json:
394
- dump_file = (self.description_pathname.with_stem(self.description_pathname.stem + "-dump")
395
- .with_suffix(".json"))
509
+ dump_file = Path(self.input_pathname.stem + "-dump").with_suffix(".json")
396
510
  logger.info(f"Dump Json '{dump_file}'")
397
511
  self.write_json(dump_file)
398
512
  self.root.make_fmu(self.fmu_directory, debug=self.debug, description_pathname=self.description_pathname)
@@ -5,7 +5,7 @@ from colorama import Fore, Style, init
5
5
 
6
6
  from .fmu_operations import *
7
7
  from .fmu_container import FMUContainerError
8
- from .assembly import Assembly
8
+ from .assembly import Assembly, AssemblyError
9
9
  from .checker import checker_list
10
10
  from .version import __version__ as version
11
11
  from .help import Help
@@ -176,6 +176,12 @@ def fmucontainer():
176
176
  parser.add_argument("-no-auto-output", action="store_false", dest="auto_output", default=True,
177
177
  help="Create ONLY explicit output.")
178
178
 
179
+ parser.add_argument("-auto-parameter", action="store_true", dest="auto_parameter", default=False,
180
+ help="Expose parameters of the embedded fmu's.")
181
+
182
+ parser.add_argument("-auto-local", action="store_true", dest="auto_local", default=False,
183
+ help="Expose local variables of the embedded fmu's.")
184
+
179
185
  parser.add_argument("-no-auto-link", action="store_false", dest="auto_link", default=True,
180
186
  help="Create ONLY explicit links.")
181
187
 
@@ -198,20 +204,23 @@ def fmucontainer():
198
204
 
199
205
  for description in config.container_descriptions_list:
200
206
  try:
201
- filename, step_size = description.split(":")
202
- step_size = float(step_size)
207
+ tokens = description.split(":")
208
+ filename = ":".join(tokens[:-1])
209
+ step_size = float(tokens[-1])
203
210
  except ValueError:
204
211
  step_size = None
205
212
  filename = description
213
+
206
214
  try:
207
215
  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)
210
-
216
+ auto_input=config.auto_input, auto_output=config.auto_output,
217
+ auto_local=config.auto_local, mt=config.mt,
218
+ profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug,
219
+ auto_parameter=config.auto_parameter)
211
220
  except FileNotFoundError as e:
212
221
  logger.fatal(f"Cannot read file: {e}")
213
222
  continue
214
- except FMUContainerError as e:
223
+ except (FMUContainerError, AssemblyError) as e:
215
224
  logger.fatal(f"{filename}: {e}")
216
225
  continue
217
226