fmu-manipulation-toolbox 1.8.4.2rc1__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 +22 -13
- fmu_manipulation_toolbox/checker.py +16 -11
- 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 +1054 -0
- fmu_manipulation_toolbox/gui.py +48 -56
- fmu_manipulation_toolbox/gui_style.py +8 -0
- fmu_manipulation_toolbox/help.py +3 -0
- fmu_manipulation_toolbox/operations.py +577 -0
- fmu_manipulation_toolbox/remoting.py +107 -0
- fmu_manipulation_toolbox/resources/darwin64/container.dylib +0 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Annotation.xsd +51 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3AttributeGroups.xsd +119 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3BuildDescription.xsd +117 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3InterfaceType.xsd +80 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3LayeredStandardManifest.xsd +93 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3ModelDescription.xsd +131 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Terminal.xsd +87 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3TerminalsAndIcons.xsd +84 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Type.xsd +207 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Unit.xsd +69 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3Variable.xsd +413 -0
- fmu_manipulation_toolbox/resources/fmi-3.0/fmi3VariableDependency.xsd +64 -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 +331 -0
- {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/METADATA +1 -1
- fmu_manipulation_toolbox-1.9.dist-info/RECORD +71 -0
- fmu_manipulation_toolbox-1.9.dist-info/entry_points.txt +7 -0
- fmu_manipulation_toolbox/cli.py +0 -235
- fmu_manipulation_toolbox/fmu_container.py +0 -753
- fmu_manipulation_toolbox/fmu_operations.py +0 -489
- fmu_manipulation_toolbox-1.8.4.2rc1.dist-info/RECORD +0 -52
- fmu_manipulation_toolbox-1.8.4.2rc1.dist-info/entry_points.txt +0 -3
- {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/licenses/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.4.2rc1.dist-info → fmu_manipulation_toolbox-1.9.dist-info}/top_level.txt +0 -0
|
@@ -1,753 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
import uuid
|
|
5
|
-
import zipfile
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import *
|
|
9
|
-
|
|
10
|
-
from .fmu_operations import FMU, OperationAbstract
|
|
11
|
-
from .version import __version__ as tool_version
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class FMUPort:
|
|
18
|
-
def __init__(self, attrs: Dict[str, str]):
|
|
19
|
-
self.name = attrs["name"]
|
|
20
|
-
self.vr = int(attrs["valueReference"])
|
|
21
|
-
self.causality = attrs.get("causality", "local")
|
|
22
|
-
self.attrs = attrs.copy()
|
|
23
|
-
self.attrs.pop("name")
|
|
24
|
-
self.attrs.pop("valueReference")
|
|
25
|
-
if "causality" in self.attrs:
|
|
26
|
-
self.attrs.pop("causality")
|
|
27
|
-
self.type_name = None
|
|
28
|
-
self.child = None
|
|
29
|
-
|
|
30
|
-
def set_port_type(self, type_name: str, attrs: Dict[str, str]):
|
|
31
|
-
self.type_name = type_name
|
|
32
|
-
self.child = attrs.copy()
|
|
33
|
-
for unsupported in ("unit", "declaredType"):
|
|
34
|
-
if unsupported in self.child:
|
|
35
|
-
self.child.pop(unsupported)
|
|
36
|
-
|
|
37
|
-
def xml(self, vr: int, name=None, causality=None, start=None):
|
|
38
|
-
|
|
39
|
-
if self.child is None:
|
|
40
|
-
raise FMUContainerError(f"FMUPort has no child. Bug?")
|
|
41
|
-
|
|
42
|
-
child_str = f"<{self.type_name}"
|
|
43
|
-
if self.child:
|
|
44
|
-
if start is not None and 'start' in self.child:
|
|
45
|
-
self.child['start'] = start
|
|
46
|
-
child_str += " " + " ".join([f'{key}="{value}"' for (key, value) in self.child.items()]) + "/>"
|
|
47
|
-
else:
|
|
48
|
-
child_str += "/>"
|
|
49
|
-
|
|
50
|
-
if name is None:
|
|
51
|
-
name = self.name
|
|
52
|
-
if causality is None:
|
|
53
|
-
causality = self.causality
|
|
54
|
-
|
|
55
|
-
variability = "continuous" if self.type_name == "Real" else "discrete"
|
|
56
|
-
|
|
57
|
-
scalar_attrs = {
|
|
58
|
-
"name": name,
|
|
59
|
-
"valueReference": vr,
|
|
60
|
-
"causality": causality,
|
|
61
|
-
"variability": variability,
|
|
62
|
-
}
|
|
63
|
-
scalar_attrs.update(self.attrs)
|
|
64
|
-
|
|
65
|
-
scalar_attrs_str = " ".join([f'{key}="{value}"' for (key, value) in scalar_attrs.items()])
|
|
66
|
-
|
|
67
|
-
return f'<ScalarVariable {scalar_attrs_str}>{child_str}</ScalarVariable>'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class EmbeddedFMU(OperationAbstract):
|
|
71
|
-
capability_list = ("needsExecutionTool",
|
|
72
|
-
"canBeInstantiatedOnlyOncePerProcess")
|
|
73
|
-
|
|
74
|
-
def __init__(self, filename):
|
|
75
|
-
self.fmu = FMU(filename)
|
|
76
|
-
self.name = Path(filename).name
|
|
77
|
-
self.id = Path(filename).stem
|
|
78
|
-
|
|
79
|
-
self.fmi_version = None
|
|
80
|
-
self.step_size = None
|
|
81
|
-
self.start_time = None
|
|
82
|
-
self.stop_time = None
|
|
83
|
-
self.model_identifier = None
|
|
84
|
-
self.guid = None
|
|
85
|
-
self.ports: Dict[str, FMUPort] = {}
|
|
86
|
-
|
|
87
|
-
self.capabilities: Dict[str, str] = {}
|
|
88
|
-
self.current_port = None # used during apply_operation()
|
|
89
|
-
|
|
90
|
-
self.fmu.apply_operation(self) # Should be the last command in constructor!
|
|
91
|
-
if self.model_identifier is None:
|
|
92
|
-
raise FMUContainerError(f"FMU '{self.name}' does not implement Co-Simulation mode.")
|
|
93
|
-
|
|
94
|
-
def fmi_attrs(self, attrs):
|
|
95
|
-
self.guid = attrs['guid']
|
|
96
|
-
self.fmi_version = attrs['fmiVersion']
|
|
97
|
-
|
|
98
|
-
def scalar_attrs(self, attrs) -> int:
|
|
99
|
-
self.current_port = FMUPort(attrs)
|
|
100
|
-
self.ports[self.current_port.name] = self.current_port
|
|
101
|
-
|
|
102
|
-
return 0
|
|
103
|
-
|
|
104
|
-
def cosimulation_attrs(self, attrs: Dict[str, str]):
|
|
105
|
-
self.model_identifier = attrs['modelIdentifier']
|
|
106
|
-
for capability in self.capability_list:
|
|
107
|
-
self.capabilities[capability] = attrs.get(capability, "false")
|
|
108
|
-
|
|
109
|
-
def experiment_attrs(self, attrs: Dict[str, str]):
|
|
110
|
-
try:
|
|
111
|
-
self.step_size = float(attrs['stepSize'])
|
|
112
|
-
except KeyError:
|
|
113
|
-
logger.warning(f"FMU '{self.name}' does not specify preferred step size")
|
|
114
|
-
self.start_time = float(attrs.get("startTime", 0.0))
|
|
115
|
-
self.stop_time = float(attrs.get("stopTime", self.start_time + 1.0))
|
|
116
|
-
|
|
117
|
-
def scalar_type(self, type_name, attrs):
|
|
118
|
-
if self.current_port:
|
|
119
|
-
if type_name == "Enumeration":
|
|
120
|
-
type_name = "Integer"
|
|
121
|
-
self.current_port.set_port_type(type_name, attrs)
|
|
122
|
-
self.current_port = None
|
|
123
|
-
|
|
124
|
-
def __repr__(self):
|
|
125
|
-
return f"FMU '{self.name}' ({len(self.ports)} variables)"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class FMUContainerError(Exception):
|
|
129
|
-
def __init__(self, reason: str):
|
|
130
|
-
self.reason = reason
|
|
131
|
-
|
|
132
|
-
def __repr__(self):
|
|
133
|
-
return f"{self.reason}"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class ContainerPort:
|
|
137
|
-
def __init__(self, fmu: EmbeddedFMU, port_name: str):
|
|
138
|
-
self.fmu = fmu
|
|
139
|
-
try:
|
|
140
|
-
self.port = fmu.ports[port_name]
|
|
141
|
-
except KeyError:
|
|
142
|
-
raise FMUContainerError(f"Port '{fmu.name}/{port_name}' does not exist")
|
|
143
|
-
self.vr = None
|
|
144
|
-
|
|
145
|
-
def __repr__(self):
|
|
146
|
-
return f"Port {self.fmu.name}/{self.port.name}"
|
|
147
|
-
|
|
148
|
-
def __hash__(self):
|
|
149
|
-
return hash(str(self))
|
|
150
|
-
|
|
151
|
-
def __eq__(self, other):
|
|
152
|
-
return str(self) == str(other)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class ContainerInput:
|
|
156
|
-
def __init__(self, name: str, cport_to: ContainerPort):
|
|
157
|
-
self.name = name
|
|
158
|
-
self.type_name = cport_to.port.type_name
|
|
159
|
-
self.causality = cport_to.port.causality
|
|
160
|
-
self.cport_list = [cport_to]
|
|
161
|
-
self.vr = None
|
|
162
|
-
|
|
163
|
-
def add_cport(self, cport_to: ContainerPort):
|
|
164
|
-
if cport_to in self.cport_list: # Cannot be reached ! (Assembly prevent this to happen)
|
|
165
|
-
raise FMUContainerError(f"Duplicate INPUT {cport_to} already connected to {self.name}")
|
|
166
|
-
|
|
167
|
-
if cport_to.port.type_name != self.type_name:
|
|
168
|
-
raise FMUContainerError(f"Cannot connect {self.name} of type {self.type_name} to "
|
|
169
|
-
f"{cport_to} of type {cport_to.port.type_name}")
|
|
170
|
-
|
|
171
|
-
if cport_to.port.causality != self.causality:
|
|
172
|
-
raise FMUContainerError(f"Cannot connect {self.causality.upper()} {self.name} to "
|
|
173
|
-
f"{cport_to.port.causality.upper()} {cport_to}")
|
|
174
|
-
|
|
175
|
-
self.cport_list.append(cport_to)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
class Local:
|
|
179
|
-
def __init__(self, cport_from: ContainerPort):
|
|
180
|
-
self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
|
|
181
|
-
self.cport_from = cport_from
|
|
182
|
-
self.cport_to_list: List[ContainerPort] = []
|
|
183
|
-
self.vr = None
|
|
184
|
-
|
|
185
|
-
if not cport_from.port.causality == "output":
|
|
186
|
-
raise FMUContainerError(f"{cport_from} is {cport_from.port.causality} instead of OUTPUT")
|
|
187
|
-
|
|
188
|
-
def add_target(self, cport_to: ContainerPort):
|
|
189
|
-
if not cport_to.port.causality == "input":
|
|
190
|
-
raise FMUContainerError(f"{cport_to} is {cport_to.port.causality} instead of INPUT")
|
|
191
|
-
|
|
192
|
-
if cport_to.port.type_name == self.cport_from.port.type_name:
|
|
193
|
-
self.cport_to_list.append(cport_to)
|
|
194
|
-
else:
|
|
195
|
-
raise FMUContainerError(f"failed to connect {self.cport_from} to {cport_to} due to type.")
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
class ValueReferenceTable:
|
|
199
|
-
def __init__(self):
|
|
200
|
-
self.vr_table: Dict[str, int] = {
|
|
201
|
-
"Real": 0,
|
|
202
|
-
"Integer": 0,
|
|
203
|
-
"Boolean": 0,
|
|
204
|
-
"String": 0,
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
def get_vr(self, cport: ContainerPort) -> int:
|
|
208
|
-
return self.add_vr(cport.port.type_name)
|
|
209
|
-
|
|
210
|
-
def add_vr(self, type_name: str) -> int:
|
|
211
|
-
vr = self.vr_table[type_name]
|
|
212
|
-
self.vr_table[type_name] += 1
|
|
213
|
-
return vr
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
class AutoWired:
|
|
217
|
-
def __init__(self):
|
|
218
|
-
self.rule_input = []
|
|
219
|
-
self.rule_output = []
|
|
220
|
-
self.rule_link = []
|
|
221
|
-
self.nb_param = 0
|
|
222
|
-
|
|
223
|
-
def __repr__(self):
|
|
224
|
-
return (f"{self.nb_param} parameters, {len(self.rule_input) - self.nb_param} inputs,"
|
|
225
|
-
f" {len(self.rule_output)} outputs, {len(self.rule_link)} links.")
|
|
226
|
-
|
|
227
|
-
def add_input(self, from_port, to_fmu, to_port):
|
|
228
|
-
self.rule_input.append([from_port, to_fmu, to_port])
|
|
229
|
-
|
|
230
|
-
def add_parameter(self, from_port, to_fmu, to_port):
|
|
231
|
-
self.rule_input.append([from_port, to_fmu, to_port])
|
|
232
|
-
self.nb_param += 1
|
|
233
|
-
|
|
234
|
-
def add_output(self, from_fmu, from_port, to_port):
|
|
235
|
-
self.rule_output.append([from_fmu, from_port, to_port])
|
|
236
|
-
|
|
237
|
-
def add_link(self, from_fmu, from_port, to_fmu, to_port):
|
|
238
|
-
self.rule_link.append([from_fmu, from_port, to_fmu, to_port])
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
class FMUContainer:
|
|
242
|
-
def __init__(self, identifier: str, fmu_directory: Union[str, Path], description_pathname=None):
|
|
243
|
-
self.fmu_directory = Path(fmu_directory)
|
|
244
|
-
self.identifier = identifier
|
|
245
|
-
if not self.fmu_directory.is_dir():
|
|
246
|
-
raise FMUContainerError(f"{self.fmu_directory} is not a valid directory")
|
|
247
|
-
self.involved_fmu: Dict[str, EmbeddedFMU] = {}
|
|
248
|
-
self.execution_order: List[EmbeddedFMU] = []
|
|
249
|
-
|
|
250
|
-
self.description_pathname = description_pathname
|
|
251
|
-
|
|
252
|
-
self.start_time = None
|
|
253
|
-
self.stop_time = None
|
|
254
|
-
|
|
255
|
-
# Rules
|
|
256
|
-
self.inputs: Dict[str, ContainerInput] = {}
|
|
257
|
-
self.outputs: Dict[str, ContainerPort] = {}
|
|
258
|
-
self.locals: Dict[ContainerPort, Local] = {}
|
|
259
|
-
|
|
260
|
-
self.rules: Dict[ContainerPort, str] = {}
|
|
261
|
-
self.start_values: Dict[ContainerPort, str] = {}
|
|
262
|
-
|
|
263
|
-
def get_fmu(self, fmu_filename: str) -> EmbeddedFMU:
|
|
264
|
-
if fmu_filename in self.involved_fmu:
|
|
265
|
-
return self.involved_fmu[fmu_filename]
|
|
266
|
-
|
|
267
|
-
try:
|
|
268
|
-
fmu = EmbeddedFMU(self.fmu_directory / fmu_filename)
|
|
269
|
-
self.involved_fmu[fmu.name] = fmu
|
|
270
|
-
self.execution_order.append(fmu)
|
|
271
|
-
if not fmu.fmi_version == "2.0":
|
|
272
|
-
raise FMUContainerError("Only FMI-2.0 is supported by FMUContainer")
|
|
273
|
-
logger.debug(f"Adding FMU #{len(self.execution_order)}: {fmu}")
|
|
274
|
-
except Exception as e:
|
|
275
|
-
raise FMUContainerError(f"Cannot load '{fmu_filename}': {e}")
|
|
276
|
-
|
|
277
|
-
return fmu
|
|
278
|
-
|
|
279
|
-
def mark_ruled(self, cport: ContainerPort, rule: str):
|
|
280
|
-
if cport in self.rules:
|
|
281
|
-
previous_rule = self.rules[cport]
|
|
282
|
-
if rule not in ("OUTPUT", "LINK") and previous_rule not in ("OUTPUT", "LINK"):
|
|
283
|
-
raise FMUContainerError(f"try to {rule} port {cport} which is already {previous_rule}")
|
|
284
|
-
|
|
285
|
-
self.rules[cport] = rule
|
|
286
|
-
|
|
287
|
-
def get_all_cports(self):
|
|
288
|
-
return [ContainerPort(fmu, port_name) for fmu in self.execution_order for port_name in fmu.ports]
|
|
289
|
-
|
|
290
|
-
def add_input(self, container_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
291
|
-
if not container_port_name:
|
|
292
|
-
container_port_name = to_port_name
|
|
293
|
-
cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
|
|
294
|
-
if cport_to.port.causality not in ("input", "parameter"): # check causality
|
|
295
|
-
raise FMUContainerError(f"Tried to use '{cport_to}' as INPUT of the container but FMU causality is "
|
|
296
|
-
f"'{cport_to.port.causality}'.")
|
|
297
|
-
|
|
298
|
-
try:
|
|
299
|
-
input_port = self.inputs[container_port_name]
|
|
300
|
-
input_port.add_cport(cport_to)
|
|
301
|
-
except KeyError:
|
|
302
|
-
self.inputs[container_port_name] = ContainerInput(container_port_name, cport_to)
|
|
303
|
-
|
|
304
|
-
logger.debug(f"INPUT: {to_fmu_filename}:{to_port_name}")
|
|
305
|
-
self.mark_ruled(cport_to, 'INPUT')
|
|
306
|
-
|
|
307
|
-
def add_output(self, from_fmu_filename: str, from_port_name: str, container_port_name: str):
|
|
308
|
-
if not container_port_name: # empty is allowed
|
|
309
|
-
container_port_name = from_port_name
|
|
310
|
-
|
|
311
|
-
cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
|
|
312
|
-
if cport_from.port.causality not in ("output", "local"): # check causality
|
|
313
|
-
raise FMUContainerError(f"Tried to use '{cport_from}' as OUTPUT of the container but FMU causality is "
|
|
314
|
-
f"'{cport_from.port.causality}'.")
|
|
315
|
-
|
|
316
|
-
if container_port_name in self.outputs:
|
|
317
|
-
raise FMUContainerError(f"Duplicate OUTPUT {container_port_name} already connected to {cport_from}")
|
|
318
|
-
|
|
319
|
-
logger.debug(f"OUTPUT: {from_fmu_filename}:{from_port_name}")
|
|
320
|
-
self.mark_ruled(cport_from, 'OUTPUT')
|
|
321
|
-
self.outputs[container_port_name] = cport_from
|
|
322
|
-
|
|
323
|
-
def drop_port(self, from_fmu_filename: str, from_port_name: str):
|
|
324
|
-
cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
|
|
325
|
-
if not cport_from.port.causality == "output": # check causality
|
|
326
|
-
raise FMUContainerError(f"{cport_from}: trying to DROP {cport_from.port.causality}")
|
|
327
|
-
|
|
328
|
-
logger.debug(f"DROP: {from_fmu_filename}:{from_port_name}")
|
|
329
|
-
self.mark_ruled(cport_from, 'DROP')
|
|
330
|
-
|
|
331
|
-
def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
332
|
-
cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
|
|
333
|
-
try:
|
|
334
|
-
local = self.locals[cport_from]
|
|
335
|
-
except KeyError:
|
|
336
|
-
local = Local(cport_from)
|
|
337
|
-
|
|
338
|
-
cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
|
|
339
|
-
local.add_target(cport_to) # Causality is check in the add() function
|
|
340
|
-
|
|
341
|
-
self.mark_ruled(cport_from, 'LINK')
|
|
342
|
-
self.mark_ruled(cport_to, 'LINK')
|
|
343
|
-
self.locals[cport_from] = local
|
|
344
|
-
|
|
345
|
-
def add_start_value(self, fmu_filename: str, port_name: str, value: str):
|
|
346
|
-
cport = ContainerPort(self.get_fmu(fmu_filename), port_name)
|
|
347
|
-
|
|
348
|
-
try:
|
|
349
|
-
if cport.port.type_name == 'Real':
|
|
350
|
-
value = float(value)
|
|
351
|
-
elif cport.port.type_name == 'Integer':
|
|
352
|
-
value = int(value)
|
|
353
|
-
elif cport.port.type_name == 'Boolean':
|
|
354
|
-
value = int(bool(value))
|
|
355
|
-
else:
|
|
356
|
-
value = value
|
|
357
|
-
except ValueError:
|
|
358
|
-
raise FMUContainerError(f"Start value is not conforming to '{cport.port.type_name}' format.")
|
|
359
|
-
|
|
360
|
-
self.start_values[cport] = value
|
|
361
|
-
|
|
362
|
-
def find_inputs(self, port_to_connect: FMUPort) -> List[ContainerPort]:
|
|
363
|
-
candidates = []
|
|
364
|
-
for cport in self.get_all_cports():
|
|
365
|
-
if (cport.port.causality == 'input' and cport not in self.rules and cport.port.name == port_to_connect.name
|
|
366
|
-
and cport.port.type_name == port_to_connect.type_name):
|
|
367
|
-
candidates.append(cport)
|
|
368
|
-
return candidates
|
|
369
|
-
|
|
370
|
-
def add_implicit_rule(self, auto_input=True, auto_output=True, auto_link=True, auto_parameter=False,
|
|
371
|
-
auto_local=False) -> AutoWired:
|
|
372
|
-
|
|
373
|
-
auto_wired = AutoWired()
|
|
374
|
-
# Auto Link outputs
|
|
375
|
-
for cport in self.get_all_cports():
|
|
376
|
-
if cport.port.causality == 'output':
|
|
377
|
-
candidates_cport_list = self.find_inputs(cport.port)
|
|
378
|
-
if auto_link and candidates_cport_list:
|
|
379
|
-
for candidate_cport in candidates_cport_list:
|
|
380
|
-
logger.info(f"AUTO LINK: {cport} -> {candidate_cport}")
|
|
381
|
-
self.add_link(cport.fmu.name, cport.port.name,
|
|
382
|
-
candidate_cport.fmu.name, candidate_cport.port.name)
|
|
383
|
-
auto_wired.add_link(cport.fmu.name, cport.port.name,
|
|
384
|
-
candidate_cport.fmu.name, candidate_cport.port.name)
|
|
385
|
-
elif auto_output and cport not in self.rules:
|
|
386
|
-
logger.info(f"AUTO OUTPUT: Expose {cport}")
|
|
387
|
-
self.add_output(cport.fmu.name, cport.port.name, cport.port.name)
|
|
388
|
-
auto_wired.add_output(cport.fmu.name, cport.port.name, cport.port.name)
|
|
389
|
-
elif cport.port.causality == 'local':
|
|
390
|
-
local_portname = None
|
|
391
|
-
if cport.port.name.startswith("container."):
|
|
392
|
-
local_portname = "container." + cport.fmu.id + "." + cport.port.name[10:]
|
|
393
|
-
logger.info(f"PROFILING: Expose {cport}")
|
|
394
|
-
elif auto_local:
|
|
395
|
-
local_portname = cport.fmu.id + "." + cport.port.name
|
|
396
|
-
logger.info(f"AUTO LOCAL: Expose {cport}")
|
|
397
|
-
if local_portname:
|
|
398
|
-
self.add_output(cport.fmu.name, cport.port.name, local_portname)
|
|
399
|
-
auto_wired.add_output(cport.fmu.name, cport.port.name, local_portname)
|
|
400
|
-
|
|
401
|
-
if auto_input:
|
|
402
|
-
# Auto link inputs
|
|
403
|
-
for cport in self.get_all_cports():
|
|
404
|
-
if cport not in self.rules:
|
|
405
|
-
if cport.port.causality == 'parameter' and auto_parameter:
|
|
406
|
-
parameter_name = cport.fmu.id + "." + cport.port.name
|
|
407
|
-
logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
|
|
408
|
-
self.add_input(parameter_name, cport.fmu.name, cport.port.name)
|
|
409
|
-
auto_wired.add_parameter(parameter_name, cport.fmu.name, cport.port.name)
|
|
410
|
-
elif cport.port.causality == 'input':
|
|
411
|
-
logger.info(f"AUTO INPUT: Expose {cport}")
|
|
412
|
-
self.add_input(cport.port.name, cport.fmu.name, cport.port.name)
|
|
413
|
-
auto_wired.add_input(cport.port.name, cport.fmu.name, cport.port.name)
|
|
414
|
-
|
|
415
|
-
logger.info(f"Auto-wiring: {auto_wired}")
|
|
416
|
-
|
|
417
|
-
return auto_wired
|
|
418
|
-
|
|
419
|
-
def minimum_step_size(self) -> float:
|
|
420
|
-
step_size = None
|
|
421
|
-
for fmu in self.execution_order:
|
|
422
|
-
if step_size:
|
|
423
|
-
if fmu.step_size and fmu.step_size < step_size:
|
|
424
|
-
step_size = fmu.step_size
|
|
425
|
-
else:
|
|
426
|
-
step_size = fmu.step_size
|
|
427
|
-
|
|
428
|
-
if not step_size:
|
|
429
|
-
step_size = 0.1
|
|
430
|
-
logger.warning(f"Defaulting to step_size={step_size}")
|
|
431
|
-
|
|
432
|
-
return step_size
|
|
433
|
-
|
|
434
|
-
def sanity_check(self, step_size: Optional[float]):
|
|
435
|
-
for fmu in self.execution_order:
|
|
436
|
-
if not fmu.step_size:
|
|
437
|
-
continue
|
|
438
|
-
ts_ratio = step_size / fmu.step_size
|
|
439
|
-
if ts_ratio < 1.0:
|
|
440
|
-
logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
|
|
441
|
-
f"step_size={fmu.step_size}s.")
|
|
442
|
-
if ts_ratio != int(ts_ratio):
|
|
443
|
-
logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
|
|
444
|
-
f"step_size={fmu.step_size}s.")
|
|
445
|
-
for port_name in fmu.ports:
|
|
446
|
-
cport = ContainerPort(fmu, port_name)
|
|
447
|
-
if cport not in self.rules:
|
|
448
|
-
if cport.port.causality == 'input':
|
|
449
|
-
logger.error(f"{cport} is not connected")
|
|
450
|
-
if cport.port.causality == 'output':
|
|
451
|
-
logger.warning(f"{cport} is not connected")
|
|
452
|
-
|
|
453
|
-
def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
|
|
454
|
-
profiling=False):
|
|
455
|
-
if isinstance(fmu_filename, str):
|
|
456
|
-
fmu_filename = Path(fmu_filename)
|
|
457
|
-
|
|
458
|
-
if step_size is None:
|
|
459
|
-
logger.info(f"step_size will be deduced from the embedded FMU's")
|
|
460
|
-
step_size = self.minimum_step_size()
|
|
461
|
-
self.sanity_check(step_size)
|
|
462
|
-
|
|
463
|
-
logger.info(f"Building FMU '{fmu_filename}', step_size={step_size}")
|
|
464
|
-
|
|
465
|
-
base_directory = self.fmu_directory / fmu_filename.with_suffix('')
|
|
466
|
-
resources_directory = self.make_fmu_skeleton(base_directory)
|
|
467
|
-
with open(base_directory / "modelDescription.xml", "wt") as xml_file:
|
|
468
|
-
self.make_fmu_xml(xml_file, step_size, profiling)
|
|
469
|
-
with open(resources_directory / "container.txt", "wt") as txt_file:
|
|
470
|
-
self.make_fmu_txt(txt_file, step_size, mt, profiling)
|
|
471
|
-
|
|
472
|
-
self.make_fmu_package(base_directory, fmu_filename)
|
|
473
|
-
if not debug:
|
|
474
|
-
self.make_fmu_cleanup(base_directory)
|
|
475
|
-
|
|
476
|
-
def make_fmu_xml(self, xml_file, step_size: float, profiling: bool):
|
|
477
|
-
vr_table = ValueReferenceTable()
|
|
478
|
-
|
|
479
|
-
timestamp = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
480
|
-
guid = str(uuid.uuid4())
|
|
481
|
-
embedded_fmu = ", ".join([fmu_name for fmu_name in self.involved_fmu])
|
|
482
|
-
try:
|
|
483
|
-
author = os.getlogin()
|
|
484
|
-
except OSError:
|
|
485
|
-
author = "Unspecified"
|
|
486
|
-
|
|
487
|
-
capabilities = {}
|
|
488
|
-
for capability in EmbeddedFMU.capability_list:
|
|
489
|
-
capabilities[capability] = "false"
|
|
490
|
-
for fmu in self.involved_fmu.values():
|
|
491
|
-
if fmu.capabilities[capability] == "true":
|
|
492
|
-
capabilities[capability] = "true"
|
|
493
|
-
|
|
494
|
-
if self.start_time is None:
|
|
495
|
-
self.start_time = self.execution_order[0].start_time
|
|
496
|
-
logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
|
|
497
|
-
else:
|
|
498
|
-
logger.info(f"start_time={self.start_time}")
|
|
499
|
-
|
|
500
|
-
if self.stop_time is None:
|
|
501
|
-
self.stop_time = self.execution_order[0].stop_time
|
|
502
|
-
logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
|
|
503
|
-
else:
|
|
504
|
-
logger.info(f"stop_time={self.stop_time}")
|
|
505
|
-
|
|
506
|
-
xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
507
|
-
<fmiModelDescription
|
|
508
|
-
fmiVersion="2.0"
|
|
509
|
-
modelName="{self.identifier}"
|
|
510
|
-
generationTool="FMUContainer-{tool_version}"
|
|
511
|
-
generationDateAndTime="{timestamp}"
|
|
512
|
-
guid="{guid}"
|
|
513
|
-
description="FMUContainer with {embedded_fmu}"
|
|
514
|
-
author="{author}"
|
|
515
|
-
license="Proprietary"
|
|
516
|
-
copyright="See Embedded FMU's copyrights."
|
|
517
|
-
variableNamingConvention="structured">
|
|
518
|
-
|
|
519
|
-
<CoSimulation
|
|
520
|
-
modelIdentifier="{self.identifier}"
|
|
521
|
-
canHandleVariableCommunicationStepSize="true"
|
|
522
|
-
canBeInstantiatedOnlyOncePerProcess="{capabilities['canBeInstantiatedOnlyOncePerProcess']}"
|
|
523
|
-
canNotUseMemoryManagementFunctions="true"
|
|
524
|
-
canGetAndSetFMUstate="false"
|
|
525
|
-
canSerializeFMUstate="false"
|
|
526
|
-
providesDirectionalDerivative="false"
|
|
527
|
-
needsExecutionTool="{capabilities['needsExecutionTool']}">
|
|
528
|
-
</CoSimulation>
|
|
529
|
-
|
|
530
|
-
<LogCategories>
|
|
531
|
-
<Category name="fmucontainer"/>
|
|
532
|
-
</LogCategories>
|
|
533
|
-
|
|
534
|
-
<DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
|
|
535
|
-
|
|
536
|
-
<ModelVariables>
|
|
537
|
-
""")
|
|
538
|
-
if profiling:
|
|
539
|
-
for fmu in self.execution_order:
|
|
540
|
-
vr = vr_table.add_vr("Real")
|
|
541
|
-
name = f"container.{fmu.id}.rt_ratio"
|
|
542
|
-
print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
|
|
543
|
-
f'<Real /></ScalarVariable>', file=xml_file)
|
|
544
|
-
|
|
545
|
-
# Local variable should be first to ensure to attribute them the lowest VR.
|
|
546
|
-
for local in self.locals.values():
|
|
547
|
-
vr = vr_table.get_vr(local.cport_from)
|
|
548
|
-
print(f' {local.cport_from.port.xml(vr, name=local.name, causality="local")}', file=xml_file)
|
|
549
|
-
local.vr = vr
|
|
550
|
-
|
|
551
|
-
for input_port_name, input_port in self.inputs.items():
|
|
552
|
-
vr = vr_table.add_vr(input_port.type_name)
|
|
553
|
-
# Get Start and XML from first connected input
|
|
554
|
-
start = self.start_values.get(input_port.cport_list[0], None)
|
|
555
|
-
print(f" {input_port.cport_list[0].port.xml(vr, name=input_port_name, start=start)}", file=xml_file)
|
|
556
|
-
input_port.vr = vr
|
|
557
|
-
|
|
558
|
-
for output_port_name, cport in self.outputs.items():
|
|
559
|
-
vr = vr_table.get_vr(cport)
|
|
560
|
-
print(f" {cport.port.xml(vr, name=output_port_name)}", file=xml_file)
|
|
561
|
-
cport.vr = vr
|
|
562
|
-
|
|
563
|
-
xml_file.write(""" </ModelVariables>
|
|
564
|
-
|
|
565
|
-
<ModelStructure>
|
|
566
|
-
<Outputs>
|
|
567
|
-
""")
|
|
568
|
-
|
|
569
|
-
index_offset = len(self.locals) + len(self.inputs) + 1
|
|
570
|
-
for i, _ in enumerate(self.outputs.keys()):
|
|
571
|
-
print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
|
|
572
|
-
xml_file.write(""" </Outputs>
|
|
573
|
-
<InitialUnknowns>
|
|
574
|
-
""")
|
|
575
|
-
for i, _ in enumerate(self.outputs.keys()):
|
|
576
|
-
print(f' <Unknown index="{index_offset+i}"/>', file=xml_file)
|
|
577
|
-
xml_file.write(""" </InitialUnknowns>
|
|
578
|
-
</ModelStructure>
|
|
579
|
-
|
|
580
|
-
</fmiModelDescription>
|
|
581
|
-
""")
|
|
582
|
-
|
|
583
|
-
def make_fmu_txt(self, txt_file, step_size: float, mt: bool, profiling: bool):
|
|
584
|
-
if mt:
|
|
585
|
-
print("# Use MT\n1", file=txt_file)
|
|
586
|
-
else:
|
|
587
|
-
print("# Don't use MT\n0", file=txt_file)
|
|
588
|
-
|
|
589
|
-
if profiling:
|
|
590
|
-
print("# Profiling ENABLED\n1", file=txt_file)
|
|
591
|
-
else:
|
|
592
|
-
print("# Profiling DISABLED\n0", file=txt_file)
|
|
593
|
-
|
|
594
|
-
print(f"# Internal time step in seconds", file=txt_file)
|
|
595
|
-
print(f"{step_size}", file=txt_file)
|
|
596
|
-
print(f"# NB of embedded FMU's", file=txt_file)
|
|
597
|
-
print(f"{len(self.involved_fmu)}", file=txt_file)
|
|
598
|
-
fmu_rank: Dict[str, int] = {}
|
|
599
|
-
for i, fmu in enumerate(self.execution_order):
|
|
600
|
-
print(f"{fmu.name}", file=txt_file)
|
|
601
|
-
print(f"{fmu.model_identifier}", file=txt_file)
|
|
602
|
-
print(f"{fmu.guid}", file=txt_file)
|
|
603
|
-
fmu_rank[fmu.name] = i
|
|
604
|
-
|
|
605
|
-
# Prepare data structure
|
|
606
|
-
type_names_list = ("Real", "Integer", "Boolean", "String") # Ordered list
|
|
607
|
-
inputs_per_type: Dict[str, List[ContainerInput]] = {} # Container's INPUT
|
|
608
|
-
outputs_per_type: Dict[str, List[ContainerPort]] = {} # Container's OUTPUT
|
|
609
|
-
|
|
610
|
-
inputs_fmu_per_type: Dict[str, Dict[str, Dict[ContainerPort, int]]] = {} # [type][fmu]
|
|
611
|
-
start_values_fmu_per_type = {}
|
|
612
|
-
outputs_fmu_per_type = {}
|
|
613
|
-
locals_per_type: Dict[str, List[Local]] = {}
|
|
614
|
-
|
|
615
|
-
for type_name in type_names_list:
|
|
616
|
-
inputs_per_type[type_name] = []
|
|
617
|
-
outputs_per_type[type_name] = []
|
|
618
|
-
locals_per_type[type_name] = []
|
|
619
|
-
|
|
620
|
-
inputs_fmu_per_type[type_name] = {}
|
|
621
|
-
start_values_fmu_per_type[type_name] = {}
|
|
622
|
-
outputs_fmu_per_type[type_name] = {}
|
|
623
|
-
|
|
624
|
-
for fmu in self.execution_order:
|
|
625
|
-
inputs_fmu_per_type[type_name][fmu.name] = {}
|
|
626
|
-
start_values_fmu_per_type[type_name][fmu.name] = {}
|
|
627
|
-
outputs_fmu_per_type[type_name][fmu.name] = {}
|
|
628
|
-
|
|
629
|
-
# Fill data structure
|
|
630
|
-
# Inputs
|
|
631
|
-
for input_port_name, input_port in self.inputs.items():
|
|
632
|
-
inputs_per_type[input_port.type_name].append(input_port)
|
|
633
|
-
for cport, value in self.start_values.items():
|
|
634
|
-
start_values_fmu_per_type[cport.port.type_name][cport.fmu.name][cport] = value
|
|
635
|
-
# Outputs
|
|
636
|
-
for output_port_name, cport in self.outputs.items():
|
|
637
|
-
outputs_per_type[cport.port.type_name].append(cport)
|
|
638
|
-
# Locals
|
|
639
|
-
for local in self.locals.values():
|
|
640
|
-
vr = local.vr
|
|
641
|
-
locals_per_type[local.cport_from.port.type_name].append(local)
|
|
642
|
-
outputs_fmu_per_type[local.cport_from.port.type_name][local.cport_from.fmu.name][local.cport_from] = vr
|
|
643
|
-
for cport_to in local.cport_to_list:
|
|
644
|
-
inputs_fmu_per_type[cport_to.port.type_name][cport_to.fmu.name][cport_to] = vr
|
|
645
|
-
|
|
646
|
-
print(f"# NB local variables Real, Integer, Boolean, String", file=txt_file)
|
|
647
|
-
for type_name in type_names_list:
|
|
648
|
-
nb = len(locals_per_type[type_name])
|
|
649
|
-
if profiling and type_name == "Real":
|
|
650
|
-
nb += len(self.execution_order)
|
|
651
|
-
print(f"{nb} ", file=txt_file, end='')
|
|
652
|
-
print("", file=txt_file)
|
|
653
|
-
|
|
654
|
-
print("# CONTAINER I/O: <VR> <NB> <FMU_INDEX> <FMU_VR> [<FMU_INDEX> <FMU_VR>]", file=txt_file)
|
|
655
|
-
for type_name in type_names_list:
|
|
656
|
-
print(f"# {type_name}", file=txt_file)
|
|
657
|
-
nb = len(inputs_per_type[type_name]) + len(outputs_per_type[type_name]) + len(locals_per_type[type_name])
|
|
658
|
-
nb_input_link = 0
|
|
659
|
-
for input_port in inputs_per_type[type_name]:
|
|
660
|
-
nb_input_link += len(input_port.cport_list) - 1
|
|
661
|
-
|
|
662
|
-
if profiling and type_name == "Real":
|
|
663
|
-
nb += len(self.execution_order)
|
|
664
|
-
print(f"{nb} {nb+nb_input_link}", file=txt_file)
|
|
665
|
-
for profiling_port, _ in enumerate(self.execution_order):
|
|
666
|
-
print(f"{profiling_port} 1 -2 {profiling_port}", file=txt_file)
|
|
667
|
-
else:
|
|
668
|
-
print(f"{nb} {nb+nb_input_link}", file=txt_file)
|
|
669
|
-
for input_port in inputs_per_type[type_name]:
|
|
670
|
-
cport_string = [f"{fmu_rank[cport.fmu.name]} {cport.port.vr}" for cport in input_port.cport_list]
|
|
671
|
-
print(f"{input_port.vr} {len(input_port.cport_list)}", " ".join(cport_string), file=txt_file)
|
|
672
|
-
for cport in outputs_per_type[type_name]:
|
|
673
|
-
print(f"{cport.vr} 1 {fmu_rank[cport.fmu.name]} {cport.port.vr}", file=txt_file)
|
|
674
|
-
for local in locals_per_type[type_name]:
|
|
675
|
-
print(f"{local.vr} 1 -1 {local.vr}", file=txt_file)
|
|
676
|
-
|
|
677
|
-
# LINKS
|
|
678
|
-
for fmu in self.execution_order:
|
|
679
|
-
for type_name in type_names_list:
|
|
680
|
-
print(f"# Inputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
|
|
681
|
-
print(len(inputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
|
|
682
|
-
for cport, vr in inputs_fmu_per_type[type_name][fmu.name].items():
|
|
683
|
-
print(f"{vr} {cport.port.vr}", file=txt_file)
|
|
684
|
-
|
|
685
|
-
for type_name in type_names_list:
|
|
686
|
-
print(f"# Start values of {fmu.name} - {type_name}: <FMU_VR> <RESET> <VALUE>", file=txt_file)
|
|
687
|
-
print(len(start_values_fmu_per_type[type_name][fmu.name]), file=txt_file)
|
|
688
|
-
for cport, value in start_values_fmu_per_type[type_name][fmu.name].items():
|
|
689
|
-
reset = 1 if cport.port.causality == "input" else 0
|
|
690
|
-
print(f"{cport.port.vr} {reset} {value}", file=txt_file)
|
|
691
|
-
|
|
692
|
-
for type_name in type_names_list:
|
|
693
|
-
print(f"# Outputs of {fmu.name} - {type_name}: <VR> <FMU_VR>", file=txt_file)
|
|
694
|
-
print(len(outputs_fmu_per_type[type_name][fmu.name]), file=txt_file)
|
|
695
|
-
for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
|
|
696
|
-
print(f"{vr} {cport.port.vr}", file=txt_file)
|
|
697
|
-
|
|
698
|
-
@staticmethod
|
|
699
|
-
def long_path(path: Union[str, Path]) -> str:
|
|
700
|
-
# https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
|
|
701
|
-
if os.name == 'nt':
|
|
702
|
-
return "\\\\?\\" + os.path.abspath(str(path))
|
|
703
|
-
else:
|
|
704
|
-
return path
|
|
705
|
-
|
|
706
|
-
def make_fmu_skeleton(self, base_directory: Path) -> Path:
|
|
707
|
-
logger.debug(f"Initialize directory '{base_directory}'")
|
|
708
|
-
|
|
709
|
-
origin = Path(__file__).parent / "resources"
|
|
710
|
-
resources_directory = base_directory / "resources"
|
|
711
|
-
documentation_directory = base_directory / "documentation"
|
|
712
|
-
binaries_directory = base_directory / "binaries"
|
|
713
|
-
|
|
714
|
-
base_directory.mkdir(exist_ok=True)
|
|
715
|
-
resources_directory.mkdir(exist_ok=True)
|
|
716
|
-
binaries_directory.mkdir(exist_ok=True)
|
|
717
|
-
documentation_directory.mkdir(exist_ok=True)
|
|
718
|
-
|
|
719
|
-
if self.description_pathname:
|
|
720
|
-
logger.debug(f"Copying {self.description_pathname}")
|
|
721
|
-
shutil.copy(self.description_pathname, documentation_directory)
|
|
722
|
-
|
|
723
|
-
shutil.copy(origin / "model.png", base_directory)
|
|
724
|
-
for bitness in ('win32', 'win64'):
|
|
725
|
-
library_filename = origin / bitness / "container.dll"
|
|
726
|
-
if library_filename.is_file():
|
|
727
|
-
binary_directory = binaries_directory / bitness
|
|
728
|
-
binary_directory.mkdir(exist_ok=True)
|
|
729
|
-
shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
|
|
730
|
-
|
|
731
|
-
for i, fmu in enumerate(self.involved_fmu.values()):
|
|
732
|
-
shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
|
|
733
|
-
self.long_path(resources_directory / f"{i:02x}"), dirs_exist_ok=True)
|
|
734
|
-
return resources_directory
|
|
735
|
-
|
|
736
|
-
def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
|
|
737
|
-
logger.debug(f"Zipping directory '{base_directory}' => '{fmu_filename}'")
|
|
738
|
-
zip_directory = self.long_path(str(base_directory.absolute()))
|
|
739
|
-
offset = len(zip_directory) + 1
|
|
740
|
-
with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
741
|
-
def add_file(directory: Path):
|
|
742
|
-
for entry in directory.iterdir():
|
|
743
|
-
if entry.is_dir():
|
|
744
|
-
add_file(directory / entry)
|
|
745
|
-
elif entry.is_file:
|
|
746
|
-
zip_file.write(str(entry), str(entry)[offset:])
|
|
747
|
-
|
|
748
|
-
add_file(Path(zip_directory))
|
|
749
|
-
logger.info(f"'{fmu_filename}' is available.")
|
|
750
|
-
|
|
751
|
-
def make_fmu_cleanup(self, base_directory: Path):
|
|
752
|
-
logger.debug(f"Delete directory '{base_directory}'")
|
|
753
|
-
shutil.rmtree(self.long_path(base_directory))
|