fmu-manipulation-toolbox 1.8.1__tar.gz → 1.8.2.dev3__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.dev3}/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/README.md +2 -1
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/__version__.py +1 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/assembly.py +120 -41
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/cli.py +5 -2
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/fmu_container.py +55 -18
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/fmu_operations.py +2 -3
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/gui.py +330 -79
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/gui_style.py +129 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/container.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/icon_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox-1.8.2.dev3/fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox.egg-info/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox.egg-info/SOURCES.txt +3 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox.egg-info/requires.txt +1 -1
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/setup.py +2 -1
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/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-1.8.2.dev3}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/__init__.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/__main__.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/checker.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/help.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/checkbox-checked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/checkbox-checked.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/checkbox-unchecked.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/help.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/icon-round.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/icon.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/license.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/mask.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/resources/model.png +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox/version.py +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox.egg-info/dependency_links.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox.egg-info/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/fmu_manipulation_toolbox.egg-info/top_level.txt +0 -0
- {fmu_manipulation_toolbox-1.8.1 → fmu_manipulation_toolbox-1.8.2.dev3}/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.dev3
|
|
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-dev3'
|
|
@@ -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):
|
|
36
42
|
self.name = name
|
|
37
43
|
if step_size:
|
|
38
44
|
try:
|
|
@@ -47,12 +53,13 @@ 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
|
|
50
57
|
|
|
51
58
|
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] = {}
|
|
59
|
+
self.children: Dict[str, AssemblyNode] = {} # sub-containers
|
|
60
|
+
self.fmu_names_list: List[str] = [] # FMUs contained at this level (ordered list)
|
|
61
|
+
self.input_ports: Dict[Port, str] = {} # value is input port name, key is the source
|
|
62
|
+
self.output_ports: Dict[Port, str] = {} # value is output port name, key is the origin
|
|
56
63
|
self.start_values: Dict[Port, str] = {}
|
|
57
64
|
self.drop_ports: List[Port] = []
|
|
58
65
|
self.links: List[Connection] = []
|
|
@@ -64,13 +71,17 @@ class AssemblyNode:
|
|
|
64
71
|
if sub_node.parent is not None:
|
|
65
72
|
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already parented.")
|
|
66
73
|
|
|
67
|
-
sub_node.
|
|
68
|
-
|
|
69
|
-
self.children.append(sub_node)
|
|
74
|
+
if sub_node.name in self.children:
|
|
75
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already child of {self.name}")
|
|
70
76
|
|
|
77
|
+
sub_node.parent = self
|
|
78
|
+
if sub_node.name not in self.fmu_names_list:
|
|
79
|
+
self.fmu_names_list.append(sub_node.name)
|
|
80
|
+
self.children[sub_node.name] = sub_node
|
|
71
81
|
|
|
72
82
|
def add_fmu(self, fmu_name: str):
|
|
73
|
-
self.fmu_names_list
|
|
83
|
+
if fmu_name not in self.fmu_names_list:
|
|
84
|
+
self.fmu_names_list.append(fmu_name)
|
|
74
85
|
|
|
75
86
|
def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
76
87
|
self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
|
|
@@ -89,12 +100,13 @@ class AssemblyNode:
|
|
|
89
100
|
self.start_values[Port(fmu_filename, port_name)] = value
|
|
90
101
|
|
|
91
102
|
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
|
|
92
|
-
for node in self.children:
|
|
103
|
+
for node in self.children.values():
|
|
93
104
|
node.make_fmu(fmu_directory, debug=debug)
|
|
94
105
|
|
|
95
|
-
|
|
106
|
+
identifier = str(Path(self.name).stem)
|
|
107
|
+
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname)
|
|
96
108
|
|
|
97
|
-
for fmu_name in
|
|
109
|
+
for fmu_name in self.fmu_names_list:
|
|
98
110
|
container.get_fmu(fmu_name)
|
|
99
111
|
|
|
100
112
|
for port, source in self.input_ports.items():
|
|
@@ -115,14 +127,77 @@ class AssemblyNode:
|
|
|
115
127
|
|
|
116
128
|
container.add_implicit_rule(auto_input=self.auto_input,
|
|
117
129
|
auto_output=self.auto_output,
|
|
118
|
-
auto_link=self.auto_link
|
|
130
|
+
auto_link=self.auto_link,
|
|
131
|
+
auto_parameter=self.auto_parameter)
|
|
119
132
|
|
|
120
133
|
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
|
|
121
134
|
|
|
122
|
-
for node in self.children:
|
|
135
|
+
for node in self.children.values():
|
|
123
136
|
logger.info(f"Deleting transient FMU Container '{node.name}'")
|
|
124
137
|
(fmu_directory / node.name).unlink()
|
|
125
138
|
|
|
139
|
+
def get_final_from(self, port: Port) -> Port:
|
|
140
|
+
if port in self.input_ports:
|
|
141
|
+
ancestor = Port(self.name, self.input_ports[port])
|
|
142
|
+
if self.parent:
|
|
143
|
+
return self.parent.get_final_from(ancestor) # input port
|
|
144
|
+
else:
|
|
145
|
+
return ancestor # TOPLEVEL input port
|
|
146
|
+
elif port.fmu_name in self.fmu_names_list:
|
|
147
|
+
if port.fmu_name in self.children:
|
|
148
|
+
child = self.children[port.fmu_name]
|
|
149
|
+
ancestors = [key for key, val in child.output_ports.items() if val == port.port_name]
|
|
150
|
+
if len(ancestors) == 1:
|
|
151
|
+
return child.get_final_from(ancestors[0]) # child output port
|
|
152
|
+
else:
|
|
153
|
+
return port # embedded FMU
|
|
154
|
+
|
|
155
|
+
raise AssemblyError(f"{self.name}: Port {port} is not connected upstream.")
|
|
156
|
+
|
|
157
|
+
def get_final_to(self, port: Port) -> Port:
|
|
158
|
+
if port in self.output_ports:
|
|
159
|
+
successor = Port(self.name, self.output_ports[port])
|
|
160
|
+
if self.parent:
|
|
161
|
+
return self.parent.get_final_to(successor) # Output port
|
|
162
|
+
else:
|
|
163
|
+
return successor # TOLEVEL output port
|
|
164
|
+
elif port.fmu_name in self.fmu_names_list:
|
|
165
|
+
if port.fmu_name in self.children:
|
|
166
|
+
child = self.children[port.fmu_name]
|
|
167
|
+
successors = [key for key, val in child.input_ports.items() if val == port.port_name]
|
|
168
|
+
if len(successors) == 1:
|
|
169
|
+
return child.get_final_to(successors[0]) # Child input port
|
|
170
|
+
else:
|
|
171
|
+
return port # embedded FMU
|
|
172
|
+
|
|
173
|
+
raise AssemblyError(f"Node {self.name}: Port {port} is not connected downstream.")
|
|
174
|
+
|
|
175
|
+
def get_fmu_connections(self, fmu_name: str) -> List[Connection]:
|
|
176
|
+
connections = []
|
|
177
|
+
if fmu_name not in self.fmu_names_list:
|
|
178
|
+
raise AssemblyError(f"Internal Error: FMU {fmu_name} is not embedded by {self.name}.")
|
|
179
|
+
for link in self.links:
|
|
180
|
+
if link.from_port.fmu_name == fmu_name:
|
|
181
|
+
connections.append(Connection(link.from_port, self.get_final_to(link.to_port)))
|
|
182
|
+
elif link.to_port.fmu_name == fmu_name:
|
|
183
|
+
connections.append(Connection(self.get_final_from(link.from_port), link.to_port))
|
|
184
|
+
|
|
185
|
+
for to_port, input_port_name in self.input_ports.items():
|
|
186
|
+
if to_port.fmu_name == fmu_name:
|
|
187
|
+
if self.parent:
|
|
188
|
+
connections.append(Connection(self.parent.get_final_from(Port(self.name, input_port_name)), to_port))
|
|
189
|
+
else:
|
|
190
|
+
connections.append(Connection(Port(self.name, input_port_name), to_port))
|
|
191
|
+
|
|
192
|
+
for from_port, output_port_name in self.output_ports.items():
|
|
193
|
+
if from_port.fmu_name == fmu_name:
|
|
194
|
+
if self.parent:
|
|
195
|
+
connections.append(Connection(from_port, self.parent.get_final_to(Port(self.name, output_port_name))))
|
|
196
|
+
else:
|
|
197
|
+
connections.append(Connection(from_port, Port(self.name, output_port_name))) ###HERE
|
|
198
|
+
|
|
199
|
+
return connections
|
|
200
|
+
|
|
126
201
|
|
|
127
202
|
class AssemblyError(Exception):
|
|
128
203
|
def __init__(self, reason: str):
|
|
@@ -134,13 +209,14 @@ class AssemblyError(Exception):
|
|
|
134
209
|
|
|
135
210
|
class Assembly:
|
|
136
211
|
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 = "."):
|
|
212
|
+
auto_output=True, mt=False, profiling=False, fmu_directory: Path = Path("."), auto_parameter=False):
|
|
138
213
|
self.filename = Path(filename)
|
|
139
214
|
self.default_auto_input = auto_input
|
|
140
215
|
self.debug = debug
|
|
141
216
|
self.default_auto_output = auto_output
|
|
142
217
|
self.default_step_size = step_size
|
|
143
218
|
self.default_auto_link = auto_link
|
|
219
|
+
self.default_auto_parameter = auto_parameter
|
|
144
220
|
self.default_mt = mt
|
|
145
221
|
self.default_profiling = profiling
|
|
146
222
|
self.fmu_directory = fmu_directory
|
|
@@ -197,7 +273,8 @@ class Assembly:
|
|
|
197
273
|
name = str(self.filename.with_suffix(".fmu"))
|
|
198
274
|
self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
199
275
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
200
|
-
auto_input=self.default_auto_input, auto_output=self.default_auto_output
|
|
276
|
+
auto_input=self.default_auto_input, auto_output=self.default_auto_output,
|
|
277
|
+
auto_parameter=self.default_auto_parameter)
|
|
201
278
|
|
|
202
279
|
with open(self.input_pathname) as file:
|
|
203
280
|
reader = csv.reader(file, delimiter=';')
|
|
@@ -292,49 +369,51 @@ class Assembly:
|
|
|
292
369
|
raise AssemblyError(f"Cannot read json: {e}")
|
|
293
370
|
self.root = self._json_decode_node(data)
|
|
294
371
|
if not self.root.name:
|
|
295
|
-
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
372
|
+
self.root.name = str(self.filename.with_suffix(".fmu").name)
|
|
296
373
|
|
|
297
|
-
def _json_decode_node(self, data:Dict) -> AssemblyNode:
|
|
374
|
+
def _json_decode_node(self, data: Dict) -> AssemblyNode:
|
|
298
375
|
name = data.get("name", None) # 1
|
|
299
|
-
|
|
376
|
+
mt = data.get("mt", self.default_mt) # 2
|
|
377
|
+
profiling = data.get("profiling", self.default_profiling) # 3
|
|
300
378
|
auto_link = data.get("auto_link", self.default_auto_link) # 4
|
|
301
379
|
auto_input = data.get("auto_input", self.default_auto_input) # 5
|
|
302
380
|
auto_output = data.get("auto_output", self.default_auto_output) # 6
|
|
303
|
-
|
|
304
|
-
|
|
381
|
+
auto_parameter = data.get("auto_parameter", self.default_auto_parameter) # 6b
|
|
382
|
+
step_size = data.get("step_size", self.default_step_size) # 7
|
|
305
383
|
|
|
306
384
|
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
307
|
-
auto_input=auto_input, auto_output=auto_output)
|
|
385
|
+
auto_input=auto_input, auto_output=auto_output, auto_parameter=auto_parameter)
|
|
308
386
|
|
|
309
387
|
for key, value in data.items():
|
|
310
|
-
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling'
|
|
388
|
+
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling',
|
|
389
|
+
'auto_parameter'):
|
|
311
390
|
continue # Already read
|
|
312
391
|
|
|
313
|
-
elif key == "container":
|
|
392
|
+
elif key == "container": # 8
|
|
314
393
|
if not isinstance(value, list):
|
|
315
394
|
raise AssemblyError("JSON: 'container' keyword should define a list.")
|
|
316
395
|
for sub_data in value:
|
|
317
396
|
node.add_sub_node(self._json_decode_node(sub_data))
|
|
318
397
|
|
|
319
|
-
elif key == "fmu":
|
|
398
|
+
elif key == "fmu": # 9
|
|
320
399
|
if not isinstance(value, list):
|
|
321
400
|
raise AssemblyError("JSON: 'fmu' keyword should define a list.")
|
|
322
401
|
for fmu in value:
|
|
323
402
|
node.add_fmu(fmu)
|
|
324
403
|
|
|
325
|
-
elif key == "input":
|
|
404
|
+
elif key == "input": # 10
|
|
326
405
|
self._json_decode_keyword('input', value, node.add_input)
|
|
327
406
|
|
|
328
|
-
elif key == "output":
|
|
407
|
+
elif key == "output": # 11
|
|
329
408
|
self._json_decode_keyword('output', value, node.add_output)
|
|
330
409
|
|
|
331
|
-
elif key == "link":
|
|
410
|
+
elif key == "link": # 12
|
|
332
411
|
self._json_decode_keyword('link', value, node.add_link)
|
|
333
412
|
|
|
334
|
-
elif key == "start":
|
|
413
|
+
elif key == "start": # 13
|
|
335
414
|
self._json_decode_keyword('start', value, node.add_start_value)
|
|
336
415
|
|
|
337
|
-
elif key == "drop":
|
|
416
|
+
elif key == "drop": # 14
|
|
338
417
|
self._json_decode_keyword('drop', value, node.add_drop_port)
|
|
339
418
|
|
|
340
419
|
else:
|
|
@@ -354,7 +433,6 @@ class Assembly:
|
|
|
354
433
|
except TypeError:
|
|
355
434
|
raise AssemblyError(f"JSON: '{keyword}' value does not contain right number of fields: {line}.")
|
|
356
435
|
|
|
357
|
-
|
|
358
436
|
def write_json(self, filename: Union[str, Path]):
|
|
359
437
|
with open(self.fmu_directory / filename, "wt") as file:
|
|
360
438
|
data = self._json_encode_node(self.root)
|
|
@@ -362,21 +440,22 @@ class Assembly:
|
|
|
362
440
|
|
|
363
441
|
def _json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
|
|
364
442
|
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
|
|
443
|
+
json_node["name"] = node.name # 1
|
|
444
|
+
json_node["mt"] = node.mt # 2
|
|
445
|
+
json_node["profiling"] = node.profiling # 3
|
|
446
|
+
json_node["auto_link"] = node.auto_link # 4
|
|
447
|
+
json_node["auto_input"] = node.auto_input # 5
|
|
448
|
+
json_node["auto_output"] = node.auto_output # 6
|
|
449
|
+
json_node["auto_parameter"] = node.auto_parameter # 6b
|
|
371
450
|
|
|
372
451
|
if node.step_size:
|
|
373
|
-
json_node["step_size"] = node.step_size
|
|
452
|
+
json_node["step_size"] = node.step_size # 7
|
|
374
453
|
|
|
375
454
|
if node.children:
|
|
376
|
-
json_node["container"] = [self._json_encode_node(child) for child in node.children]
|
|
455
|
+
json_node["container"] = [self._json_encode_node(child) for child in node.children.values()] # 8
|
|
377
456
|
|
|
378
457
|
if node.fmu_names_list:
|
|
379
|
-
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] #9
|
|
458
|
+
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] # 9
|
|
380
459
|
|
|
381
460
|
if node.input_ports:
|
|
382
461
|
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"] # 10
|
|
@@ -396,7 +475,7 @@ class Assembly:
|
|
|
396
475
|
for port, value in node.start_values.items()]
|
|
397
476
|
|
|
398
477
|
if node.drop_ports:
|
|
399
|
-
json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports]
|
|
478
|
+
json_node["drop"] = [[f"{port.fmu_name}", f"{port.port_name}"] for port in node.drop_ports] # 14
|
|
400
479
|
|
|
401
480
|
return json_node
|
|
402
481
|
|
|
@@ -176,6 +176,9 @@ 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
|
+
|
|
179
182
|
parser.add_argument("-no-auto-link", action="store_false", dest="auto_link", default=True,
|
|
180
183
|
help="Create ONLY explicit links.")
|
|
181
184
|
|
|
@@ -206,8 +209,8 @@ def fmucontainer():
|
|
|
206
209
|
try:
|
|
207
210
|
assembly = Assembly(filename, step_size=step_size, auto_link=config.auto_link,
|
|
208
211
|
auto_input=config.auto_input, auto_output=config.auto_output, mt=config.mt,
|
|
209
|
-
profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug
|
|
210
|
-
|
|
212
|
+
profiling=config.profiling, fmu_directory=fmu_directory, debug=config.debug,
|
|
213
|
+
auto_parameter=config.auto_parameter)
|
|
211
214
|
except FileNotFoundError as e:
|
|
212
215
|
logger.fatal(f"Cannot read file: {e}")
|
|
213
216
|
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,18 @@ 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):
|
|
303
312
|
# Auto Link outputs
|
|
304
313
|
for fmu in self.execution_order:
|
|
305
314
|
for port_name in fmu.ports:
|
|
306
315
|
cport = ContainerPort(fmu, port_name)
|
|
307
316
|
if cport not in self.rules:
|
|
308
|
-
if cport.port.causality == '
|
|
317
|
+
if cport.port.causality == 'parameter' and auto_parameter:
|
|
318
|
+
parameter_name = cport.fmu.model_identifier+"."+cport.port.name
|
|
319
|
+
logger.info(f"AUTO PARAMETER: {cport} as {parameter_name}")
|
|
320
|
+
self.inputs[parameter_name] = cport
|
|
321
|
+
self.mark_ruled(cport, 'PARAMETER')
|
|
322
|
+
elif cport.port.causality == 'output':
|
|
309
323
|
candidate_cport = self.find_input(cport.port)
|
|
310
324
|
if auto_link and candidate_cport:
|
|
311
325
|
local = Local(cport)
|
|
@@ -314,11 +328,15 @@ class FMUContainer:
|
|
|
314
328
|
self.mark_ruled(cport, 'LINK')
|
|
315
329
|
self.mark_ruled(candidate_cport, 'LINK')
|
|
316
330
|
self.locals[cport] = local
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
331
|
+
elif auto_output:
|
|
332
|
+
self.mark_ruled(cport, 'OUTPUT')
|
|
333
|
+
self.outputs[port_name] = cport
|
|
334
|
+
logger.info(f"AUTO OUTPUT: Expose {cport}")
|
|
335
|
+
elif cport.port.causality == 'local' and port_name.startswith("container."):
|
|
336
|
+
local_portname = "container." + cport.fmu.id + "." + port_name[10:]
|
|
337
|
+
self.mark_ruled(cport, 'OUTPUT')
|
|
338
|
+
self.outputs[local_portname] = cport
|
|
339
|
+
logger.info(f"PROFILING: Expose {cport}")
|
|
322
340
|
|
|
323
341
|
if auto_input:
|
|
324
342
|
# Auto link inputs
|
|
@@ -406,6 +424,18 @@ class FMUContainer:
|
|
|
406
424
|
if fmu.capabilities[capability] == "true":
|
|
407
425
|
capabilities[capability] = "true"
|
|
408
426
|
|
|
427
|
+
if self.start_time is None:
|
|
428
|
+
self.start_time = self.execution_order[0].start_time
|
|
429
|
+
logger.info(f"start_time={self.start_time} (deduced from '{self.execution_order[0].name}')")
|
|
430
|
+
else:
|
|
431
|
+
logger.info(f"start_time={self.start_time}")
|
|
432
|
+
|
|
433
|
+
if self.stop_time is None:
|
|
434
|
+
self.stop_time = self.execution_order[0].stop_time
|
|
435
|
+
logger.info(f"stop_time={self.stop_time} (deduced from '{self.execution_order[0].name}')")
|
|
436
|
+
else:
|
|
437
|
+
logger.info(f"stop_time={self.stop_time}")
|
|
438
|
+
|
|
409
439
|
xml_file.write(f"""<?xml version="1.0" encoding="ISO-8859-1"?>
|
|
410
440
|
<fmiModelDescription
|
|
411
441
|
fmiVersion="2.0"
|
|
@@ -434,14 +464,14 @@ class FMUContainer:
|
|
|
434
464
|
<Category name="fmucontainer"/>
|
|
435
465
|
</LogCategories>
|
|
436
466
|
|
|
437
|
-
<DefaultExperiment stepSize="{step_size}"/>
|
|
467
|
+
<DefaultExperiment stepSize="{step_size}" startTime="{self.start_time}" stopTime="{self.stop_time}"/>
|
|
438
468
|
|
|
439
469
|
<ModelVariables>
|
|
440
470
|
""")
|
|
441
471
|
if profiling:
|
|
442
472
|
for fmu in self.execution_order:
|
|
443
473
|
vr = vr_table.add_vr("Real")
|
|
444
|
-
name = f"container.{fmu.
|
|
474
|
+
name = f"container.{fmu.id}.rt_ratio"
|
|
445
475
|
print(f'<ScalarVariable valueReference="{vr}" name="{name}" causality="local">'
|
|
446
476
|
f'<Real /></ScalarVariable>', file=xml_file)
|
|
447
477
|
|
|
@@ -591,6 +621,14 @@ class FMUContainer:
|
|
|
591
621
|
for cport, vr in outputs_fmu_per_type[type_name][fmu.name].items():
|
|
592
622
|
print(f"{vr} {cport.port.vr}", file=txt_file)
|
|
593
623
|
|
|
624
|
+
@staticmethod
|
|
625
|
+
def long_path(path: Union[str, Path]) -> str:
|
|
626
|
+
# https://stackoverflow.com/questions/14075465/copy-a-file-with-a-too-long-path-to-another-directory-in-python
|
|
627
|
+
if os.name == 'nt':
|
|
628
|
+
return "\\\\?\\" + os.path.abspath(str(path))
|
|
629
|
+
else:
|
|
630
|
+
return path
|
|
631
|
+
|
|
594
632
|
def make_fmu_skeleton(self, base_directory: Path) -> Path:
|
|
595
633
|
logger.debug(f"Initialize directory '{base_directory}'")
|
|
596
634
|
|
|
@@ -617,8 +655,8 @@ class FMUContainer:
|
|
|
617
655
|
shutil.copy(library_filename, binary_directory / f"{self.identifier}.dll")
|
|
618
656
|
|
|
619
657
|
for fmu in self.involved_fmu.values():
|
|
620
|
-
shutil.copytree(fmu.fmu.tmp_directory,
|
|
621
|
-
|
|
658
|
+
shutil.copytree(self.long_path(fmu.fmu.tmp_directory),
|
|
659
|
+
self.long_path(resources_directory / fmu.name), dirs_exist_ok=True)
|
|
622
660
|
return resources_directory
|
|
623
661
|
|
|
624
662
|
def make_fmu_package(self, base_directory: Path, fmu_filename: Path):
|
|
@@ -626,11 +664,10 @@ class FMUContainer:
|
|
|
626
664
|
with zipfile.ZipFile(self.fmu_directory / fmu_filename, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
627
665
|
for root, dirs, files in os.walk(base_directory):
|
|
628
666
|
for file in files:
|
|
629
|
-
zip_file.write(os.path.join(root, file),
|
|
667
|
+
zip_file.write(self.long_path(os.path.join(root, file)),
|
|
630
668
|
os.path.relpath(os.path.join(root, file), base_directory))
|
|
631
669
|
logger.info(f"'{fmu_filename}' is available.")
|
|
632
670
|
|
|
633
|
-
|
|
634
|
-
def make_fmu_cleanup(base_directory: Path):
|
|
671
|
+
def make_fmu_cleanup(self, base_directory: Path):
|
|
635
672
|
logger.debug(f"Delete directory '{base_directory}'")
|
|
636
|
-
shutil.rmtree(base_directory)
|
|
673
|
+
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:
|