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
@@ -74,9 +74,12 @@ class EmbeddedFMU(OperationAbstract):
74
74
  def __init__(self, filename):
75
75
  self.fmu = FMU(filename)
76
76
  self.name = Path(filename).name
77
+ self.id = Path(filename).stem
77
78
 
78
79
  self.fmi_version = None
79
80
  self.step_size = None
81
+ self.start_time = None
82
+ self.stop_time = None
80
83
  self.model_identifier = None
81
84
  self.guid = None
82
85
  self.ports: Dict[str, FMUPort] = {}
@@ -103,15 +106,18 @@ class EmbeddedFMU(OperationAbstract):
103
106
  for capability in self.capability_list:
104
107
  self.capabilities[capability] = attrs.get(capability, "false")
105
108
 
106
- def experiment_attrs(self, attrs):
109
+ def experiment_attrs(self, attrs: Dict[str, str]):
107
110
  try:
108
111
  self.step_size = float(attrs['stepSize'])
109
112
  except KeyError:
110
113
  logger.warning(f"FMU '{self.name}' does not specify preferred step size")
111
- pass
114
+ self.start_time = float(attrs.get("startTime", 0.0))
115
+ self.stop_time = float(attrs.get("stopTime", self.start_time + 1.0))
112
116
 
113
117
  def scalar_type(self, type_name, attrs):
114
118
  if self.current_port:
119
+ if type_name == "Enumeration":
120
+ type_name = "Integer"
115
121
  self.current_port.set_port_type(type_name, attrs)
116
122
  self.current_port = None
117
123
 
@@ -148,7 +154,7 @@ class ContainerPort:
148
154
 
149
155
  class Local:
150
156
  def __init__(self, cport_from: ContainerPort):
151
- self.name = cport_from.fmu.name[:-4] + "." + cport_from.port.name # strip .fmu suffix
157
+ self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
152
158
  self.cport_from = cport_from
153
159
  self.cport_to_list: List[ContainerPort] = []
154
160
  self.vr = None
@@ -195,6 +201,9 @@ class FMUContainer:
195
201
 
196
202
  self.description_pathname = description_pathname
197
203
 
204
+ self.start_time = None
205
+ self.stop_time = None
206
+
198
207
  # Rules
199
208
  self.inputs: Dict[str, ContainerPort] = {}
200
209
  self.outputs: Dict[str, ContainerPort] = {}
@@ -291,7 +300,7 @@ class FMUContainer:
291
300
 
292
301
  self.start_values[cport] = value
293
302
 
294
- def find_input(self, port_to_connect: FMUPort) -> Union[ContainerPort, None]:
303
+ def find_input(self, port_to_connect: FMUPort) -> Optional[ContainerPort]:
295
304
  for fmu in self.execution_order:
296
305
  for port in fmu.ports.values():
297
306
  if (port.causality == 'input' and port.name == port_to_connect.name
@@ -299,13 +308,18 @@ class FMUContainer:
299
308
  return ContainerPort(fmu, port.name)
300
309
  return None
301
310
 
302
- def add_implicit_rule(self, auto_input: bool = True, auto_output: bool = True, auto_link: bool = True):
311
+ def add_implicit_rule(self, auto_input=True, auto_output=True, auto_link=True, auto_parameter=False):
303
312
  # Auto Link outputs
304
313
  for fmu in self.execution_order:
305
314
  for port_name in fmu.ports:
306
315
  cport = ContainerPort(fmu, port_name)
307
316
  if cport not in self.rules:
308
- if cport.port.causality == 'output':
317
+ if cport.port.causality == 'parameter' and auto_parameter:
318
+ parameter_name = cport.fmu.model_identifier+"."+cport.port.name
319
+ logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
320
+ self.inputs[parameter_name] = cport
321
+ self.mark_ruled(cport, 'PARAMETER')
322
+ elif cport.port.causality == 'output':
309
323
  candidate_cport = self.find_input(cport.port)
310
324
  if auto_link and candidate_cport:
311
325
  local = Local(cport)
@@ -314,11 +328,15 @@ class FMUContainer:
314
328
  self.mark_ruled(cport, 'LINK')
315
329
  self.mark_ruled(candidate_cport, 'LINK')
316
330
  self.locals[cport] = local
317
- else:
318
- if auto_output:
319
- self.mark_ruled(cport, 'OUTPUT')
320
- self.outputs[port_name] = cport
321
- logger.info(f"AUTO OUTPUT: Expose {cport}")
331
+ elif auto_output:
332
+ self.mark_ruled(cport, 'OUTPUT')
333
+ self.outputs[port_name] = cport
334
+ logger.info(f"AUTO OUTPUT: Expose {cport}")
335
+ elif cport.port.causality == 'local' and port_name.startswith("container."):
336
+ local_portname = "container." + cport.fmu.id + "." + port_name[10:]
337
+ self.mark_ruled(cport, 'OUTPUT')
338
+ self.outputs[local_portname] = cport
339
+ logger.info(f"PROFILING: Expose {cport}")
322
340
 
323
341
  if auto_input:
324
342
  # Auto link inputs
@@ -346,31 +364,26 @@ class FMUContainer:
346
364
 
347
365
  return step_size
348
366
 
349
- def sanity_check(self, step_size: Union[float, None]):
350
- nb_error = 0
367
+ def sanity_check(self, step_size: Optional[float]):
351
368
  for fmu in self.execution_order:
352
369
  if not fmu.step_size:
353
370
  continue
354
371
  ts_ratio = step_size / fmu.step_size
355
372
  if ts_ratio < 1.0:
356
- logger.error(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
357
- f"step_size={fmu.step_size}s")
373
+ logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
374
+ f"step_size={fmu.step_size}s.")
358
375
  if ts_ratio != int(ts_ratio):
359
- logger.error(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
360
- f"step_size={fmu.step_size}s")
376
+ logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
377
+ f"step_size={fmu.step_size}s.")
361
378
  for port_name in fmu.ports:
362
379
  cport = ContainerPort(fmu, port_name)
363
380
  if cport not in self.rules:
364
381
  if cport.port.causality == 'input':
365
382
  logger.error(f"{cport} is not connected")
366
- nb_error += 1
367
383
  if cport.port.causality == 'output':
368
384
  logger.warning(f"{cport} is not connected")
369
385
 
370
- if nb_error:
371
- raise FMUContainerError(f"Some ports are not connected.")
372
-
373
- def make_fmu(self, fmu_filename: Union[str, Path], step_size: Union[float, None] = None, debug=False, mt=False,
386
+ def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
374
387
  profiling=False):
375
388
  if isinstance(fmu_filename, str):
376
389
  fmu_filename = Path(fmu_filename)
@@ -411,6 +424,18 @@ class FMUContainer:
411
424
  if fmu.capabilities[capability] == "true":
412
425
  capabilities[capability] = "true"
413
426
 
427
+ if self.start_time is None:
428
+ self.start_time = self.execution_order[0].start_time
429
+ logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
430
+ else:
431
+ logger.info(f"start_time={self.start_time}")
432
+
433
+ if self.stop_time is None:
434
+ self.stop_time = self.execution_order[0].stop_time
435
+ logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
436
+ else:
437
+ logger.info(f"stop_time={self.stop_time}")
438
+
414
439
  xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
415
440
  <fmiModelDescription
416
441
  fmiVersion="2.0"
@@ -439,14 +464,14 @@ class FMUContainer:
439
464
  <Category name="fmucontainer"/>
440
465
  </LogCategories>
441
466
 
442
- <DefaultExperiment stepSize="{step_size}"/>
467
+ <DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
443
468
 
444
469
  <ModelVariables>
445
470
  """)
446
471
  if profiling:
447
472
  for fmu in self.execution_order:
448
473
  vr = vr_table.add_vr("Real")
449
- name = f"container.{fmu.model_identifier}.rt_ratio"
474
+ name = f"container.{fmu.id}.rt_ratio"
450
475
  print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
451
476
  f'<Real /></ScalarVariable>', file=xml_file)
452
477
 
@@ -596,6 +621,14 @@ class FMUContainer:
596
621
  for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
597
622
  print(f"{vr} {cport.port.vr}", file=txt_file)
598
623
 
624
+ @staticmethod
625
+ def long_path(path: Union[str, Path]) -> str:
626
+ # https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
627
+ if os.name == 'nt':
628
+ return "\\\\?\\" + os.path.abspath(str(path))
629
+ else:
630
+ return path
631
+
599
632
  def make_fmu_skeleton(self, base_directory: Path) -> Path:
600
633
  logger.debug(f"Initialize directory '{base_directory}'")
601
634
 
@@ -622,8 +655,8 @@ class FMUContainer:
622
655
  shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
623
656
 
624
657
  for fmu in self.involved_fmu.values():
625
- shutil.copytree(fmu.fmu.tmp_directory, resources_directory / fmu.name, dirs_exist_ok=True)
626
-
658
+ shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
659
+ self.long_path(resources_directory / fmu.name), dirs_exist_ok=True)
627
660
  return resources_directory
628
661
 
629
662
  def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
@@ -631,7 +664,7 @@ class FMUContainer:
631
664
  with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
632
665
  for root, dirs, files in os.walk(base_directory):
633
666
  for file in files:
634
- zip_file.write(os.path.join(root, file),
667
+ zip_file.write(self.long_path(os.path.join(root, file)),
635
668
  os.path.relpath(os.path.join(root, file), base_directory))
636
669
  logger.info(f"'{fmu_filename}' is available.")
637
670
 
@@ -89,9 +89,8 @@ class Manipulation:
89
89
  self.remove_port(attrs['name'])
90
90
  else:
91
91
  self.keep_port(attrs['name'])
92
- else:
92
+ else: # Keep ScalarVariable as it is.
93
93
  self.keep_port(attrs['name'])
94
- self.skip_until = name # do not read inner tags
95
94
  elif name == 'CoSimulation':
96
95
  self.operation.cosimulation_attrs(attrs)
97
96
  elif name == 'DefaultExperiment':
@@ -100,7 +99,7 @@ class Manipulation:
100
99
  self.operation.fmi_attrs(attrs)
101
100
  elif name == 'Unknown':
102
101
  self.unknown_attrs(attrs)
103
- elif name in ('Real', 'Integer', 'String', 'Boolean'):
102
+ elif name in ('Real', 'Integer', 'String', 'Boolean', 'Enumeration'):
104
103
  self.operation.scalar_type(name, attrs)
105
104
 
106
105
  except ManipulationSkipTag: