fmu-manipulation-toolbox 1.7.5__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 +1 -0
- fmu_manipulation_toolbox/__main__.py +25 -0
- fmu_manipulation_toolbox/__version__.py +1 -0
- fmu_manipulation_toolbox/checker.py +61 -0
- fmu_manipulation_toolbox/cli.py +216 -0
- fmu_manipulation_toolbox/fmu_container.py +784 -0
- fmu_manipulation_toolbox/fmu_operations.py +489 -0
- fmu_manipulation_toolbox/gui.py +493 -0
- fmu_manipulation_toolbox/help.py +87 -0
- fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png +0 -0
- fmu_manipulation_toolbox/resources/checkbox-checked-hover.png +0 -0
- fmu_manipulation_toolbox/resources/checkbox-checked.png +0 -0
- fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png +0 -0
- fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png +0 -0
- fmu_manipulation_toolbox/resources/checkbox-unchecked.png +0 -0
- fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd +58 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd +78 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd +345 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd +218 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd +89 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd +116 -0
- fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd +92 -0
- fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox/resources/help.png +0 -0
- fmu_manipulation_toolbox/resources/icon.png +0 -0
- fmu_manipulation_toolbox/resources/license.txt +34 -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/model.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/version.py +9 -0
- fmu_manipulation_toolbox-1.7.5.dist-info/LICENSE.txt +22 -0
- fmu_manipulation_toolbox-1.7.5.dist-info/METADATA +20 -0
- fmu_manipulation_toolbox-1.7.5.dist-info/RECORD +46 -0
- fmu_manipulation_toolbox-1.7.5.dist-info/WHEEL +5 -0
- fmu_manipulation_toolbox-1.7.5.dist-info/entry_points.txt +3 -0
- fmu_manipulation_toolbox-1.7.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import html
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
import tempfile
|
|
7
|
+
import xml.parsers.expat
|
|
8
|
+
import zipfile
|
|
9
|
+
import hashlib
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FMU:
|
|
14
|
+
"""Unpack and Repack facilities for FMU package. Once unpacked, we can process Operation on
|
|
15
|
+
modelDescription.xml file."""
|
|
16
|
+
def __init__(self, fmu_filename):
|
|
17
|
+
self.fmu_filename = fmu_filename
|
|
18
|
+
self.tmp_directory = tempfile.mkdtemp()
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
with zipfile.ZipFile(self.fmu_filename) as zin:
|
|
22
|
+
zin.extractall(self.tmp_directory)
|
|
23
|
+
except FileNotFoundError:
|
|
24
|
+
raise FMUException(f"'{fmu_filename}' does not exist")
|
|
25
|
+
self.descriptor_filename = os.path.join(self.tmp_directory, "modelDescription.xml")
|
|
26
|
+
if not os.path.isfile(self.descriptor_filename):
|
|
27
|
+
raise FMUException(f"'{fmu_filename}' is not valid: {self.descriptor_filename} not found")
|
|
28
|
+
|
|
29
|
+
def __del__(self):
|
|
30
|
+
shutil.rmtree(self.tmp_directory)
|
|
31
|
+
|
|
32
|
+
def save_descriptor(self, filename):
|
|
33
|
+
shutil.copyfile(os.path.join(self.tmp_directory, "modelDescription.xml"), filename)
|
|
34
|
+
|
|
35
|
+
def repack(self, filename):
|
|
36
|
+
with zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) as zout:
|
|
37
|
+
for root, dirs, files in os.walk(self.tmp_directory):
|
|
38
|
+
for file in files:
|
|
39
|
+
zout.write(os.path.join(root, file),
|
|
40
|
+
os.path.relpath(os.path.join(root, file), self.tmp_directory))
|
|
41
|
+
# TODO: Add check on output file
|
|
42
|
+
|
|
43
|
+
def apply_operation(self, operation, apply_on=None):
|
|
44
|
+
manipulation = Manipulation(operation, self)
|
|
45
|
+
manipulation.manipulate(self.descriptor_filename, apply_on)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FMUException(Exception):
|
|
49
|
+
def __init__(self, reason):
|
|
50
|
+
self.reason = reason
|
|
51
|
+
|
|
52
|
+
def __repr__(self):
|
|
53
|
+
return self.reason
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Manipulation:
|
|
57
|
+
"""Parse modelDescription.xml file and create a modified version"""
|
|
58
|
+
def __init__(self, operation, fmu):
|
|
59
|
+
self.output_filename = tempfile.mktemp()
|
|
60
|
+
self.out = None
|
|
61
|
+
self.operation = operation
|
|
62
|
+
self.parser = xml.parsers.expat.ParserCreate()
|
|
63
|
+
self.parser.StartElementHandler = self.start_element
|
|
64
|
+
self.parser.EndElementHandler = self.end_element
|
|
65
|
+
self.parser.CharacterDataHandler = self.char_data
|
|
66
|
+
self.skip_until = None
|
|
67
|
+
self.operation.set_fmu(fmu)
|
|
68
|
+
|
|
69
|
+
self.current_port = 0
|
|
70
|
+
self.port_translation = []
|
|
71
|
+
self.port_name = []
|
|
72
|
+
self.apply_on = None
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def escape(value):
|
|
76
|
+
if isinstance(value, str):
|
|
77
|
+
return html.escape(html.unescape(value))
|
|
78
|
+
else:
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
def start_element(self, name, attrs):
|
|
82
|
+
if self.skip_until:
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
if name == 'ScalarVariable':
|
|
86
|
+
causality = OperationAbstract.scalar_get_causality(attrs)
|
|
87
|
+
if not self.apply_on or causality in self.apply_on:
|
|
88
|
+
if self.operation.scalar_attrs(attrs):
|
|
89
|
+
self.remove_port(attrs['name'])
|
|
90
|
+
else:
|
|
91
|
+
self.keep_port(attrs['name'])
|
|
92
|
+
else:
|
|
93
|
+
self.keep_port(attrs['name'])
|
|
94
|
+
self.skip_until = name # do not read inner tags
|
|
95
|
+
elif name == 'CoSimulation':
|
|
96
|
+
self.operation.cosimulation_attrs(attrs)
|
|
97
|
+
elif name == 'DefaultExperiment':
|
|
98
|
+
self.operation.experiment_attrs(attrs)
|
|
99
|
+
elif name == 'fmiModelDescription':
|
|
100
|
+
self.operation.fmi_attrs(attrs)
|
|
101
|
+
elif name == 'Unknown':
|
|
102
|
+
self.unknown_attrs(attrs)
|
|
103
|
+
elif name in ('Real', 'Integer', 'String', 'Boolean'):
|
|
104
|
+
self.operation.scalar_type(name, attrs)
|
|
105
|
+
|
|
106
|
+
except ManipulationSkipTag:
|
|
107
|
+
self.skip_until = name
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if attrs:
|
|
111
|
+
attrs_list = [f'{key}="{self.escape(value)}"' for (key, value) in attrs.items()]
|
|
112
|
+
print(f"<{name}", " ".join(attrs_list), ">", end='', file=self.out)
|
|
113
|
+
else:
|
|
114
|
+
print(f"<{name}>", end='', file=self.out)
|
|
115
|
+
|
|
116
|
+
def end_element(self, name):
|
|
117
|
+
if self.skip_until:
|
|
118
|
+
if self.skip_until == name:
|
|
119
|
+
self.skip_until = None
|
|
120
|
+
return
|
|
121
|
+
else:
|
|
122
|
+
print(f"</{name}>", end='', file=self.out)
|
|
123
|
+
|
|
124
|
+
def char_data(self, data):
|
|
125
|
+
if not self.skip_until:
|
|
126
|
+
print(data, end='', file=self.out)
|
|
127
|
+
|
|
128
|
+
def remove_port(self, name):
|
|
129
|
+
self.port_name.append(name)
|
|
130
|
+
self.port_translation.append(None)
|
|
131
|
+
raise ManipulationSkipTag
|
|
132
|
+
|
|
133
|
+
def keep_port(self, name):
|
|
134
|
+
self.port_name.append(name)
|
|
135
|
+
self.current_port += 1
|
|
136
|
+
self.port_translation.append(self.current_port)
|
|
137
|
+
|
|
138
|
+
def unknown_attrs(self, attrs):
|
|
139
|
+
index = int(attrs['index']) - 1
|
|
140
|
+
new_index = self.port_translation[index]
|
|
141
|
+
if new_index:
|
|
142
|
+
attrs['index'] = self.port_translation[int(attrs['index']) - 1]
|
|
143
|
+
else:
|
|
144
|
+
print(f"WARNING: Removed port '{self.port_name[index]}' is involved in dependencies tree.")
|
|
145
|
+
raise ManipulationSkipTag
|
|
146
|
+
|
|
147
|
+
def manipulate(self, descriptor_filename, apply_on=None):
|
|
148
|
+
self.apply_on = apply_on
|
|
149
|
+
with open(self.output_filename, "w", encoding="utf-8") as self.out, open(descriptor_filename, "rb") as file:
|
|
150
|
+
self.parser.ParseFile(file)
|
|
151
|
+
self.operation.closure()
|
|
152
|
+
os.replace(self.output_filename, descriptor_filename)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ManipulationSkipTag(Exception):
|
|
156
|
+
"""Exception: We need to skip every thing until matching closing tag"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class OperationAbstract:
|
|
160
|
+
"""This class hold hooks called during parsing"""
|
|
161
|
+
fmu: FMU = None
|
|
162
|
+
|
|
163
|
+
def set_fmu(self, fmu):
|
|
164
|
+
self.fmu = fmu
|
|
165
|
+
|
|
166
|
+
def fmi_attrs(self, attrs):
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
def scalar_attrs(self, attrs) -> int:
|
|
170
|
+
""" return 0 to keep port, otherwise remove it"""
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
def cosimulation_attrs(self, attrs):
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
def experiment_attrs(self, attrs):
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
def scalar_type(self, type_name, attrs):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
def closure(self):
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def scalar_get_causality(attrs) -> str:
|
|
187
|
+
try:
|
|
188
|
+
causality = attrs['causality']
|
|
189
|
+
except KeyError:
|
|
190
|
+
causality = 'local' # Default value according to FMI Specifications.
|
|
191
|
+
|
|
192
|
+
return causality
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class OperationSaveNamesToCSV(OperationAbstract):
|
|
196
|
+
def __repr__(self):
|
|
197
|
+
return f"Dump names into '{self.output_filename}'"
|
|
198
|
+
|
|
199
|
+
def __init__(self, filename):
|
|
200
|
+
self.output_filename = filename
|
|
201
|
+
self.csvfile = open(filename, 'w', newline='')
|
|
202
|
+
self.writer = csv.writer(self.csvfile, delimiter=';', quotechar="'", quoting=csv.QUOTE_MINIMAL)
|
|
203
|
+
self.writer.writerow(['name', 'newName', 'valueReference', 'causality', 'variability', 'scalarType',
|
|
204
|
+
'startValue'])
|
|
205
|
+
self.name = None
|
|
206
|
+
self.vr = None
|
|
207
|
+
self.variability = None
|
|
208
|
+
self.causality = None
|
|
209
|
+
|
|
210
|
+
def reset(self):
|
|
211
|
+
self.name = None
|
|
212
|
+
self.vr = None
|
|
213
|
+
self.variability = None
|
|
214
|
+
self.causality = None
|
|
215
|
+
|
|
216
|
+
def closure(self):
|
|
217
|
+
self.csvfile.close()
|
|
218
|
+
|
|
219
|
+
def scalar_attrs(self, attrs):
|
|
220
|
+
self.name = attrs['name']
|
|
221
|
+
self.vr = attrs['valueReference']
|
|
222
|
+
self.causality = self.scalar_get_causality(attrs)
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
self.variability = attrs['variability']
|
|
226
|
+
except KeyError:
|
|
227
|
+
self.variability = 'continuous' # Default value according to FMI Specifications.
|
|
228
|
+
|
|
229
|
+
return 0
|
|
230
|
+
|
|
231
|
+
def scalar_type(self, type_name, attrs):
|
|
232
|
+
if "start" in attrs:
|
|
233
|
+
start = attrs["start"]
|
|
234
|
+
else:
|
|
235
|
+
start = ""
|
|
236
|
+
self.writer.writerow([self.name, self.name, self.vr, self.causality, self.variability, type_name, start])
|
|
237
|
+
self.reset()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class OperationStripTopLevel(OperationAbstract):
|
|
241
|
+
def __repr__(self):
|
|
242
|
+
return "Remove Top Level Bus"
|
|
243
|
+
|
|
244
|
+
def scalar_attrs(self, attrs):
|
|
245
|
+
new_name = attrs['name'].split('.', 1)[-1]
|
|
246
|
+
attrs['name'] = new_name
|
|
247
|
+
return 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class OperationMergeTopLevel(OperationAbstract):
|
|
251
|
+
def __repr__(self):
|
|
252
|
+
return "Merge Top Level Bus with signal names"
|
|
253
|
+
|
|
254
|
+
def scalar_attrs(self, attrs):
|
|
255
|
+
old = attrs['name']
|
|
256
|
+
attrs['name'] = old.replace('.', '_', 1)
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class OperationRenameFromCSV(OperationAbstract):
|
|
261
|
+
def __repr__(self):
|
|
262
|
+
return f"Rename according to '{self.csv_filename}'"
|
|
263
|
+
|
|
264
|
+
def __init__(self, csv_filename):
|
|
265
|
+
self.csv_filename = csv_filename
|
|
266
|
+
self.translations = {}
|
|
267
|
+
self.current_port = 0
|
|
268
|
+
self.port_translation = []
|
|
269
|
+
try:
|
|
270
|
+
with open(csv_filename, newline='') as csvfile:
|
|
271
|
+
reader = csv.reader(csvfile, delimiter=';', quotechar="'")
|
|
272
|
+
for row in reader:
|
|
273
|
+
self.translations[row[0]] = row[1]
|
|
274
|
+
except FileNotFoundError:
|
|
275
|
+
raise OperationException(f"file '{csv_filename}' is not found")
|
|
276
|
+
except KeyError:
|
|
277
|
+
raise OperationException(f"file '{csv_filename}' should contain two columns")
|
|
278
|
+
|
|
279
|
+
def scalar_attrs(self, attrs):
|
|
280
|
+
name = attrs['name']
|
|
281
|
+
try:
|
|
282
|
+
new_name = self.translations[attrs['name']]
|
|
283
|
+
except KeyError:
|
|
284
|
+
new_name = name # if port is not in CSV file, keep old name
|
|
285
|
+
|
|
286
|
+
if new_name:
|
|
287
|
+
attrs['name'] = new_name
|
|
288
|
+
return 0
|
|
289
|
+
else:
|
|
290
|
+
# we want to delete this name!
|
|
291
|
+
return 1
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class OperationAddRemotingWinAbstract(OperationAbstract):
|
|
295
|
+
bitness_from = None
|
|
296
|
+
bitness_to = None
|
|
297
|
+
|
|
298
|
+
def __repr__(self):
|
|
299
|
+
return f"Add '{self.bitness_to}' remoting on '{self.bitness_from}' FMU"
|
|
300
|
+
|
|
301
|
+
def cosimulation_attrs(self, attrs):
|
|
302
|
+
fmu_bin = {
|
|
303
|
+
"win32": os.path.join(self.fmu.tmp_directory, "binaries", f"win32"),
|
|
304
|
+
"win64": os.path.join(self.fmu.tmp_directory, "binaries", f"win64"),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if not os.path.isdir(fmu_bin[self.bitness_from]):
|
|
308
|
+
raise OperationException(f"{self.bitness_from} interface does not exist")
|
|
309
|
+
|
|
310
|
+
if os.path.isdir(fmu_bin[self.bitness_to]):
|
|
311
|
+
print(f"INFO: {self.bitness_to} already exists. Add front-end.")
|
|
312
|
+
shutil.move(os.path.join(fmu_bin[self.bitness_to], attrs['modelIdentifier'] + ".dll"),
|
|
313
|
+
os.path.join(fmu_bin[self.bitness_to], attrs['modelIdentifier'] + "-remoted.dll"))
|
|
314
|
+
else:
|
|
315
|
+
os.mkdir(fmu_bin[self.bitness_to])
|
|
316
|
+
|
|
317
|
+
from_path = Path(__file__).parent / "resources" / self.bitness_to
|
|
318
|
+
shutil.copyfile(from_path / "client_sm.dll",
|
|
319
|
+
Path(fmu_bin[self.bitness_to]) / Path(attrs['modelIdentifier']).with_suffix(".dll"))
|
|
320
|
+
|
|
321
|
+
shutil.copyfile(from_path / "server_sm.exe",
|
|
322
|
+
Path(fmu_bin[self.bitness_from]) / "server_sm.exe")
|
|
323
|
+
|
|
324
|
+
shutil.copyfile(Path(__file__).parent / "resources" / "license.txt",
|
|
325
|
+
Path(fmu_bin[self.bitness_to]) / "license.txt")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class OperationAddRemotingWin64(OperationAddRemotingWinAbstract):
|
|
329
|
+
bitness_from = "win32"
|
|
330
|
+
bitness_to = "win64"
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class OperationAddFrontendWin32(OperationAddRemotingWinAbstract):
|
|
334
|
+
bitness_from = "win32"
|
|
335
|
+
bitness_to = "win32"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class OperationAddFrontendWin64(OperationAddRemotingWinAbstract):
|
|
339
|
+
bitness_from = "win64"
|
|
340
|
+
bitness_to = "win64"
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class OperationAddRemotingWin32(OperationAddRemotingWinAbstract):
|
|
344
|
+
bitness_from = "win64"
|
|
345
|
+
bitness_to = "win32"
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class OperationRemoveRegexp(OperationAbstract):
|
|
349
|
+
def __repr__(self):
|
|
350
|
+
return f"Remove ports matching '{self.regex_string}'"
|
|
351
|
+
|
|
352
|
+
def __init__(self, regex_string):
|
|
353
|
+
self.regex_string = regex_string
|
|
354
|
+
self.regex = re.compile(regex_string)
|
|
355
|
+
self.current_port = 0
|
|
356
|
+
self.port_translation = []
|
|
357
|
+
|
|
358
|
+
def scalar_attrs(self, attrs):
|
|
359
|
+
name = attrs['name']
|
|
360
|
+
if self.regex.match(name):
|
|
361
|
+
return 1 # Remove port
|
|
362
|
+
else:
|
|
363
|
+
return 0
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class OperationKeepOnlyRegexp(OperationAbstract):
|
|
367
|
+
def __repr__(self):
|
|
368
|
+
return f"Keep only ports matching '{self.regex_string}'"
|
|
369
|
+
|
|
370
|
+
def __init__(self, regex_string):
|
|
371
|
+
self.regex_string = regex_string
|
|
372
|
+
self.regex = re.compile(regex_string)
|
|
373
|
+
|
|
374
|
+
def scalar_attrs(self, attrs):
|
|
375
|
+
name = attrs['name']
|
|
376
|
+
if self.regex.match(name):
|
|
377
|
+
return 0
|
|
378
|
+
else:
|
|
379
|
+
return 1 # Remove port
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class OperationSummary(OperationAbstract):
|
|
383
|
+
def __init__(self):
|
|
384
|
+
self.nb_port_per_causality = {}
|
|
385
|
+
|
|
386
|
+
def __repr__(self):
|
|
387
|
+
return f"FMU Summary"
|
|
388
|
+
|
|
389
|
+
def fmi_attrs(self, attrs):
|
|
390
|
+
print(f"| fmu filename = {self.fmu.fmu_filename}")
|
|
391
|
+
print(f"| temporary directory = {self.fmu.tmp_directory}")
|
|
392
|
+
hash_md5 = hashlib.md5()
|
|
393
|
+
with open(self.fmu.fmu_filename, "rb") as f:
|
|
394
|
+
for chunk in iter(lambda: f.read(4096), b""):
|
|
395
|
+
hash_md5.update(chunk)
|
|
396
|
+
digest = hash_md5.hexdigest()
|
|
397
|
+
print(f"| MD5Sum = {digest}")
|
|
398
|
+
|
|
399
|
+
print(f"|\n| FMI properties: ")
|
|
400
|
+
for (k, v) in attrs.items():
|
|
401
|
+
print(f"| - {k} = {v}")
|
|
402
|
+
print(f"|")
|
|
403
|
+
|
|
404
|
+
def cosimulation_attrs(self, attrs):
|
|
405
|
+
print("| Co-Simulation capabilities: ")
|
|
406
|
+
for (k, v) in attrs.items():
|
|
407
|
+
print(f"| - {k} = {v}")
|
|
408
|
+
print(f"|")
|
|
409
|
+
|
|
410
|
+
def experiment_attrs(self, attrs):
|
|
411
|
+
print("| Default Experiment values: ")
|
|
412
|
+
for (k, v) in attrs.items():
|
|
413
|
+
print(f"| - {k} = {v}")
|
|
414
|
+
print(f"|")
|
|
415
|
+
|
|
416
|
+
def scalar_attrs(self, attrs) -> int:
|
|
417
|
+
causality = self.scalar_get_causality(attrs)
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
self.nb_port_per_causality[causality] += 1
|
|
421
|
+
except KeyError:
|
|
422
|
+
self.nb_port_per_causality[causality] = 1
|
|
423
|
+
|
|
424
|
+
return 0
|
|
425
|
+
|
|
426
|
+
def closure(self):
|
|
427
|
+
print("| Supported platforms: ")
|
|
428
|
+
try:
|
|
429
|
+
for platform in os.listdir(os.path.join(self.fmu.tmp_directory, "binaries")):
|
|
430
|
+
print(f"| - {platform}")
|
|
431
|
+
except FileNotFoundError:
|
|
432
|
+
pass # no binaries
|
|
433
|
+
|
|
434
|
+
if os.path.isdir(os.path.join(self.fmu.tmp_directory, "sources")):
|
|
435
|
+
print(f"| - RT (sources available)")
|
|
436
|
+
|
|
437
|
+
resource_dir = os.path.join(self.fmu.tmp_directory, "resources")
|
|
438
|
+
if os.path.isdir(resource_dir):
|
|
439
|
+
print("|\n| Embedded resources:")
|
|
440
|
+
for resource in os.listdir(resource_dir):
|
|
441
|
+
print(f"| - {resource}")
|
|
442
|
+
|
|
443
|
+
extra_dir = os.path.join(self.fmu.tmp_directory, "extra")
|
|
444
|
+
if os.path.isdir(extra_dir):
|
|
445
|
+
print("|\n| Additional (meta-)data:")
|
|
446
|
+
for extra in os.listdir(extra_dir):
|
|
447
|
+
print(f"| - {extra}")
|
|
448
|
+
|
|
449
|
+
print("|\n| Number of signals")
|
|
450
|
+
for causality, nb_ports in self.nb_port_per_causality.items():
|
|
451
|
+
print(f"| {causality} : {nb_ports}")
|
|
452
|
+
|
|
453
|
+
print("|\n| [End of report]")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class OperationRemoveSources(OperationAbstract):
|
|
457
|
+
def __repr__(self):
|
|
458
|
+
return f"Remove sources"
|
|
459
|
+
|
|
460
|
+
def cosimulation_attrs(self, attrs):
|
|
461
|
+
try:
|
|
462
|
+
shutil.rmtree(os.path.join(self.fmu.tmp_directory, "sources"))
|
|
463
|
+
except FileNotFoundError:
|
|
464
|
+
print("This FMU does not embed sources.")
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class OperationTrimUntil(OperationAbstract):
|
|
468
|
+
def __init__(self, separator):
|
|
469
|
+
self.separator = separator
|
|
470
|
+
|
|
471
|
+
def __repr__(self):
|
|
472
|
+
return f"Trim names until (and including) '{self.separator}'"
|
|
473
|
+
|
|
474
|
+
def scalar_attrs(self, attrs) -> int:
|
|
475
|
+
name = attrs['name']
|
|
476
|
+
try:
|
|
477
|
+
attrs['name'] = name[name.index(self.separator)+len(self.separator):-1]
|
|
478
|
+
except KeyError:
|
|
479
|
+
pass # no separator
|
|
480
|
+
|
|
481
|
+
return 0
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class OperationException(Exception):
|
|
485
|
+
def __init__(self, reason):
|
|
486
|
+
self.reason = reason
|
|
487
|
+
|
|
488
|
+
def __repr__(self):
|
|
489
|
+
return self.reason
|