fmu-manipulation-toolbox 1.8.1__tar.gz → 1.8.2__tar.gz
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-1.8.1 → fmu_manipulation_toolbox-1.8.2}/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/README.md +2 -1
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/__version__.py +1 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/assembly.py +127 -41
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/cli.py +14 -5
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/fmu_container.py +62 -18
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/fmu_operations.py +2 -3
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/gui.py +330 -79
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/gui_style.py +129 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/container.png +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/icon_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/SOURCES.txt +3 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/requires.txt +1 -1
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/setup.py +2 -1
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/tests/test_suite.py +19 -0
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/__version__.py +0 -1
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox-1.8.1/fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/__init__.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/__main__.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/checker.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/help.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-checked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-checked.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-unchecked.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/help.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/icon-round.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/icon.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/license.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/mask.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/model.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/version.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/dependency_links.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/top_level.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/setup.cfg +0 -0
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: fmu_manipulation_toolbox
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
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
|
|
7
7
|
License-File: LICENSE.txt
|
|
8
|
-
Requires-Dist:
|
|
8
|
+
Requires-Dist: PySide6>=6.8.0
|
|
9
9
|
Requires-Dist: xmlschema>=3.3.1
|
|
10
10
|
Requires-Dist: elementpath>=4.4.0
|
|
11
11
|
Requires-Dist: colorama>=0.4.6
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: description
|
|
14
|
+
Dynamic: home-page
|
|
15
|
+
Dynamic: requires-dist
|
|
16
|
+
Dynamic: summary
|
|
12
17
|
|
|
13
18
|
FMU Manipulation Toolbox is a python application which help to modify a Functional Mock-up Units (FMUs)
|
|
14
19
|
without recompilation. It mainly modifies the `modelDescription.xml` file. It is highly customizable.
|
|
@@ -123,7 +123,7 @@ optional arguments:
|
|
|
123
123
|
|
|
124
124
|
```
|
|
125
125
|
usage: fmucontainer [-h] [-fmu-directory FMU_DIRECTORY] -container filename.{csv|json|ssp},[:step_size] [-debug]
|
|
126
|
-
[-no-auto-input] [-no-auto-output] [-no-auto-link] [-mt] [-profile] [-dump-json]
|
|
126
|
+
[-no-auto-input] [-no-auto-output] [-auto-parameter] [-no-auto-link] [-mt] [-profile] [-dump-json]
|
|
127
127
|
|
|
128
128
|
Generate FMU from FMU's
|
|
129
129
|
|
|
@@ -136,6 +136,7 @@ optional arguments:
|
|
|
136
136
|
-debug Add lot of useful log during the process. (default: False)
|
|
137
137
|
-no-auto-input Create ONLY explicit input. (default: True)
|
|
138
138
|
-no-auto-output Create ONLY explicit output. (default: True)
|
|
139
|
+
-auto-parameter Expose parameters of the embedded fmu's. (default: False)
|
|
139
140
|
-no-auto-link Create ONLY explicit links. (default: True)
|
|
140
141
|
-mt Enable Multi-Threaded mode for the generated container. (default: False)
|
|
141
142
|
-profile Enable Profiling mode for the generated container. (default: False)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'V1.8.2'
|
|
@@ -23,16 +23,22 @@ class Port:
|
|
|
23
23
|
def __eq__(self, other):
|
|
24
24
|
return str(self) == str(other)
|
|
25
25
|
|
|
26
|
+
def __str__(self):
|
|
27
|
+
return f"{self.fmu_name}/{self.port_name}"
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
class Connection:
|
|
28
31
|
def __init__(self, from_port: Port, to_port: Port):
|
|
29
32
|
self.from_port = from_port
|
|
30
33
|
self.to_port = to_port
|
|
31
34
|
|
|
35
|
+
def __str__(self):
|
|
36
|
+
return f"{self.from_port} -> {self.to_port}"
|
|
37
|
+
|
|
32
38
|
|
|
33
39
|
class AssemblyNode:
|
|
34
40
|
def __init__(self, name: Optional[str], step_size: float = None, mt=False, profiling=False,
|
|
35
|
-
auto_link=True, auto_input=True, auto_output=True):
|
|
41
|
+
auto_link=True, auto_input=True, auto_output=True, auto_parameter=False, auto_local=False):
|
|
36
42
|
self.name = name
|
|
37
43
|
if step_size:
|
|
38
44
|
try:
|
|
@@ -47,12 +53,14 @@ class AssemblyNode:
|
|
|
47
53
|
self.auto_link = auto_link
|
|
48
54
|
self.auto_input = auto_input
|
|
49
55
|
self.auto_output = auto_output
|
|
56
|
+
self.auto_parameter = auto_parameter
|
|
57
|
+
self.auto_local = auto_local
|
|
50
58
|
|
|
51
59
|
self.parent: Optional[AssemblyNode] = None
|
|
52
|
-
self.children:
|
|
53
|
-
self.fmu_names_list:
|
|
54
|
-
self.input_ports: Dict[Port, str] = {}
|
|
55
|
-
self.output_ports: Dict[Port, str] = {}
|
|
60
|
+
self.children: Dict[str, AssemblyNode] = {} # sub-containers
|
|
61
|
+
self.fmu_names_list: List[str] = [] # FMUs contained at this level (ordered list)
|
|
62
|
+
self.input_ports: Dict[Port, str] = {} # value is input port name, key is the source
|
|
63
|
+
self.output_ports: Dict[Port, str] = {} # value is output port name, key is the origin
|
|
56
64
|
self.start_values: Dict[Port, str] = {}
|
|
57
65
|
self.drop_ports: List[Port] = []
|
|
58
66
|
self.links: List[Connection] = []
|
|
@@ -64,13 +72,17 @@ class AssemblyNode:
|
|
|
64
72
|
if sub_node.parent is not None:
|
|
65
73
|
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already parented.")
|
|
66
74
|
|
|
67
|
-
sub_node.
|
|
68
|
-
|
|
69
|
-
self.children.append(sub_node)
|
|
75
|
+
if sub_node.name in self.children:
|
|
76
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already child of {self.name}")
|
|
70
77
|
|
|
78
|
+
sub_node.parent = self
|
|
79
|
+
if sub_node.name not in self.fmu_names_list:
|
|
80
|
+
self.fmu_names_list.append(sub_node.name)
|
|
81
|
+
self.children[sub_node.name] = sub_node
|
|
71
82
|
|
|
72
83
|
def add_fmu(self, fmu_name: str):
|
|
73
|
-
self.fmu_names_list
|
|
84
|
+
if fmu_name not in self.fmu_names_list:
|
|
85
|
+
self.fmu_names_list.append(fmu_name)
|
|
74
86
|
|
|
75
87
|
def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
76
88
|
self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
|
|
@@ -89,12 +101,13 @@ class AssemblyNode:
|
|
|
89
101
|
self.start_values[Port(fmu_filename, port_name)] = value
|
|
90
102
|
|
|
91
103
|
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
|
|
92
|
-
for node in self.children:
|
|
104
|
+
for node in self.children.values():
|
|
93
105
|
node.make_fmu(fmu_directory, debug=debug)
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
identifier = str(Path(self.name).stem)
|
|
108
|
+
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname)
|
|
96
109
|
|
|
97
|
-
for fmu_name in
|
|
110
|
+
for fmu_name in self.fmu_names_list:
|
|
98
111
|
container.get_fmu(fmu_name)
|
|
99
112
|
|
|
100
113
|
for port, source in self.input_ports.items():
|
|
@@ -115,14 +128,78 @@ class AssemblyNode:
|
|
|
115
128
|
|
|
116
129
|
container.add_implicit_rule(auto_input=self.auto_input,
|
|
117
130
|
auto_output=self.auto_output,
|
|
118
|
-
auto_link=self.auto_link
|
|
131
|
+
auto_link=self.auto_link,
|
|
132
|
+
auto_parameter=self.auto_parameter,
|
|
133
|
+
auto_local=self.auto_local)
|
|
119
134
|
|
|
120
135
|
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
|
|
121
136
|
|
|
122
|
-
for node in self.children:
|
|
137
|
+
for node in self.children.values():
|
|
123
138
|
logger.info(f"Deleting transient FMU Container '{node.name}'")
|
|
124
139
|
(fmu_directory / node.name).unlink()
|
|
125
140
|
|
|
141
|
+
def get_final_from(self, port: Port) -> Port:
|
|
142
|
+
if port in self.input_ports:
|
|
143
|
+
ancestor = Port(self.name, self.input_ports[port])
|
|
144
|
+
if self.parent:
|
|
145
|
+
return self.parent.get_final_from(ancestor) # input port
|
|
146
|
+
else:
|
|
147
|
+
return ancestor # TOPLEVEL input port
|
|
148
|
+
elif port.fmu_name in self.fmu_names_list:
|
|
149
|
+
if port.fmu_name in self.children:
|
|
150
|
+
child = self.children[port.fmu_name]
|
|
151
|
+
ancestors = [key for key, val in child.output_ports.items() if val == port.port_name]
|
|
152
|
+
if len(ancestors) == 1:
|
|
153
|
+
return child.get_final_from(ancestors[0]) # child output port
|
|
154
|
+
else:
|
|
155
|
+
return port # embedded FMU
|
|
156
|
+
|
|
157
|
+
raise AssemblyError(f"{self.name}: Port {port} is not connected upstream.")
|
|
158
|
+
|
|
159
|
+
def get_final_to(self, port: Port) -> Port:
|
|
160
|
+
if port in self.output_ports:
|
|
161
|
+
successor = Port(self.name, self.output_ports[port])
|
|
162
|
+
if self.parent:
|
|
163
|
+
return self.parent.get_final_to(successor) # Output port
|
|
164
|
+
else:
|
|
165
|
+
return successor # TOLEVEL output port
|
|
166
|
+
elif port.fmu_name in self.fmu_names_list:
|
|
167
|
+
if port.fmu_name in self.children:
|
|
168
|
+
child = self.children[port.fmu_name]
|
|
169
|
+
successors = [key for key, val in child.input_ports.items() if val == port.port_name]
|
|
170
|
+
if len(successors) == 1:
|
|
171
|
+
return child.get_final_to(successors[0]) # Child input port
|
|
172
|
+
else:
|
|
173
|
+
return port # embedded FMU
|
|
174
|
+
|
|
175
|
+
raise AssemblyError(f"Node {self.name}: Port {port} is not connected downstream.")
|
|
176
|
+
|
|
177
|
+
def get_fmu_connections(self, fmu_name: str) -> List[Connection]:
|
|
178
|
+
connections = []
|
|
179
|
+
if fmu_name not in self.fmu_names_list:
|
|
180
|
+
raise AssemblyError(f"Internal Error: FMU {fmu_name} is not embedded by {self.name}.")
|
|
181
|
+
for link in self.links:
|
|
182
|
+
if link.from_port.fmu_name == fmu_name:
|
|
183
|
+
connections.append(Connection(link.from_port, self.get_final_to(link.to_port)))
|
|
184
|
+
elif link.to_port.fmu_name == fmu_name:
|
|
185
|
+
connections.append(Connection(self.get_final_from(link.from_port), link.to_port))
|
|
186
|
+
|
|
187
|
+
for to_port, input_port_name in self.input_ports.items():
|
|
188
|
+
if to_port.fmu_name == fmu_name:
|
|
189
|
+
if self.parent:
|
|
190
|
+
connections.append(Connection(self.parent.get_final_from(Port(self.name, input_port_name)), to_port))
|
|
191
|
+
else:
|
|
192
|
+
connections.append(Connection(Port(self.name, input_port_name), to_port))
|
|
193
|
+
|
|
194
|
+
for from_port, output_port_name in self.output_ports.items():
|
|
195
|
+
if from_port.fmu_name == fmu_name:
|
|
196
|
+
if self.parent:
|
|
197
|
+
connections.append(Connection(from_port, self.parent.get_final_to(Port(self.name, output_port_name))))
|
|
198
|
+
else:
|
|
199
|
+
connections.append(Connection(from_port, Port(self.name, output_port_name))) ###HERE
|
|
200
|
+
|
|
201
|
+
return connections
|
|
202
|
+
|
|
126
203
|
|
|
127
204
|
class AssemblyError(Exception):
|
|
128
205
|
def __init__(self, reason: str):
|
|
@@ -134,13 +211,16 @@ class AssemblyError(Exception):
|
|
|
134
211
|
|
|
135
212
|
class Assembly:
|
|
136
213
|
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 = ".")
|
|
214
|
+
auto_output=True, mt=False, profiling=False, fmu_directory: Path = Path("."), auto_parameter=False,
|
|
215
|
+
auto_local=False):
|
|
138
216
|
self.filename = Path(filename)
|
|
139
217
|
self.default_auto_input = auto_input
|
|
140
218
|
self.debug = debug
|
|
141
219
|
self.default_auto_output = auto_output
|
|
142
220
|
self.default_step_size = step_size
|
|
143
221
|
self.default_auto_link = auto_link
|
|
222
|
+
self.default_auto_parameter = auto_parameter
|
|
223
|
+
self.default_auto_local = auto_local
|
|
144
224
|
self.default_mt = mt
|
|
145
225
|
self.default_profiling = profiling
|
|
146
226
|
self.fmu_directory = fmu_directory
|
|
@@ -197,7 +277,8 @@ class Assembly:
|
|
|
197
277
|
name = str(self.filename.with_suffix(".fmu"))
|
|
198
278
|
self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
199
279
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
200
|
-
auto_input=self.default_auto_input, auto_output=self.default_auto_output
|
|
280
|
+
auto_input=self.default_auto_input, auto_output=self.default_auto_output,
|
|
281
|
+
auto_parameter=self.default_auto_parameter, auto_local=self.default_auto_local)
|
|
201
282
|
|
|
202
283
|
with open(self.input_pathname) as file:
|
|
203
284
|
reader = csv.reader(file, delimiter=';')
|
|
@@ -292,49 +373,53 @@ class Assembly:
|
|
|
292
373
|
raise AssemblyError(f"Cannot read json: {e}")
|
|
293
374
|
self.root = self._json_decode_node(data)
|
|
294
375
|
if not self.root.name:
|
|
295
|
-
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
376
|
+
self.root.name = str(self.filename.with_suffix(".fmu").name)
|
|
296
377
|
|
|
297
|
-
def _json_decode_node(self, data:Dict) -> AssemblyNode:
|
|
378
|
+
def _json_decode_node(self, data: Dict) -> AssemblyNode:
|
|
298
379
|
name = data.get("name", None) # 1
|
|
299
|
-
|
|
380
|
+
mt = data.get("mt", self.default_mt) # 2
|
|
381
|
+
profiling = data.get("profiling", self.default_profiling) # 3
|
|
300
382
|
auto_link = data.get("auto_link", self.default_auto_link) # 4
|
|
301
383
|
auto_input = data.get("auto_input", self.default_auto_input) # 5
|
|
302
384
|
auto_output = data.get("auto_output", self.default_auto_output) # 6
|
|
303
|
-
|
|
304
|
-
|
|
385
|
+
auto_parameter = data.get("auto_parameter", self.default_auto_parameter) # 6b
|
|
386
|
+
auto_local = data.get("auto_local", self.default_auto_local) # 6c
|
|
387
|
+
step_size = data.get("step_size", self.default_step_size) # 7
|
|
305
388
|
|
|
306
389
|
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
307
|
-
auto_input=auto_input, auto_output=auto_output
|
|
390
|
+
auto_input=auto_input, auto_output=auto_output, auto_parameter=auto_parameter,
|
|
391
|
+
auto_local=auto_local)
|
|
308
392
|
|
|
309
393
|
for key, value in data.items():
|
|
310
|
-
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling'
|
|
394
|
+
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling',
|
|
395
|
+
'auto_parameter', 'auto_local'):
|
|
311
396
|
continue # Already read
|
|
312
397
|
|
|
313
|
-
elif key == "container":
|
|
398
|
+
elif key == "container": # 8
|
|
314
399
|
if not isinstance(value, list):
|
|
315
400
|
raise AssemblyError("JSON: 'container' keyword should define a list.")
|
|
316
401
|
for sub_data in value:
|
|
317
402
|
node.add_sub_node(self._json_decode_node(sub_data))
|
|
318
403
|
|
|
319
|
-
elif key == "fmu":
|
|
404
|
+
elif key == "fmu": # 9
|
|
320
405
|
if not isinstance(value, list):
|
|
321
406
|
raise AssemblyError("JSON: 'fmu' keyword should define a list.")
|
|
322
407
|
for fmu in value:
|
|
323
408
|
node.add_fmu(fmu)
|
|
324
409
|
|
|
325
|
-
elif key == "input":
|
|
410
|
+
elif key == "input": # 10
|
|
326
411
|
self._json_decode_keyword('input', value, node.add_input)
|
|
327
412
|
|
|
328
|
-
elif key == "output":
|
|
413
|
+
elif key == "output": # 11
|
|
329
414
|
self._json_decode_keyword('output', value, node.add_output)
|
|
330
415
|
|
|
331
|
-
elif key == "link":
|
|
416
|
+
elif key == "link": # 12
|
|
332
417
|
self._json_decode_keyword('link', value, node.add_link)
|
|
333
418
|
|
|
334
|
-
elif key == "start":
|
|
419
|
+
elif key == "start": # 13
|
|
335
420
|
self._json_decode_keyword('start', value, node.add_start_value)
|
|
336
421
|
|
|
337
|
-
elif key == "drop":
|
|
422
|
+
elif key == "drop": # 14
|
|
338
423
|
self._json_decode_keyword('drop', value, node.add_drop_port)
|
|
339
424
|
|
|
340
425
|
else:
|
|
@@ -354,7 +439,6 @@ class Assembly:
|
|
|
354
439
|
except TypeError:
|
|
355
440
|
raise AssemblyError(f"JSON: '{keyword}' value does not contain right number of fields: {line}.")
|
|
356
441
|
|
|
357
|
-
|
|
358
442
|
def write_json(self, filename: Union[str, Path]):
|
|
359
443
|
with open(self.fmu_directory / filename, "wt") as file:
|
|
360
444
|
data = self._json_encode_node(self.root)
|
|
@@ -362,21 +446,23 @@ class Assembly:
|
|
|
362
446
|
|
|
363
447
|
def _json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
|
|
364
448
|
json_node = dict()
|
|
365
|
-
json_node["name"] = node.name
|
|
366
|
-
json_node["mt"] = node.mt
|
|
367
|
-
json_node["profiling"] = node.profiling
|
|
368
|
-
json_node["auto_link"] = node.auto_link
|
|
369
|
-
json_node["auto_input"] = node.auto_input
|
|
370
|
-
json_node["auto_output"] = node.auto_output
|
|
449
|
+
json_node["name"] = node.name # 1
|
|
450
|
+
json_node["mt"] = node.mt # 2
|
|
451
|
+
json_node["profiling"] = node.profiling # 3
|
|
452
|
+
json_node["auto_link"] = node.auto_link # 4
|
|
453
|
+
json_node["auto_input"] = node.auto_input # 5
|
|
454
|
+
json_node["auto_output"] = node.auto_output # 6
|
|
455
|
+
json_node["auto_parameter"] = node.auto_parameter # 6b
|
|
456
|
+
json_node["auto_local"] = node.auto_local # 6c
|
|
371
457
|
|
|
372
458
|
if node.step_size:
|
|
373
|
-
json_node["step_size"] = node.step_size
|
|
459
|
+
json_node["step_size"] = node.step_size # 7
|
|
374
460
|
|
|
375
461
|
if node.children:
|
|
376
|
-
json_node["container"] = [self._json_encode_node(child) for child in node.children]
|
|
462
|
+
json_node["container"] = [self._json_encode_node(child) for child in node.children.values()] # 8
|
|
377
463
|
|
|
378
464
|
if node.fmu_names_list:
|
|
379
|
-
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] #9
|
|
465
|
+
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] # 9
|
|
380
466
|
|
|
381
467
|
if node.input_ports:
|
|
382
468
|
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"] # 10
|
|
@@ -396,7 +482,7 @@ class Assembly:
|
|
|
396
482
|
for port, value in node.start_values.items()]
|
|
397
483
|
|
|
398
484
|
if node.drop_ports:
|
|
399
|
-
json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports]
|
|
485
|
+
json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports] # 14
|
|
400
486
|
|
|
401
487
|
return json_node
|
|
402
488
|
|
{fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/cli.py
RENAMED
|
@@ -176,6 +176,12 @@ def fmucontainer():
|
|
|
176
176
|
parser.add_argument("-no-auto-output", action="store_false", dest="auto_output", default=True,
|
|
177
177
|
help="Create ONLY explicit output.")
|
|
178
178
|
|
|
179
|
+
parser.add_argument("-auto-parameter", action="store_true", dest="auto_parameter", default=False,
|
|
180
|
+
help="Expose parameters of the embedded fmu's.")
|
|
181
|
+
|
|
182
|
+
parser.add_argument("-auto-local", action="store_true", dest="auto_local", default=False,
|
|
183
|
+
help="Expose local variables of the embedded fmu's.")
|
|
184
|
+
|
|
179
185
|
parser.add_argument("-no-auto-link", action="store_false", dest="auto_link", default=True,
|
|
180
186
|
help="Create ONLY explicit links.")
|
|
181
187
|
|
|
@@ -198,16 +204,19 @@ def fmucontainer():
|
|
|
198
204
|
|
|
199
205
|
for description in config.container_descriptions_list:
|
|
200
206
|
try:
|
|
201
|
-
|
|
202
|
-
|
|
207
|
+
tokens = description.split(":")
|
|
208
|
+
filename = ":".join(tokens[:-1])
|
|
209
|
+
step_size = float(tokens[-1])
|
|
203
210
|
except ValueError:
|
|
204
211
|
step_size = None
|
|
205
212
|
filename = description
|
|
213
|
+
|
|
206
214
|
try:
|
|
207
215
|
assembly = Assembly(filename, step_size=step_size, auto_link=config.auto_link,
|
|
208
|
-
auto_input=config.auto_input, auto_output=config.auto_output,
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
auto_input=config.auto_input, auto_output=config.auto_output,
|
|
217
|
+
auto_local=config.auto_local, mt=config.mt,
|
|
218
|
+
profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug,
|
|
219
|
+
auto_parameter=config.auto_parameter)
|
|
211
220
|
except FileNotFoundError as e:
|
|
212
221
|
logger.fatal(f"Cannot read file: {e}")
|
|
213
222
|
continue
|
|
@@ -74,9 +74,12 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
74
74
|
def __init__(self, filename):
|
|
75
75
|
self.fmu = FMU(filename)
|
|
76
76
|
self.name = Path(filename).name
|
|
77
|
+
self.id = Path(filename).stem
|
|
77
78
|
|
|
78
79
|
self.fmi_version = None
|
|
79
80
|
self.step_size = None
|
|
81
|
+
self.start_time = None
|
|
82
|
+
self.stop_time = None
|
|
80
83
|
self.model_identifier = None
|
|
81
84
|
self.guid = None
|
|
82
85
|
self.ports: Dict[str, FMUPort] = {}
|
|
@@ -103,15 +106,18 @@ class EmbeddedFMU(OperationAbstract):
|
|
|
103
106
|
for capability in self.capability_list:
|
|
104
107
|
self.capabilities[capability] = attrs.get(capability, "false")
|
|
105
108
|
|
|
106
|
-
def experiment_attrs(self, attrs):
|
|
109
|
+
def experiment_attrs(self, attrs: Dict[str, str]):
|
|
107
110
|
try:
|
|
108
111
|
self.step_size = float(attrs['stepSize'])
|
|
109
112
|
except KeyError:
|
|
110
113
|
logger.warning(f"FMU '{self.name}' does not specify preferred step size")
|
|
111
|
-
|
|
114
|
+
self.start_time = float(attrs.get("startTime", 0.0))
|
|
115
|
+
self.stop_time = float(attrs.get("stopTime", self.start_time + 1.0))
|
|
112
116
|
|
|
113
117
|
def scalar_type(self, type_name, attrs):
|
|
114
118
|
if self.current_port:
|
|
119
|
+
if type_name == "Enumeration":
|
|
120
|
+
type_name = "Integer"
|
|
115
121
|
self.current_port.set_port_type(type_name, attrs)
|
|
116
122
|
self.current_port = None
|
|
117
123
|
|
|
@@ -148,7 +154,7 @@ class ContainerPort:
|
|
|
148
154
|
|
|
149
155
|
class Local:
|
|
150
156
|
def __init__(self, cport_from: ContainerPort):
|
|
151
|
-
self.name = cport_from.fmu.
|
|
157
|
+
self.name = cport_from.fmu.id + "." + cport_from.port.name # strip .fmu suffix
|
|
152
158
|
self.cport_from = cport_from
|
|
153
159
|
self.cport_to_list: List[ContainerPort] = []
|
|
154
160
|
self.vr = None
|
|
@@ -195,6 +201,9 @@ class FMUContainer:
|
|
|
195
201
|
|
|
196
202
|
self.description_pathname = description_pathname
|
|
197
203
|
|
|
204
|
+
self.start_time = None
|
|
205
|
+
self.stop_time = None
|
|
206
|
+
|
|
198
207
|
# Rules
|
|
199
208
|
self.inputs: Dict[str, ContainerPort] = {}
|
|
200
209
|
self.outputs: Dict[str, ContainerPort] = {}
|
|
@@ -299,13 +308,19 @@ class FMUContainer:
|
|
|
299
308
|
return ContainerPort(fmu, port.name)
|
|
300
309
|
return None
|
|
301
310
|
|
|
302
|
-
def add_implicit_rule(self, auto_input
|
|
311
|
+
def add_implicit_rule(self, auto_input=True, auto_output=True, auto_link=True, auto_parameter=False,
|
|
312
|
+
auto_local=False):
|
|
303
313
|
# Auto Link outputs
|
|
304
314
|
for fmu in self.execution_order:
|
|
305
315
|
for port_name in fmu.ports:
|
|
306
316
|
cport = ContainerPort(fmu, port_name)
|
|
307
317
|
if cport not in self.rules:
|
|
308
|
-
if cport.port.causality == '
|
|
318
|
+
if cport.port.causality == 'parameter' and auto_parameter:
|
|
319
|
+
parameter_name = cport.fmu.model_identifier+"."+cport.port.name
|
|
320
|
+
logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
|
|
321
|
+
self.inputs[parameter_name] = cport
|
|
322
|
+
self.mark_ruled(cport, 'PARAMETER')
|
|
323
|
+
elif cport.port.causality == 'output':
|
|
309
324
|
candidate_cport = self.find_input(cport.port)
|
|
310
325
|
if auto_link and candidate_cport:
|
|
311
326
|
local = Local(cport)
|
|
@@ -314,11 +329,21 @@ class FMUContainer:
|
|
|
314
329
|
self.mark_ruled(cport, 'LINK')
|
|
315
330
|
self.mark_ruled(candidate_cport, 'LINK')
|
|
316
331
|
self.locals[cport] = local
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
elif auto_output:
|
|
333
|
+
self.mark_ruled(cport, 'OUTPUT')
|
|
334
|
+
self.outputs[port_name] = cport
|
|
335
|
+
logger.info(f"AUTO OUTPUT: Expose {cport}")
|
|
336
|
+
elif cport.port.causality == 'local':
|
|
337
|
+
local_portname = None
|
|
338
|
+
if port_name.startswith("container."):
|
|
339
|
+
local_portname = "container." + cport.fmu.id + "." + port_name[10:]
|
|
340
|
+
logger.info(f"PROFILING: Expose {cport}")
|
|
341
|
+
elif auto_local:
|
|
342
|
+
local_portname = cport.fmu.id + "." + port_name
|
|
343
|
+
logger.info(f"AUTO LOCAL: Expose {cport}")
|
|
344
|
+
if local_portname:
|
|
345
|
+
self.mark_ruled(cport, 'OUTPUT')
|
|
346
|
+
self.outputs[local_portname] = cport
|
|
322
347
|
|
|
323
348
|
if auto_input:
|
|
324
349
|
# Auto link inputs
|
|
@@ -406,6 +431,18 @@ class FMUContainer:
|
|
|
406
431
|
if fmu.capabilities[capability] == "true":
|
|
407
432
|
capabilities[capability] = "true"
|
|
408
433
|
|
|
434
|
+
if self.start_time is None:
|
|
435
|
+
self.start_time = self.execution_order[0].start_time
|
|
436
|
+
logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
|
|
437
|
+
else:
|
|
438
|
+
logger.info(f"start_time={self.start_time}")
|
|
439
|
+
|
|
440
|
+
if self.stop_time is None:
|
|
441
|
+
self.stop_time = self.execution_order[0].stop_time
|
|
442
|
+
logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
|
|
443
|
+
else:
|
|
444
|
+
logger.info(f"stop_time={self.stop_time}")
|
|
445
|
+
|
|
409
446
|
xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
410
447
|
<fmiModelDescription
|
|
411
448
|
fmiVersion="2.0"
|
|
@@ -434,14 +471,14 @@ class FMUContainer:
|
|
|
434
471
|
<Category name="fmucontainer"/>
|
|
435
472
|
</LogCategories>
|
|
436
473
|
|
|
437
|
-
<DefaultExperiment stepSize="{step_size}"/>
|
|
474
|
+
<DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
|
|
438
475
|
|
|
439
476
|
<ModelVariables>
|
|
440
477
|
""")
|
|
441
478
|
if profiling:
|
|
442
479
|
for fmu in self.execution_order:
|
|
443
480
|
vr = vr_table.add_vr("Real")
|
|
444
|
-
name = f"container.{fmu.
|
|
481
|
+
name = f"container.{fmu.id}.rt_ratio"
|
|
445
482
|
print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
|
|
446
483
|
f'<Real /></ScalarVariable>', file=xml_file)
|
|
447
484
|
|
|
@@ -591,6 +628,14 @@ class FMUContainer:
|
|
|
591
628
|
for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
|
|
592
629
|
print(f"{vr} {cport.port.vr}", file=txt_file)
|
|
593
630
|
|
|
631
|
+
@staticmethod
|
|
632
|
+
def long_path(path: Union[str, Path]) -> str:
|
|
633
|
+
# https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
|
|
634
|
+
if os.name == 'nt':
|
|
635
|
+
return "\\\\?\\" + os.path.abspath(str(path))
|
|
636
|
+
else:
|
|
637
|
+
return path
|
|
638
|
+
|
|
594
639
|
def make_fmu_skeleton(self, base_directory: Path) -> Path:
|
|
595
640
|
logger.debug(f"Initialize directory '{base_directory}'")
|
|
596
641
|
|
|
@@ -617,8 +662,8 @@ class FMUContainer:
|
|
|
617
662
|
shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
|
|
618
663
|
|
|
619
664
|
for fmu in self.involved_fmu.values():
|
|
620
|
-
shutil.copytree(fmu.fmu.tmp_directory,
|
|
621
|
-
|
|
665
|
+
shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
|
|
666
|
+
self.long_path(resources_directory / fmu.name), dirs_exist_ok=True)
|
|
622
667
|
return resources_directory
|
|
623
668
|
|
|
624
669
|
def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
|
|
@@ -626,11 +671,10 @@ class FMUContainer:
|
|
|
626
671
|
with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
627
672
|
for root, dirs, files in os.walk(base_directory):
|
|
628
673
|
for file in files:
|
|
629
|
-
zip_file.write(os.path.join(root, file),
|
|
674
|
+
zip_file.write(self.long_path(os.path.join(root, file)),
|
|
630
675
|
os.path.relpath(os.path.join(root, file), base_directory))
|
|
631
676
|
logger.info(f"'{fmu_filename}' is available.")
|
|
632
677
|
|
|
633
|
-
|
|
634
|
-
def make_fmu_cleanup(base_directory: Path):
|
|
678
|
+
def make_fmu_cleanup(self, base_directory: Path):
|
|
635
679
|
logger.debug(f"Delete directory '{base_directory}'")
|
|
636
|
-
shutil.rmtree(base_directory)
|
|
680
|
+
shutil.rmtree(self.long_path(base_directory))
|
|
@@ -89,9 +89,8 @@ class Manipulation:
|
|
|
89
89
|
self.remove_port(attrs['name'])
|
|
90
90
|
else:
|
|
91
91
|
self.keep_port(attrs['name'])
|
|
92
|
-
else:
|
|
92
|
+
else: # Keep ScalarVariable as it is.
|
|
93
93
|
self.keep_port(attrs['name'])
|
|
94
|
-
self.skip_until = name # do not read inner tags
|
|
95
94
|
elif name == 'CoSimulation':
|
|
96
95
|
self.operation.cosimulation_attrs(attrs)
|
|
97
96
|
elif name == 'DefaultExperiment':
|
|
@@ -100,7 +99,7 @@ class Manipulation:
|
|
|
100
99
|
self.operation.fmi_attrs(attrs)
|
|
101
100
|
elif name == 'Unknown':
|
|
102
101
|
self.unknown_attrs(attrs)
|
|
103
|
-
elif name in ('Real', 'Integer', 'String', 'Boolean'):
|
|
102
|
+
elif name in ('Real', 'Integer', 'String', 'Boolean', 'Enumeration'):
|
|
104
103
|
self.operation.scalar_type(name, attrs)
|
|
105
104
|
|
|
106
105
|
except ManipulationSkipTag:
|