fmu-manipulation-toolbox 1.7.5__py3-none-any.whl → 1.8.1__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 +484 -0
- fmu_manipulation_toolbox/cli.py +27 -17
- fmu_manipulation_toolbox/fmu_container.py +41 -189
- fmu_manipulation_toolbox/gui.py +58 -45
- fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox/resources/icon-round.png +0 -0
- fmu_manipulation_toolbox/resources/icon.png +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/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.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/METADATA +1 -1
- {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/RECORD +25 -22
- {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/WHEEL +0 -0
- {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/top_level.txt +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
'V1.
|
|
1
|
+
'V1.8.1'
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import *
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import uuid
|
|
7
|
+
import xml.parsers.expat
|
|
8
|
+
import zipfile
|
|
9
|
+
|
|
10
|
+
from .fmu_container import FMUContainer
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Port:
|
|
16
|
+
def __init__(self, fmu_name: str, port_name: str):
|
|
17
|
+
self.fmu_name = fmu_name
|
|
18
|
+
self.port_name = port_name
|
|
19
|
+
|
|
20
|
+
def __hash__(self):
|
|
21
|
+
return hash(f"{self.fmu_name}/{self.port_name}")
|
|
22
|
+
|
|
23
|
+
def __eq__(self, other):
|
|
24
|
+
return str(self) == str(other)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Connection:
|
|
28
|
+
def __init__(self, from_port: Port, to_port: Port):
|
|
29
|
+
self.from_port = from_port
|
|
30
|
+
self.to_port = to_port
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AssemblyNode:
|
|
34
|
+
def __init__(self, name: Optional[str], step_size: float = None, mt=False, profiling=False,
|
|
35
|
+
auto_link=True, auto_input=True, auto_output=True):
|
|
36
|
+
self.name = name
|
|
37
|
+
if step_size:
|
|
38
|
+
try:
|
|
39
|
+
self.step_size = float(step_size)
|
|
40
|
+
except ValueError:
|
|
41
|
+
logger.warning(f"Step size '{step_size}' is incorrect format.")
|
|
42
|
+
self.step_size = None
|
|
43
|
+
else:
|
|
44
|
+
self.step_size = None
|
|
45
|
+
self.mt = mt
|
|
46
|
+
self.profiling = profiling
|
|
47
|
+
self.auto_link = auto_link
|
|
48
|
+
self.auto_input = auto_input
|
|
49
|
+
self.auto_output = auto_output
|
|
50
|
+
|
|
51
|
+
self.parent: Optional[AssemblyNode] = None
|
|
52
|
+
self.children: List[AssemblyNode] = [] # sub-containers
|
|
53
|
+
self.fmu_names_list: Set[str] = set() # FMUs contained at this level
|
|
54
|
+
self.input_ports: Dict[Port, str] = {}
|
|
55
|
+
self.output_ports: Dict[Port, str] = {}
|
|
56
|
+
self.start_values: Dict[Port, str] = {}
|
|
57
|
+
self.drop_ports: List[Port] = []
|
|
58
|
+
self.links: List[Connection] = []
|
|
59
|
+
|
|
60
|
+
def add_sub_node(self, sub_node):
|
|
61
|
+
if sub_node.name is None:
|
|
62
|
+
sub_node.name = str(uuid.uuid4())+".fmu"
|
|
63
|
+
|
|
64
|
+
if sub_node.parent is not None:
|
|
65
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already parented.")
|
|
66
|
+
|
|
67
|
+
sub_node.parent = self
|
|
68
|
+
self.fmu_names_list.add(sub_node.name)
|
|
69
|
+
self.children.append(sub_node)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def add_fmu(self, fmu_name: str):
|
|
73
|
+
self.fmu_names_list.add(fmu_name)
|
|
74
|
+
|
|
75
|
+
def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
76
|
+
self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
|
|
77
|
+
|
|
78
|
+
def add_output(self, from_fmu_filename: str, from_port_name: str, to_port_name: str):
|
|
79
|
+
self.output_ports[Port(from_fmu_filename, from_port_name)] = to_port_name
|
|
80
|
+
|
|
81
|
+
def add_drop_port(self, fmu_filename: str, port_name: str):
|
|
82
|
+
self.drop_ports.append(Port(fmu_filename, port_name))
|
|
83
|
+
|
|
84
|
+
def add_link(self, from_fmu_filename: str, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
85
|
+
self.links.append(Connection(Port(from_fmu_filename, from_port_name),
|
|
86
|
+
Port(to_fmu_filename, to_port_name)))
|
|
87
|
+
|
|
88
|
+
def add_start_value(self, fmu_filename: str, port_name: str, value: str):
|
|
89
|
+
self.start_values[Port(fmu_filename, port_name)] = value
|
|
90
|
+
|
|
91
|
+
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
|
|
92
|
+
for node in self.children:
|
|
93
|
+
node.make_fmu(fmu_directory, debug=debug)
|
|
94
|
+
|
|
95
|
+
container = FMUContainer(self.name, fmu_directory, description_pathname=description_pathname)
|
|
96
|
+
|
|
97
|
+
for fmu_name in sorted(self.fmu_names_list):
|
|
98
|
+
container.get_fmu(fmu_name)
|
|
99
|
+
|
|
100
|
+
for port, source in self.input_ports.items():
|
|
101
|
+
container.add_input(source, port.fmu_name, port.port_name)
|
|
102
|
+
|
|
103
|
+
for port, target in self.output_ports.items():
|
|
104
|
+
container.add_output(port.fmu_name, port.port_name, target)
|
|
105
|
+
|
|
106
|
+
for link in self.links:
|
|
107
|
+
container.add_link(link.from_port.fmu_name, link.from_port.port_name,
|
|
108
|
+
link.to_port.fmu_name, link.to_port.port_name)
|
|
109
|
+
|
|
110
|
+
for drop in self.drop_ports:
|
|
111
|
+
container.drop_port(drop.fmu_name, drop.port_name)
|
|
112
|
+
|
|
113
|
+
for port, value in self.start_values.items():
|
|
114
|
+
container.add_start_value(port.fmu_name, port.port_name, value)
|
|
115
|
+
|
|
116
|
+
container.add_implicit_rule(auto_input=self.auto_input,
|
|
117
|
+
auto_output=self.auto_output,
|
|
118
|
+
auto_link=self.auto_link)
|
|
119
|
+
|
|
120
|
+
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
|
|
121
|
+
|
|
122
|
+
for node in self.children:
|
|
123
|
+
logger.info(f"Deleting transient FMU Container '{node.name}'")
|
|
124
|
+
(fmu_directory / node.name).unlink()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class AssemblyError(Exception):
|
|
128
|
+
def __init__(self, reason: str):
|
|
129
|
+
self.reason = reason
|
|
130
|
+
|
|
131
|
+
def __repr__(self):
|
|
132
|
+
return f"{self.reason}"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Assembly:
|
|
136
|
+
def __init__(self, filename: str, step_size=None, auto_link=True, auto_input=True, debug=False,
|
|
137
|
+
auto_output=True, mt=False, profiling=False, fmu_directory: Path = "."):
|
|
138
|
+
self.filename = Path(filename)
|
|
139
|
+
self.default_auto_input = auto_input
|
|
140
|
+
self.debug = debug
|
|
141
|
+
self.default_auto_output = auto_output
|
|
142
|
+
self.default_step_size = step_size
|
|
143
|
+
self.default_auto_link = auto_link
|
|
144
|
+
self.default_mt = mt
|
|
145
|
+
self.default_profiling = profiling
|
|
146
|
+
self.fmu_directory = fmu_directory
|
|
147
|
+
self.transient_filenames: List[Path] = []
|
|
148
|
+
self.transient_dirnames: Set[Path] = set()
|
|
149
|
+
|
|
150
|
+
if not fmu_directory.is_dir():
|
|
151
|
+
raise AssemblyError(f"FMU directory is not valid: '{fmu_directory}'")
|
|
152
|
+
|
|
153
|
+
self.input_pathname = fmu_directory / self.filename
|
|
154
|
+
self.description_pathname = self.input_pathname # For inclusion in FMU
|
|
155
|
+
self.root = None
|
|
156
|
+
self.read()
|
|
157
|
+
|
|
158
|
+
def add_transient_file(self, filename: str):
|
|
159
|
+
self.transient_filenames.append(self.fmu_directory / filename)
|
|
160
|
+
self.transient_dirnames.add(Path(filename).parent)
|
|
161
|
+
|
|
162
|
+
def __del__(self):
|
|
163
|
+
if not self.debug:
|
|
164
|
+
for filename in self.transient_filenames:
|
|
165
|
+
try:
|
|
166
|
+
filename.unlink()
|
|
167
|
+
except FileNotFoundError:
|
|
168
|
+
pass
|
|
169
|
+
for dirname in self.transient_dirnames:
|
|
170
|
+
while not str(dirname) == ".":
|
|
171
|
+
try:
|
|
172
|
+
(self.fmu_directory / dirname).rmdir()
|
|
173
|
+
except FileNotFoundError:
|
|
174
|
+
pass
|
|
175
|
+
dirname = dirname.parent
|
|
176
|
+
|
|
177
|
+
def read(self):
|
|
178
|
+
logger.info(f"Reading '{self.filename}'")
|
|
179
|
+
if self.filename.suffix == ".json":
|
|
180
|
+
self.read_json()
|
|
181
|
+
elif self.filename.suffix == ".ssp":
|
|
182
|
+
self.read_ssp()
|
|
183
|
+
elif self.filename.suffix == ".csv":
|
|
184
|
+
self.read_csv()
|
|
185
|
+
else:
|
|
186
|
+
raise AssemblyError(f"Not supported file format '{self.filename}")
|
|
187
|
+
|
|
188
|
+
def write(self, filename: str):
|
|
189
|
+
if filename.endswith(".csv"):
|
|
190
|
+
return self.write_csv(filename)
|
|
191
|
+
elif filename.endswith(".json"):
|
|
192
|
+
return self.write_json(filename)
|
|
193
|
+
else:
|
|
194
|
+
logger.critical(f"Unable to write to '{filename}': format unsupported.")
|
|
195
|
+
|
|
196
|
+
def read_csv(self):
|
|
197
|
+
name = str(self.filename.with_suffix(".fmu"))
|
|
198
|
+
self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
199
|
+
mt=self.default_mt, profiling=self.default_profiling,
|
|
200
|
+
auto_input=self.default_auto_input, auto_output=self.default_auto_output)
|
|
201
|
+
|
|
202
|
+
with open(self.input_pathname) as file:
|
|
203
|
+
reader = csv.reader(file, delimiter=';')
|
|
204
|
+
self._check_csv_headers(reader)
|
|
205
|
+
for i, row in enumerate(reader):
|
|
206
|
+
if not row or row[0][0] == '#': # skip blank line of comment
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
rule, from_fmu_filename, from_port_name, to_fmu_filename, to_port_name = row
|
|
211
|
+
except ValueError:
|
|
212
|
+
logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
self._read_csv_rule(self.root, rule.upper(),
|
|
217
|
+
from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
|
|
218
|
+
except AssemblyError as e:
|
|
219
|
+
logger.error(f"Line #{i+2}: {e}. Line skipped.")
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def _check_csv_headers(reader):
|
|
224
|
+
headers = next(reader)
|
|
225
|
+
headers_lowered = [h.lower() for h in headers]
|
|
226
|
+
if not headers_lowered == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
|
|
227
|
+
raise AssemblyError("Header (1st line of the file) is not well formatted.")
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def _read_csv_rule(node: AssemblyNode, rule: str, from_fmu_filename: str, from_port_name: str,
|
|
231
|
+
to_fmu_filename: str, to_port_name: str):
|
|
232
|
+
if rule == "FMU":
|
|
233
|
+
if not from_fmu_filename:
|
|
234
|
+
raise AssemblyError("Missing FMU information.")
|
|
235
|
+
node.add_fmu(from_fmu_filename)
|
|
236
|
+
|
|
237
|
+
elif rule == "INPUT":
|
|
238
|
+
if not to_fmu_filename or not to_port_name:
|
|
239
|
+
raise AssemblyError("Missing INPUT ports information.")
|
|
240
|
+
if not from_port_name:
|
|
241
|
+
from_port_name = to_port_name
|
|
242
|
+
node.add_input(from_port_name, to_fmu_filename, to_port_name)
|
|
243
|
+
|
|
244
|
+
elif rule == "OUTPUT":
|
|
245
|
+
if not from_fmu_filename or not from_port_name:
|
|
246
|
+
raise AssemblyError("Missing OUTPUT ports information.")
|
|
247
|
+
if not to_port_name:
|
|
248
|
+
to_port_name = from_port_name
|
|
249
|
+
node.add_output(from_fmu_filename, from_port_name, to_port_name)
|
|
250
|
+
|
|
251
|
+
elif rule == "DROP":
|
|
252
|
+
if not from_fmu_filename or not from_port_name:
|
|
253
|
+
raise AssemblyError("Missing DROP ports information.")
|
|
254
|
+
node.add_drop_port(from_fmu_filename, from_port_name)
|
|
255
|
+
|
|
256
|
+
elif rule == "LINK":
|
|
257
|
+
node.add_link(from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
|
|
258
|
+
|
|
259
|
+
elif rule == "START":
|
|
260
|
+
if not from_fmu_filename or not from_port_name or not to_fmu_filename:
|
|
261
|
+
raise AssemblyError("Missing START ports information.")
|
|
262
|
+
|
|
263
|
+
node.add_start_value(from_fmu_filename, from_port_name, to_fmu_filename)
|
|
264
|
+
else:
|
|
265
|
+
raise AssemblyError(f"unexpected rule '{rule}'. Line skipped.")
|
|
266
|
+
|
|
267
|
+
def write_csv(self, filename: Union[str, Path]):
|
|
268
|
+
if self.root.children:
|
|
269
|
+
raise AssemblyError("This assembly is not flat. Cannot export to CSV file.")
|
|
270
|
+
|
|
271
|
+
with open(self.fmu_directory / filename, "wt") as outfile:
|
|
272
|
+
outfile.write("rule;from_fmu;from_port;to_fmu;to_port\n")
|
|
273
|
+
for fmu in self.root.fmu_names_list:
|
|
274
|
+
outfile.write(f"FMU;{fmu};;;\n")
|
|
275
|
+
for port, source in self.root.input_ports.items():
|
|
276
|
+
outfile.write(f"INPUT;;{source};{port.fmu_name};{port.port_name}\n")
|
|
277
|
+
for port, target in self.root.output_ports.items():
|
|
278
|
+
outfile.write(f"OUTPUT;{port.fmu_name};{port.port_name};;{target}\n")
|
|
279
|
+
for link in self.root.links:
|
|
280
|
+
outfile.write(f"LINK;{link.from_port.fmu_name};{link.from_port.port_name};"
|
|
281
|
+
f"{link.to_port.fmu_name};{link.to_port.port_name}\n")
|
|
282
|
+
for port, value in self.root.start_values.items():
|
|
283
|
+
outfile.write(f"START;{port.fmu_name};{port.port_name};{value};\n")
|
|
284
|
+
for port in self.root.drop_ports:
|
|
285
|
+
outfile.write(f"DROP;{port.fmu_name};{port.port_name};;\n")
|
|
286
|
+
|
|
287
|
+
def read_json(self):
|
|
288
|
+
with open(self.input_pathname) as file:
|
|
289
|
+
try:
|
|
290
|
+
data = json.load(file)
|
|
291
|
+
except json.decoder.JSONDecodeError as e:
|
|
292
|
+
raise AssemblyError(f"Cannot read json: {e}")
|
|
293
|
+
self.root = self._json_decode_node(data)
|
|
294
|
+
if not self.root.name:
|
|
295
|
+
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
296
|
+
|
|
297
|
+
def _json_decode_node(self, data:Dict) -> AssemblyNode:
|
|
298
|
+
name = data.get("name", None) # 1
|
|
299
|
+
step_size = data.get("step_size", self.default_step_size) # 7
|
|
300
|
+
auto_link = data.get("auto_link", self.default_auto_link) # 4
|
|
301
|
+
auto_input = data.get("auto_input", self.default_auto_input) # 5
|
|
302
|
+
auto_output = data.get("auto_output", self.default_auto_output) # 6
|
|
303
|
+
mt = data.get("mt", self.default_mt) # 2
|
|
304
|
+
profiling = data.get("profiling", self.default_profiling) # 3
|
|
305
|
+
|
|
306
|
+
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
307
|
+
auto_input=auto_input, auto_output=auto_output)
|
|
308
|
+
|
|
309
|
+
for key, value in data.items():
|
|
310
|
+
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling'):
|
|
311
|
+
continue # Already read
|
|
312
|
+
|
|
313
|
+
elif key == "container": # 8
|
|
314
|
+
if not isinstance(value, list):
|
|
315
|
+
raise AssemblyError("JSON: 'container' keyword should define a list.")
|
|
316
|
+
for sub_data in value:
|
|
317
|
+
node.add_sub_node(self._json_decode_node(sub_data))
|
|
318
|
+
|
|
319
|
+
elif key == "fmu": # 9
|
|
320
|
+
if not isinstance(value, list):
|
|
321
|
+
raise AssemblyError("JSON: 'fmu' keyword should define a list.")
|
|
322
|
+
for fmu in value:
|
|
323
|
+
node.add_fmu(fmu)
|
|
324
|
+
|
|
325
|
+
elif key == "input": # 10
|
|
326
|
+
self._json_decode_keyword('input', value, node.add_input)
|
|
327
|
+
|
|
328
|
+
elif key == "output": # 11
|
|
329
|
+
self._json_decode_keyword('output', value, node.add_output)
|
|
330
|
+
|
|
331
|
+
elif key == "link": # 12
|
|
332
|
+
self._json_decode_keyword('link', value, node.add_link)
|
|
333
|
+
|
|
334
|
+
elif key == "start": # 13
|
|
335
|
+
self._json_decode_keyword('start', value, node.add_start_value)
|
|
336
|
+
|
|
337
|
+
elif key == "drop": #14
|
|
338
|
+
self._json_decode_keyword('drop', value, node.add_drop_port)
|
|
339
|
+
|
|
340
|
+
else:
|
|
341
|
+
logger.error(f"JSON: unexpected keyword {key}. Skipped.")
|
|
342
|
+
|
|
343
|
+
return node
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _json_decode_keyword(keyword: str, value, function):
|
|
347
|
+
if not isinstance(value, list):
|
|
348
|
+
raise AssemblyError(f"JSON: '{keyword}' keyword should define a list.")
|
|
349
|
+
for line in value:
|
|
350
|
+
if not isinstance(line, list):
|
|
351
|
+
raise AssemblyError(f"JSON: unexpected '{keyword}' value: {line}.")
|
|
352
|
+
try:
|
|
353
|
+
function(*line)
|
|
354
|
+
except TypeError:
|
|
355
|
+
raise AssemblyError(f"JSON: '{keyword}' value does not contain right number of fields: {line}.")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def write_json(self, filename: Union[str, Path]):
|
|
359
|
+
with open(self.fmu_directory / filename, "wt") as file:
|
|
360
|
+
data = self._json_encode_node(self.root)
|
|
361
|
+
json.dump(data, file, indent=2)
|
|
362
|
+
|
|
363
|
+
def _json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
|
|
364
|
+
json_node = dict()
|
|
365
|
+
json_node["name"] = node.name # 1
|
|
366
|
+
json_node["mt"] = node.mt # 2
|
|
367
|
+
json_node["profiling"] = node.profiling # 3
|
|
368
|
+
json_node["auto_link"] = node.auto_link # 4
|
|
369
|
+
json_node["auto_input"] = node.auto_input # 5
|
|
370
|
+
json_node["auto_output"] = node.auto_output # 6
|
|
371
|
+
|
|
372
|
+
if node.step_size:
|
|
373
|
+
json_node["step_size"] = node.step_size # 7
|
|
374
|
+
|
|
375
|
+
if node.children:
|
|
376
|
+
json_node["container"] = [self._json_encode_node(child) for child in node.children] # 8
|
|
377
|
+
|
|
378
|
+
if node.fmu_names_list:
|
|
379
|
+
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] #9
|
|
380
|
+
|
|
381
|
+
if node.input_ports:
|
|
382
|
+
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"] # 10
|
|
383
|
+
for port, source in node.input_ports.items()]
|
|
384
|
+
|
|
385
|
+
if node.output_ports:
|
|
386
|
+
json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"] # 11
|
|
387
|
+
for port, target in node.output_ports.items()]
|
|
388
|
+
|
|
389
|
+
if node.links:
|
|
390
|
+
json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}", # 12
|
|
391
|
+
f"{link.to_port.fmu_name}", f"{link.to_port.port_name}"]
|
|
392
|
+
for link in node.links]
|
|
393
|
+
|
|
394
|
+
if node.start_values:
|
|
395
|
+
json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value] # 13
|
|
396
|
+
for port, value in node.start_values.items()]
|
|
397
|
+
|
|
398
|
+
if node.drop_ports:
|
|
399
|
+
json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports] # 14
|
|
400
|
+
|
|
401
|
+
return json_node
|
|
402
|
+
|
|
403
|
+
def read_ssp(self):
|
|
404
|
+
logger.warning("This feature is ALPHA stage.")
|
|
405
|
+
|
|
406
|
+
with zipfile.ZipFile(self.fmu_directory / self.filename) as zin:
|
|
407
|
+
for file in zin.filelist:
|
|
408
|
+
if file.filename.endswith(".fmu") or file.filename.endswith(".ssd"):
|
|
409
|
+
zin.extract(file, path=self.fmu_directory)
|
|
410
|
+
logger.debug(f"Extracted: {file.filename}")
|
|
411
|
+
self.add_transient_file(file.filename)
|
|
412
|
+
|
|
413
|
+
self.description_pathname = self.fmu_directory / "SystemStructure.ssd"
|
|
414
|
+
if self.description_pathname.is_file():
|
|
415
|
+
sdd = SSDParser(step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
416
|
+
mt=self.default_mt, profiling=self.default_profiling,
|
|
417
|
+
auto_input=self.default_auto_input, auto_output=self.default_auto_output)
|
|
418
|
+
self.root = sdd.parse(self.description_pathname)
|
|
419
|
+
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
420
|
+
|
|
421
|
+
def make_fmu(self, dump_json=False):
|
|
422
|
+
if dump_json:
|
|
423
|
+
dump_file = Path(self.input_pathname.stem + "-dump").with_suffix(".json")
|
|
424
|
+
logger.info(f"Dump Json '{dump_file}'")
|
|
425
|
+
self.write_json(dump_file)
|
|
426
|
+
self.root.make_fmu(self.fmu_directory, debug=self.debug, description_pathname=self.description_pathname)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class SSDParser:
|
|
430
|
+
def __init__(self, **kwargs):
|
|
431
|
+
self.node_stack: List[AssemblyNode] = []
|
|
432
|
+
self.root = None
|
|
433
|
+
self.fmu_filenames: Dict[str, str] = {} # Component name => FMU filename
|
|
434
|
+
self.node_attrs = kwargs
|
|
435
|
+
|
|
436
|
+
def parse(self, ssd_filepath: Path) -> AssemblyNode:
|
|
437
|
+
logger.debug(f"Analysing {ssd_filepath}")
|
|
438
|
+
with open(ssd_filepath, "rb") as file:
|
|
439
|
+
parser = xml.parsers.expat.ParserCreate()
|
|
440
|
+
parser.StartElementHandler = self.start_element
|
|
441
|
+
parser.EndElementHandler = self.end_element
|
|
442
|
+
parser.ParseFile(file)
|
|
443
|
+
|
|
444
|
+
return self.root
|
|
445
|
+
|
|
446
|
+
def start_element(self, tag_name, attrs):
|
|
447
|
+
if tag_name == 'ssd:Connection':
|
|
448
|
+
if 'startElement' in attrs:
|
|
449
|
+
if 'endElement' in attrs:
|
|
450
|
+
fmu_start = self.fmu_filenames[attrs['startElement']]
|
|
451
|
+
fmu_end = self.fmu_filenames[attrs['endElement']]
|
|
452
|
+
self.node_stack[-1].add_link(fmu_start, attrs['startConnector'],
|
|
453
|
+
fmu_end, attrs['endConnector'])
|
|
454
|
+
else:
|
|
455
|
+
fmu_start = self.fmu_filenames[attrs['startElement']]
|
|
456
|
+
self.node_stack[-1].add_output(fmu_start, attrs['startConnector'],
|
|
457
|
+
attrs['endConnector'])
|
|
458
|
+
else:
|
|
459
|
+
fmu_end = self.fmu_filenames[attrs['endElement']]
|
|
460
|
+
self.node_stack[-1].add_input(attrs['startConnector'],
|
|
461
|
+
fmu_end, attrs['endConnector'])
|
|
462
|
+
|
|
463
|
+
elif tag_name == 'ssd:System':
|
|
464
|
+
logger.info(f"SSP System: {attrs['name']}")
|
|
465
|
+
filename = attrs['name'] + ".fmu"
|
|
466
|
+
self.fmu_filenames[attrs['name']] = filename
|
|
467
|
+
node = AssemblyNode(filename, **self.node_attrs)
|
|
468
|
+
if self.node_stack:
|
|
469
|
+
self.node_stack[-1].add_sub_node(node)
|
|
470
|
+
else:
|
|
471
|
+
self.root = node
|
|
472
|
+
|
|
473
|
+
self.node_stack.append(node)
|
|
474
|
+
|
|
475
|
+
elif tag_name == 'ssd:Component':
|
|
476
|
+
filename = attrs['source']
|
|
477
|
+
name = attrs['name']
|
|
478
|
+
self.fmu_filenames[name] = filename
|
|
479
|
+
self.node_stack[-1].add_fmu(filename)
|
|
480
|
+
logger.debug(f"Component {name} => {filename}")
|
|
481
|
+
|
|
482
|
+
def end_element(self, tag_name):
|
|
483
|
+
if tag_name == 'ssd:System':
|
|
484
|
+
self.node_stack.pop()
|
fmu_manipulation_toolbox/cli.py
CHANGED
|
@@ -4,7 +4,8 @@ import sys
|
|
|
4
4
|
from colorama import Fore, Style, init
|
|
5
5
|
|
|
6
6
|
from .fmu_operations import *
|
|
7
|
-
from .fmu_container import
|
|
7
|
+
from .fmu_container import FMUContainerError
|
|
8
|
+
from .assembly import Assembly, AssemblyError
|
|
8
9
|
from .checker import checker_list
|
|
9
10
|
from .version import __version__ as version
|
|
10
11
|
from .help import Help
|
|
@@ -158,11 +159,12 @@ def fmucontainer():
|
|
|
158
159
|
|
|
159
160
|
parser.add_argument('-h', '-help', action="help")
|
|
160
161
|
|
|
161
|
-
parser.add_argument("-fmu-directory", action="store", dest="fmu_directory", required=
|
|
162
|
-
help="Directory containing initial FMU’s and used to generate containers."
|
|
162
|
+
parser.add_argument("-fmu-directory", action="store", dest="fmu_directory", required=False, default=".",
|
|
163
|
+
help="Directory containing initial FMU’s and used to generate containers. "
|
|
164
|
+
"If not defined, current directory is used.")
|
|
163
165
|
|
|
164
166
|
parser.add_argument("-container", action="append", dest="container_descriptions_list", default=[],
|
|
165
|
-
metavar="filename.csv:step_size",
|
|
167
|
+
metavar="filename.{csv|json|ssp},[:step_size]", required=True,
|
|
166
168
|
help="Description of the container to create.")
|
|
167
169
|
|
|
168
170
|
parser.add_argument("-debug", action="store_true", dest="debug",
|
|
@@ -183,34 +185,42 @@ def fmucontainer():
|
|
|
183
185
|
parser.add_argument("-profile", action="store_true", dest="profiling", default=False,
|
|
184
186
|
help="Enable Profiling mode for the generated container.")
|
|
185
187
|
|
|
188
|
+
parser.add_argument("-dump-json", action="store_true", dest="dump", default=False,
|
|
189
|
+
help="Dump a JSON file for each container.")
|
|
190
|
+
|
|
186
191
|
config = parser.parse_args()
|
|
187
192
|
|
|
188
193
|
if config.debug:
|
|
189
194
|
logger.setLevel(logging.DEBUG)
|
|
190
195
|
|
|
196
|
+
fmu_directory = Path(config.fmu_directory)
|
|
197
|
+
logger.info(f"FMU directory: '{fmu_directory}'")
|
|
198
|
+
|
|
191
199
|
for description in config.container_descriptions_list:
|
|
192
200
|
try:
|
|
193
|
-
|
|
201
|
+
filename, step_size = description.split(":")
|
|
194
202
|
step_size = float(step_size)
|
|
195
203
|
except ValueError:
|
|
196
204
|
step_size = None
|
|
197
|
-
|
|
205
|
+
filename = description
|
|
206
|
+
try:
|
|
207
|
+
assembly = Assembly(filename, step_size=step_size, auto_link=config.auto_link,
|
|
208
|
+
auto_input=config.auto_input, auto_output=config.auto_output, mt=config.mt,
|
|
209
|
+
profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug)
|
|
198
210
|
|
|
199
|
-
|
|
211
|
+
except FileNotFoundError as e:
|
|
212
|
+
logger.fatal(f"Cannot read file: {e}")
|
|
213
|
+
continue
|
|
214
|
+
except (FMUContainerError, AssemblyError) as e:
|
|
215
|
+
logger.fatal(f"{filename}: {e}")
|
|
216
|
+
continue
|
|
200
217
|
|
|
201
218
|
try:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
auto_output=config.auto_output,
|
|
206
|
-
auto_link=config.auto_link)
|
|
207
|
-
container.make_fmu(container_filename, step_size=step_size, debug=config.debug, mt=config.mt,
|
|
208
|
-
profiling=config.profiling)
|
|
209
|
-
except (FileNotFoundError, FMUContainerError, FMUException) as e:
|
|
210
|
-
logger.error(f"Cannot build container from '{filename_description}': {e}")
|
|
219
|
+
assembly.make_fmu(dump_json=config.dump)
|
|
220
|
+
except FMUContainerError as e:
|
|
221
|
+
logger.fatal(f"{filename}: {e}")
|
|
211
222
|
continue
|
|
212
223
|
|
|
213
224
|
|
|
214
|
-
# for debug purpose
|
|
215
225
|
if __name__ == "__main__":
|
|
216
226
|
fmucontainer()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import csv
|
|
2
1
|
import logging
|
|
3
2
|
import os
|
|
4
3
|
import shutil
|
|
@@ -8,9 +7,10 @@ from datetime import datetime
|
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from typing import *
|
|
10
9
|
|
|
11
|
-
from .fmu_operations import FMU, OperationAbstract
|
|
10
|
+
from .fmu_operations import FMU, OperationAbstract
|
|
12
11
|
from .version import __version__ as tool_version
|
|
13
12
|
|
|
13
|
+
|
|
14
14
|
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
15
15
|
|
|
16
16
|
|
|
@@ -30,15 +30,14 @@ class FMUPort:
|
|
|
30
30
|
def set_port_type(self, type_name: str, attrs: Dict[str, str]):
|
|
31
31
|
self.type_name = type_name
|
|
32
32
|
self.child = attrs.copy()
|
|
33
|
-
|
|
34
|
-
self.child
|
|
35
|
-
|
|
36
|
-
pass
|
|
33
|
+
for unsupported in ("unit", "declaredType"):
|
|
34
|
+
if unsupported in self.child:
|
|
35
|
+
self.child.pop(unsupported)
|
|
37
36
|
|
|
38
37
|
def xml(self, vr: int, name=None, causality=None, start=None):
|
|
39
38
|
|
|
40
39
|
if self.child is None:
|
|
41
|
-
raise
|
|
40
|
+
raise FMUContainerError(f"FMUPort has no child. Bug?")
|
|
42
41
|
|
|
43
42
|
child_str = f"<{self.type_name}"
|
|
44
43
|
if self.child:
|
|
@@ -70,7 +69,6 @@ class FMUPort:
|
|
|
70
69
|
|
|
71
70
|
class EmbeddedFMU(OperationAbstract):
|
|
72
71
|
capability_list = ("needsExecutionTool",
|
|
73
|
-
"canHandleVariableCommunicationStepSize",
|
|
74
72
|
"canBeInstantiatedOnlyOncePerProcess")
|
|
75
73
|
|
|
76
74
|
def __init__(self, filename):
|
|
@@ -87,6 +85,8 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
87
85
|
self.current_port = None # used during apply_operation()
|
|
88
86
|
|
|
89
87
|
self.fmu.apply_operation(self) # Should be the last command in constructor!
|
|
88
|
+
if self.model_identifier is None:
|
|
89
|
+
raise FMUContainerError(f"FMU '{self.name}' does not implement Co-Simulation mode.")
|
|
90
90
|
|
|
91
91
|
def fmi_attrs(self, attrs):
|
|
92
92
|
self.guid = attrs['guid']
|
|
@@ -104,10 +104,16 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
104
104
|
self.capabilities[capability] = attrs.get(capability, "false")
|
|
105
105
|
|
|
106
106
|
def experiment_attrs(self, attrs):
|
|
107
|
-
|
|
107
|
+
try:
|
|
108
|
+
self.step_size = float(attrs['stepSize'])
|
|
109
|
+
except KeyError:
|
|
110
|
+
logger.warning(f"FMU '{self.name}' does not specify preferred step size")
|
|
111
|
+
pass
|
|
108
112
|
|
|
109
113
|
def scalar_type(self, type_name, attrs):
|
|
110
|
-
self.current_port
|
|
114
|
+
if self.current_port:
|
|
115
|
+
self.current_port.set_port_type(type_name, attrs)
|
|
116
|
+
self.current_port = None
|
|
111
117
|
|
|
112
118
|
def __repr__(self):
|
|
113
119
|
return f"FMU '{self.name}' ({len(self.ports)} variables)"
|
|
@@ -172,14 +178,14 @@ class ValueReferenceTable:
|
|
|
172
178
|
def get_vr(self, cport: ContainerPort) -> int:
|
|
173
179
|
return self.add_vr(cport.port.type_name)
|
|
174
180
|
|
|
175
|
-
def add_vr(self, type_name:str) -> int:
|
|
181
|
+
def add_vr(self, type_name: str) -> int:
|
|
176
182
|
vr = self.vr_table[type_name]
|
|
177
183
|
self.vr_table[type_name] += 1
|
|
178
184
|
return vr
|
|
179
185
|
|
|
180
186
|
|
|
181
187
|
class FMUContainer:
|
|
182
|
-
def __init__(self, identifier: str, fmu_directory: Union[str, Path]):
|
|
188
|
+
def __init__(self, identifier: str, fmu_directory: Union[str, Path], description_pathname=None):
|
|
183
189
|
self.fmu_directory = Path(fmu_directory)
|
|
184
190
|
self.identifier = identifier
|
|
185
191
|
if not self.fmu_directory.is_dir():
|
|
@@ -187,8 +193,7 @@ class FMUContainer:
|
|
|
187
193
|
self.involved_fmu: Dict[str, EmbeddedFMU] = {}
|
|
188
194
|
self.execution_order: List[EmbeddedFMU] = []
|
|
189
195
|
|
|
190
|
-
self.description_pathname =
|
|
191
|
-
self.period = None # Will be set up by FMUContainerSpecReader
|
|
196
|
+
self.description_pathname = description_pathname
|
|
192
197
|
|
|
193
198
|
# Rules
|
|
194
199
|
self.inputs: Dict[str, ContainerPort] = {}
|
|
@@ -207,10 +212,10 @@ class FMUContainer:
|
|
|
207
212
|
self.involved_fmu[fmu_filename] = fmu
|
|
208
213
|
self.execution_order.append(fmu)
|
|
209
214
|
if not fmu.fmi_version == "2.0":
|
|
210
|
-
raise
|
|
215
|
+
raise FMUContainerError("Only FMI-2.0 is supported by FMUContainer")
|
|
211
216
|
logger.debug(f"Adding FMU #{len(self.execution_order)}: {fmu}")
|
|
212
217
|
except Exception as e:
|
|
213
|
-
raise
|
|
218
|
+
raise FMUContainerError(f"Cannot load '{fmu_filename}': {e}")
|
|
214
219
|
|
|
215
220
|
return fmu
|
|
216
221
|
|
|
@@ -227,7 +232,8 @@ class FMUContainer:
|
|
|
227
232
|
container_port_name = to_port_name
|
|
228
233
|
cport_to = ContainerPort(self.get_fmu(to_fmu_filename), to_port_name)
|
|
229
234
|
if not cport_to.port.causality == "input": # check causality
|
|
230
|
-
raise
|
|
235
|
+
raise FMUContainerError(f"Tried to use '{cport_to}' as INPUT of the container but FMU causality is "
|
|
236
|
+
f"'{cport_to.port.causality}'.")
|
|
231
237
|
|
|
232
238
|
logger.debug(f"INPUT: {to_fmu_filename}:{to_port_name}")
|
|
233
239
|
self.mark_ruled(cport_to, 'INPUT')
|
|
@@ -239,7 +245,8 @@ class FMUContainer:
|
|
|
239
245
|
|
|
240
246
|
cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
|
|
241
247
|
if cport_from.port.causality not in ("output", "local"): # check causality
|
|
242
|
-
raise
|
|
248
|
+
raise FMUContainerError(f"Tried to use '{cport_from}' as INPUT of the container but FMU causality is "
|
|
249
|
+
f"'{cport_from.port.causality}'.")
|
|
243
250
|
|
|
244
251
|
logger.debug(f"OUTPUT: {from_fmu_filename}:{from_port_name}")
|
|
245
252
|
self.mark_ruled(cport_from, 'OUTPUT')
|
|
@@ -248,7 +255,7 @@ class FMUContainer:
|
|
|
248
255
|
def drop_port(self, from_fmu_filename: str, from_port_name: str):
|
|
249
256
|
cport_from = ContainerPort(self.get_fmu(from_fmu_filename), from_port_name)
|
|
250
257
|
if not cport_from.port.causality == "output": # check causality
|
|
251
|
-
raise
|
|
258
|
+
raise FMUContainerError(f"{cport_from}: trying to DROP {cport_from.port.causality}")
|
|
252
259
|
|
|
253
260
|
logger.debug(f"DROP: {from_fmu_filename}:{from_port_name}")
|
|
254
261
|
self.mark_ruled(cport_from, 'DROP')
|
|
@@ -284,7 +291,7 @@ class FMUContainer:
|
|
|
284
291
|
|
|
285
292
|
self.start_values[cport] = value
|
|
286
293
|
|
|
287
|
-
def find_input(self, port_to_connect: FMUPort) ->
|
|
294
|
+
def find_input(self, port_to_connect: FMUPort) -> Optional[ContainerPort]:
|
|
288
295
|
for fmu in self.execution_order:
|
|
289
296
|
for port in fmu.ports.values():
|
|
290
297
|
if (port.causality == 'input' and port.name == port_to_connect.name
|
|
@@ -339,29 +346,26 @@ class FMUContainer:
|
|
|
339
346
|
|
|
340
347
|
return step_size
|
|
341
348
|
|
|
342
|
-
def sanity_check(self, step_size:
|
|
343
|
-
nb_error = 0
|
|
349
|
+
def sanity_check(self, step_size: Optional[float]):
|
|
344
350
|
for fmu in self.execution_order:
|
|
351
|
+
if not fmu.step_size:
|
|
352
|
+
continue
|
|
345
353
|
ts_ratio = step_size / fmu.step_size
|
|
346
354
|
if ts_ratio < 1.0:
|
|
347
|
-
logger.
|
|
348
|
-
|
|
355
|
+
logger.warning(f"Container step_size={step_size}s is lower than FMU '{fmu.name}' "
|
|
356
|
+
f"step_size={fmu.step_size}s.")
|
|
349
357
|
if ts_ratio != int(ts_ratio):
|
|
350
|
-
logger.
|
|
351
|
-
|
|
358
|
+
logger.warning(f"Container step_size={step_size}s should divisible by FMU '{fmu.name}' "
|
|
359
|
+
f"step_size={fmu.step_size}s.")
|
|
352
360
|
for port_name in fmu.ports:
|
|
353
361
|
cport = ContainerPort(fmu, port_name)
|
|
354
362
|
if cport not in self.rules:
|
|
355
363
|
if cport.port.causality == 'input':
|
|
356
364
|
logger.error(f"{cport} is not connected")
|
|
357
|
-
nb_error += 1
|
|
358
365
|
if cport.port.causality == 'output':
|
|
359
366
|
logger.warning(f"{cport} is not connected")
|
|
360
367
|
|
|
361
|
-
|
|
362
|
-
raise FMUContainerError(f"Some ports are not connected.")
|
|
363
|
-
|
|
364
|
-
def make_fmu(self, fmu_filename: Union[str, Path], step_size: Union[float, None] = None, debug=False, mt=False,
|
|
368
|
+
def make_fmu(self, fmu_filename: Union[str, Path], step_size: Optional[float] = None, debug=False, mt=False,
|
|
365
369
|
profiling=False):
|
|
366
370
|
if isinstance(fmu_filename, str):
|
|
367
371
|
fmu_filename = Path(fmu_filename)
|
|
@@ -412,12 +416,12 @@ class FMUContainer:
|
|
|
412
416
|
description="FMUContainer with {embedded_fmu}"
|
|
413
417
|
author="{author}"
|
|
414
418
|
license="Proprietary"
|
|
415
|
-
copyright="
|
|
419
|
+
copyright="See Embedded FMU's copyrights."
|
|
416
420
|
variableNamingConvention="structured">
|
|
417
421
|
|
|
418
422
|
<CoSimulation
|
|
419
423
|
modelIdentifier="{self.identifier}"
|
|
420
|
-
canHandleVariableCommunicationStepSize="
|
|
424
|
+
canHandleVariableCommunicationStepSize="true"
|
|
421
425
|
canBeInstantiatedOnlyOncePerProcess="{capabilities['canBeInstantiatedOnlyOncePerProcess']}"
|
|
422
426
|
canNotUseMemoryManagementFunctions="true"
|
|
423
427
|
canGetAndSetFMUstate="false"
|
|
@@ -438,7 +442,8 @@ class FMUContainer:
|
|
|
438
442
|
for fmu in self.execution_order:
|
|
439
443
|
vr = vr_table.add_vr("Real")
|
|
440
444
|
name = f"container.{fmu.model_identifier}.rt_ratio"
|
|
441
|
-
print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local"
|
|
445
|
+
print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
|
|
446
|
+
f'<Real /></ScalarVariable>', file=xml_file)
|
|
442
447
|
|
|
443
448
|
# Local variable should be first to ensure to attribute them the lowest VR.
|
|
444
449
|
for local in self.locals.values():
|
|
@@ -555,7 +560,7 @@ class FMUContainer:
|
|
|
555
560
|
if profiling and type_name == "Real":
|
|
556
561
|
nb += len(self.execution_order)
|
|
557
562
|
print(nb, file=txt_file)
|
|
558
|
-
for profiling_port,_ in enumerate(self.execution_order):
|
|
563
|
+
for profiling_port, _ in enumerate(self.execution_order):
|
|
559
564
|
print(f"{profiling_port} -2 {profiling_port}", file=txt_file)
|
|
560
565
|
else:
|
|
561
566
|
print(nb, file=txt_file)
|
|
@@ -600,6 +605,7 @@ class FMUContainer:
|
|
|
600
605
|
documentation_directory.mkdir(exist_ok=True)
|
|
601
606
|
|
|
602
607
|
if self.description_pathname:
|
|
608
|
+
logger.debug(f"Copying {self.description_pathname}")
|
|
603
609
|
shutil.copy(self.description_pathname, documentation_directory)
|
|
604
610
|
|
|
605
611
|
shutil.copy(origin / "model.png", base_directory)
|
|
@@ -628,157 +634,3 @@ class FMUContainer:
|
|
|
628
634
|
def make_fmu_cleanup(base_directory: Path):
|
|
629
635
|
logger.debug(f"Delete directory '{base_directory}'")
|
|
630
636
|
shutil.rmtree(base_directory)
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
class FMUContainerSpecReader:
|
|
634
|
-
def __init__(self, fmu_directory: Union[Path, str]):
|
|
635
|
-
self.fmu_directory = Path(fmu_directory)
|
|
636
|
-
|
|
637
|
-
def read(self, description_filename: Union[str, Path]) -> FMUContainer:
|
|
638
|
-
if isinstance(description_filename, str):
|
|
639
|
-
description_filename = Path(description_filename)
|
|
640
|
-
|
|
641
|
-
if description_filename.suffix == ".csv":
|
|
642
|
-
return self.read_csv(description_filename)
|
|
643
|
-
else:
|
|
644
|
-
logger.critical(f"Unable to read from '{description_filename}': format unsupported.")
|
|
645
|
-
|
|
646
|
-
def read_csv(self, description_filename: Path) -> FMUContainer:
|
|
647
|
-
container = FMUContainer(description_filename.stem, self.fmu_directory)
|
|
648
|
-
container.description_pathname = self.fmu_directory / description_filename
|
|
649
|
-
logger.info(f"Building FMU Container from '{container.description_pathname}'")
|
|
650
|
-
|
|
651
|
-
with open(container.description_pathname) as file:
|
|
652
|
-
reader = csv.reader(file, delimiter=';')
|
|
653
|
-
self.check_headers(reader)
|
|
654
|
-
for i, row in enumerate(reader):
|
|
655
|
-
if not row or row[0][0] == '#': # skip blank line of comment
|
|
656
|
-
continue
|
|
657
|
-
|
|
658
|
-
try:
|
|
659
|
-
rule, from_fmu_filename, from_port_name, to_fmu_filename, to_port_name = row
|
|
660
|
-
except ValueError:
|
|
661
|
-
logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
|
|
662
|
-
continue
|
|
663
|
-
|
|
664
|
-
rule = rule.upper()
|
|
665
|
-
if rule in ("LINK", "INPUT", "OUTPUT", "DROP", "FMU", "START"):
|
|
666
|
-
try:
|
|
667
|
-
self._read_csv_rule(container, rule,
|
|
668
|
-
from_fmu_filename, from_port_name,
|
|
669
|
-
to_fmu_filename, to_port_name)
|
|
670
|
-
except FMUContainerError as e:
|
|
671
|
-
logger.error(f"Line #{i+2}: {e}. Line skipped.")
|
|
672
|
-
continue
|
|
673
|
-
except FMUException as e:
|
|
674
|
-
logger.critical(f"Line #{i + 2}: {e}.")
|
|
675
|
-
raise
|
|
676
|
-
else:
|
|
677
|
-
logger.error(f"Line #{i+2}: unexpected rule '{rule}'. Line skipped.")
|
|
678
|
-
|
|
679
|
-
return container
|
|
680
|
-
|
|
681
|
-
@staticmethod
|
|
682
|
-
def _read_csv_rule(container: FMUContainer, rule: str, from_fmu_filename: str, from_port_name: str,
|
|
683
|
-
to_fmu_filename: str, to_port_name: str):
|
|
684
|
-
if rule == "FMU":
|
|
685
|
-
if not from_fmu_filename:
|
|
686
|
-
raise FMUException("Missing FMU information.")
|
|
687
|
-
container.get_fmu(from_fmu_filename)
|
|
688
|
-
|
|
689
|
-
elif rule == "INPUT":
|
|
690
|
-
if not to_fmu_filename or not to_port_name:
|
|
691
|
-
raise FMUException("Missing INPUT ports information.")
|
|
692
|
-
container.add_input(from_port_name, to_fmu_filename, to_port_name)
|
|
693
|
-
|
|
694
|
-
elif rule == "OUTPUT":
|
|
695
|
-
if not from_fmu_filename or not from_port_name:
|
|
696
|
-
raise FMUException("Missing OUTPUT ports information.")
|
|
697
|
-
container.add_output(from_fmu_filename, from_port_name, to_port_name)
|
|
698
|
-
|
|
699
|
-
elif rule == "DROP":
|
|
700
|
-
if not from_fmu_filename or not from_port_name:
|
|
701
|
-
raise FMUException("Missing DROP ports information.")
|
|
702
|
-
container.drop_port(from_fmu_filename, from_port_name)
|
|
703
|
-
|
|
704
|
-
elif rule == "LINK":
|
|
705
|
-
container.add_link(from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
|
|
706
|
-
|
|
707
|
-
elif rule == "START":
|
|
708
|
-
if not from_fmu_filename or not from_port_name or not to_fmu_filename:
|
|
709
|
-
raise FMUException("Missing START ports information.")
|
|
710
|
-
|
|
711
|
-
container.add_start_value(from_fmu_filename, from_port_name, to_fmu_filename)
|
|
712
|
-
# no else: check on rule is already done in read_description()
|
|
713
|
-
|
|
714
|
-
@staticmethod
|
|
715
|
-
def check_headers(reader):
|
|
716
|
-
headers = next(reader)
|
|
717
|
-
if not headers == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
|
|
718
|
-
raise FMUContainerError("Header (1st line of the file) is not well formatted.")
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
class FMUContainerSpecWriter:
|
|
722
|
-
def __init__(self, container: FMUContainer):
|
|
723
|
-
self.container = container
|
|
724
|
-
|
|
725
|
-
def write(self, description_filename: Union[str, Path]):
|
|
726
|
-
if description_filename.endswith(".csv"):
|
|
727
|
-
return self.write_csv(description_filename)
|
|
728
|
-
elif description_filename.endswith(".json"):
|
|
729
|
-
return self.write_json(description_filename)
|
|
730
|
-
else:
|
|
731
|
-
logger.critical(f"Unable to write to '{description_filename}': format unsupported.")
|
|
732
|
-
|
|
733
|
-
def write_csv(self, description_filename: Union[str, Path]):
|
|
734
|
-
with open(description_filename, "wt") as outfile:
|
|
735
|
-
print("rule;from_fmu;from_port;to_fmu;to_port", file=outfile)
|
|
736
|
-
for fmu in self.container.involved_fmu.keys():
|
|
737
|
-
print(f"FMU;{fmu};;;", file=outfile)
|
|
738
|
-
for cport in self.container.inputs.values():
|
|
739
|
-
print(f"INPUT;;;{cport.fmu.name};{cport.port.name}", file=outfile)
|
|
740
|
-
for cport in self.container.outputs.values():
|
|
741
|
-
print(f"OUTPUT;{cport.fmu.name};{cport.port.name};;", file=outfile)
|
|
742
|
-
for local in self.container.locals.values():
|
|
743
|
-
for target in local.cport_to_list:
|
|
744
|
-
print(f"LINK;{local.cport_from.fmu.name};{local.cport_from.port.name};"
|
|
745
|
-
f"{target.fmu.name};{target.port.name}",
|
|
746
|
-
file=outfile)
|
|
747
|
-
for cport, value in self.container.start_values.items():
|
|
748
|
-
print(f"START;{cport.fmu.name};{cport.port.name};{value};", file=outfile)
|
|
749
|
-
|
|
750
|
-
def write_json(self, description_filename: Union[str, Path]):
|
|
751
|
-
with open(description_filename, "wt") as outfile:
|
|
752
|
-
print("{", file=outfile)
|
|
753
|
-
|
|
754
|
-
print(f' "fmu": [', file=outfile)
|
|
755
|
-
fmus = [f' "{fmu}"' for fmu in self.container.involved_fmu.keys()]
|
|
756
|
-
print(",\n".join(fmus), file=outfile)
|
|
757
|
-
print(f' ],', file=outfile)
|
|
758
|
-
|
|
759
|
-
print(f' "input": [', file=outfile)
|
|
760
|
-
inputs = [f' [{cport.fmu.name}, {cport.port.name}, {container_name}]'
|
|
761
|
-
for container_name, cport in self.container.inputs.items()]
|
|
762
|
-
print(",\n".join(inputs), file=outfile)
|
|
763
|
-
print(f' ],', file=outfile)
|
|
764
|
-
|
|
765
|
-
print(f' "output": [', file=outfile)
|
|
766
|
-
outputs = [f' ["{cport.fmu.name}", "{cport.port.name}", "{container_name}"]'
|
|
767
|
-
for container_name, cport in self.container.outputs.items()]
|
|
768
|
-
print(",\n".join(outputs), file=outfile)
|
|
769
|
-
print(f' ],', file=outfile)
|
|
770
|
-
|
|
771
|
-
print(f' "link": [', file=outfile)
|
|
772
|
-
links = [f' ["{local.cport_from.fmu.name}", "{local.cport_from.port.name}", '
|
|
773
|
-
f'"{target.fmu.name}", "{target.port.name}"]'
|
|
774
|
-
for local in self.container.locals.values()
|
|
775
|
-
for target in local.cport_to_list]
|
|
776
|
-
print(",\n".join(links), file=outfile)
|
|
777
|
-
print(f' ],', file=outfile)
|
|
778
|
-
print(f' "start": [', file=outfile)
|
|
779
|
-
start = [f' ["{cport.fmu.name}", "{cport.port.name}, "{value}"]'
|
|
780
|
-
for cport, value in self.container.start_values.items()]
|
|
781
|
-
print(f' ],', file=outfile)
|
|
782
|
-
|
|
783
|
-
#print(f' "period": {self.container.})
|
|
784
|
-
print("}", file=outfile)
|
fmu_manipulation_toolbox/gui.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import os.path
|
|
2
2
|
import sys
|
|
3
3
|
from .version import __version__ as version
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
QTextBrowser, QInputDialog, QMenu
|
|
7
|
-
from
|
|
4
|
+
from PyQt6.QtCore import Qt, QObject, QUrl, pyqtSignal, QDir, QSize, QPoint
|
|
5
|
+
from PyQt6.QtWidgets import (QApplication, QWidget, QGridLayout, QLabel, QLineEdit, QPushButton, QFileDialog,
|
|
6
|
+
QTextBrowser, QInputDialog, QMenu)
|
|
7
|
+
from PyQt6.QtGui import (QPixmap, QImage, QFont, QTextCursor, QIcon, QDesktopServices, QAction, QPainter, QColor)
|
|
8
8
|
import textwrap
|
|
9
9
|
from functools import partial
|
|
10
10
|
from typing import Optional
|
|
@@ -42,7 +42,7 @@ class DropZoneWidget(QLabel):
|
|
|
42
42
|
|
|
43
43
|
def dropEvent(self, event):
|
|
44
44
|
if event.mimeData().hasImage:
|
|
45
|
-
event.setDropAction(Qt.CopyAction)
|
|
45
|
+
event.setDropAction(Qt.DropAction.CopyAction)
|
|
46
46
|
try:
|
|
47
47
|
file_path = event.mimeData().urls()[0].toLocalFile()
|
|
48
48
|
except IndexError:
|
|
@@ -59,8 +59,8 @@ class DropZoneWidget(QLabel):
|
|
|
59
59
|
else:
|
|
60
60
|
default_directory = os.path.expanduser('~')
|
|
61
61
|
|
|
62
|
-
fmu_filename, _ = QFileDialog.getOpenFileName(self, 'Select FMU to Manipulate',
|
|
63
|
-
default_directory, "FMU files (*.fmu)")
|
|
62
|
+
fmu_filename, _ = QFileDialog.getOpenFileName(parent=self, caption='Select FMU to Manipulate',
|
|
63
|
+
directory=default_directory, filter="FMU files (*.fmu)")
|
|
64
64
|
if fmu_filename:
|
|
65
65
|
self.set_fmu(fmu_filename)
|
|
66
66
|
|
|
@@ -70,23 +70,23 @@ class DropZoneWidget(QLabel):
|
|
|
70
70
|
elif not os.path.isfile(filename):
|
|
71
71
|
filename = os.path.join(os.path.dirname(__file__), "resources", "fmu.png")
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
self.
|
|
73
|
+
base_image = QImage(filename).scaled(self.WIDTH, self.HEIGHT, Qt.AspectRatioMode.IgnoreAspectRatio,
|
|
74
|
+
Qt.TransformationMode.SmoothTransformation)
|
|
75
|
+
mask_filename = os.path.join(os.path.dirname(__file__), "resources", "mask.png")
|
|
76
|
+
mask_image = QImage(mask_filename).scaled(self.WIDTH, self.HEIGHT, Qt.AspectRatioMode.IgnoreAspectRatio,
|
|
77
|
+
Qt.TransformationMode.SmoothTransformation)
|
|
78
|
+
rounded_image = QImage(self.WIDTH, self.HEIGHT, QImage.Format.Format_ARGB32)
|
|
79
|
+
rounded_image.fill(QColor(0, 0, 0, 0))
|
|
80
|
+
painter = QPainter()
|
|
81
|
+
painter.begin(rounded_image)
|
|
82
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
83
|
+
painter.drawImage(QPoint(0, 0), base_image)
|
|
84
|
+
painter.drawImage(QPoint(0, 0), mask_image)
|
|
85
|
+
painter.end()
|
|
86
|
+
pixmap = QPixmap.fromImage(rounded_image)
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
rounded = QPixmap(pixmap.size())
|
|
80
|
-
rounded.fill(QColor("transparent"))
|
|
88
|
+
self.setPixmap(pixmap)
|
|
81
89
|
|
|
82
|
-
painter = QPainter(rounded)
|
|
83
|
-
painter.setRenderHint(QPainter.Antialiasing)
|
|
84
|
-
painter.setBrush(QBrush(pixmap))
|
|
85
|
-
painter.setPen(Qt.NoPen)
|
|
86
|
-
painter.drawRoundedRect(pixmap.rect(), 20, 20)
|
|
87
|
-
del painter # Mandatory to avoid a crash
|
|
88
|
-
self.update() # Mandatory to avoid a crash
|
|
89
|
-
return rounded
|
|
90
90
|
|
|
91
91
|
def set_fmu(self, filename):
|
|
92
92
|
try:
|
|
@@ -147,7 +147,7 @@ class LogWidget(QTextBrowser):
|
|
|
147
147
|
LogWidget.XStream.stdout().messageWritten.connect(self.insertPlainText)
|
|
148
148
|
LogWidget.XStream.stderr().messageWritten.connect(self.insertPlainText)
|
|
149
149
|
|
|
150
|
-
def loadResource(self,
|
|
150
|
+
def loadResource(self, _, name):
|
|
151
151
|
image_path = os.path.join(os.path.dirname(__file__), "resources", name.toString())
|
|
152
152
|
return QPixmap(image_path)
|
|
153
153
|
|
|
@@ -162,7 +162,7 @@ class HelpWidget(QLabel):
|
|
|
162
162
|
filename = os.path.join(os.path.dirname(__file__), "resources", "help.png")
|
|
163
163
|
image = QPixmap(filename)
|
|
164
164
|
self.setPixmap(image)
|
|
165
|
-
self.setAlignment(Qt.AlignRight)
|
|
165
|
+
self.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
166
166
|
|
|
167
167
|
def mousePressEvent(self, event):
|
|
168
168
|
QDesktopServices.openUrl(QUrl(self.HELP_URL))
|
|
@@ -209,14 +209,16 @@ class FilterWidget(QPushButton):
|
|
|
209
209
|
|
|
210
210
|
|
|
211
211
|
class FMUManipulationToolboxlMainWindow(QWidget):
|
|
212
|
-
def __init__(self,
|
|
212
|
+
def __init__(self, *args, **kwargs):
|
|
213
213
|
super().__init__(*args, **kwargs)
|
|
214
214
|
|
|
215
215
|
self.setWindowTitle('FMU Manipulation Toolbox')
|
|
216
|
-
self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon.png')))
|
|
217
216
|
|
|
218
217
|
# set the grid layout
|
|
219
218
|
self.layout = QGridLayout()
|
|
219
|
+
self.layout.setVerticalSpacing(4)
|
|
220
|
+
self.layout.setHorizontalSpacing(4)
|
|
221
|
+
self.layout.setContentsMargins(10, 10, 10, 10)
|
|
220
222
|
self.setLayout(self.layout)
|
|
221
223
|
|
|
222
224
|
self.dropped_fmu = DropZoneWidget()
|
|
@@ -266,7 +268,7 @@ class FMUManipulationToolboxlMainWindow(QWidget):
|
|
|
266
268
|
|
|
267
269
|
line += 1
|
|
268
270
|
self.apply_filter_label = QLabel("Apply modification only on: ")
|
|
269
|
-
self.layout.addWidget(self.apply_filter_label, line, 1, 1, 2, alignment=Qt.AlignRight)
|
|
271
|
+
self.layout.addWidget(self.apply_filter_label, line, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignRight)
|
|
270
272
|
self.set_tooltip(self.apply_filter_label, 'gui-apply-only')
|
|
271
273
|
|
|
272
274
|
causality = ["parameter", "calculatedParameter", "input", "output", "local", "independent"]
|
|
@@ -289,7 +291,7 @@ class FMUManipulationToolboxlMainWindow(QWidget):
|
|
|
289
291
|
|
|
290
292
|
exit_button = QPushButton('Exit')
|
|
291
293
|
self.layout.addWidget(exit_button, line, 0, 1, 2)
|
|
292
|
-
exit_button.clicked.connect(
|
|
294
|
+
exit_button.clicked.connect(self.close)
|
|
293
295
|
exit_button.setProperty("class", "quit")
|
|
294
296
|
|
|
295
297
|
save_log_button = QPushButton('Save log as')
|
|
@@ -415,7 +417,7 @@ class FMUManipulationToolboxlMainWindow(QWidget):
|
|
|
415
417
|
|
|
416
418
|
def apply_operation(self, operation):
|
|
417
419
|
if self.dropped_fmu.fmu:
|
|
418
|
-
self.log_widget.moveCursor(QTextCursor.End)
|
|
420
|
+
self.log_widget.moveCursor(QTextCursor.MoveOperation.End)
|
|
419
421
|
fmu_filename = os.path.basename(self.dropped_fmu.fmu.fmu_filename)
|
|
420
422
|
print('-' * 100)
|
|
421
423
|
self.log_widget.insertHtml(f"<strong>{fmu_filename}: {operation}</strong><br>")
|
|
@@ -434,20 +436,21 @@ class FMUManipulationToolboxlMainWindow(QWidget):
|
|
|
434
436
|
scroll_bar.setValue(scroll_bar.maximum())
|
|
435
437
|
|
|
436
438
|
|
|
437
|
-
class Application:
|
|
439
|
+
class Application(QApplication):
|
|
438
440
|
"""
|
|
439
441
|
Analyse and modify your FMUs.
|
|
440
442
|
|
|
441
|
-
Note: modifying the modelDescription.xml can damage your FMU !
|
|
442
|
-
way the FMU is generated, is preferable when possible.
|
|
443
|
+
Note: modifying the modelDescription.xml can damage your FMU !
|
|
444
|
+
Communicating with the FMU-developer and adapting the way the FMU is generated, is preferable when possible.
|
|
443
445
|
|
|
444
446
|
"""
|
|
445
|
-
def __init__(self):
|
|
447
|
+
def __init__(self, *args, **kwargs):
|
|
448
|
+
super().__init__(*args, **kwargs)
|
|
449
|
+
|
|
446
450
|
QDir.addSearchPath('images', os.path.join(os.path.dirname(__file__), "resources"))
|
|
447
|
-
self.app = QApplication(sys.argv)
|
|
448
451
|
font = QFont("Verdana")
|
|
449
452
|
font.setPointSize(10)
|
|
450
|
-
self.
|
|
453
|
+
self.setFont(font)
|
|
451
454
|
css_dark = """
|
|
452
455
|
QWidget {background: #4b4e51; color: #b5bab9}
|
|
453
456
|
QPushButton { min-height: 30px; padding: 1px 1px 0.2em 0.2em; border: 1px solid #282830; border-radius: 5px;}
|
|
@@ -465,8 +468,8 @@ QPushButton.save:hover {background-color: #675a78; color: #dddddd;}
|
|
|
465
468
|
QPushButton.quit {background-color: #4571a4; color: #dddddd;}
|
|
466
469
|
QPushButton.quit:hover {background-color: #5682b5; color: #dddddd;}
|
|
467
470
|
QToolTip {color: black}
|
|
468
|
-
QLabel.dropped_fmu {background-color: #b5bab9
|
|
469
|
-
QLabel.dropped_fmu:hover {background-color: #c6cbca
|
|
471
|
+
QLabel.dropped_fmu {background-color: #b5bab9}
|
|
472
|
+
QLabel.dropped_fmu:hover {background-color: #c6cbca}
|
|
470
473
|
QTextBrowser {background-color: #282830; color: #b5bab9;}
|
|
471
474
|
QMenu {font-size: 18px;}
|
|
472
475
|
QMenu::item {padding: 2px 250px 2px 20px; border: 1px solid transparent;}
|
|
@@ -479,15 +482,25 @@ QMenu::indicator:unchecked:hover {width: 35px; image: url(images:checkbox-unc
|
|
|
479
482
|
QMenu::indicator:unchecked:disabled {width: 35px; image: url(images:checkbox-unchecked-disabled.png); }
|
|
480
483
|
"""
|
|
481
484
|
|
|
482
|
-
self.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
self.setStyleSheet(css_dark)
|
|
486
|
+
|
|
487
|
+
if os.name == 'nt':
|
|
488
|
+
self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon-round.png')))
|
|
489
|
+
|
|
490
|
+
# https://stackoverflow.com/questions/1551605/how-to-set-applications-taskbar-icon-in-windows-7/1552105#1552105
|
|
491
|
+
import ctypes
|
|
492
|
+
application_id = 'FMU_Manipulation_Toolbox' # arbitrary string
|
|
493
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(application_id)
|
|
494
|
+
else:
|
|
495
|
+
self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'resources', 'icon.png')))
|
|
487
496
|
|
|
488
|
-
|
|
489
|
-
self.app.exit()
|
|
497
|
+
self.window = FMUManipulationToolboxlMainWindow()
|
|
490
498
|
|
|
491
499
|
|
|
492
500
|
def main():
|
|
493
|
-
Application()
|
|
501
|
+
application = Application(sys.argv)
|
|
502
|
+
|
|
503
|
+
print(" " * 80, f"Version {version}")
|
|
504
|
+
print(application.__doc__)
|
|
505
|
+
|
|
506
|
+
sys.exit(application.exec())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
{fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fmu_manipulation_toolbox
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
4
4
|
Summary: FMU Manipulation Toobox is a python application which help to modify a Functional Mock-up Units (FMUs) without recompilation or to group them into FMU Containers
|
|
5
5
|
Home-page: https://github.com/grouperenault/fmu_manipulation_toolbox/
|
|
6
6
|
Author: Nicolas.LAURENT@Renault.com
|
{fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/RECORD
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
fmu_manipulation_toolbox/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
2
|
fmu_manipulation_toolbox/__main__.py,sha256=mAzrtkil506DS0F3g3CEbHdtZsZotzntcNhIn_lNJkw,344
|
|
3
|
-
fmu_manipulation_toolbox/__version__.py,sha256=
|
|
3
|
+
fmu_manipulation_toolbox/__version__.py,sha256=UHHZBWWhJrlslwhXubEi_Un0xr55BiMWPTaN6R2jGLM,9
|
|
4
|
+
fmu_manipulation_toolbox/assembly.py,sha256=i0BDfgAca5WgrIYDv1z1mMxaKTiP-tBlPtZJJqMxHgI,20776
|
|
4
5
|
fmu_manipulation_toolbox/checker.py,sha256=lE2MpH4BAKCDjUvbr06N56u7ao8hWXaJgMKaLvmhFTQ,2272
|
|
5
|
-
fmu_manipulation_toolbox/cli.py,sha256=
|
|
6
|
-
fmu_manipulation_toolbox/fmu_container.py,sha256=
|
|
6
|
+
fmu_manipulation_toolbox/cli.py,sha256=yAyfpogj_NgGt0mQRFA9dBy0NvN8vFomKUUrAtOIA5c,10102
|
|
7
|
+
fmu_manipulation_toolbox/fmu_container.py,sha256=rbe71nS_kv2QyhcsaW70YlxR3iA_YO2_VX2ihRVxFQs,26338
|
|
7
8
|
fmu_manipulation_toolbox/fmu_operations.py,sha256=Z3LVOnDvwzoBrqfibZPAn_Osw6MIuGrXtaboGFqp0DA,15836
|
|
8
|
-
fmu_manipulation_toolbox/gui.py,sha256=
|
|
9
|
+
fmu_manipulation_toolbox/gui.py,sha256=VKhQpIEEilScBSwTSJkDUwSbtsmB_VMuX0QwhZczTq4,20628
|
|
9
10
|
fmu_manipulation_toolbox/help.py,sha256=aklKiLrsE0adSzQ5uoEB1sBDmI6s4l231gavu4XxxzA,5856
|
|
10
11
|
fmu_manipulation_toolbox/version.py,sha256=OhBLkZ1-nhC77kyvffPNAf6m8OZe1bYTnNf_PWs1NvM,392
|
|
11
12
|
fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png,sha256=FWIuyrXlaNLLePHfXj7j9ca5rT8Hgr14KCe1EqTCZyk,2288
|
|
@@ -14,12 +15,14 @@ fmu_manipulation_toolbox/resources/checkbox-checked.png,sha256=gzyFqvRFsZixVh6Zl
|
|
|
14
15
|
fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png,sha256=KNdiE8zJ8H-mH81spHL8Ck-V87dj-fPPPrPNZFRv3wA,1783
|
|
15
16
|
fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png,sha256=7XT54vwzDfSK-i6oJ5BBKGXKz8duRRVtoUzaOlWX0WE,1797
|
|
16
17
|
fmu_manipulation_toolbox/resources/checkbox-unchecked.png,sha256=w3MG3RwFeTwkVOAFi8ZBs6yNlmtVnXxXY5atNyvLw54,1793
|
|
17
|
-
fmu_manipulation_toolbox/resources/drop_fmu.png,sha256=
|
|
18
|
+
fmu_manipulation_toolbox/resources/drop_fmu.png,sha256=xt2YzoQVcvr95Ix-4kw6wT04yf660sUHGHkskHRE_l0,10365
|
|
18
19
|
fmu_manipulation_toolbox/resources/fmu.png,sha256=7bI_cb3pcqEnwBCCL30v3MXPwOK8OtbhFFouRq9lTj8,12048
|
|
19
20
|
fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png,sha256=hUhuGg88BU5j7KkqhnU981wSpzn9ftQuOSu8pMYuP-Y,37997
|
|
20
21
|
fmu_manipulation_toolbox/resources/help.png,sha256=WrIbjmlgIqdo6UWYj6L6gG-BCGWlu4qma8HRgRk8k7o,1822
|
|
21
|
-
fmu_manipulation_toolbox/resources/icon.png,sha256=
|
|
22
|
+
fmu_manipulation_toolbox/resources/icon-round.png,sha256=j7jk-9NVQQZJMNazjpj8WeC1q6ptXwTaiO38kPoDEnE,83754
|
|
23
|
+
fmu_manipulation_toolbox/resources/icon.png,sha256=Ovui-UDBARqdTlVQwTn5Ud8seSsVh9pdElwLq5s6xKg,69976
|
|
22
24
|
fmu_manipulation_toolbox/resources/license.txt,sha256=5ODuU8g8pIkK-NMWXu_rjZ6k7gM7b-N2rmg87-2Kmqw,1583
|
|
25
|
+
fmu_manipulation_toolbox/resources/mask.png,sha256=px1U4hQGL0AmZ4BQPknOVREpMpTSejbah3ntkpqAzFA,3008
|
|
23
26
|
fmu_manipulation_toolbox/resources/model.png,sha256=EAf_HnZJe8zYGZygerG1MMt2U-tMMZlifzXPj4_iORA,208788
|
|
24
27
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd,sha256=OGfyJtaJntKypX5KDpuZ-nV1oYLZ6HV16pkpKOmYox4,2731
|
|
25
28
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd,sha256=HwyV7LBse-PQSv4z1xjmtzPU3Hjnv4mluq9YdSBNHMQ,3704
|
|
@@ -28,19 +31,19 @@ fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd,sha256=hYZGmhv
|
|
|
28
31
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd,sha256=8A0hl2wb1suoxv47xezkmvVTyJM2ZJp5RPQ2xQ_SjlY,3883
|
|
29
32
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd,sha256=pB9Pe-yBMGZN-JQAu6VB_lXS99kz23mwiUSY74ONZd4,5403
|
|
30
33
|
fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd,sha256=Tc9RWKqvUXpB1qsoU64DAHO4Gfu5DotrQJ3Ece7GnTU,4647
|
|
31
|
-
fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=
|
|
32
|
-
fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=
|
|
33
|
-
fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=
|
|
34
|
-
fmu_manipulation_toolbox/resources/linux64/container.so,sha256=
|
|
35
|
-
fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=
|
|
36
|
-
fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=
|
|
37
|
-
fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=
|
|
38
|
-
fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=
|
|
39
|
-
fmu_manipulation_toolbox/resources/win64/container.dll,sha256=
|
|
40
|
-
fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=
|
|
41
|
-
fmu_manipulation_toolbox-1.
|
|
42
|
-
fmu_manipulation_toolbox-1.
|
|
43
|
-
fmu_manipulation_toolbox-1.
|
|
44
|
-
fmu_manipulation_toolbox-1.
|
|
45
|
-
fmu_manipulation_toolbox-1.
|
|
46
|
-
fmu_manipulation_toolbox-1.
|
|
34
|
+
fmu_manipulation_toolbox/resources/linux32/client_sm.so,sha256=xVdY2zy13pa2DcvFiweSNpp7E6DiONqeoBdlcJHrW_k,35940
|
|
35
|
+
fmu_manipulation_toolbox/resources/linux32/server_sm,sha256=1TLGqNPyM5UVOrCfzNqWyF6ClLksY3EiY3CSsrnp6c0,22836
|
|
36
|
+
fmu_manipulation_toolbox/resources/linux64/client_sm.so,sha256=EhY1XHo1YcQn6yqZ7wk5okqtZyp0MrcCsGcudqE-aIM,37000
|
|
37
|
+
fmu_manipulation_toolbox/resources/linux64/container.so,sha256=_uhkJYZa_z1tV0NBATWC8iAGt7lPY11_VA29a_5hXsM,45384
|
|
38
|
+
fmu_manipulation_toolbox/resources/linux64/server_sm,sha256=ulfoPvmaYe9nInYcVEyj7mD9zDzGk56OUoWx1mPKLiE,22768
|
|
39
|
+
fmu_manipulation_toolbox/resources/win32/client_sm.dll,sha256=usY6k86X-Y0De5-SxCvgJ3yRzPpR4KCNTShYzDeec80,17920
|
|
40
|
+
fmu_manipulation_toolbox/resources/win32/server_sm.exe,sha256=3570x6uDaKUIYBy9uOmID66ffvy-bxVa8Q2il4m5KZc,15872
|
|
41
|
+
fmu_manipulation_toolbox/resources/win64/client_sm.dll,sha256=G6bNin2QuwbTiToJNSsSM1Dil899MHnZ5FNm-4ExH5I,22016
|
|
42
|
+
fmu_manipulation_toolbox/resources/win64/container.dll,sha256=RBBo9kBBYhYe_KLPl5MtJJCVuL1Zf1LNT87mT88JCPg,32768
|
|
43
|
+
fmu_manipulation_toolbox/resources/win64/server_sm.exe,sha256=Pcx1z-2E7_iHmAzUDEFNTBCf02gfzg5pdYdso5GbP5A,19456
|
|
44
|
+
fmu_manipulation_toolbox-1.8.1.dist-info/LICENSE.txt,sha256=c_862mzyk6ownO3Gt6cVs0-53IXLi_-ZEQFNDVabESw,1285
|
|
45
|
+
fmu_manipulation_toolbox-1.8.1.dist-info/METADATA,sha256=24_m-PSpY9JZqU4LxMHDN3G1IdGydt_8aSXvDU7vm-8,967
|
|
46
|
+
fmu_manipulation_toolbox-1.8.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
47
|
+
fmu_manipulation_toolbox-1.8.1.dist-info/entry_points.txt,sha256=jCPLMBdS-eOvmRfDv7n0wHZSbJHccHviW03mz5vwO-Q,124
|
|
48
|
+
fmu_manipulation_toolbox-1.8.1.dist-info/top_level.txt,sha256=9D_h-5BMjSqf9z-XFkbJL_bMppR2XNYW3WNuPkXou0k,25
|
|
49
|
+
fmu_manipulation_toolbox-1.8.1.dist-info/RECORD,,
|
{fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fmu_manipulation_toolbox-1.7.5.dist-info → fmu_manipulation_toolbox-1.8.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|