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