fmu-manipulation-toolbox 1.9rc1__py3-none-any.whl → 1.9rc3__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 (22) hide show
  1. fmu_manipulation_toolbox/__init__.py +0 -1
  2. fmu_manipulation_toolbox/__main__.py +1 -1
  3. fmu_manipulation_toolbox/__version__.py +1 -1
  4. fmu_manipulation_toolbox/assembly.py +1 -1
  5. fmu_manipulation_toolbox/checker.py +11 -8
  6. fmu_manipulation_toolbox/container.py +225 -140
  7. fmu_manipulation_toolbox/gui.py +45 -55
  8. fmu_manipulation_toolbox/gui_style.py +8 -0
  9. fmu_manipulation_toolbox/operations.py +68 -41
  10. fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
  11. fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
  12. fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
  13. fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
  14. fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
  15. fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
  16. fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
  17. {fmu_manipulation_toolbox-1.9rc1.dist-info → fmu_manipulation_toolbox-1.9rc3.dist-info}/METADATA +1 -1
  18. {fmu_manipulation_toolbox-1.9rc1.dist-info → fmu_manipulation_toolbox-1.9rc3.dist-info}/RECORD +22 -22
  19. {fmu_manipulation_toolbox-1.9rc1.dist-info → fmu_manipulation_toolbox-1.9rc3.dist-info}/WHEEL +0 -0
  20. {fmu_manipulation_toolbox-1.9rc1.dist-info → fmu_manipulation_toolbox-1.9rc3.dist-info}/entry_points.txt +0 -0
  21. {fmu_manipulation_toolbox-1.9rc1.dist-info → fmu_manipulation_toolbox-1.9rc3.dist-info}/licenses/LICENSE.txt +0 -0
  22. {fmu_manipulation_toolbox-1.9rc1.dist-info → fmu_manipulation_toolbox-1.9rc3.dist-info}/top_level.txt +0 -0
@@ -1 +0,0 @@
1
-
@@ -10,7 +10,7 @@ def gui():
10
10
 
11
11
 
12
12
  def cli():
13
- from .cli import fmutool
13
+ from .cli.fmutool import fmutool
14
14
  fmutool()
15
15
 
16
16
 
@@ -1 +1 @@
1
- 'V1.9-pre1'
1
+ 'V1.9-rc3'
@@ -103,7 +103,7 @@ class AssemblyNode:
103
103
 
104
104
  def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None, fmi_version=2):
105
105
  for node in self.children.values():
106
- node.make_fmu(fmu_directory, debug=debug)
106
+ node.make_fmu(fmu_directory, debug=debug, fmi_version=fmi_version)
107
107
 
108
108
  identifier = str(Path(self.name).stem)
109
109
  container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname,
@@ -1,10 +1,13 @@
1
1
  import importlib.util
2
2
  import inspect
3
+ import logging
3
4
  import os
4
5
  import xmlschema
5
6
  from xmlschema.validators.exceptions import XMLSchemaValidationError
6
7
  from .operations import OperationAbstract
7
8
 
9
+ logger = logging.getLogger("fmu_manipulation_toolbox")
10
+
8
11
 
9
12
  class OperationGenericCheck(OperationAbstract):
10
13
  SUPPORTED_FMI_VERSIONS = ('2.0', '3.0')
@@ -17,7 +20,7 @@ class OperationGenericCheck(OperationAbstract):
17
20
 
18
21
  def fmi_attrs(self, attrs):
19
22
  if attrs['fmiVersion'] not in self.SUPPORTED_FMI_VERSIONS:
20
- print(f"ERROR: Expected FMI {','.join(self.SUPPORTED_FMI_VERSIONS)} versions.")
23
+ logger.error(f"Expected FMI {','.join(self.SUPPORTED_FMI_VERSIONS)} versions.")
21
24
  return
22
25
 
23
26
  fmi_name = f"fmi{attrs['fmiVersion'][0]}"
@@ -27,15 +30,15 @@ class OperationGenericCheck(OperationAbstract):
27
30
  try:
28
31
  xmlschema.validate(self.fmu.descriptor_filename, schema=xsd_filename)
29
32
  except XMLSchemaValidationError as error:
30
- print(error.reason, error.msg)
33
+ logger.error(error.reason, error.msg)
31
34
  else:
32
35
  self.compliant_with_version = attrs['fmiVersion']
33
36
 
34
37
  def closure(self):
35
38
  if self.compliant_with_version:
36
- print(f"INFO: This FMU seems to be compliant with FMI-{self.compliant_with_version}.")
39
+ logger.info(f"This FMU seems to be compliant with FMI-{self.compliant_with_version}.")
37
40
  else:
38
- print(f"ERROR: This FMU does not validate with FMI standard.")
41
+ logger.error(f"This FMU does not validate with FMI standard.")
39
42
 
40
43
 
41
44
  checker_list = [OperationGenericCheck]
@@ -44,20 +47,20 @@ checker_list = [OperationGenericCheck]
44
47
  def add_from_file(checker_filename: str):
45
48
  spec = importlib.util.spec_from_file_location(checker_filename, checker_filename)
46
49
  if not spec:
47
- print(f"ERROR: Cannot load {checker_filename}. Is this a python file?")
50
+ logger.error(f"Cannot load '{checker_filename}'. Is this a python file?")
48
51
  return
49
52
  try:
50
53
  checker_module = importlib.util.module_from_spec(spec)
51
54
  try:
52
55
  spec.loader.exec_module(checker_module)
53
56
  except (ModuleNotFoundError, SyntaxError) as error:
54
- print(f"ERROR: Cannot load {checker_filename}: {error})")
57
+ logger.error(f"Cannot load '{checker_filename}': {error})")
55
58
  return
56
59
 
57
60
  for checker_name, checker_class in inspect.getmembers(checker_module, inspect.isclass):
58
61
  if OperationAbstract in checker_class.__bases__:
59
62
  checker_list.append(checker_class)
60
- print(f"Adding checker: {checker_filename}|{checker_name}")
63
+ logger.info(f"Adding checker: {checker_filename}|{checker_name}")
61
64
 
62
65
  except AttributeError:
63
- print(f"ERROR: {checker_filename} should implement class 'OperationCheck'")
66
+ logger.error(f"'{checker_filename}' should implement class 'OperationCheck'")
@@ -40,29 +40,6 @@ class EmbeddedFMUPort:
40
40
  }
41
41
  }
42
42
 
43
- FMI_TO_CONTAINER = {
44
- 2: {
45
- 'Real': 'real64',
46
- 'Integer': 'integer32',
47
- 'String': 'string',
48
- 'Boolean': 'boolean'
49
- },
50
- 3: {
51
- 'Float64': 'real64',
52
- 'Float32': 'real32',
53
- 'Int8': 'integer8',
54
- 'UInt8': 'uinteger8',
55
- 'Int16': 'integer16',
56
- 'UInt16': 'uinteger16',
57
- 'Int32': 'integer32',
58
- 'UInt32': 'uinteger32',
59
- 'Int64': 'integer64',
60
- 'UInt64': 'uinteger64',
61
- 'String': 'string',
62
- 'Boolean': 'boolean1'
63
- }
64
- }
65
-
66
43
  CONTAINER_TO_FMI = {
67
44
  2: {
68
45
  'real64': 'Real',
@@ -90,7 +67,7 @@ class EmbeddedFMUPort:
90
67
  "real64", "real32",
91
68
  "integer8", "uinteger8", "integer16", "uinteger16", "integer32", "uinteger32", "integer64", "uinteger64",
92
69
  "boolean", "boolean1",
93
- "strings"
70
+ "string"
94
71
  )
95
72
 
96
73
 
@@ -112,7 +89,7 @@ class EmbeddedFMUPort:
112
89
 
113
90
 
114
91
 
115
- def xml(self, vr: int, name=None, causality=None, start=None, fmi_version=2):
92
+ def xml(self, vr: int, name=None, causality=None, start=None, fmi_version=2) -> str:
116
93
  if name is None:
117
94
  name = self.name
118
95
  if causality is None:
@@ -122,7 +99,12 @@ class EmbeddedFMUPort:
122
99
  if self.variability is None:
123
100
  self.variability = "continuous" if "real" in self.type_name else "discrete"
124
101
 
125
- fmi_type = self.CONTAINER_TO_FMI[fmi_version][self.type_name]
102
+ try:
103
+ fmi_type = self.CONTAINER_TO_FMI[fmi_version][self.type_name]
104
+ except KeyError:
105
+ logger.error(f"Cannot expose '{name}' because type '{self.type_name}' is not compatible "
106
+ f"with FMI-{fmi_version}.0")
107
+ return ""
126
108
 
127
109
  if fmi_version == 2:
128
110
  child_attrs = {
@@ -146,19 +128,36 @@ class EmbeddedFMUPort:
146
128
  scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
147
129
  return f'<ScalarVariable {scalar_attrs_str}>{child_str}</ScalarVariable>'
148
130
  else:
149
- scalar_attrs = {
150
- "name": name,
151
- "valueReference": vr,
152
- "causality": causality,
153
- "variability": self.variability,
154
- "initial": self.initial,
155
- "description": self.description,
156
- "start": start
157
- }
158
- filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
159
- scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
131
+ if fmi_type in ('String', 'Binary'):
132
+ if start:
133
+ child_str = f'<Start value="{start}"/>'
134
+ else:
135
+ child_str = ''
136
+ scalar_attrs = {
137
+ "name": name,
138
+ "valueReference": vr,
139
+ "causality": causality,
140
+ "variability": self.variability,
141
+ "initial": self.initial,
142
+ "description": self.description,
143
+ }
144
+ filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
145
+ scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
146
+ return f'<{fmi_type} {scalar_attrs_str}>{child_str}</{fmi_type}>'
147
+ else:
148
+ scalar_attrs = {
149
+ "name": name,
150
+ "valueReference": vr,
151
+ "causality": causality,
152
+ "variability": self.variability,
153
+ "initial": self.initial,
154
+ "description": self.description,
155
+ "start": start
156
+ }
157
+ filtered_attrs = {key: value for key, value in scalar_attrs.items() if value is not None}
158
+ scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in filtered_attrs.items()])
160
159
 
161
- return f'<{fmi_type} {scalar_attrs_str}/>'
160
+ return f'<{fmi_type} {scalar_attrs_str}/>'
162
161
 
163
162
 
164
163
  class EmbeddedFMU(OperationAbstract):
@@ -265,12 +264,51 @@ class ContainerInput:
265
264
  self.cport_list.append(cport_to)
266
265
 
267
266
 
268
- class Local:
267
+ class Link:
268
+ CONVERSION_FUNCTION = {
269
+ "real32/real64": "F32_F64",
270
+
271
+ "Int8/Int16": "D8_D16",
272
+ "Int8/UInt16": "D8_U16",
273
+ "Int8/Int32": "D8_D32",
274
+ "Int8/UInt32": "D8_U32",
275
+ "Int8/Int64": "D8_D64",
276
+ "Int8/UInt64": "D8_U64",
277
+
278
+ "UInt8/Int16": "U8_D16",
279
+ "UInt8/UInt16": "U8_U16",
280
+ "UInt8/Int32": "U8_D32",
281
+ "UInt8/UInt32": "U8_U32",
282
+ "UInt8/Int64": "U8_D64",
283
+ "UInt8/UInt64": "U8_U64",
284
+
285
+ "Int16/Int32": "D16_D32",
286
+ "Int16/UInt32": "D16_U32",
287
+ "Int16/Int64": "D16_D64",
288
+ "Int16/UInt64": "D16_U64",
289
+
290
+ "UInt16/Int32": "U16_D32",
291
+ "UInt16/UInt32": "U16_U32",
292
+ "UInt16/Int64": "U16_D64",
293
+ "UInt16/UInt64": "U16_U64",
294
+
295
+ "Int32/Int64": "D32_D64",
296
+ "Int32/UInt64": "D32_U64",
297
+
298
+ "UInt32/Int64": "U32_D64",
299
+ "UInt32/UInt64": "U32_U64",
300
+
301
+ "boolean/boolean1": "B_B1",
302
+ "boolean1/boolean": "B1_B",
303
+ }
304
+
269
305
  def __init__(self, cport_from: ContainerPort):
270
306
  self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
271
307
  self.cport_from = cport_from
272
308
  self.cport_to_list: List[ContainerPort] = []
273
- self.vr = None
309
+
310
+ self.vr: Optional[int] = None
311
+ self.vr_converted: Dict[str, Optional[int]] = {}
274
312
 
275
313
  if not cport_from.port.causality == "output":
276
314
  raise FMUContainerError(f"{cport_from} is {cport_from.port.causality} instead of OUTPUT")
@@ -281,30 +319,54 @@ class Local:
281
319
 
282
320
  if cport_to.port.type_name == self.cport_from.port.type_name:
283
321
  self.cport_to_list.append(cport_to)
322
+ elif self.get_conversion(cport_to):
323
+ self.cport_to_list.append(cport_to)
324
+ self.vr_converted[cport_to.port.type_name] = None
284
325
  else:
285
326
  raise FMUContainerError(f"failed to connect {self.cport_from} to {cport_to} due to type.")
286
327
 
328
+ def get_conversion(self, cport_to: ContainerPort) -> Optional[str]:
329
+ try:
330
+ conversion = f"{self.cport_from.port.type_name}/{cport_to.port.type_name}"
331
+ return self.CONVERSION_FUNCTION[conversion]
332
+ except KeyError:
333
+ return None
334
+
335
+ def nb_local(self) -> int:
336
+ return 1+len(self.vr_converted)
337
+
287
338
 
288
339
  class ValueReferenceTable:
289
340
  def __init__(self):
290
- self.vr_table = {}
291
- self.masks = {}
341
+ self.vr_table:Dict[str, int] = {}
342
+ self.masks: Dict[str, int] = {}
343
+ self.nb_local_variable:Dict[str, int] = {}
292
344
  for i, type_name in enumerate(EmbeddedFMUPort.ALL_TYPES):
293
345
  self.vr_table[type_name] = 0
294
346
  self.masks[type_name] = i << 24
347
+ self.nb_local_variable[type_name] = 0
295
348
 
296
-
297
- def add_vr(self, port_or_type_name: Union[ContainerPort, str]) -> int:
349
+ def add_vr(self, port_or_type_name: Union[ContainerPort, str], local: bool = False) -> int:
298
350
  if isinstance(port_or_type_name, ContainerPort):
299
351
  type_name = port_or_type_name.port.type_name
300
352
  else:
301
353
  type_name = port_or_type_name
302
354
 
355
+ if local:
356
+ self.nb_local_variable[type_name] += 1
357
+
303
358
  vr = self.vr_table[type_name]
304
359
  self.vr_table[type_name] += 1
305
360
 
306
361
  return vr | self.masks[type_name]
307
362
 
363
+ def set_link_vr(self, link: Link):
364
+ link.vr = self.add_vr(link.cport_from, local=True)
365
+ for type_name in link.vr_converted.keys():
366
+ link.vr_converted[type_name] = self.add_vr(type_name, local=True)
367
+
368
+ def nb_local(self, type_name: str) -> int:
369
+ return self.nb_local_variable[type_name]
308
370
 
309
371
  class AutoWired:
310
372
  def __init__(self):
@@ -416,24 +478,12 @@ class FMUContainer:
416
478
  # Rules
417
479
  self.inputs: Dict[str, ContainerInput] = {}
418
480
  self.outputs: Dict[str, ContainerPort] = {}
419
- self.locals: Dict[ContainerPort, Local] = {}
481
+ self.links: Dict[ContainerPort, Link] = {}
420
482
 
421
483
  self.rules: Dict[ContainerPort, str] = {}
422
484
  self.start_values: Dict[ContainerPort, str] = {}
423
485
 
424
-
425
- def convert_type_name(self, type_name: str) -> str:
426
- if self.fmi_version == 2:
427
- table = {}
428
- elif self.fmi_version == 3:
429
- table = {}
430
- else:
431
- table = {}
432
-
433
- try:
434
- return table[type_name]
435
- except KeyError:
436
- return type_name
486
+ self.vr_table = ValueReferenceTable()
437
487
 
438
488
  def get_fmu(self, fmu_filename: str) -> EmbeddedFMU:
439
489
  if fmu_filename in self.involved_fmu:
@@ -442,7 +492,7 @@ class FMUContainer:
442
492
  try:
443
493
  fmu = EmbeddedFMU(self.fmu_directory / fmu_filename)
444
494
  if fmu.fmi_version > self.fmi_version:
445
- logger.fatal(f"Try to embed FMU-{fmu.fmi_version} into container FMI-{self.fmi_version}")
495
+ logger.warning(f"Try to embed FMU-{fmu.fmi_version} into container FMI-{self.fmi_version}")
446
496
  self.involved_fmu[fmu.name] = fmu
447
497
 
448
498
  logger.debug(f"Adding FMU #{len(self.involved_fmu)}: {fmu}")
@@ -506,16 +556,17 @@ class FMUContainer:
506
556
  def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
507
557
  cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
508
558
  try:
509
- local = self.locals[cport_from]
559
+ local = self.links[cport_from]
510
560
  except KeyError:
511
- local = Local(cport_from)
561
+ local = Link(cport_from)
512
562
 
513
563
  cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
514
564
  local.add_target(cport_to) # Causality is check in the add() function
515
565
 
566
+ logger.debug(f"LINK: {cport_from} -> {cport_to}")
516
567
  self.mark_ruled(cport_from, 'LINK')
517
568
  self.mark_ruled(cport_to, 'LINK')
518
- self.locals[cport_from] = local
569
+ self.links[cport_from] = local
519
570
 
520
571
  def add_start_value(self, fmu_filename: str, port_name: str, value: str):
521
572
  cport = ContainerPort(self.get_fmu(fmu_filename), port_name)
@@ -649,8 +700,6 @@ class FMUContainer:
649
700
  self.make_fmu_cleanup(base_directory)
650
701
 
651
702
  def make_fmu_xml(self, xml_file, step_size: float, profiling: bool):
652
- vr_table = ValueReferenceTable()
653
-
654
703
  timestamp = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
655
704
  guid = str(uuid.uuid4())
656
705
  embedded_fmu = ", ".join([fmu_name for fmu_name in self.involved_fmu])
@@ -696,61 +745,86 @@ class FMUContainer:
696
745
  start_time=self.start_time, stop_time=self.stop_time,
697
746
  step_size=step_size))
698
747
 
699
- vr_time = vr_table.add_vr("real64")
748
+ vr_time = self.vr_table.add_vr("real64", local=True)
700
749
  logger.debug(f"Time vr = {vr_time}")
750
+
701
751
  if profiling:
702
752
  for fmu in self.involved_fmu.values():
703
- vr = vr_table.add_vr("real64")
753
+ vr = self.vr_table.add_vr("real64", local=True)
704
754
  port = EmbeddedFMUPort("real64", {"valueReference": vr,
705
755
  "name": f"container.{fmu.id}.rt_ratio",
706
756
  "description": f"RT ratio for embedded FMU '{fmu.name}'"})
707
757
  print(f" {port.xml(vr, fmi_version=self.fmi_version)}", file=xml_file)
708
758
 
759
+ index_offset = 2 # index of output ports. Start at 2 to skip "time" port
760
+
709
761
  # Local variable should be first to ensure to attribute them the lowest VR.
710
- for local in self.locals.values():
711
- vr = vr_table.add_vr(local.cport_from)
712
- try:
713
- print(f" {local.cport_from.port.xml(vr, name=local.name, causality='local', fmi_version=self.fmi_version)}", file=xml_file)
714
- except KeyError:
715
- logger.error(f"Cannot expose '{local.name}' because type '{local.cport_from.port.type_name}' is not compatible with FMI-{self.fmi_version}.0")
716
- local.vr = vr
762
+ for link in self.links.values():
763
+ self.vr_table.set_link_vr(link)
764
+ port_local_def = link.cport_from.port.xml(link.vr, name=link.name, causality='local',
765
+ fmi_version=self.fmi_version)
766
+ if port_local_def:
767
+ print(f" {port_local_def}", file=xml_file)
768
+ index_offset += 1
717
769
 
718
770
  for input_port_name, input_port in self.inputs.items():
719
- vr = vr_table.add_vr(input_port.type_name)
771
+ input_port.vr = self.vr_table.add_vr(input_port.type_name)
720
772
  # Get Start and XML from first connected input
721
773
  start = self.start_values.get(input_port.cport_list[0], None)
722
- print(f" {input_port.cport_list[0].port.xml(vr, name=input_port_name, start=start, fmi_version=self.fmi_version)}", file=xml_file)
723
- input_port.vr = vr
724
-
725
- for output_port_name, cport in self.outputs.items():
726
- vr = vr_table.add_vr(cport)
727
- print(f" {cport.port.xml(vr, name=output_port_name, fmi_version=self.fmi_version)}", file=xml_file)
728
- cport.vr = vr
774
+ port_input_def = input_port.cport_list[0].port.xml(input_port.vr, name=input_port_name,
775
+ start=start, fmi_version=self.fmi_version)
776
+ if port_input_def:
777
+ print(f" {port_input_def}", file=xml_file)
778
+ index_offset += 1
779
+
780
+ for output_port_name, output_port in self.outputs.items():
781
+ output_port.vr = self.vr_table.add_vr(output_port)
782
+ port_output_def = output_port.port.xml(output_port.vr, name=output_port_name,
783
+ fmi_version=self.fmi_version)
784
+ if port_output_def:
785
+ print(f" {port_output_def}", file=xml_file)
729
786
 
730
787
  if self.fmi_version == 2:
731
- self.make_fmu_xml_epilog_2(xml_file)
788
+ self.make_fmu_xml_epilog_2(xml_file, index_offset)
732
789
  elif self.fmi_version == 3:
733
790
  self.make_fmu_xml_epilog_3(xml_file)
734
791
 
735
- def make_fmu_xml_epilog_2(self, xml_file):
736
- xml_file.write(f" </ModelVariables>\n\n <ModelStructure>\n <Outputs>\n")
737
- index_offset = len(self.locals) + len(self.inputs) + 1
738
- for i, _ in enumerate(self.outputs.keys()):
739
- print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
740
-
741
- xml_file.write(" </Outputs>\n <InitialUnknowns>\n")
742
-
743
- for i, _ in enumerate(self.outputs.keys()):
744
- print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
792
+ def make_fmu_xml_epilog_2(self, xml_file, index_offset):
793
+ xml_file.write(" </ModelVariables>\n"
794
+ "\n"
795
+ " <ModelStructure>\n"
796
+ " <Outputs>\n")
745
797
 
746
- xml_file.write(" </InitialUnknowns>\n </ModelStructure>\n\n</fmiModelDescription>")
798
+ index = index_offset
799
+ for output in self.outputs.values():
800
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[2]:
801
+ print(f' <Unknown index="{index}"/>', file=xml_file)
802
+ index += 1
803
+ xml_file.write(" </Outputs>\n"
804
+ " <InitialUnknowns>\n")
805
+ index = index_offset
806
+ for output in self.outputs.values():
807
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[2]:
808
+ print(f' <Unknown index="{index}"/>', file=xml_file)
809
+ index += 1
810
+ xml_file.write(" </InitialUnknowns>\n"
811
+ " </ModelStructure>\n"
812
+ "\n"
813
+ "</fmiModelDescription>")
747
814
 
748
815
  def make_fmu_xml_epilog_3(self, xml_file):
749
- xml_file.write(f" </ModelVariables>\n\n <ModelStructure>\n")
816
+ xml_file.write(" </ModelVariables>\n"
817
+ "\n"
818
+ " <ModelStructure>\n")
750
819
  for output in self.outputs.values():
751
- print(f' <Output valueReference="{output.vr}"/>', file=xml_file)
752
-
753
- xml_file.write(" </ModelStructure>\n\n</fmiModelDescription>\n""")
820
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[3]:
821
+ print(f' <Output valueReference="{output.vr}"/>', file=xml_file)
822
+ for output in self.outputs.values():
823
+ if output.port.type_name in EmbeddedFMUPort.CONTAINER_TO_FMI[3]:
824
+ print(f' <InitialUnknown valueReference="{output.vr}"/>', file=xml_file)
825
+ xml_file.write(" </ModelStructure>\n"
826
+ "\n"
827
+ "</fmiModelDescription>")
754
828
 
755
829
  def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool, sequential: bool):
756
830
  print("# Container flags <MT> <Profiling> <Sequential>", file=txt_file)
@@ -775,12 +849,13 @@ class FMUContainer:
775
849
  inputs_fmu_per_type: Dict[str, Dict[str, Dict[ContainerPort, int]]] = {} # [type][fmu]
776
850
  start_values_fmu_per_type = {}
777
851
  outputs_fmu_per_type = {}
778
- locals_per_type: Dict[str, List[Local]] = {}
852
+ local_per_type: Dict[str, List[int]] = {}
853
+ links_per_fmu: Dict[str, List[Link]] = {}
779
854
 
780
855
  for type_name in EmbeddedFMUPort.ALL_TYPES:
781
856
  inputs_per_type[type_name] = []
782
857
  outputs_per_type[type_name] = []
783
- locals_per_type[type_name] = []
858
+ local_per_type[type_name] = []
784
859
 
785
860
  inputs_fmu_per_type[type_name] = {}
786
861
  start_values_fmu_per_type[type_name] = {}
@@ -795,78 +870,88 @@ class FMUContainer:
795
870
  # Inputs
796
871
  for input_port_name, input_port in self.inputs.items():
797
872
  inputs_per_type[input_port.type_name].append(input_port)
798
- for cport, value in self.start_values.items():
799
- start_values_fmu_per_type[cport.port.type_name][cport.fmu.name][cport] = value
873
+ for input_port, value in self.start_values.items():
874
+ start_values_fmu_per_type[input_port.port.type_name][input_port.fmu.name][input_port] = value
800
875
  # Outputs
801
- for output_port_name, cport in self.outputs.items():
802
- outputs_per_type[cport.port.type_name].append(cport)
803
- # Locals
804
- for local in self.locals.values():
805
- vr = local.vr
806
- locals_per_type[local.cport_from.port.type_name].append(local)
807
- outputs_fmu_per_type[local.cport_from.port.type_name][local.cport_from.fmu.name][local.cport_from] = vr
808
- for cport_to in local.cport_to_list:
809
- inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = vr
876
+ for output_port_name, output_port in self.outputs.items():
877
+ outputs_per_type[output_port.port.type_name].append(output_port)
878
+ # Links
879
+ for link in self.links.values():
880
+ local_per_type[link.cport_from.port.type_name].append(link.vr)
881
+ outputs_fmu_per_type[link.cport_from.port.type_name][link.cport_from.fmu.name][link.cport_from] = link.vr
882
+ for cport_to in link.cport_to_list:
883
+ if cport_to.port.type_name == link.cport_from.port.type_name:
884
+ inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = link.vr
885
+ else:
886
+ local_per_type[cport_to.port.type_name].append(link.vr_converted[cport_to.port.type_name])
887
+ links_per_fmu.setdefault(link.cport_from.fmu.name, []).append(link)
888
+ inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = link.vr_converted[cport_to.port.type_name]
889
+
810
890
 
811
891
  print(f"# NB local variables:", ", ".join(EmbeddedFMUPort.ALL_TYPES), file=txt_file)
812
- nb_local = []
813
- for type_name in EmbeddedFMUPort.ALL_TYPES:
814
- nb = len(locals_per_type[type_name])
815
- if type_name == "real64":
816
- nb += 1 # reserver a slot for "time"
817
- if profiling:
818
- nb += len(self.involved_fmu)
819
- nb_local.append(str(nb))
892
+ nb_local = [f"{self.vr_table.nb_local(type_name)}" for type_name in EmbeddedFMUPort.ALL_TYPES]
820
893
  print(" ".join(nb_local), file=txt_file, end='')
821
894
  print("", file=txt_file)
822
895
 
823
896
  print("# CONTAINER I/O: <VR> <NB> <FMU_INDEX> <FMU_VR> [<FMU_INDEX> <FMU_VR>]", file=txt_file)
824
897
  for type_name in EmbeddedFMUPort.ALL_TYPES:
825
- print(f"# {type_name}", file=txt_file)
826
- nb = len(inputs_per_type[type_name]) + len(outputs_per_type[type_name]) + len(locals_per_type[type_name])
898
+ print(f"# {type_name}" , file=txt_file)
899
+ nb_local = (len(inputs_per_type[type_name]) +
900
+ len(outputs_per_type[type_name]) +
901
+ self.vr_table.nb_local(type_name))
827
902
  nb_input_link = 0
828
903
  for input_port in inputs_per_type[type_name]:
829
904
  nb_input_link += len(input_port.cport_list) - 1
830
-
905
+ print(f"{nb_local} {nb_local + nb_input_link}", file=txt_file)
831
906
  if type_name == "real64":
832
- nb += 1 # reserver a slot for "time"
833
- if profiling:
834
- nb += len(self.involved_fmu)
835
- print(f"{nb} {nb + nb_input_link}", file=txt_file)
836
907
  print(f"0 1 -1 0", file=txt_file) # Time slot
837
908
  if profiling:
838
909
  for profiling_port, _ in enumerate(self.involved_fmu.values()):
839
- print(f"{profiling_port+1} 1 -2 {profiling_port+1}", file=txt_file)
840
- else:
841
- print(f"{nb} {nb+nb_input_link}", file=txt_file)
910
+ print(f"{profiling_port + 1} 1 -2 {profiling_port + 1}", file=txt_file)
911
+
842
912
  for input_port in inputs_per_type[type_name]:
843
913
  cport_string = [f"{fmu_rank[cport.fmu.name]} {cport.port.vr}" for cport in input_port.cport_list]
844
914
  print(f"{input_port.vr} {len(input_port.cport_list)}", " ".join(cport_string), file=txt_file)
845
- for cport in outputs_per_type[type_name]:
846
- print(f"{cport.vr} 1 {fmu_rank[cport.fmu.name]} {cport.port.vr}", file=txt_file)
847
- for local in locals_per_type[type_name]:
848
- print(f"{local.vr} 1 -1 {local.vr & 0xFFFFFF}", file=txt_file)
915
+ for output_port in outputs_per_type[type_name]:
916
+ print(f"{output_port.vr} 1 {fmu_rank[output_port.fmu.name]} {output_port.port.vr}", file=txt_file)
917
+ for local_vr in local_per_type[type_name]:
918
+ print(f"{local_vr} 1 -1 {local_vr & 0xFFFFFF}", file=txt_file)
849
919
 
850
920
  # LINKS
851
921
  for fmu in self.involved_fmu.values():
852
922
  for type_name in EmbeddedFMUPort.ALL_TYPES:
853
923
  print(f"# Inputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
854
924
  print(len(inputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
855
- for cport, vr in inputs_fmu_per_type[type_name][fmu.name].items():
856
- print(f"{vr} {cport.port.vr}", file=txt_file)
925
+ for input_port, vr in inputs_fmu_per_type[type_name][fmu.name].items():
926
+ print(f"{vr} {input_port.port.vr}", file=txt_file)
857
927
 
858
928
  for type_name in EmbeddedFMUPort.ALL_TYPES:
859
929
  print(f"# Start values of {fmu.name} - {type_name}: <FMU_VR> <RESET> <VALUE>", file=txt_file)
860
930
  print(len(start_values_fmu_per_type[type_name][fmu.name]), file=txt_file)
861
- for cport, value in start_values_fmu_per_type[type_name][fmu.name].items():
862
- reset = 1 if cport.port.causality == "input" else 0
863
- print(f"{cport.port.vr} {reset} {value}", file=txt_file)
931
+ for input_port, value in start_values_fmu_per_type[type_name][fmu.name].items():
932
+ reset = 1 if input_port.port.causality == "input" else 0
933
+ print(f"{input_port.port.vr} {reset} {value}", file=txt_file)
864
934
 
865
935
  for type_name in EmbeddedFMUPort.ALL_TYPES:
866
936
  print(f"# Outputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
867
937
  print(len(outputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
868
- for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
869
- print(f"{vr} {cport.port.vr}", file=txt_file)
938
+ for output_port, vr in outputs_fmu_per_type[type_name][fmu.name].items():
939
+ print(f"{vr} {output_port.port.vr}", file=txt_file)
940
+
941
+ print(f"# Conversion table of {fmu.name}: <VR_FROM> <VR_TO> <CONVERSION>", file=txt_file)
942
+ try:
943
+ nb = 0
944
+ for link in links_per_fmu[fmu.name]:
945
+ nb += len(link.vr_converted)
946
+ print(f"{nb}", file=txt_file)
947
+ for link in links_per_fmu[fmu.name]:
948
+ for cport_to in link.cport_to_list:
949
+ conversion = link.get_conversion(cport_to)
950
+ if conversion:
951
+ print(f"{link.vr} {link.vr_converted[cport_to.port.type_name]} {conversion}",
952
+ file=txt_file)
953
+ except KeyError:
954
+ print("0", file=txt_file)
870
955
 
871
956
  @staticmethod
872
957
  def long_path(path: Union[str, Path]) -> str:
@@ -881,7 +966,7 @@ class FMUContainer:
881
966
  logger.debug(f"Copying {origin} in {destination}")
882
967
  shutil.copy(origin, destination)
883
968
 
884
- def get_bindir_and_suffixe(self) -> (str, str, str):
969
+ def get_bindir_and_suffixe(self) -> Tuple[str, str, str]:
885
970
  suffixes = {
886
971
  "Windows": "dll",
887
972
  "Linux": "so",
@@ -2,22 +2,24 @@ import os.path
2
2
  import sys
3
3
  import textwrap
4
4
 
5
- from PySide6.QtCore import Qt, QObject, QUrl, QDir, Signal, QPoint, QModelIndex
5
+ from PySide6.QtCore import Qt, QUrl, QDir, Signal, QPoint, QModelIndex
6
6
  from PySide6.QtWidgets import (QApplication, QWidget, QGridLayout, QLabel, QLineEdit, QPushButton, QFileDialog,
7
7
  QTextBrowser, QInputDialog, QMenu, QTreeView, QAbstractItemView, QTabWidget, QTableView,
8
8
  QCheckBox)
9
9
  from PySide6.QtGui import (QPixmap, QTextCursor, QStandardItem, QIcon, QDesktopServices, QAction,
10
10
  QPainter, QColor, QImage, QStandardItemModel)
11
11
  from functools import partial
12
- from typing import Optional
13
12
 
14
- from .gui_style import gui_style
13
+
14
+ from .gui_style import gui_style, log_color
15
15
  from .operations import *
16
16
  from .assembly import Assembly, AssemblyNode
17
17
  from .checker import checker_list
18
18
  from .help import Help
19
19
  from .version import __version__ as version
20
20
 
21
+ logger = logging.getLogger("fmu_manipulation_toolbox")
22
+
21
23
 
22
24
  class DropZoneWidget(QLabel):
23
25
  WIDTH = 150
@@ -51,7 +53,7 @@ class DropZoneWidget(QLabel):
51
53
  try:
52
54
  file_path = event.mimeData().urls()[0].toLocalFile()
53
55
  except IndexError:
54
- print("Please select a regular file.")
56
+ logger.error("Please select a regular file.")
55
57
  return
56
58
  self.set_fmu(file_path)
57
59
  event.accept()
@@ -98,54 +100,41 @@ class DropZoneWidget(QLabel):
98
100
  self.fmu = FMU(filename)
99
101
  self.set_image(os.path.join(self.fmu.tmp_directory, "model.png"))
100
102
  except Exception as e:
101
- print(f"ERROR: Cannot load this FMU: {e}")
103
+ logger.error(f"Cannot load this FMU: {e}")
102
104
  self.set_image(None)
103
105
  self.fmu = None
104
106
  self.clicked.emit()
105
107
 
106
108
 
109
+ class LogHandler(logging.Handler):
110
+ LOG_COLOR = {
111
+ logging.DEBUG: QColor(log_color["DEBUG"]),
112
+ logging.INFO: QColor(log_color["INFO"]),
113
+ logging.WARNING: QColor(log_color["WARNING"]),
114
+ logging.ERROR: QColor(log_color["ERROR"]),
115
+ logging.CRITICAL: QColor(log_color["CRITICAL"]),
116
+ }
117
+
118
+ def __init__(self, text_browser, level):
119
+ super().__init__(level)
120
+ self.text_browser: QTextBrowser = text_browser
121
+ logger.addHandler(self)
122
+ logger.setLevel(level)
123
+
124
+ def emit(self, record) -> None:
125
+ self.text_browser.setTextColor(self.LOG_COLOR[record.levelno])
126
+ self.text_browser.insertPlainText(record.msg+"\n")
127
+
128
+
107
129
  class LogWidget(QTextBrowser):
108
- class XStream(QObject):
109
- _stdout = None
110
- _stderr = None
111
- messageWritten = Signal(str)
112
-
113
- def __init__(self):
114
- super().__init__()
115
-
116
- def flush(self):
117
- pass
118
-
119
- @staticmethod
120
- def fileno():
121
- return -1
122
-
123
- def write(self, msg):
124
- if not self.signalsBlocked():
125
- self.messageWritten.emit(msg)
126
-
127
- @staticmethod
128
- def stdout():
129
- if not LogWidget.XStream._stdout:
130
- LogWidget.XStream._stdout = LogWidget.XStream()
131
- sys.stdout = LogWidget.XStream._stdout
132
- return LogWidget.XStream._stdout
133
-
134
- @staticmethod
135
- def stderr():
136
- if not LogWidget.XStream._stderr:
137
- LogWidget.XStream._stderr = LogWidget.XStream()
138
- sys.stderr = LogWidget.XStream._stderr
139
- return LogWidget.XStream._stderr
130
+ def __init__(self, parent=None, level=logging.INFO):
131
+ super().__init__(parent)
140
132
 
141
- def __init__(self):
142
- super().__init__()
143
133
  self.setMinimumWidth(900)
144
134
  self.setMinimumHeight(500)
145
135
  self.setSearchPaths([os.path.join(os.path.dirname(__file__), "resources")])
146
136
  self.insertHtml('<center><img src="fmu_manipulation_toolbox.png"/></center><br/>')
147
- LogWidget.XStream.stdout().messageWritten.connect(self.insertPlainText)
148
- LogWidget.XStream.stderr().messageWritten.connect(self.insertPlainText)
137
+ self.log_handler = LogHandler(self, logging.DEBUG)
149
138
 
150
139
  def loadResource(self, _, name):
151
140
  image_path = os.path.join(os.path.dirname(__file__), "resources", name.toString())
@@ -255,10 +244,10 @@ class AssemblyTreeWidget(QTreeView):
255
244
 
256
245
  def removeRows(self, row, count, parent=QModelIndex()):
257
246
  if not self.dnd_target_node:
258
- print("NO DROP NODE!?")
247
+ logger.error("NO DROP NODE!?")
259
248
 
260
249
  source_index = self.itemFromIndex(parent).child(row, 0).data(role=Qt.ItemDataRole.UserRole+1)
261
- print(f"{source_index} ==> {self.dnd_target_node.name}")
250
+ logger.debug(f"{source_index} ==> {self.dnd_target_node.name}")
262
251
 
263
252
  self.dnd_target_node = None
264
253
  return super().removeRows(row, count, parent)
@@ -289,7 +278,7 @@ class AssemblyTreeWidget(QTreeView):
289
278
 
290
279
  def setTopIndex(self):
291
280
  topIndex = self.model.index(0, 0, self.rootIndex())
292
- print(topIndex.isValid(), topIndex.model())
281
+ logger.debug(topIndex.isValid(), topIndex.model())
293
282
  if topIndex.isValid():
294
283
  self.setCurrentIndex(topIndex)
295
284
  if self.layoutCheck:
@@ -318,9 +307,9 @@ class AssemblyTreeWidget(QTreeView):
318
307
  try:
319
308
  file_path = event.mimeData().urls()[0].toLocalFile()
320
309
  except IndexError:
321
- print("Please select a regular file.")
310
+ logger.error("Please select a regular file.")
322
311
  return
323
- print(f"DROP: {file_path}")
312
+ logger.debug(f"DROP: {file_path}")
324
313
  event.accept()
325
314
  else:
326
315
  event.ignore()
@@ -518,7 +507,7 @@ class MainWindow(WindowWithLayout):
518
507
  "FMU files (*.fmu)")
519
508
  if ok and filename:
520
509
  fmu.repack(filename)
521
- print(f"Modified version saved as {filename}.")
510
+ logger.info(f"Modified version saved as {filename}.")
522
511
 
523
512
  def save_log(self):
524
513
  if self.dropped_fmu.fmu:
@@ -533,7 +522,7 @@ class MainWindow(WindowWithLayout):
533
522
  with open(filename, "wt") as file:
534
523
  file.write(str(self.log_widget.toPlainText()))
535
524
  except Exception as e:
536
- print(f"ERROR: {e}")
525
+ logger.error(f"{e}")
537
526
 
538
527
  def add_operation(self, name, usage, severity, operation, x, y, prompt=None, prompt_file=None, arg=None,
539
528
  func=None):
@@ -603,18 +592,18 @@ class MainWindow(WindowWithLayout):
603
592
  if self.dropped_fmu.fmu:
604
593
  self.log_widget.moveCursor(QTextCursor.MoveOperation.End)
605
594
  fmu_filename = os.path.basename(self.dropped_fmu.fmu.fmu_filename)
606
- print('-' * 100)
595
+ logger.info('-' * 100)
607
596
  self.log_widget.insertHtml(f"<strong>{fmu_filename}: {operation}</strong><br>")
608
597
 
609
598
  apply_on = self.filter_list.get()
610
599
  if apply_on:
611
600
  self.log_widget.insertHtml(f"<i>Applied only for ports with causality = " +
612
601
  ", ".join(apply_on) + "</i><br>")
613
- print('-' * 100)
602
+ logger.info('-' * 100)
614
603
  try:
615
604
  self.dropped_fmu.fmu.apply_operation(operation, apply_on=apply_on)
616
605
  except Exception as e:
617
- print(f"ERROR: {e}")
606
+ logger.error(f"{e}")
618
607
 
619
608
  scroll_bar = self.log_widget.verticalScrollBar()
620
609
  scroll_bar.setValue(scroll_bar.maximum())
@@ -712,7 +701,7 @@ class ContainerWindow(WindowWithLayout):
712
701
  self.last_directory = os.path.dirname(filename)
713
702
  self.assembly_tree.load_container(filename)
714
703
  except Exception as e:
715
- print(e)
704
+ logger.error(e)
716
705
 
717
706
 
718
707
  class Application(QApplication):
@@ -724,8 +713,9 @@ Communicating with the FMU-developer and adapting the way the FMU is generated,
724
713
 
725
714
  """
726
715
  def __init__(self, *args, **kwargs):
727
- super().__init__(*args, **kwargs)
728
716
  self.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.RoundPreferFloor)
717
+ super().__init__(*args, **kwargs)
718
+
729
719
 
730
720
  QDir.addSearchPath('images', os.path.join(os.path.dirname(__file__), "resources"))
731
721
  self.setStyleSheet(gui_style)
@@ -747,8 +737,8 @@ Communicating with the FMU-developer and adapting the way the FMU is generated,
747
737
  def main():
748
738
  application = Application(sys.argv)
749
739
 
750
- print(" " * 80, f"Version {version}")
751
- print(application.__doc__)
740
+ logger.info(" " * 80 + f"Version {version}")
741
+ logger.info(application.__doc__)
752
742
 
753
743
  sys.exit(application.exec())
754
744
 
@@ -242,3 +242,11 @@ else:
242
242
  margin-left: 0;
243
243
  }
244
244
  """
245
+
246
+ log_color = {
247
+ "DEBUG": "#6E6B6B",
248
+ "INFO": "#b5bab9",
249
+ "WARNING": "#F7C61B",
250
+ "ERROR": "#F54927",
251
+ "CRITICAL": "#FF00FF",
252
+ }
@@ -1,5 +1,6 @@
1
1
  import csv
2
2
  import html
3
+ import logging
3
4
  import os
4
5
  import re
5
6
  import shutil
@@ -10,6 +11,7 @@ import hashlib
10
11
  from pathlib import Path
11
12
  from typing import *
12
13
 
14
+ logger = logging.getLogger("fmu_manipulation_toolbox")
13
15
 
14
16
  class FMU:
15
17
  """Unpack and Repack facilities for FMU package. Once unpacked, we can process Operation on
@@ -57,6 +59,7 @@ class FMUPort:
57
59
  def __init__(self):
58
60
  self.fmi_type = None
59
61
  self.attrs_list: List[Dict] = []
62
+ self.dimension = None
60
63
 
61
64
  def dict_level(self, nb):
62
65
  return " ".join([f'{key}="{value}"' for key, value in self.attrs_list[nb].items()])
@@ -99,9 +102,6 @@ class FMUPort:
99
102
  def push_attrs(self, attrs):
100
103
  self.attrs_list.append(attrs)
101
104
 
102
- def is_ready(self):
103
- return self.fmi_type is not None
104
-
105
105
 
106
106
  class FMUError(Exception):
107
107
  def __init__(self, reason):
@@ -130,6 +130,7 @@ class Manipulation:
130
130
  self.current_port_number: int = 0
131
131
  self.port_translation: List[Optional[int]] = []
132
132
  self.port_names_list: List[str] = []
133
+ self.port_removed_vr: Set[str] = set()
133
134
  self.apply_on = None
134
135
 
135
136
  @staticmethod
@@ -142,9 +143,11 @@ class Manipulation:
142
143
  def handle_port(self):
143
144
  causality = self.current_port.get('causality', 'local')
144
145
  port_name = self.current_port['name']
146
+ vr = self.current_port['valueReference']
145
147
  if not self.apply_on or causality in self.apply_on:
146
148
  if self.operation.port_attrs(self.current_port):
147
- self.remove_port(port_name)
149
+ self.remove_port(port_name, vr)
150
+ # Exception is raised by remove port !
148
151
  else:
149
152
  self.keep_port(port_name)
150
153
  else: # Keep ScalarVariable as it is.
@@ -171,21 +174,16 @@ class Manipulation:
171
174
  elif name == 'fmiModelDescription':
172
175
  self.fmu.fmi_version = int(float(attrs["fmiVersion"]))
173
176
  self.operation.fmi_attrs(attrs)
174
- elif name == 'Unknown':
177
+ elif name == 'Unknown': # FMI-2.0 only
175
178
  self.unknown_attrs(attrs)
176
-
177
- if self.current_port and self.current_port.is_ready():
178
- self.handle_port()
179
+ elif name == 'Output' or name == "ContinuousStateDerivative" or "InitialUnknown":
180
+ self.handle_structure(attrs)
179
181
 
180
182
  except ManipulationSkipTag:
181
183
  self.skip_until = name
182
184
  return
183
185
 
184
- if self.current_port:
185
- if self.current_port.is_ready():
186
- self.current_port.write_xml(self.fmu.fmi_version, self.out)
187
- self.current_port = None
188
- else: # re-copy tags
186
+ if self.current_port is None:
189
187
  if attrs:
190
188
  attrs_list = [f'{key}="{self.escape(value)}"' for (key, value) in attrs.items()]
191
189
  print(f"<{name}", " ".join(attrs_list), ">", end='', file=self.out)
@@ -198,16 +196,25 @@ class Manipulation:
198
196
  self.skip_until = None
199
197
  return
200
198
  else:
201
- if name not in FMU.FMI3_TYPES and name not in FMU.FMI2_TYPES and not name == "ScalarVariable":
199
+ if name == "ScalarVariable" or (self.fmu.fmi_version == 3 and name in FMU.FMI3_TYPES):
200
+ try:
201
+ self.handle_port()
202
+ self.current_port.write_xml(self.fmu.fmi_version, self.out)
203
+ except ManipulationSkipTag:
204
+ logger.info(f"Port '{self.current_port['name']}' is removed.")
205
+ self.current_port = None
206
+
207
+ elif self.current_port is None:
202
208
  print(f"</{name}>", end='', file=self.out)
203
209
 
204
210
  def char_data(self, data):
205
211
  if not self.skip_until:
206
212
  print(data, end='', file=self.out)
207
213
 
208
- def remove_port(self, name):
214
+ def remove_port(self, name, vr):
209
215
  self.port_names_list.append(name)
210
216
  self.port_translation.append(None)
217
+ self.port_removed_vr.add(vr)
211
218
  raise ManipulationSkipTag
212
219
 
213
220
  def keep_port(self, name):
@@ -221,9 +228,29 @@ class Manipulation:
221
228
  if new_index:
222
229
  attrs['index'] = self.port_translation[int(attrs['index']) - 1]
223
230
  else:
224
- print(f"WARNING: Removed port '{self.port_names_list[index]}' is involved in dependencies tree.")
231
+ logger.warning(f"Removed port '{self.port_names_list[index]}' is involved in dependencies tree.")
225
232
  raise ManipulationSkipTag
226
233
 
234
+ def handle_structure(self, attrs):
235
+ try:
236
+ vr = attrs['valueReference']
237
+ if vr in self.port_removed_vr:
238
+ raise ManipulationSkipTag
239
+ except KeyError:
240
+ return
241
+
242
+ try:
243
+ dependencies = attrs['dependencies']
244
+ new_dependencies = []
245
+ for dependency in dependencies.split(" "):
246
+ if dependency in self.port_removed_vr:
247
+ logger.warning(f"Removed port 'vr={dependency}' was involved in dependencies tree.")
248
+ else:
249
+ new_dependencies.append(dependency)
250
+ attrs['dependencies'] = " ".join(new_dependencies)
251
+ except KeyError:
252
+ return
253
+
227
254
  def manipulate(self, descriptor_filename, apply_on=None):
228
255
  self.apply_on = apply_on
229
256
  with open(self.output_filename, "w", encoding="utf-8") as self.out, open(descriptor_filename, "rb") as file:
@@ -347,7 +374,7 @@ class OperationAddRemotingWinAbstract(OperationAbstract):
347
374
  return f"Add '{self.bitness_to}' remoting on '{self.bitness_from}' FMU"
348
375
 
349
376
  def fmi_attrs(self, attrs):
350
- if not attrs["fmi_version"] == "2.0":
377
+ if not attrs["fmiVersion"] == "2.0":
351
378
  raise OperationError(f"Adding remoting is only available for FMI-2.0")
352
379
 
353
380
  def cosimulation_attrs(self, attrs):
@@ -360,7 +387,7 @@ class OperationAddRemotingWinAbstract(OperationAbstract):
360
387
  raise OperationError(f"{self.bitness_from} interface does not exist")
361
388
 
362
389
  if os.path.isdir(fmu_bin[self.bitness_to]):
363
- print(f"INFO: {self.bitness_to} already exists. Add front-end.")
390
+ logger.info(f"{self.bitness_to} already exists. Add front-end.")
364
391
  shutil.move(os.path.join(fmu_bin[self.bitness_to], attrs['modelIdentifier'] + ".dll"),
365
392
  os.path.join(fmu_bin[self.bitness_to], attrs['modelIdentifier'] + "-remoted.dll"))
366
393
  else:
@@ -440,31 +467,31 @@ class OperationSummary(OperationAbstract):
440
467
  return f"FMU Summary"
441
468
 
442
469
  def fmi_attrs(self, attrs):
443
- print(f"| fmu filename = {self.fmu.fmu_filename}")
444
- print(f"| temporary directory = {self.fmu.tmp_directory}")
470
+ logger.info(f"| fmu filename = {self.fmu.fmu_filename}")
471
+ logger.info(f"| temporary directory = {self.fmu.tmp_directory}")
445
472
  hash_md5 = hashlib.md5()
446
473
  with open(self.fmu.fmu_filename, "rb") as f:
447
474
  for chunk in iter(lambda: f.read(4096), b""):
448
475
  hash_md5.update(chunk)
449
476
  digest = hash_md5.hexdigest()
450
- print(f"| MD5Sum = {digest}")
477
+ logger.info(f"| MD5Sum = {digest}")
451
478
 
452
- print(f"|\n| FMI properties: ")
479
+ logger.info(f"|\n| FMI properties: ")
453
480
  for (k, v) in attrs.items():
454
- print(f"| - {k} = {v}")
455
- print(f"|")
481
+ logger.info(f"| - {k} = {v}")
482
+ logger.info(f"|")
456
483
 
457
484
  def cosimulation_attrs(self, attrs):
458
- print("| Co-Simulation capabilities: ")
485
+ logger.info("| Co-Simulation capabilities: ")
459
486
  for (k, v) in attrs.items():
460
- print(f"| - {k} = {v}")
461
- print(f"|")
487
+ logger.info(f"| - {k} = {v}")
488
+ logger.info(f"|")
462
489
 
463
490
  def experiment_attrs(self, attrs):
464
- print("| Default Experiment values: ")
491
+ logger.info("| Default Experiment values: ")
465
492
  for (k, v) in attrs.items():
466
- print(f"| - {k} = {v}")
467
- print(f"|")
493
+ logger.info(f"| - {k} = {v}")
494
+ logger.info(f"|")
468
495
 
469
496
  def port_attrs(self, fmu_port) -> int:
470
497
  causality = fmu_port.get("causality", "local")
@@ -477,33 +504,33 @@ class OperationSummary(OperationAbstract):
477
504
  return 0
478
505
 
479
506
  def closure(self):
480
- print("| Supported platforms: ")
507
+ logger.info("| Supported platforms: ")
481
508
  try:
482
509
  for platform in os.listdir(os.path.join(self.fmu.tmp_directory, "binaries")):
483
- print(f"| - {platform}")
510
+ logger.info(f"| - {platform}")
484
511
  except FileNotFoundError:
485
512
  pass # no binaries
486
513
 
487
514
  if os.path.isdir(os.path.join(self.fmu.tmp_directory, "sources")):
488
- print(f"| - RT (sources available)")
515
+ logger.info(f"| - RT (sources available)")
489
516
 
490
517
  resource_dir = os.path.join(self.fmu.tmp_directory, "resources")
491
518
  if os.path.isdir(resource_dir):
492
- print("|\n| Embedded resources:")
519
+ logger.info("|\n| Embedded resources:")
493
520
  for resource in os.listdir(resource_dir):
494
- print(f"| - {resource}")
521
+ logger.info(f"| - {resource}")
495
522
 
496
523
  extra_dir = os.path.join(self.fmu.tmp_directory, "extra")
497
524
  if os.path.isdir(extra_dir):
498
- print("|\n| Additional (meta-)data:")
525
+ logger.info("|\n| Additional (meta-)data:")
499
526
  for extra in os.listdir(extra_dir):
500
- print(f"| - {extra}")
527
+ logger.info(f"| - {extra}")
501
528
 
502
- print("|\n| Number of signals")
529
+ logger.info("|\n| Number of ports")
503
530
  for causality, nb_ports in self.nb_port_per_causality.items():
504
- print(f"| {causality} : {nb_ports}")
531
+ logger.info(f"| {causality} : {nb_ports}")
505
532
 
506
- print("|\n| [End of report]")
533
+ logger.info("|\n| [End of report]")
507
534
 
508
535
 
509
536
  class OperationRemoveSources(OperationAbstract):
@@ -514,7 +541,7 @@ class OperationRemoveSources(OperationAbstract):
514
541
  try:
515
542
  shutil.rmtree(os.path.join(self.fmu.tmp_directory, "sources"))
516
543
  except FileNotFoundError:
517
- print("This FMU does not embed sources.")
544
+ logger.info("This FMU does not embed sources.")
518
545
 
519
546
 
520
547
  class OperationTrimUntil(OperationAbstract):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmu_manipulation_toolbox
3
- Version: 1.9rc1
3
+ Version: 1.9rc3
4
4
  Summary: FMU Manipulation Toolbox is a python application for modifying Functional Mock-up Units (FMUs) without recompilation or bundling them into FMU Containers
5
5
  Home-page: https://github.com/grouperenault/fmu_manipulation_toolbox/
6
6
  Author: Nicolas.LAURENT@Renault.com
@@ -1,13 +1,13 @@
1
- fmu_manipulation_toolbox/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
- fmu_manipulation_toolbox/__main__.py,sha256=mAzrtkil506DS0F3g3CEbHdtZsZotzntcNhIn_lNJkw,344
3
- fmu_manipulation_toolbox/__version__.py,sha256=hYFpHuopEP58yzpI84myHNi5TvdMTGgMf2b3lGAPyRY,12
4
- fmu_manipulation_toolbox/assembly.py,sha256=cz8Z6zO3tWfNEj62ZEvU5pMv4byXs-F6Ri21YxLAYFs,26528
5
- fmu_manipulation_toolbox/checker.py,sha256=836wCri6aEl0M6_xHPZ94JRngFjsy8Ee-_t8bzclxBs,2332
6
- fmu_manipulation_toolbox/container.py,sha256=M8jL2WhjKqZ8pHSSSplys9eqTjGvubcAz0ggsH8nYpI,40556
7
- fmu_manipulation_toolbox/gui.py,sha256=tVTPLbGZlD5P0WYqaD5B4crG2nHjBGe73yAJrzRXpYE,29218
8
- fmu_manipulation_toolbox/gui_style.py,sha256=uzEruJumnC5Vf2cfHXVa_c-SHAQoTvDWemro3-b9Mpc,6492
1
+ fmu_manipulation_toolbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ fmu_manipulation_toolbox/__main__.py,sha256=FpG0ITBz5q-AvIbXplVh_1g1zla5souFGtpiDdECxEw,352
3
+ fmu_manipulation_toolbox/__version__.py,sha256=CGyZIselV5kCL6An0m7aR4m-Y2X2IRQadcj88KEF3Ps,11
4
+ fmu_manipulation_toolbox/assembly.py,sha256=XQ_1sB6K1Dk2mnNe-E3_6Opoeub7F9Qaln0EUDzsop8,26553
5
+ fmu_manipulation_toolbox/checker.py,sha256=jw1omfrMMIMHlIpHXpWBcQgIiS9hnHe5T9CZ5KlbVGs,2422
6
+ fmu_manipulation_toolbox/container.py,sha256=8Wf16sgUPkY-ByWeKt-3TD_4vNRQuLwH9Gs7D2Q6tAk,44959
7
+ fmu_manipulation_toolbox/gui.py,sha256=iOJ3F_zfw3mU34VofZrYdaffljTmYfuy33fJXlLaOwg,29045
8
+ fmu_manipulation_toolbox/gui_style.py,sha256=s6WdrnNd_lCMWhuBf5LKK8wrfLXCU7pFTLUfvqkJVno,6633
9
9
  fmu_manipulation_toolbox/help.py,sha256=aklKiLrsE0adSzQ5uoEB1sBDmI6s4l231gavu4XxxzA,5856
10
- fmu_manipulation_toolbox/operations.py,sha256=-oOHWLm15XbiVgIM8rbOinO2haqyH-5VF6WpFGGVxNg,18174
10
+ fmu_manipulation_toolbox/operations.py,sha256=b7N5AVHLIpQO8od4BYasb9CppHJ-ZmW8mu0ZdhndoHI,19472
11
11
  fmu_manipulation_toolbox/split.py,sha256=NVkfdTRR0jj_VOdgtxHQoKptkAg5TFVUA7nUx3_o9Pg,13336
12
12
  fmu_manipulation_toolbox/version.py,sha256=OhBLkZ1-nhC77kyvffPNAf6m8OZe1bYTnNf_PWs1NvM,392
13
13
  fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png,sha256=FWIuyrXlaNLLePHfXj7j9ca5rT8Hgr14KCe1EqTCZyk,2288
@@ -27,7 +27,7 @@ fmu_manipulation_toolbox/resources/icon_fmu.png,sha256=EuygB2xcoM2WAfKKdyKG_UvTL
27
27
  fmu_manipulation_toolbox/resources/license.txt,sha256=5ODuU8g8pIkK-NMWXu_rjZ6k7gM7b-N2rmg87-2Kmqw,1583
28
28
  fmu_manipulation_toolbox/resources/mask.png,sha256=px1U4hQGL0AmZ4BQPknOVREpMpTSejbah3ntkpqAzFA,3008
29
29
  fmu_manipulation_toolbox/resources/model.png,sha256=EAf_HnZJe8zYGZygerG1MMt2U-tMMZlifzXPj4_iORA,208788
30
- fmu_manipulation_toolbox/resources/darwin64/container.dylib,sha256=9L9N8rXVI6sYEDZAQdDymX6Z0GmFwj3_GhrtiBh1pTM,144760
30
+ fmu_manipulation_toolbox/resources/darwin64/container.dylib,sha256=qYV-5d35a7OmSHjYejuTRG8TurfctN5rkF-YjQua-Zo,161560
31
31
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd,sha256=OGfyJtaJntKypX5KDpuZ-nV1oYLZ6HV16pkpKOmYox4,2731
32
32
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd,sha256=HwyV7LBse-PQSv4z1xjmtzPU3Hjnv4mluq9YdSBNHMQ,3704
33
33
  fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd,sha256=JM4j_9q-pc40XYHb28jfT3iV3aYM5JLqD5aRjO72K1E,18939
@@ -50,16 +50,16 @@ fmu_manipulation_toolbox/resources/fmi-3.0/fmi3VariableDependency.xsd,sha256=YQS
50
50
  fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=xVdY2zy13pa2DcvFiweSNpp7E6DiONqeoBdlcJHrW_k,35940
51
51
  fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=1TLGqNPyM5UVOrCfzNqWyF6ClLksY3EiY3CSsrnp6c0,22836
52
52
  fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=EhY1XHo1YcQn6yqZ7wk5okqtZyp0MrcCsGcudqE-aIM,37000
53
- fmu_manipulation_toolbox/resources/linux64/container.so,sha256=b1BizAi-7Ag0vHBk_HjiXhBWpMnkV4Cju2FGMYqCQ7w,131144
53
+ fmu_manipulation_toolbox/resources/linux64/container.so,sha256=KT6_GdTc0saR-H6l2-0udaJtEDiHnzlFBIp0FtlOtrw,135520
54
54
  fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=ulfoPvmaYe9nInYcVEyj7mD9zDzGk56OUoWx1mPKLiE,22768
55
- fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=HkGkuf0HTsBQSCWeiTDYb2kANc2CjsjhVo9Qk8KLLl0,17920
56
- fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=2vrLiRdBoBUSckrSJhvt9F_hJowYv7_qVo0weKVeXBc,15872
57
- fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=df4rfG-SyW0EClifv8PDYqzhKMOvzwZnanaS_8lDxC8,22016
58
- fmu_manipulation_toolbox/resources/win64/container.dll,sha256=TOJIEULG8TF2vdHbfi2Adwt1NnuLsEbMvchZhzO0P6s,93696
59
- fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=PFueL3UzAVwSz8QBmysbY4Mr9G2lVcjdrFYEyWuTDRU,19968
60
- fmu_manipulation_toolbox-1.9rc1.dist-info/licenses/LICENSE.txt,sha256=c_862mzyk6ownO3Gt6cVs0-53IXLi_-ZEQFNDVabESw,1285
61
- fmu_manipulation_toolbox-1.9rc1.dist-info/METADATA,sha256=595mII6g6nVb5iilo-HAvp95-TSS_8yl-95y_vicFrs,1156
62
- fmu_manipulation_toolbox-1.9rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
63
- fmu_manipulation_toolbox-1.9rc1.dist-info/entry_points.txt,sha256=HjOZkflbI1IuSY8BpOZre20m24M4GDQGCJfPIa7NrlY,264
64
- fmu_manipulation_toolbox-1.9rc1.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
65
- fmu_manipulation_toolbox-1.9rc1.dist-info/RECORD,,
55
+ fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=q91IE2IFDzchUukNc93c7MkRHgKYA3ll3CnKthPfKB0,17920
56
+ fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=cTmbVSx_wyD-VcJMXgQWvfmByzrb9lJnTlp9Dzdu9sA,15872
57
+ fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=RYaQA4l0iOis-QbnCz_T1zRnuDTlWFtt-FzWnEnenag,22016
58
+ fmu_manipulation_toolbox/resources/win64/container.dll,sha256=vBslbM-PWm9Dj4zTwhElqm9EPHFwv4BV9obBEb0sx9I,98816
59
+ fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=oKiRKA1irVkkL-P84bOU1TC7K9h_xrnr_4EGJqoYIfU,19968
60
+ fmu_manipulation_toolbox-1.9rc3.dist-info/licenses/LICENSE.txt,sha256=c_862mzyk6ownO3Gt6cVs0-53IXLi_-ZEQFNDVabESw,1285
61
+ fmu_manipulation_toolbox-1.9rc3.dist-info/METADATA,sha256=4E3iWPeD60EJzvvBjPFnWqMI6cIEMn3wxsaLjmZwkNw,1156
62
+ fmu_manipulation_toolbox-1.9rc3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
63
+ fmu_manipulation_toolbox-1.9rc3.dist-info/entry_points.txt,sha256=HjOZkflbI1IuSY8BpOZre20m24M4GDQGCJfPIa7NrlY,264
64
+ fmu_manipulation_toolbox-1.9rc3.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
65
+ fmu_manipulation_toolbox-1.9rc3.dist-info/RECORD,,