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
@@ -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,19 @@ 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,
312
+ auto_local=False):
303
313
  # Auto Link outputs
304
314
  for fmu in self.execution_order:
305
315
  for port_name in fmu.ports:
306
316
  cport = ContainerPort(fmu, port_name)
307
317
  if cport not in self.rules:
308
- if cport.port.causality == 'output':
318
+ if cport.port.causality == 'parameter' and auto_parameter:
319
+ parameter_name = cport.fmu.model_identifier+"."+cport.port.name
320
+ logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
321
+ self.inputs[parameter_name] = cport
322
+ self.mark_ruled(cport, 'PARAMETER')
323
+ elif cport.port.causality == 'output':
309
324
  candidate_cport = self.find_input(cport.port)
310
325
  if auto_link and candidate_cport:
311
326
  local = Local(cport)
@@ -314,11 +329,21 @@ class FMUContainer:
314
329
  self.mark_ruled(cport, 'LINK')
315
330
  self.mark_ruled(candidate_cport, 'LINK')
316
331
  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}")
332
+ elif auto_output:
333
+ self.mark_ruled(cport, 'OUTPUT')
334
+ self.outputs[port_name] = cport
335
+ logger.info(f"AUTO OUTPUT: Expose {cport}")
336
+ elif cport.port.causality == 'local':
337
+ local_portname = None
338
+ if port_name.startswith("container."):
339
+ local_portname = "container." + cport.fmu.id + "." + port_name[10:]
340
+ logger.info(f"PROFILING: Expose {cport}")
341
+ elif auto_local:
342
+ local_portname = cport.fmu.id + "." + port_name
343
+ logger.info(f"AUTO LOCAL: Expose {cport}")
344
+ if local_portname:
345
+ self.mark_ruled(cport, 'OUTPUT')
346
+ self.outputs[local_portname] = cport
322
347
 
323
348
  if auto_input:
324
349
  # Auto link inputs
@@ -346,31 +371,26 @@ class FMUContainer:
346
371
 
347
372
  return step_size
348
373
 
349
- def sanity_check(self, step_size: Union[float, None]):
350
- nb_error = 0
374
+ def sanity_check(self, step_size: Optional[float]):
351
375
  for fmu in self.execution_order:
352
376
  if not fmu.step_size:
353
377
  continue
354
378
  ts_ratio = step_size / fmu.step_size
355
379
  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")
380
+ logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
381
+ f"step_size={fmu.step_size}s.")
358
382
  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")
383
+ logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
384
+ f"step_size={fmu.step_size}s.")
361
385
  for port_name in fmu.ports:
362
386
  cport = ContainerPort(fmu, port_name)
363
387
  if cport not in self.rules:
364
388
  if cport.port.causality == 'input':
365
389
  logger.error(f"{cport} is not connected")
366
- nb_error += 1
367
390
  if cport.port.causality == 'output':
368
391
  logger.warning(f"{cport} is not connected")
369
392
 
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,
393
+ def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
374
394
  profiling=False):
375
395
  if isinstance(fmu_filename, str):
376
396
  fmu_filename = Path(fmu_filename)
@@ -411,6 +431,18 @@ class FMUContainer:
411
431
  if fmu.capabilities[capability] == "true":
412
432
  capabilities[capability] = "true"
413
433
 
434
+ if self.start_time is None:
435
+ self.start_time = self.execution_order[0].start_time
436
+ logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
437
+ else:
438
+ logger.info(f"start_time={self.start_time}")
439
+
440
+ if self.stop_time is None:
441
+ self.stop_time = self.execution_order[0].stop_time
442
+ logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
443
+ else:
444
+ logger.info(f"stop_time={self.stop_time}")
445
+
414
446
  xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
415
447
  <fmiModelDescription
416
448
  fmiVersion="2.0"
@@ -439,14 +471,14 @@ class FMUContainer:
439
471
  <Category name="fmucontainer"/>
440
472
  </LogCategories>
441
473
 
442
- <DefaultExperiment stepSize="{step_size}"/>
474
+ <DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
443
475
 
444
476
  <ModelVariables>
445
477
  """)
446
478
  if profiling:
447
479
  for fmu in self.execution_order:
448
480
  vr = vr_table.add_vr("Real")
449
- name = f"container.{fmu.model_identifier}.rt_ratio"
481
+ name = f"container.{fmu.id}.rt_ratio"
450
482
  print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
451
483
  f'<Real /></ScalarVariable>', file=xml_file)
452
484
 
@@ -596,6 +628,14 @@ class FMUContainer:
596
628
  for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
597
629
  print(f"{vr} {cport.port.vr}", file=txt_file)
598
630
 
631
+ @staticmethod
632
+ def long_path(path: Union[str, Path]) -> str:
633
+ # https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
634
+ if os.name == 'nt':
635
+ return "\\\\?\\" + os.path.abspath(str(path))
636
+ else:
637
+ return path
638
+
599
639
  def make_fmu_skeleton(self, base_directory: Path) -> Path:
600
640
  logger.debug(f"Initialize directory '{base_directory}'")
601
641
 
@@ -622,8 +662,8 @@ class FMUContainer:
622
662
  shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
623
663
 
624
664
  for fmu in self.involved_fmu.values():
625
- shutil.copytree(fmu.fmu.tmp_directory, resources_directory / fmu.name, dirs_exist_ok=True)
626
-
665
+ shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
666
+ self.long_path(resources_directory / fmu.name), dirs_exist_ok=True)
627
667
  return resources_directory
628
668
 
629
669
  def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
@@ -631,11 +671,10 @@ class FMUContainer:
631
671
  with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
632
672
  for root, dirs, files in os.walk(base_directory):
633
673
  for file in files:
634
- zip_file.write(os.path.join(root, file),
674
+ zip_file.write(self.long_path(os.path.join(root, file)),
635
675
  os.path.relpath(os.path.join(root, file), base_directory))
636
676
  logger.info(f"'{fmu_filename}' is available.")
637
677
 
638
- @staticmethod
639
- def make_fmu_cleanup(base_directory: Path):
678
+ def make_fmu_cleanup(self, base_directory: Path):
640
679
  logger.debug(f"Delete directory '{base_directory}'")
641
- shutil.rmtree(base_directory)
680
+ shutil.rmtree(self.long_path(base_directory))
@@ -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: