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.
- fmu_manipulation_toolbox/__version__.py +1 -1
- fmu_manipulation_toolbox/assembly.py +241 -127
- fmu_manipulation_toolbox/cli.py +16 -7
- fmu_manipulation_toolbox/fmu_container.py +69 -30
- fmu_manipulation_toolbox/fmu_operations.py +2 -3
- fmu_manipulation_toolbox/gui.py +356 -103
- fmu_manipulation_toolbox/gui_style.py +129 -0
- fmu_manipulation_toolbox/resources/container.png +0 -0
- fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox/resources/icon_fmu.png +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/mask.png +0 -0
- fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/METADATA +8 -3
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/RECORD +25 -21
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/WHEEL +1 -1
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.dist-info → fmu_manipulation_toolbox-1.8.2.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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.
|
|
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) ->
|
|
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
|
|
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 == '
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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:
|
|
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.
|
|
357
|
-
|
|
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.
|
|
360
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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:
|