fmu-manipulation-toolbox 1.8.4.3b0__py3-none-any.whl → 1.9__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/__init__.py +0 -1
- fmu_manipulation_toolbox/__main__.py +1 -1
- fmu_manipulation_toolbox/__version__.py +1 -1
- fmu_manipulation_toolbox/assembly.py +21 -12
- fmu_manipulation_toolbox/checker.py +11 -8
- fmu_manipulation_toolbox/cli/__init__.py +0 -0
- fmu_manipulation_toolbox/cli/fmucontainer.py +105 -0
- fmu_manipulation_toolbox/cli/fmusplit.py +48 -0
- fmu_manipulation_toolbox/cli/fmutool.py +127 -0
- fmu_manipulation_toolbox/cli/utils.py +36 -0
- fmu_manipulation_toolbox/container.py +534 -247
- fmu_manipulation_toolbox/gui.py +47 -55
- fmu_manipulation_toolbox/gui_style.py +8 -0
- fmu_manipulation_toolbox/help.py +3 -0
- fmu_manipulation_toolbox/operations.py +251 -179
- fmu_manipulation_toolbox/remoting.py +107 -0
- fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
- fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox/resources/linux64/server_sm +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/split.py +67 -34
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/RECORD +34 -27
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.4.3b0.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
@@ -7,16 +8,18 @@ import tempfile
|
|
|
7
8
|
import xml.parsers.expat
|
|
8
9
|
import zipfile
|
|
9
10
|
import hashlib
|
|
10
|
-
from pathlib import Path
|
|
11
11
|
from typing import *
|
|
12
12
|
|
|
13
|
+
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
13
14
|
|
|
14
15
|
class FMU:
|
|
15
16
|
"""Unpack and Repack facilities for FMU package. Once unpacked, we can process Operation on
|
|
16
17
|
modelDescription.xml file."""
|
|
17
18
|
|
|
18
19
|
FMI2_TYPES = ('Real', 'Integer', 'String', 'Boolean', 'Enumeration')
|
|
19
|
-
FMI3_TYPES = ('Float64'
|
|
20
|
+
FMI3_TYPES = ('Float64', 'Float32',
|
|
21
|
+
'Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
|
|
22
|
+
'String', 'Boolean', 'Enumeration')
|
|
20
23
|
|
|
21
24
|
def __init__(self, fmu_filename):
|
|
22
25
|
self.fmu_filename = fmu_filename
|
|
@@ -51,6 +54,54 @@ class FMU:
|
|
|
51
54
|
manipulation.manipulate(self.descriptor_filename, apply_on)
|
|
52
55
|
|
|
53
56
|
|
|
57
|
+
class FMUPort:
|
|
58
|
+
def __init__(self):
|
|
59
|
+
self.fmi_type = None
|
|
60
|
+
self.attrs_list: List[Dict] = []
|
|
61
|
+
self.dimension = None
|
|
62
|
+
|
|
63
|
+
def dict_level(self, nb):
|
|
64
|
+
return " ".join([f'{key}="{value}"' for key, value in self.attrs_list[nb].items()])
|
|
65
|
+
|
|
66
|
+
def write_xml(self, fmi_version: int, file):
|
|
67
|
+
if fmi_version == 2:
|
|
68
|
+
print(f" <ScalarVariable {self.dict_level(0)}>", file=file)
|
|
69
|
+
print(f" <{self.fmi_type} {self.dict_level(1)}/>", file=file)
|
|
70
|
+
print(f" </ScalarVariable>", file=file)
|
|
71
|
+
elif fmi_version == 3:
|
|
72
|
+
print(f" <{self.fmi_type} {self.dict_level(0)}/>", file=file)
|
|
73
|
+
else:
|
|
74
|
+
raise FMUError(f"FMUPort writing: unsupported FMI version {fmi_version}")
|
|
75
|
+
|
|
76
|
+
def __contains__(self, item):
|
|
77
|
+
for attrs in self.attrs_list:
|
|
78
|
+
if item in attrs:
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def __getitem__(self, item):
|
|
83
|
+
for attrs in self.attrs_list:
|
|
84
|
+
if item in attrs:
|
|
85
|
+
return attrs[item]
|
|
86
|
+
raise KeyError
|
|
87
|
+
|
|
88
|
+
def __setitem__(self, key, value):
|
|
89
|
+
for attrs in self.attrs_list:
|
|
90
|
+
if key in attrs:
|
|
91
|
+
attrs[key] = value
|
|
92
|
+
return
|
|
93
|
+
raise KeyError
|
|
94
|
+
|
|
95
|
+
def get(self, item, default_value):
|
|
96
|
+
try:
|
|
97
|
+
return self[item]
|
|
98
|
+
except KeyError:
|
|
99
|
+
return default_value
|
|
100
|
+
|
|
101
|
+
def push_attrs(self, attrs):
|
|
102
|
+
self.attrs_list.append(attrs)
|
|
103
|
+
|
|
104
|
+
|
|
54
105
|
class FMUError(Exception):
|
|
55
106
|
def __init__(self, reason):
|
|
56
107
|
self.reason = reason
|
|
@@ -61,6 +112,8 @@ class FMUError(Exception):
|
|
|
61
112
|
|
|
62
113
|
class Manipulation:
|
|
63
114
|
"""Parse modelDescription.xml file and create a modified version"""
|
|
115
|
+
TAGS_MODEL_STRUCTURE = ("InitialUnknowns", "Derivatives", "Outputs")
|
|
116
|
+
|
|
64
117
|
def __init__(self, operation, fmu):
|
|
65
118
|
self.output_filename = tempfile.mktemp()
|
|
66
119
|
self.out = None
|
|
@@ -69,13 +122,23 @@ class Manipulation:
|
|
|
69
122
|
self.parser.StartElementHandler = self.start_element
|
|
70
123
|
self.parser.EndElementHandler = self.end_element
|
|
71
124
|
self.parser.CharacterDataHandler = self.char_data
|
|
125
|
+
|
|
126
|
+
# used for filter
|
|
72
127
|
self.skip_until: Optional[str] = None
|
|
128
|
+
|
|
129
|
+
# used to remove empty sections
|
|
130
|
+
self.delayed_tag = None
|
|
131
|
+
self.delayed_tag_open = False
|
|
132
|
+
|
|
73
133
|
self.operation.set_fmu(fmu)
|
|
74
134
|
self.fmu = fmu
|
|
75
135
|
|
|
76
|
-
self.current_port:
|
|
77
|
-
|
|
136
|
+
self.current_port: Optional[FMUPort] = None
|
|
137
|
+
|
|
138
|
+
self.current_port_number: int = 0
|
|
139
|
+
self.port_translation: List[Optional[int]] = []
|
|
78
140
|
self.port_names_list: List[str] = []
|
|
141
|
+
self.port_removed_vr: Set[str] = set()
|
|
79
142
|
self.apply_on = None
|
|
80
143
|
|
|
81
144
|
@staticmethod
|
|
@@ -85,48 +148,65 @@ class Manipulation:
|
|
|
85
148
|
else:
|
|
86
149
|
return value
|
|
87
150
|
|
|
88
|
-
def
|
|
89
|
-
causality =
|
|
90
|
-
port_name =
|
|
151
|
+
def handle_port(self):
|
|
152
|
+
causality = self.current_port.get('causality', 'local')
|
|
153
|
+
port_name = self.current_port['name']
|
|
154
|
+
vr = self.current_port['valueReference']
|
|
91
155
|
if not self.apply_on or causality in self.apply_on:
|
|
92
|
-
if self.operation.
|
|
93
|
-
self.remove_port(port_name)
|
|
156
|
+
if self.operation.port_attrs(self.current_port):
|
|
157
|
+
self.remove_port(port_name, vr)
|
|
158
|
+
# Exception is raised by remove port !
|
|
94
159
|
else:
|
|
95
160
|
self.keep_port(port_name)
|
|
96
161
|
else: # Keep ScalarVariable as it is.
|
|
97
162
|
self.keep_port(port_name)
|
|
98
163
|
|
|
99
|
-
|
|
100
164
|
def start_element(self, name, attrs):
|
|
101
165
|
if self.skip_until:
|
|
102
166
|
return
|
|
167
|
+
|
|
103
168
|
try:
|
|
104
|
-
if name == 'ScalarVariable':
|
|
105
|
-
self.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
self.
|
|
169
|
+
if name == 'ScalarVariable': # FMI 2.0 only
|
|
170
|
+
self.current_port = FMUPort()
|
|
171
|
+
self.current_port.push_attrs(attrs)
|
|
172
|
+
elif self.fmu.fmi_version == 2 and name in self.fmu.FMI2_TYPES:
|
|
173
|
+
if self.current_port: # <Enumeration> can be found before port defition. Ignored.
|
|
174
|
+
self.current_port.fmi_type = name
|
|
175
|
+
self.current_port.push_attrs(attrs)
|
|
176
|
+
elif self.fmu.fmi_version == 3 and name in self.fmu.FMI3_TYPES:
|
|
177
|
+
self.current_port = FMUPort()
|
|
178
|
+
self.current_port.fmi_type = name
|
|
179
|
+
self.current_port.push_attrs(attrs)
|
|
109
180
|
elif name == 'CoSimulation':
|
|
110
181
|
self.operation.cosimulation_attrs(attrs)
|
|
111
182
|
elif name == 'DefaultExperiment':
|
|
112
183
|
self.operation.experiment_attrs(attrs)
|
|
113
184
|
elif name == 'fmiModelDescription':
|
|
185
|
+
self.fmu.fmi_version = int(float(attrs["fmiVersion"]))
|
|
114
186
|
self.operation.fmi_attrs(attrs)
|
|
115
|
-
elif name == 'Unknown':
|
|
187
|
+
elif name == 'Unknown': # FMI-2.0 only
|
|
116
188
|
self.unknown_attrs(attrs)
|
|
117
|
-
elif name
|
|
118
|
-
self.
|
|
189
|
+
elif name == 'Output' or name == "ContinuousStateDerivative" or "InitialUnknown": # FMI-3.0 only
|
|
190
|
+
self.handle_structure(attrs)
|
|
119
191
|
|
|
120
192
|
except ManipulationSkipTag:
|
|
121
193
|
self.skip_until = name
|
|
122
194
|
return
|
|
123
195
|
|
|
124
|
-
if
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
if self.current_port is None:
|
|
197
|
+
if self.delayed_tag and not self.delayed_tag_open:
|
|
198
|
+
print(f"<{self.delayed_tag}>", end='', file=self.out)
|
|
199
|
+
self.delayed_tag_open = True
|
|
200
|
+
|
|
201
|
+
if attrs:
|
|
202
|
+
attrs_list = [f'{key}="{self.escape(value)}"' for (key, value) in attrs.items()]
|
|
203
|
+
print(f"<{name}", " ".join(attrs_list), ">", end='', file=self.out)
|
|
204
|
+
else:
|
|
205
|
+
if name in self.TAGS_MODEL_STRUCTURE:
|
|
206
|
+
self.delayed_tag = name
|
|
207
|
+
self.delayed_tag_open = False
|
|
208
|
+
else:
|
|
209
|
+
print(f"<{name}>", end='', file=self.out)
|
|
130
210
|
|
|
131
211
|
def end_element(self, name):
|
|
132
212
|
if self.skip_until:
|
|
@@ -134,31 +214,106 @@ class Manipulation:
|
|
|
134
214
|
self.skip_until = None
|
|
135
215
|
return
|
|
136
216
|
else:
|
|
137
|
-
|
|
217
|
+
if name == "ScalarVariable" or (self.fmu.fmi_version == 3 and name in FMU.FMI3_TYPES):
|
|
218
|
+
try:
|
|
219
|
+
self.handle_port()
|
|
220
|
+
self.current_port.write_xml(self.fmu.fmi_version, self.out)
|
|
221
|
+
except ManipulationSkipTag:
|
|
222
|
+
logger.info(f"Port '{self.current_port['name']}' is removed.")
|
|
223
|
+
self.current_port = None
|
|
224
|
+
|
|
225
|
+
elif self.current_port is None:
|
|
226
|
+
if self.delayed_tag and name == self.delayed_tag:
|
|
227
|
+
if self.delayed_tag_open:
|
|
228
|
+
print(f"</{self.delayed_tag}>", end='', file=self.out)
|
|
229
|
+
else:
|
|
230
|
+
logger.debug(f"Remove tag <{self.delayed_tag}> from modelDescription.xml")
|
|
231
|
+
self.delayed_tag = None
|
|
232
|
+
else:
|
|
233
|
+
print(f"</{name}>", end='', file=self.out)
|
|
138
234
|
|
|
139
235
|
def char_data(self, data):
|
|
140
236
|
if not self.skip_until:
|
|
141
237
|
print(data, end='', file=self.out)
|
|
142
238
|
|
|
143
|
-
def remove_port(self, name):
|
|
239
|
+
def remove_port(self, name, vr):
|
|
144
240
|
self.port_names_list.append(name)
|
|
145
241
|
self.port_translation.append(None)
|
|
242
|
+
self.port_removed_vr.add(vr)
|
|
146
243
|
raise ManipulationSkipTag
|
|
147
244
|
|
|
148
245
|
def keep_port(self, name):
|
|
149
246
|
self.port_names_list.append(name)
|
|
150
|
-
self.
|
|
151
|
-
self.port_translation.append(self.
|
|
247
|
+
self.current_port_number += 1
|
|
248
|
+
self.port_translation.append(self.current_port_number)
|
|
152
249
|
|
|
153
250
|
def unknown_attrs(self, attrs):
|
|
154
|
-
index = int(attrs['index'])
|
|
155
|
-
new_index = self.port_translation[index]
|
|
156
|
-
if new_index:
|
|
157
|
-
attrs['index'] =
|
|
251
|
+
index = int(attrs['index'])
|
|
252
|
+
new_index = self.port_translation[index-1]
|
|
253
|
+
if new_index is not None:
|
|
254
|
+
attrs['index'] = str(new_index)
|
|
255
|
+
if attrs.get('dependencies', ""):
|
|
256
|
+
if 'dependenciesKind' in attrs:
|
|
257
|
+
new_dependencies = []
|
|
258
|
+
new_kinds = []
|
|
259
|
+
for dependency, kind in zip(attrs['dependencies'].split(' '), attrs['dependenciesKind'].split(' ')):
|
|
260
|
+
new_dependency = self.port_translation[int(dependency)-1]
|
|
261
|
+
if new_dependency is not None:
|
|
262
|
+
new_dependencies.append(str(new_dependency))
|
|
263
|
+
new_kinds.append(kind)
|
|
264
|
+
if new_dependencies:
|
|
265
|
+
attrs['dependencies'] = " ".join(new_dependencies)
|
|
266
|
+
attrs['dependenciesKind'] = " ".join(new_kinds)
|
|
267
|
+
else:
|
|
268
|
+
attrs.pop('dependencies')
|
|
269
|
+
attrs.pop('dependenciesKind')
|
|
270
|
+
else:
|
|
271
|
+
new_dependencies = []
|
|
272
|
+
for dependency in attrs['dependencies'].split(' '):
|
|
273
|
+
new_dependency = self.port_translation[int(dependency)-1]
|
|
274
|
+
if new_dependency is not None:
|
|
275
|
+
new_dependencies.append(str(new_dependency))
|
|
276
|
+
if new_dependencies:
|
|
277
|
+
attrs['dependencies'] = " ".join(new_dependencies)
|
|
278
|
+
else:
|
|
279
|
+
attrs.pop('dependencies')
|
|
158
280
|
else:
|
|
159
|
-
|
|
281
|
+
logger.warning(f"Removed port '{self.port_names_list[index-1]}' is involved in dependencies tree.")
|
|
160
282
|
raise ManipulationSkipTag
|
|
161
283
|
|
|
284
|
+
def handle_structure(self, attrs):
|
|
285
|
+
try:
|
|
286
|
+
vr = attrs['valueReference']
|
|
287
|
+
if vr in self.port_removed_vr:
|
|
288
|
+
logger.warning(f"Removed port vr={vr} is involved in dependencies tree.")
|
|
289
|
+
raise ManipulationSkipTag
|
|
290
|
+
except KeyError:
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
if attrs.get('dependencies', ""):
|
|
294
|
+
if 'dependenciesKind' in attrs:
|
|
295
|
+
new_dependencies = []
|
|
296
|
+
new_kinds = []
|
|
297
|
+
for dependency, kind in zip(attrs['dependencies'].split(' '), attrs['dependenciesKind'].split(' ')):
|
|
298
|
+
if dependency not in self.port_removed_vr:
|
|
299
|
+
new_dependencies.append(dependency)
|
|
300
|
+
new_kinds.append(kind)
|
|
301
|
+
if new_dependencies:
|
|
302
|
+
attrs['dependencies'] = " ".join(new_dependencies)
|
|
303
|
+
attrs['dependenciesKind'] = " ".join(new_kinds)
|
|
304
|
+
else:
|
|
305
|
+
attrs.pop('dependencies')
|
|
306
|
+
attrs.pop('dependenciesKind')
|
|
307
|
+
else:
|
|
308
|
+
new_dependencies = []
|
|
309
|
+
for dependency in attrs['dependencies'].split(' '):
|
|
310
|
+
if dependency not in self.port_removed_vr:
|
|
311
|
+
new_dependencies.append(dependency)
|
|
312
|
+
if new_dependencies:
|
|
313
|
+
attrs['dependencies'] = " ".join(new_dependencies)
|
|
314
|
+
else:
|
|
315
|
+
attrs.pop('dependencies')
|
|
316
|
+
|
|
162
317
|
def manipulate(self, descriptor_filename, apply_on=None):
|
|
163
318
|
self.apply_on = apply_on
|
|
164
319
|
with open(self.output_filename, "w", encoding="utf-8") as self.out, open(descriptor_filename, "rb") as file:
|
|
@@ -181,31 +336,19 @@ class OperationAbstract:
|
|
|
181
336
|
def fmi_attrs(self, attrs):
|
|
182
337
|
pass
|
|
183
338
|
|
|
184
|
-
def scalar_attrs(self, attrs) -> int:
|
|
185
|
-
""" return 0 to keep port, otherwise remove it"""
|
|
186
|
-
return 0
|
|
187
|
-
|
|
188
339
|
def cosimulation_attrs(self, attrs):
|
|
189
340
|
pass
|
|
190
341
|
|
|
191
342
|
def experiment_attrs(self, attrs):
|
|
192
343
|
pass
|
|
193
344
|
|
|
194
|
-
def
|
|
195
|
-
|
|
345
|
+
def port_attrs(self, fmu_port: FMUPort) -> int:
|
|
346
|
+
""" return 0 to keep port, otherwise remove it"""
|
|
347
|
+
return 0
|
|
196
348
|
|
|
197
349
|
def closure(self):
|
|
198
350
|
pass
|
|
199
351
|
|
|
200
|
-
@staticmethod
|
|
201
|
-
def scalar_get_causality(attrs) -> str:
|
|
202
|
-
try:
|
|
203
|
-
causality = attrs['causality']
|
|
204
|
-
except KeyError:
|
|
205
|
-
causality = 'local' # Default value according to FMI Specifications.
|
|
206
|
-
|
|
207
|
-
return causality
|
|
208
|
-
|
|
209
352
|
|
|
210
353
|
class OperationSaveNamesToCSV(OperationAbstract):
|
|
211
354
|
def __repr__(self):
|
|
@@ -217,48 +360,29 @@ class OperationSaveNamesToCSV(OperationAbstract):
|
|
|
217
360
|
self.writer = csv.writer(self.csvfile, delimiter=';', quotechar="'", quoting=csv.QUOTE_MINIMAL)
|
|
218
361
|
self.writer.writerow(['name', 'newName', 'valueReference', 'causality', 'variability', 'scalarType',
|
|
219
362
|
'startValue'])
|
|
220
|
-
self.name = None
|
|
221
|
-
self.vr = None
|
|
222
|
-
self.variability = None
|
|
223
|
-
self.causality = None
|
|
224
|
-
|
|
225
|
-
def reset(self):
|
|
226
|
-
self.name = None
|
|
227
|
-
self.vr = None
|
|
228
|
-
self.variability = None
|
|
229
|
-
self.causality = None
|
|
230
363
|
|
|
231
364
|
def closure(self):
|
|
232
365
|
self.csvfile.close()
|
|
233
366
|
|
|
234
|
-
def
|
|
235
|
-
self.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
self.variability = 'continuous' # Default value according to FMI Specifications.
|
|
367
|
+
def port_attrs(self, fmu_port: FMUPort) -> int:
|
|
368
|
+
self.writer.writerow([fmu_port["name"],
|
|
369
|
+
fmu_port["name"],
|
|
370
|
+
fmu_port["valueReference"],
|
|
371
|
+
fmu_port.get("causality", "local"),
|
|
372
|
+
fmu_port.get("variability", "continuous"),
|
|
373
|
+
fmu_port.fmi_type,
|
|
374
|
+
fmu_port.get("start", "")])
|
|
243
375
|
|
|
244
376
|
return 0
|
|
245
377
|
|
|
246
|
-
def scalar_type(self, type_name, attrs):
|
|
247
|
-
if "start" in attrs:
|
|
248
|
-
start = attrs["start"]
|
|
249
|
-
else:
|
|
250
|
-
start = ""
|
|
251
|
-
self.writer.writerow([self.name, self.name, self.vr, self.causality, self.variability, type_name, start])
|
|
252
|
-
self.reset()
|
|
253
|
-
|
|
254
378
|
|
|
255
379
|
class OperationStripTopLevel(OperationAbstract):
|
|
256
380
|
def __repr__(self):
|
|
257
381
|
return "Remove Top Level Bus"
|
|
258
382
|
|
|
259
|
-
def
|
|
260
|
-
new_name =
|
|
261
|
-
|
|
383
|
+
def port_attrs(self, fmu_port):
|
|
384
|
+
new_name = fmu_port['name'].split('.', 1)[-1]
|
|
385
|
+
fmu_port['name'] = new_name
|
|
262
386
|
return 0
|
|
263
387
|
|
|
264
388
|
|
|
@@ -266,9 +390,9 @@ class OperationMergeTopLevel(OperationAbstract):
|
|
|
266
390
|
def __repr__(self):
|
|
267
391
|
return "Merge Top Level Bus with signal names"
|
|
268
392
|
|
|
269
|
-
def
|
|
270
|
-
old =
|
|
271
|
-
|
|
393
|
+
def port_attrs(self, fmu_port):
|
|
394
|
+
old = fmu_port['name']
|
|
395
|
+
fmu_port['name'] = old.replace('.', '_', 1)
|
|
272
396
|
return 0
|
|
273
397
|
|
|
274
398
|
|
|
@@ -279,88 +403,32 @@ class OperationRenameFromCSV(OperationAbstract):
|
|
|
279
403
|
def __init__(self, csv_filename):
|
|
280
404
|
self.csv_filename = csv_filename
|
|
281
405
|
self.translations = {}
|
|
282
|
-
|
|
283
|
-
self.port_translation = []
|
|
406
|
+
|
|
284
407
|
try:
|
|
285
408
|
with open(csv_filename, newline='') as csvfile:
|
|
286
409
|
reader = csv.reader(csvfile, delimiter=';', quotechar="'")
|
|
287
410
|
for row in reader:
|
|
288
411
|
self.translations[row[0]] = row[1]
|
|
289
412
|
except FileNotFoundError:
|
|
290
|
-
raise
|
|
413
|
+
raise OperationError(f"file '{csv_filename}' is not found")
|
|
291
414
|
except KeyError:
|
|
292
|
-
raise
|
|
415
|
+
raise OperationError(f"file '{csv_filename}' should contain two columns")
|
|
293
416
|
|
|
294
|
-
def
|
|
295
|
-
name =
|
|
417
|
+
def port_attrs(self, fmu_port):
|
|
418
|
+
name = fmu_port['name']
|
|
296
419
|
try:
|
|
297
|
-
new_name = self.translations[
|
|
420
|
+
new_name = self.translations[fmu_port['name']]
|
|
298
421
|
except KeyError:
|
|
299
422
|
new_name = name # if port is not in CSV file, keep old name
|
|
300
423
|
|
|
301
424
|
if new_name:
|
|
302
|
-
|
|
425
|
+
fmu_port['name'] = new_name
|
|
303
426
|
return 0
|
|
304
427
|
else:
|
|
305
428
|
# we want to delete this name!
|
|
306
429
|
return 1
|
|
307
430
|
|
|
308
431
|
|
|
309
|
-
class OperationAddRemotingWinAbstract(OperationAbstract):
|
|
310
|
-
bitness_from = None
|
|
311
|
-
bitness_to = None
|
|
312
|
-
|
|
313
|
-
def __repr__(self):
|
|
314
|
-
return f"Add '{self.bitness_to}' remoting on '{self.bitness_from}' FMU"
|
|
315
|
-
|
|
316
|
-
def cosimulation_attrs(self, attrs):
|
|
317
|
-
fmu_bin = {
|
|
318
|
-
"win32": os.path.join(self.fmu.tmp_directory, "binaries", f"win32"),
|
|
319
|
-
"win64": os.path.join(self.fmu.tmp_directory, "binaries", f"win64"),
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if not os.path.isdir(fmu_bin[self.bitness_from]):
|
|
323
|
-
raise OperationException(f"{self.bitness_from} interface does not exist")
|
|
324
|
-
|
|
325
|
-
if os.path.isdir(fmu_bin[self.bitness_to]):
|
|
326
|
-
print(f"INFO: {self.bitness_to} already exists. Add front-end.")
|
|
327
|
-
shutil.move(os.path.join(fmu_bin[self.bitness_to], attrs['modelIdentifier'] + ".dll"),
|
|
328
|
-
os.path.join(fmu_bin[self.bitness_to], attrs['modelIdentifier'] + "-remoted.dll"))
|
|
329
|
-
else:
|
|
330
|
-
os.mkdir(fmu_bin[self.bitness_to])
|
|
331
|
-
|
|
332
|
-
to_path = Path(__file__).parent / "resources" / self.bitness_to
|
|
333
|
-
shutil.copyfile(to_path / "client_sm.dll",
|
|
334
|
-
Path(fmu_bin[self.bitness_to]) / Path(attrs['modelIdentifier']).with_suffix(".dll"))
|
|
335
|
-
|
|
336
|
-
from_path = Path(__file__).parent / "resources" / self.bitness_from
|
|
337
|
-
shutil.copyfile(from_path / "server_sm.exe",
|
|
338
|
-
Path(fmu_bin[self.bitness_from]) / "server_sm.exe")
|
|
339
|
-
|
|
340
|
-
shutil.copyfile(Path(__file__).parent / "resources" / "license.txt",
|
|
341
|
-
Path(fmu_bin[self.bitness_to]) / "license.txt")
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
class OperationAddRemotingWin64(OperationAddRemotingWinAbstract):
|
|
345
|
-
bitness_from = "win32"
|
|
346
|
-
bitness_to = "win64"
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
class OperationAddFrontendWin32(OperationAddRemotingWinAbstract):
|
|
350
|
-
bitness_from = "win32"
|
|
351
|
-
bitness_to = "win32"
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
class OperationAddFrontendWin64(OperationAddRemotingWinAbstract):
|
|
355
|
-
bitness_from = "win64"
|
|
356
|
-
bitness_to = "win64"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
class OperationAddRemotingWin32(OperationAddRemotingWinAbstract):
|
|
360
|
-
bitness_from = "win64"
|
|
361
|
-
bitness_to = "win32"
|
|
362
|
-
|
|
363
|
-
|
|
364
432
|
class OperationRemoveRegexp(OperationAbstract):
|
|
365
433
|
def __repr__(self):
|
|
366
434
|
return f"Remove ports matching '{self.regex_string}'"
|
|
@@ -368,11 +436,11 @@ class OperationRemoveRegexp(OperationAbstract):
|
|
|
368
436
|
def __init__(self, regex_string):
|
|
369
437
|
self.regex_string = regex_string
|
|
370
438
|
self.regex = re.compile(regex_string)
|
|
371
|
-
self.
|
|
439
|
+
self.current_port_number = 0
|
|
372
440
|
self.port_translation = []
|
|
373
441
|
|
|
374
|
-
def
|
|
375
|
-
name =
|
|
442
|
+
def port_attrs(self, fmu_port):
|
|
443
|
+
name = fmu_port['name']
|
|
376
444
|
if self.regex.match(name):
|
|
377
445
|
return 1 # Remove port
|
|
378
446
|
else:
|
|
@@ -387,8 +455,8 @@ class OperationKeepOnlyRegexp(OperationAbstract):
|
|
|
387
455
|
self.regex_string = regex_string
|
|
388
456
|
self.regex = re.compile(regex_string)
|
|
389
457
|
|
|
390
|
-
def
|
|
391
|
-
name =
|
|
458
|
+
def port_attrs(self, fmu_port):
|
|
459
|
+
name = fmu_port['name']
|
|
392
460
|
if self.regex.match(name):
|
|
393
461
|
return 0
|
|
394
462
|
else:
|
|
@@ -403,34 +471,34 @@ class OperationSummary(OperationAbstract):
|
|
|
403
471
|
return f"FMU Summary"
|
|
404
472
|
|
|
405
473
|
def fmi_attrs(self, attrs):
|
|
406
|
-
|
|
407
|
-
|
|
474
|
+
logger.info(f"| fmu filename = {self.fmu.fmu_filename}")
|
|
475
|
+
logger.info(f"| temporary directory = {self.fmu.tmp_directory}")
|
|
408
476
|
hash_md5 = hashlib.md5()
|
|
409
477
|
with open(self.fmu.fmu_filename, "rb") as f:
|
|
410
478
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
411
479
|
hash_md5.update(chunk)
|
|
412
480
|
digest = hash_md5.hexdigest()
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
481
|
+
logger.info(f"| MD5Sum = {digest}")
|
|
482
|
+
logger.info(f"|")
|
|
483
|
+
logger.info(f"| FMI properties: ")
|
|
416
484
|
for (k, v) in attrs.items():
|
|
417
|
-
|
|
418
|
-
|
|
485
|
+
logger.info(f"| - {k} = {v}")
|
|
486
|
+
logger.info(f"|")
|
|
419
487
|
|
|
420
488
|
def cosimulation_attrs(self, attrs):
|
|
421
|
-
|
|
489
|
+
logger.info("| Co-Simulation capabilities: ")
|
|
422
490
|
for (k, v) in attrs.items():
|
|
423
|
-
|
|
424
|
-
|
|
491
|
+
logger.info(f"| - {k} = {v}")
|
|
492
|
+
logger.info(f"|")
|
|
425
493
|
|
|
426
494
|
def experiment_attrs(self, attrs):
|
|
427
|
-
|
|
495
|
+
logger.info("| Default Experiment values: ")
|
|
428
496
|
for (k, v) in attrs.items():
|
|
429
|
-
|
|
430
|
-
|
|
497
|
+
logger.info(f"| - {k} = {v}")
|
|
498
|
+
logger.info(f"|")
|
|
431
499
|
|
|
432
|
-
def
|
|
433
|
-
causality =
|
|
500
|
+
def port_attrs(self, fmu_port) -> int:
|
|
501
|
+
causality = fmu_port.get("causality", "local")
|
|
434
502
|
|
|
435
503
|
try:
|
|
436
504
|
self.nb_port_per_causality[causality] += 1
|
|
@@ -440,33 +508,37 @@ class OperationSummary(OperationAbstract):
|
|
|
440
508
|
return 0
|
|
441
509
|
|
|
442
510
|
def closure(self):
|
|
443
|
-
|
|
511
|
+
logger.info("| Supported platforms: ")
|
|
444
512
|
try:
|
|
445
513
|
for platform in os.listdir(os.path.join(self.fmu.tmp_directory, "binaries")):
|
|
446
|
-
|
|
514
|
+
logger.info(f"| - {platform}")
|
|
447
515
|
except FileNotFoundError:
|
|
448
516
|
pass # no binaries
|
|
449
517
|
|
|
450
518
|
if os.path.isdir(os.path.join(self.fmu.tmp_directory, "sources")):
|
|
451
|
-
|
|
519
|
+
logger.info(f"| - RT (sources available)")
|
|
452
520
|
|
|
453
521
|
resource_dir = os.path.join(self.fmu.tmp_directory, "resources")
|
|
454
522
|
if os.path.isdir(resource_dir):
|
|
455
|
-
|
|
523
|
+
logger.info("|")
|
|
524
|
+
logger.info("| Embedded resources:")
|
|
456
525
|
for resource in os.listdir(resource_dir):
|
|
457
|
-
|
|
526
|
+
logger.info(f"| - {resource}")
|
|
458
527
|
|
|
459
528
|
extra_dir = os.path.join(self.fmu.tmp_directory, "extra")
|
|
460
529
|
if os.path.isdir(extra_dir):
|
|
461
|
-
|
|
530
|
+
logger.info("|")
|
|
531
|
+
logger.info("| Additional (meta-)data:")
|
|
462
532
|
for extra in os.listdir(extra_dir):
|
|
463
|
-
|
|
533
|
+
logger.info(f"| - {extra}")
|
|
464
534
|
|
|
465
|
-
|
|
535
|
+
logger.info("|")
|
|
536
|
+
logger.info("| Number of ports")
|
|
466
537
|
for causality, nb_ports in self.nb_port_per_causality.items():
|
|
467
|
-
|
|
538
|
+
logger.info(f"| {causality} : {nb_ports}")
|
|
468
539
|
|
|
469
|
-
|
|
540
|
+
logger.info("|")
|
|
541
|
+
logger.info("| [End of report]")
|
|
470
542
|
|
|
471
543
|
|
|
472
544
|
class OperationRemoveSources(OperationAbstract):
|
|
@@ -477,7 +549,7 @@ class OperationRemoveSources(OperationAbstract):
|
|
|
477
549
|
try:
|
|
478
550
|
shutil.rmtree(os.path.join(self.fmu.tmp_directory, "sources"))
|
|
479
551
|
except FileNotFoundError:
|
|
480
|
-
|
|
552
|
+
logger.info("This FMU does not embed sources.")
|
|
481
553
|
|
|
482
554
|
|
|
483
555
|
class OperationTrimUntil(OperationAbstract):
|
|
@@ -487,17 +559,17 @@ class OperationTrimUntil(OperationAbstract):
|
|
|
487
559
|
def __repr__(self):
|
|
488
560
|
return f"Trim names until (and including) '{self.separator}'"
|
|
489
561
|
|
|
490
|
-
def
|
|
491
|
-
name =
|
|
562
|
+
def port_attrs(self, fmu_port) -> int:
|
|
563
|
+
name = fmu_port['name']
|
|
492
564
|
try:
|
|
493
|
-
|
|
565
|
+
fmu_port['name'] = name[name.index(self.separator)+len(self.separator):-1]
|
|
494
566
|
except KeyError:
|
|
495
567
|
pass # no separator
|
|
496
568
|
|
|
497
569
|
return 0
|
|
498
570
|
|
|
499
571
|
|
|
500
|
-
class
|
|
572
|
+
class OperationError(Exception):
|
|
501
573
|
def __init__(self, reason):
|
|
502
574
|
self.reason = reason
|
|
503
575
|
|