fmu-manipulation-toolbox 1.8__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 → fmu_manipulation_toolbox-1.8.2}/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/README.md +7 -4
- fmu_manipulation_toolbox-1.8.2/fmu_manipulation_toolbox/__version__.py +1 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/assembly.py +241 -127
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/cli.py +16 -7
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/fmu_container.py +69 -30
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/fmu_operations.py +2 -3
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/gui.py +356 -103
- 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.2/fmu_manipulation_toolbox/resources/mask.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8 → 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 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/SOURCES.txt +4 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/requires.txt +1 -1
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/setup.py +2 -1
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/tests/test_suite.py +45 -3
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/__version__.py +0 -1
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- fmu_manipulation_toolbox-1.8/fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/__init__.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/__main__.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/checker.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/help.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-checked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-checked.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/checkbox-unchecked.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/help.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/icon-round.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/icon.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/license.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/resources/model.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/version.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/dependency_links.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox.egg-info/top_level.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → 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.
|
|
@@ -122,22 +122,25 @@ optional arguments:
|
|
|
122
122
|
### FMU Containers
|
|
123
123
|
|
|
124
124
|
```
|
|
125
|
-
fmucontainer [-h] -fmu-directory FMU_DIRECTORY
|
|
126
|
-
|
|
125
|
+
usage: fmucontainer [-h] [-fmu-directory FMU_DIRECTORY] -container filename.{csv|json|ssp},[:step_size] [-debug]
|
|
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
|
|
|
130
130
|
optional arguments:
|
|
131
131
|
-h, -help
|
|
132
|
-
-fmu-directory FMU_DIRECTORY Directory containing initial FMU’s and used to generate containers.
|
|
133
|
-
|
|
132
|
+
-fmu-directory FMU_DIRECTORY Directory containing initial FMU’s and used to generate containers. If not defined,
|
|
133
|
+
current directory is used. (default: .)
|
|
134
|
+
-container filename.{csv|json|ssp},[:step_size]
|
|
134
135
|
Description of the container to create. (default: [])
|
|
135
136
|
-debug Add lot of useful log during the process. (default: False)
|
|
136
137
|
-no-auto-input Create ONLY explicit input. (default: True)
|
|
137
138
|
-no-auto-output Create ONLY explicit output. (default: True)
|
|
139
|
+
-auto-parameter Expose parameters of the embedded fmu's. (default: False)
|
|
138
140
|
-no-auto-link Create ONLY explicit links. (default: True)
|
|
139
141
|
-mt Enable Multi-Threaded mode for the generated container. (default: False)
|
|
140
142
|
-profile Enable Profiling mode for the generated container. (default: False)
|
|
143
|
+
-dump-json Dump a JSON file for each container. (default: False)
|
|
141
144
|
```
|
|
142
145
|
|
|
143
146
|
## API
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'V1.8.2'
|
{fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/assembly.py
RENAMED
|
@@ -7,7 +7,7 @@ import uuid
|
|
|
7
7
|
import xml.parsers.expat
|
|
8
8
|
import zipfile
|
|
9
9
|
|
|
10
|
-
from .fmu_container import FMUContainer
|
|
10
|
+
from .fmu_container import FMUContainer
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger("fmu_manipulation_toolbox")
|
|
13
13
|
|
|
@@ -23,28 +23,44 @@ 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
|
-
def __init__(self, name: str, step_size: float = None, mt=False, profiling=False,
|
|
35
|
-
auto_link=True, auto_input=True, auto_output=True):
|
|
40
|
+
def __init__(self, name: Optional[str], step_size: float = None, mt=False, profiling=False,
|
|
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:
|
|
44
|
+
try:
|
|
45
|
+
self.step_size = float(step_size)
|
|
46
|
+
except ValueError:
|
|
47
|
+
logger.warning(f"Step size '{step_size}' is incorrect format.")
|
|
48
|
+
self.step_size = None
|
|
49
|
+
else:
|
|
50
|
+
self.step_size = None
|
|
38
51
|
self.mt = mt
|
|
39
52
|
self.profiling = profiling
|
|
40
53
|
self.auto_link = auto_link
|
|
41
54
|
self.auto_input = auto_input
|
|
42
55
|
self.auto_output = auto_output
|
|
43
|
-
self.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
56
|
+
self.auto_parameter = auto_parameter
|
|
57
|
+
self.auto_local = auto_local
|
|
58
|
+
|
|
59
|
+
self.parent: Optional[AssemblyNode] = None
|
|
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
|
|
48
64
|
self.start_values: Dict[Port, str] = {}
|
|
49
65
|
self.drop_ports: List[Port] = []
|
|
50
66
|
self.links: List[Connection] = []
|
|
@@ -53,11 +69,20 @@ class AssemblyNode:
|
|
|
53
69
|
if sub_node.name is None:
|
|
54
70
|
sub_node.name = str(uuid.uuid4())+".fmu"
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
if sub_node.parent is not None:
|
|
73
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already parented.")
|
|
74
|
+
|
|
75
|
+
if sub_node.name in self.children:
|
|
76
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already child of {self.name}")
|
|
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
|
|
58
82
|
|
|
59
83
|
def add_fmu(self, fmu_name: str):
|
|
60
|
-
self.fmu_names_list
|
|
84
|
+
if fmu_name not in self.fmu_names_list:
|
|
85
|
+
self.fmu_names_list.append(fmu_name)
|
|
61
86
|
|
|
62
87
|
def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
63
88
|
self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
|
|
@@ -76,12 +101,13 @@ class AssemblyNode:
|
|
|
76
101
|
self.start_values[Port(fmu_filename, port_name)] = value
|
|
77
102
|
|
|
78
103
|
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
|
|
79
|
-
for node in self.children:
|
|
104
|
+
for node in self.children.values():
|
|
80
105
|
node.make_fmu(fmu_directory, debug=debug)
|
|
81
106
|
|
|
82
|
-
|
|
107
|
+
identifier = str(Path(self.name).stem)
|
|
108
|
+
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname)
|
|
83
109
|
|
|
84
|
-
for fmu_name in
|
|
110
|
+
for fmu_name in self.fmu_names_list:
|
|
85
111
|
container.get_fmu(fmu_name)
|
|
86
112
|
|
|
87
113
|
for port, source in self.input_ports.items():
|
|
@@ -102,14 +128,78 @@ class AssemblyNode:
|
|
|
102
128
|
|
|
103
129
|
container.add_implicit_rule(auto_input=self.auto_input,
|
|
104
130
|
auto_output=self.auto_output,
|
|
105
|
-
auto_link=self.auto_link
|
|
131
|
+
auto_link=self.auto_link,
|
|
132
|
+
auto_parameter=self.auto_parameter,
|
|
133
|
+
auto_local=self.auto_local)
|
|
106
134
|
|
|
107
135
|
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
|
|
108
136
|
|
|
109
|
-
for node in self.children:
|
|
137
|
+
for node in self.children.values():
|
|
110
138
|
logger.info(f"Deleting transient FMU Container '{node.name}'")
|
|
111
139
|
(fmu_directory / node.name).unlink()
|
|
112
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
|
+
|
|
113
203
|
|
|
114
204
|
class AssemblyError(Exception):
|
|
115
205
|
def __init__(self, reason: str):
|
|
@@ -121,13 +211,16 @@ class AssemblyError(Exception):
|
|
|
121
211
|
|
|
122
212
|
class Assembly:
|
|
123
213
|
def __init__(self, filename: str, step_size=None, auto_link=True, auto_input=True, debug=False,
|
|
124
|
-
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):
|
|
125
216
|
self.filename = Path(filename)
|
|
126
217
|
self.default_auto_input = auto_input
|
|
127
218
|
self.debug = debug
|
|
128
219
|
self.default_auto_output = auto_output
|
|
129
220
|
self.default_step_size = step_size
|
|
130
221
|
self.default_auto_link = auto_link
|
|
222
|
+
self.default_auto_parameter = auto_parameter
|
|
223
|
+
self.default_auto_local = auto_local
|
|
131
224
|
self.default_mt = mt
|
|
132
225
|
self.default_profiling = profiling
|
|
133
226
|
self.fmu_directory = fmu_directory
|
|
@@ -135,9 +228,10 @@ class Assembly:
|
|
|
135
228
|
self.transient_dirnames: Set[Path] = set()
|
|
136
229
|
|
|
137
230
|
if not fmu_directory.is_dir():
|
|
138
|
-
raise
|
|
231
|
+
raise AssemblyError(f"FMU directory is not valid: '{fmu_directory}'")
|
|
139
232
|
|
|
140
|
-
self.
|
|
233
|
+
self.input_pathname = fmu_directory / self.filename
|
|
234
|
+
self.description_pathname = self.input_pathname # For inclusion in FMU
|
|
141
235
|
self.root = None
|
|
142
236
|
self.read()
|
|
143
237
|
|
|
@@ -169,7 +263,7 @@ class Assembly:
|
|
|
169
263
|
elif self.filename.suffix == ".csv":
|
|
170
264
|
self.read_csv()
|
|
171
265
|
else:
|
|
172
|
-
raise
|
|
266
|
+
raise AssemblyError(f"Not supported file format '{self.filename}")
|
|
173
267
|
|
|
174
268
|
def write(self, filename: str):
|
|
175
269
|
if filename.endswith(".csv"):
|
|
@@ -183,11 +277,12 @@ class Assembly:
|
|
|
183
277
|
name = str(self.filename.with_suffix(".fmu"))
|
|
184
278
|
self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
185
279
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
186
|
-
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)
|
|
187
282
|
|
|
188
|
-
with open(self.
|
|
283
|
+
with open(self.input_pathname) as file:
|
|
189
284
|
reader = csv.reader(file, delimiter=';')
|
|
190
|
-
self.
|
|
285
|
+
self._check_csv_headers(reader)
|
|
191
286
|
for i, row in enumerate(reader):
|
|
192
287
|
if not row or row[0][0] == '#': # skip blank line of comment
|
|
193
288
|
continue
|
|
@@ -198,36 +293,19 @@ class Assembly:
|
|
|
198
293
|
logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
|
|
199
294
|
continue
|
|
200
295
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
logger.error(f"Line #{i+2}: {e}. Line skipped.")
|
|
208
|
-
continue
|
|
209
|
-
else:
|
|
210
|
-
logger.error(f"Line #{i+2}: unexpected rule '{rule}'. Line skipped.")
|
|
211
|
-
|
|
212
|
-
def write_csv(self, filename: Union[str, Path]):
|
|
213
|
-
if self.root.children:
|
|
214
|
-
raise AssemblyError("This assembly is not flat. Cannot export to CSV file.")
|
|
296
|
+
try:
|
|
297
|
+
self._read_csv_rule(self.root, rule.upper(),
|
|
298
|
+
from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
|
|
299
|
+
except AssemblyError as e:
|
|
300
|
+
logger.error(f"Line #{i+2}: {e}. Line skipped.")
|
|
301
|
+
continue
|
|
215
302
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
for port, target in self.root.output_ports.items():
|
|
223
|
-
outfile.write(f"OUTPUT;{port.fmu_name};{port.port_name};;{target}\n")
|
|
224
|
-
for link in self.root.links:
|
|
225
|
-
outfile.write(f"LINK;{link.from_port.fmu_name};{link.from_port.port_name};"
|
|
226
|
-
f"{link.to_port.fmu_name};{link.to_port.port_name}\n")
|
|
227
|
-
for port, value in self.root.start_values.items():
|
|
228
|
-
outfile.write(f"START;{port.fmu_name};{port.port_name};{value};\n")
|
|
229
|
-
for port in self.root.drop_ports:
|
|
230
|
-
outfile.write(f"DROP;{port.fmu_name};{port.port_name};;\n")
|
|
303
|
+
@staticmethod
|
|
304
|
+
def _check_csv_headers(reader):
|
|
305
|
+
headers = next(reader)
|
|
306
|
+
headers_lowered = [h.lower() for h in headers]
|
|
307
|
+
if not headers_lowered == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
|
|
308
|
+
raise AssemblyError("Header (1st line of the file) is not well formatted.")
|
|
231
309
|
|
|
232
310
|
@staticmethod
|
|
233
311
|
def _read_csv_rule(node: AssemblyNode, rule: str, from_fmu_filename: str, from_port_name: str,
|
|
@@ -264,114 +342,150 @@ class Assembly:
|
|
|
264
342
|
raise AssemblyError("Missing START ports information.")
|
|
265
343
|
|
|
266
344
|
node.add_start_value(from_fmu_filename, from_port_name, to_fmu_filename)
|
|
267
|
-
|
|
345
|
+
else:
|
|
346
|
+
raise AssemblyError(f"unexpected rule '{rule}'. Line skipped.")
|
|
268
347
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
348
|
+
def write_csv(self, filename: Union[str, Path]):
|
|
349
|
+
if self.root.children:
|
|
350
|
+
raise AssemblyError("This assembly is not flat. Cannot export to CSV file.")
|
|
351
|
+
|
|
352
|
+
with open(self.fmu_directory / filename, "wt") as outfile:
|
|
353
|
+
outfile.write("rule;from_fmu;from_port;to_fmu;to_port\n")
|
|
354
|
+
for fmu in self.root.fmu_names_list:
|
|
355
|
+
outfile.write(f"FMU;{fmu};;;\n")
|
|
356
|
+
for port, source in self.root.input_ports.items():
|
|
357
|
+
outfile.write(f"INPUT;;{source};{port.fmu_name};{port.port_name}\n")
|
|
358
|
+
for port, target in self.root.output_ports.items():
|
|
359
|
+
outfile.write(f"OUTPUT;{port.fmu_name};{port.port_name};;{target}\n")
|
|
360
|
+
for link in self.root.links:
|
|
361
|
+
outfile.write(f"LINK;{link.from_port.fmu_name};{link.from_port.port_name};"
|
|
362
|
+
f"{link.to_port.fmu_name};{link.to_port.port_name}\n")
|
|
363
|
+
for port, value in self.root.start_values.items():
|
|
364
|
+
outfile.write(f"START;{port.fmu_name};{port.port_name};{value};\n")
|
|
365
|
+
for port in self.root.drop_ports:
|
|
366
|
+
outfile.write(f"DROP;{port.fmu_name};{port.port_name};;\n")
|
|
274
367
|
|
|
275
368
|
def read_json(self):
|
|
276
|
-
with open(self.
|
|
369
|
+
with open(self.input_pathname) as file:
|
|
277
370
|
try:
|
|
278
371
|
data = json.load(file)
|
|
279
372
|
except json.decoder.JSONDecodeError as e:
|
|
280
|
-
raise
|
|
281
|
-
self.root = self.
|
|
373
|
+
raise AssemblyError(f"Cannot read json: {e}")
|
|
374
|
+
self.root = self._json_decode_node(data)
|
|
282
375
|
if not self.root.name:
|
|
283
|
-
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
376
|
+
self.root.name = str(self.filename.with_suffix(".fmu").name)
|
|
377
|
+
|
|
378
|
+
def _json_decode_node(self, data: Dict) -> AssemblyNode:
|
|
379
|
+
name = data.get("name", None) # 1
|
|
380
|
+
mt = data.get("mt", self.default_mt) # 2
|
|
381
|
+
profiling = data.get("profiling", self.default_profiling) # 3
|
|
382
|
+
auto_link = data.get("auto_link", self.default_auto_link) # 4
|
|
383
|
+
auto_input = data.get("auto_input", self.default_auto_input) # 5
|
|
384
|
+
auto_output = data.get("auto_output", self.default_auto_output) # 6
|
|
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
|
|
388
|
+
|
|
389
|
+
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
390
|
+
auto_input=auto_input, auto_output=auto_output, auto_parameter=auto_parameter,
|
|
391
|
+
auto_local=auto_local)
|
|
392
|
+
|
|
393
|
+
for key, value in data.items():
|
|
394
|
+
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling',
|
|
395
|
+
'auto_parameter', 'auto_local'):
|
|
396
|
+
continue # Already read
|
|
397
|
+
|
|
398
|
+
elif key == "container": # 8
|
|
399
|
+
if not isinstance(value, list):
|
|
400
|
+
raise AssemblyError("JSON: 'container' keyword should define a list.")
|
|
401
|
+
for sub_data in value:
|
|
402
|
+
node.add_sub_node(self._json_decode_node(sub_data))
|
|
403
|
+
|
|
404
|
+
elif key == "fmu": # 9
|
|
405
|
+
if not isinstance(value, list):
|
|
406
|
+
raise AssemblyError("JSON: 'fmu' keyword should define a list.")
|
|
407
|
+
for fmu in value:
|
|
408
|
+
node.add_fmu(fmu)
|
|
409
|
+
|
|
410
|
+
elif key == "input": # 10
|
|
411
|
+
self._json_decode_keyword('input', value, node.add_input)
|
|
412
|
+
|
|
413
|
+
elif key == "output": # 11
|
|
414
|
+
self._json_decode_keyword('output', value, node.add_output)
|
|
415
|
+
|
|
416
|
+
elif key == "link": # 12
|
|
417
|
+
self._json_decode_keyword('link', value, node.add_link)
|
|
418
|
+
|
|
419
|
+
elif key == "start": # 13
|
|
420
|
+
self._json_decode_keyword('start', value, node.add_start_value)
|
|
421
|
+
|
|
422
|
+
elif key == "drop": # 14
|
|
423
|
+
self._json_decode_keyword('drop', value, node.add_drop_port)
|
|
424
|
+
|
|
425
|
+
else:
|
|
426
|
+
logger.error(f"JSON: unexpected keyword {key}. Skipped.")
|
|
427
|
+
|
|
428
|
+
return node
|
|
429
|
+
|
|
430
|
+
@staticmethod
|
|
431
|
+
def _json_decode_keyword(keyword: str, value, function):
|
|
432
|
+
if not isinstance(value, list):
|
|
433
|
+
raise AssemblyError(f"JSON: '{keyword}' keyword should define a list.")
|
|
434
|
+
for line in value:
|
|
435
|
+
if not isinstance(line, list):
|
|
436
|
+
raise AssemblyError(f"JSON: unexpected '{keyword}' value: {line}.")
|
|
437
|
+
try:
|
|
438
|
+
function(*line)
|
|
439
|
+
except TypeError:
|
|
440
|
+
raise AssemblyError(f"JSON: '{keyword}' value does not contain right number of fields: {line}.")
|
|
284
441
|
|
|
285
442
|
def write_json(self, filename: Union[str, Path]):
|
|
286
443
|
with open(self.fmu_directory / filename, "wt") as file:
|
|
287
|
-
data = self.
|
|
444
|
+
data = self._json_encode_node(self.root)
|
|
288
445
|
json.dump(data, file, indent=2)
|
|
289
446
|
|
|
290
|
-
def
|
|
447
|
+
def _json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
|
|
291
448
|
json_node = dict()
|
|
292
|
-
json_node["name"] = node.name
|
|
293
|
-
json_node["mt"] = node.mt
|
|
294
|
-
json_node["profiling"] = node.profiling
|
|
295
|
-
json_node["auto_link"] = node.auto_link
|
|
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
|
|
457
|
+
|
|
296
458
|
if node.step_size:
|
|
297
|
-
json_node["step_size"] = node.step_size
|
|
459
|
+
json_node["step_size"] = node.step_size # 7
|
|
298
460
|
|
|
299
461
|
if node.children:
|
|
300
|
-
json_node["container"] = [self.
|
|
462
|
+
json_node["container"] = [self._json_encode_node(child) for child in node.children.values()] # 8
|
|
301
463
|
|
|
302
464
|
if node.fmu_names_list:
|
|
303
|
-
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)]
|
|
465
|
+
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] # 9
|
|
304
466
|
|
|
305
467
|
if node.input_ports:
|
|
306
|
-
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"]
|
|
468
|
+
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"] # 10
|
|
307
469
|
for port, source in node.input_ports.items()]
|
|
308
470
|
|
|
309
471
|
if node.output_ports:
|
|
310
|
-
json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"]
|
|
472
|
+
json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"] # 11
|
|
311
473
|
for port, target in node.output_ports.items()]
|
|
312
474
|
|
|
313
475
|
if node.links:
|
|
314
|
-
json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}",
|
|
476
|
+
json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}", # 12
|
|
315
477
|
f"{link.to_port.fmu_name}", f"{link.to_port.port_name}"]
|
|
316
478
|
for link in node.links]
|
|
317
479
|
|
|
318
480
|
if node.start_values:
|
|
319
|
-
json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value]
|
|
481
|
+
json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value] # 13
|
|
320
482
|
for port, value in node.start_values.items()]
|
|
321
483
|
|
|
322
484
|
if node.drop_ports:
|
|
323
|
-
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
|
|
324
486
|
|
|
325
487
|
return json_node
|
|
326
488
|
|
|
327
|
-
def json_decode_node(self, data) -> AssemblyNode:
|
|
328
|
-
name = data.get("name", None)
|
|
329
|
-
step_size = data.get("step_size", self.default_step_size)
|
|
330
|
-
auto_link = data.get("auto_link", self.default_auto_link)
|
|
331
|
-
auto_input = data.get("auto_input", self.default_auto_input)
|
|
332
|
-
auto_output = data.get("auto_output", self.default_auto_output)
|
|
333
|
-
mt = data.get("mt", self.default_mt)
|
|
334
|
-
profiling = data.get("profiling", self.default_profiling)
|
|
335
|
-
|
|
336
|
-
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
337
|
-
auto_input=auto_input, auto_output=auto_output)
|
|
338
|
-
|
|
339
|
-
if "container" in data:
|
|
340
|
-
if not isinstance(data["container"], list):
|
|
341
|
-
raise FMUContainerError("JSON: 'container' keyword should define a list.")
|
|
342
|
-
for sub_data in data["container"]:
|
|
343
|
-
node.add_sub_node(self.json_decode_node(sub_data))
|
|
344
|
-
|
|
345
|
-
if "fmu" in data:
|
|
346
|
-
if not isinstance(data["fmu"], list):
|
|
347
|
-
raise FMUContainerError("JSON: 'fmu' keyword should define a list.")
|
|
348
|
-
for fmu in data["fmu"]:
|
|
349
|
-
node.add_fmu(fmu)
|
|
350
|
-
|
|
351
|
-
if "input" in data:
|
|
352
|
-
if not isinstance(data["input"], list):
|
|
353
|
-
raise FMUContainerError("JSON: 'input' keyword should define a list.")
|
|
354
|
-
for line in data["input"]:
|
|
355
|
-
node.add_input(line[1], line[2], line[0])
|
|
356
|
-
|
|
357
|
-
if "output" in data:
|
|
358
|
-
if not isinstance(data["output"], list):
|
|
359
|
-
raise FMUContainerError("JSON: 'output' keyword should define a list.")
|
|
360
|
-
for line in data["output"]:
|
|
361
|
-
node.add_output(line[0], line[1], line[2])
|
|
362
|
-
|
|
363
|
-
if "start" in data:
|
|
364
|
-
if not isinstance(data["start"], list):
|
|
365
|
-
raise FMUContainerError("JSON: 'start' keyword should define a list.")
|
|
366
|
-
for line in data["start"]:
|
|
367
|
-
node.add_start_value(line[0], line[1], line[2])
|
|
368
|
-
|
|
369
|
-
if "drop" in data:
|
|
370
|
-
for line in data["drop"]:
|
|
371
|
-
node.add_drop_port(line[0], line[1])
|
|
372
|
-
|
|
373
|
-
return node
|
|
374
|
-
|
|
375
489
|
def read_ssp(self):
|
|
376
490
|
logger.warning("This feature is ALPHA stage.")
|
|
377
491
|
|
|
@@ -388,11 +502,11 @@ class Assembly:
|
|
|
388
502
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
389
503
|
auto_input=self.default_auto_input, auto_output=self.default_auto_output)
|
|
390
504
|
self.root = sdd.parse(self.description_pathname)
|
|
505
|
+
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
391
506
|
|
|
392
507
|
def make_fmu(self, dump_json=False):
|
|
393
508
|
if dump_json:
|
|
394
|
-
dump_file = (self.
|
|
395
|
-
.with_suffix(".json"))
|
|
509
|
+
dump_file = Path(self.input_pathname.stem + "-dump").with_suffix(".json")
|
|
396
510
|
logger.info(f"Dump Json '{dump_file}'")
|
|
397
511
|
self.write_json(dump_file)
|
|
398
512
|
self.root.make_fmu(self.fmu_directory, debug=self.debug, description_pathname=self.description_pathname)
|
{fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2}/fmu_manipulation_toolbox/cli.py
RENAMED
|
@@ -5,7 +5,7 @@ from colorama import Fore, Style, init
|
|
|
5
5
|
|
|
6
6
|
from .fmu_operations import *
|
|
7
7
|
from .fmu_container import FMUContainerError
|
|
8
|
-
from .assembly import Assembly
|
|
8
|
+
from .assembly import Assembly, AssemblyError
|
|
9
9
|
from .checker import checker_list
|
|
10
10
|
from .version import __version__ as version
|
|
11
11
|
from .help import Help
|
|
@@ -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,20 +204,23 @@ 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
|
|
214
|
-
except FMUContainerError as e:
|
|
223
|
+
except (FMUContainerError, AssemblyError) as e:
|
|
215
224
|
logger.fatal(f"{filename}: {e}")
|
|
216
225
|
continue
|
|
217
226
|
|