fmu-manipulation-toolbox 1.8__tar.gz → 1.8.2.dev2__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.dev2}/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/README.md +7 -4
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/__version__.py +1 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/assembly.py +233 -126
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/cli.py +7 -4
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/fmu_container.py +60 -27
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/fmu_operations.py +2 -3
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/gui.py +356 -103
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/gui_style.py +129 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/container.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/drop_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/fmu_manipulation_toolbox.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/icon_fmu.png +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/linux64/container.so +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/mask.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/win32/client_sm.dll +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/win32/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/win64/client_sm.dll +0 -0
- fmu_manipulation_toolbox-1.8.2.dev2/fmu_manipulation_toolbox/resources/win64/container.dll +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/win64/server_sm.exe +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox.egg-info/PKG-INFO +8 -3
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox.egg-info/SOURCES.txt +4 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox.egg-info/requires.txt +1 -1
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/setup.py +2 -1
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/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-1.8.2.dev2}/LICENSE.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/__init__.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/__main__.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/checker.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/help.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/checkbox-checked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/checkbox-checked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/checkbox-checked.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/checkbox-unchecked-disabled.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/checkbox-unchecked-hover.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/checkbox-unchecked.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Annotation.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2AttributeGroups.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ModelDescription.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2ScalarVariable.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Type.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2Unit.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/fmi-2.0/fmi2VariableDependency.xsd +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/help.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/icon-round.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/icon.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/license.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/linux32/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/linux32/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/linux64/client_sm.so +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/linux64/server_sm +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/resources/model.png +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox/version.py +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox.egg-info/dependency_links.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox.egg-info/entry_points.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/fmu_manipulation_toolbox.egg-info/top_level.txt +0 -0
- {fmu_manipulation_toolbox-1.8 → fmu_manipulation_toolbox-1.8.2.dev2}/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.dev2
|
|
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-dev2'
|
|
@@ -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,43 @@ 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):
|
|
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.
|
|
56
|
+
self.auto_parameter = auto_parameter
|
|
44
57
|
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
58
|
+
self.parent: Optional[AssemblyNode] = None
|
|
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
|
|
48
63
|
self.start_values: Dict[Port, str] = {}
|
|
49
64
|
self.drop_ports: List[Port] = []
|
|
50
65
|
self.links: List[Connection] = []
|
|
@@ -53,11 +68,20 @@ class AssemblyNode:
|
|
|
53
68
|
if sub_node.name is None:
|
|
54
69
|
sub_node.name = str(uuid.uuid4())+".fmu"
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
if sub_node.parent is not None:
|
|
72
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already parented.")
|
|
73
|
+
|
|
74
|
+
if sub_node.name in self.children:
|
|
75
|
+
raise AssemblyError(f"Internal Error: AssemblyNode {sub_node.name} is already child of {self.name}")
|
|
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
|
|
58
81
|
|
|
59
82
|
def add_fmu(self, fmu_name: str):
|
|
60
|
-
self.fmu_names_list
|
|
83
|
+
if fmu_name not in self.fmu_names_list:
|
|
84
|
+
self.fmu_names_list.append(fmu_name)
|
|
61
85
|
|
|
62
86
|
def add_input(self, from_port_name: str, to_fmu_filename: str, to_port_name: str):
|
|
63
87
|
self.input_ports[Port(to_fmu_filename, to_port_name)] = from_port_name
|
|
@@ -76,12 +100,13 @@ class AssemblyNode:
|
|
|
76
100
|
self.start_values[Port(fmu_filename, port_name)] = value
|
|
77
101
|
|
|
78
102
|
def make_fmu(self, fmu_directory: Path, debug=False, description_pathname=None):
|
|
79
|
-
for node in self.children:
|
|
103
|
+
for node in self.children.values():
|
|
80
104
|
node.make_fmu(fmu_directory, debug=debug)
|
|
81
105
|
|
|
82
|
-
|
|
106
|
+
identifier = str(Path(self.name).stem)
|
|
107
|
+
container = FMUContainer(identifier, fmu_directory, description_pathname=description_pathname)
|
|
83
108
|
|
|
84
|
-
for fmu_name in
|
|
109
|
+
for fmu_name in self.fmu_names_list:
|
|
85
110
|
container.get_fmu(fmu_name)
|
|
86
111
|
|
|
87
112
|
for port, source in self.input_ports.items():
|
|
@@ -102,14 +127,77 @@ class AssemblyNode:
|
|
|
102
127
|
|
|
103
128
|
container.add_implicit_rule(auto_input=self.auto_input,
|
|
104
129
|
auto_output=self.auto_output,
|
|
105
|
-
auto_link=self.auto_link
|
|
130
|
+
auto_link=self.auto_link,
|
|
131
|
+
auto_parameter=self.auto_parameter)
|
|
106
132
|
|
|
107
133
|
container.make_fmu(self.name, self.step_size, mt=self.mt, profiling=self.profiling, debug=debug)
|
|
108
134
|
|
|
109
|
-
for node in self.children:
|
|
135
|
+
for node in self.children.values():
|
|
110
136
|
logger.info(f"Deleting transient FMU Container '{node.name}'")
|
|
111
137
|
(fmu_directory / node.name).unlink()
|
|
112
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
|
+
|
|
113
201
|
|
|
114
202
|
class AssemblyError(Exception):
|
|
115
203
|
def __init__(self, reason: str):
|
|
@@ -121,13 +209,14 @@ class AssemblyError(Exception):
|
|
|
121
209
|
|
|
122
210
|
class Assembly:
|
|
123
211
|
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 = "."):
|
|
212
|
+
auto_output=True, mt=False, profiling=False, fmu_directory: Path = Path("."), auto_parameter=False):
|
|
125
213
|
self.filename = Path(filename)
|
|
126
214
|
self.default_auto_input = auto_input
|
|
127
215
|
self.debug = debug
|
|
128
216
|
self.default_auto_output = auto_output
|
|
129
217
|
self.default_step_size = step_size
|
|
130
218
|
self.default_auto_link = auto_link
|
|
219
|
+
self.default_auto_parameter = auto_parameter
|
|
131
220
|
self.default_mt = mt
|
|
132
221
|
self.default_profiling = profiling
|
|
133
222
|
self.fmu_directory = fmu_directory
|
|
@@ -135,9 +224,10 @@ class Assembly:
|
|
|
135
224
|
self.transient_dirnames: Set[Path] = set()
|
|
136
225
|
|
|
137
226
|
if not fmu_directory.is_dir():
|
|
138
|
-
raise
|
|
227
|
+
raise AssemblyError(f"FMU directory is not valid: '{fmu_directory}'")
|
|
139
228
|
|
|
140
|
-
self.
|
|
229
|
+
self.input_pathname = fmu_directory / self.filename
|
|
230
|
+
self.description_pathname = self.input_pathname # For inclusion in FMU
|
|
141
231
|
self.root = None
|
|
142
232
|
self.read()
|
|
143
233
|
|
|
@@ -169,7 +259,7 @@ class Assembly:
|
|
|
169
259
|
elif self.filename.suffix == ".csv":
|
|
170
260
|
self.read_csv()
|
|
171
261
|
else:
|
|
172
|
-
raise
|
|
262
|
+
raise AssemblyError(f"Not supported file format '{self.filename}")
|
|
173
263
|
|
|
174
264
|
def write(self, filename: str):
|
|
175
265
|
if filename.endswith(".csv"):
|
|
@@ -183,11 +273,12 @@ class Assembly:
|
|
|
183
273
|
name = str(self.filename.with_suffix(".fmu"))
|
|
184
274
|
self.root = AssemblyNode(name, step_size=self.default_step_size, auto_link=self.default_auto_link,
|
|
185
275
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
186
|
-
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)
|
|
187
278
|
|
|
188
|
-
with open(self.
|
|
279
|
+
with open(self.input_pathname) as file:
|
|
189
280
|
reader = csv.reader(file, delimiter=';')
|
|
190
|
-
self.
|
|
281
|
+
self._check_csv_headers(reader)
|
|
191
282
|
for i, row in enumerate(reader):
|
|
192
283
|
if not row or row[0][0] == '#': # skip blank line of comment
|
|
193
284
|
continue
|
|
@@ -198,36 +289,19 @@ class Assembly:
|
|
|
198
289
|
logger.error(f"Line #{i+2}: expecting 5 columns. Line skipped.")
|
|
199
290
|
continue
|
|
200
291
|
|
|
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.")
|
|
292
|
+
try:
|
|
293
|
+
self._read_csv_rule(self.root, rule.upper(),
|
|
294
|
+
from_fmu_filename, from_port_name, to_fmu_filename, to_port_name)
|
|
295
|
+
except AssemblyError as e:
|
|
296
|
+
logger.error(f"Line #{i+2}: {e}. Line skipped.")
|
|
297
|
+
continue
|
|
215
298
|
|
|
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")
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _check_csv_headers(reader):
|
|
301
|
+
headers = next(reader)
|
|
302
|
+
headers_lowered = [h.lower() for h in headers]
|
|
303
|
+
if not headers_lowered == ["rule", "from_fmu", "from_port", "to_fmu", "to_port"]:
|
|
304
|
+
raise AssemblyError("Header (1st line of the file) is not well formatted.")
|
|
231
305
|
|
|
232
306
|
@staticmethod
|
|
233
307
|
def _read_csv_rule(node: AssemblyNode, rule: str, from_fmu_filename: str, from_port_name: str,
|
|
@@ -264,114 +338,147 @@ class Assembly:
|
|
|
264
338
|
raise AssemblyError("Missing START ports information.")
|
|
265
339
|
|
|
266
340
|
node.add_start_value(from_fmu_filename, from_port_name, to_fmu_filename)
|
|
267
|
-
|
|
341
|
+
else:
|
|
342
|
+
raise AssemblyError(f"unexpected rule '{rule}'. Line skipped.")
|
|
268
343
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
344
|
+
def write_csv(self, filename: Union[str, Path]):
|
|
345
|
+
if self.root.children:
|
|
346
|
+
raise AssemblyError("This assembly is not flat. Cannot export to CSV file.")
|
|
347
|
+
|
|
348
|
+
with open(self.fmu_directory / filename, "wt") as outfile:
|
|
349
|
+
outfile.write("rule;from_fmu;from_port;to_fmu;to_port\n")
|
|
350
|
+
for fmu in self.root.fmu_names_list:
|
|
351
|
+
outfile.write(f"FMU;{fmu};;;\n")
|
|
352
|
+
for port, source in self.root.input_ports.items():
|
|
353
|
+
outfile.write(f"INPUT;;{source};{port.fmu_name};{port.port_name}\n")
|
|
354
|
+
for port, target in self.root.output_ports.items():
|
|
355
|
+
outfile.write(f"OUTPUT;{port.fmu_name};{port.port_name};;{target}\n")
|
|
356
|
+
for link in self.root.links:
|
|
357
|
+
outfile.write(f"LINK;{link.from_port.fmu_name};{link.from_port.port_name};"
|
|
358
|
+
f"{link.to_port.fmu_name};{link.to_port.port_name}\n")
|
|
359
|
+
for port, value in self.root.start_values.items():
|
|
360
|
+
outfile.write(f"START;{port.fmu_name};{port.port_name};{value};\n")
|
|
361
|
+
for port in self.root.drop_ports:
|
|
362
|
+
outfile.write(f"DROP;{port.fmu_name};{port.port_name};;\n")
|
|
274
363
|
|
|
275
364
|
def read_json(self):
|
|
276
|
-
with open(self.
|
|
365
|
+
with open(self.input_pathname) as file:
|
|
277
366
|
try:
|
|
278
367
|
data = json.load(file)
|
|
279
368
|
except json.decoder.JSONDecodeError as e:
|
|
280
|
-
raise
|
|
281
|
-
self.root = self.
|
|
369
|
+
raise AssemblyError(f"Cannot read json: {e}")
|
|
370
|
+
self.root = self._json_decode_node(data)
|
|
282
371
|
if not self.root.name:
|
|
283
|
-
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
372
|
+
self.root.name = str(self.filename.with_suffix(".fmu").name)
|
|
373
|
+
|
|
374
|
+
def _json_decode_node(self, data: Dict) -> AssemblyNode:
|
|
375
|
+
name = data.get("name", None) # 1
|
|
376
|
+
mt = data.get("mt", self.default_mt) # 2
|
|
377
|
+
profiling = data.get("profiling", self.default_profiling) # 3
|
|
378
|
+
auto_link = data.get("auto_link", self.default_auto_link) # 4
|
|
379
|
+
auto_input = data.get("auto_input", self.default_auto_input) # 5
|
|
380
|
+
auto_output = data.get("auto_output", self.default_auto_output) # 6
|
|
381
|
+
auto_parameter = data.get("auto_parameter", self.default_auto_parameter) # 6b
|
|
382
|
+
step_size = data.get("step_size", self.default_step_size) # 7
|
|
383
|
+
|
|
384
|
+
node = AssemblyNode(name, step_size=step_size, auto_link=auto_link, mt=mt, profiling=profiling,
|
|
385
|
+
auto_input=auto_input, auto_output=auto_output, auto_parameter=auto_parameter)
|
|
386
|
+
|
|
387
|
+
for key, value in data.items():
|
|
388
|
+
if key in ('name', 'step_size', 'auto_link', 'auto_input', 'auto_output', 'mt', 'profiling',
|
|
389
|
+
'auto_parameter'):
|
|
390
|
+
continue # Already read
|
|
391
|
+
|
|
392
|
+
elif key == "container": # 8
|
|
393
|
+
if not isinstance(value, list):
|
|
394
|
+
raise AssemblyError("JSON: 'container' keyword should define a list.")
|
|
395
|
+
for sub_data in value:
|
|
396
|
+
node.add_sub_node(self._json_decode_node(sub_data))
|
|
397
|
+
|
|
398
|
+
elif key == "fmu": # 9
|
|
399
|
+
if not isinstance(value, list):
|
|
400
|
+
raise AssemblyError("JSON: 'fmu' keyword should define a list.")
|
|
401
|
+
for fmu in value:
|
|
402
|
+
node.add_fmu(fmu)
|
|
403
|
+
|
|
404
|
+
elif key == "input": # 10
|
|
405
|
+
self._json_decode_keyword('input', value, node.add_input)
|
|
406
|
+
|
|
407
|
+
elif key == "output": # 11
|
|
408
|
+
self._json_decode_keyword('output', value, node.add_output)
|
|
409
|
+
|
|
410
|
+
elif key == "link": # 12
|
|
411
|
+
self._json_decode_keyword('link', value, node.add_link)
|
|
412
|
+
|
|
413
|
+
elif key == "start": # 13
|
|
414
|
+
self._json_decode_keyword('start', value, node.add_start_value)
|
|
415
|
+
|
|
416
|
+
elif key == "drop": # 14
|
|
417
|
+
self._json_decode_keyword('drop', value, node.add_drop_port)
|
|
418
|
+
|
|
419
|
+
else:
|
|
420
|
+
logger.error(f"JSON: unexpected keyword {key}. Skipped.")
|
|
421
|
+
|
|
422
|
+
return node
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def _json_decode_keyword(keyword: str, value, function):
|
|
426
|
+
if not isinstance(value, list):
|
|
427
|
+
raise AssemblyError(f"JSON: '{keyword}' keyword should define a list.")
|
|
428
|
+
for line in value:
|
|
429
|
+
if not isinstance(line, list):
|
|
430
|
+
raise AssemblyError(f"JSON: unexpected '{keyword}' value: {line}.")
|
|
431
|
+
try:
|
|
432
|
+
function(*line)
|
|
433
|
+
except TypeError:
|
|
434
|
+
raise AssemblyError(f"JSON: '{keyword}' value does not contain right number of fields: {line}.")
|
|
284
435
|
|
|
285
436
|
def write_json(self, filename: Union[str, Path]):
|
|
286
437
|
with open(self.fmu_directory / filename, "wt") as file:
|
|
287
|
-
data = self.
|
|
438
|
+
data = self._json_encode_node(self.root)
|
|
288
439
|
json.dump(data, file, indent=2)
|
|
289
440
|
|
|
290
|
-
def
|
|
441
|
+
def _json_encode_node(self, node: AssemblyNode) -> Dict[str, Any]:
|
|
291
442
|
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
|
|
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
|
|
450
|
+
|
|
296
451
|
if node.step_size:
|
|
297
|
-
json_node["step_size"] = node.step_size
|
|
452
|
+
json_node["step_size"] = node.step_size # 7
|
|
298
453
|
|
|
299
454
|
if node.children:
|
|
300
|
-
json_node["container"] = [self.
|
|
455
|
+
json_node["container"] = [self._json_encode_node(child) for child in node.children.values()] # 8
|
|
301
456
|
|
|
302
457
|
if node.fmu_names_list:
|
|
303
|
-
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)]
|
|
458
|
+
json_node["fmu"] = [f"{fmu_name}" for fmu_name in sorted(node.fmu_names_list)] # 9
|
|
304
459
|
|
|
305
460
|
if node.input_ports:
|
|
306
|
-
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"]
|
|
461
|
+
json_node["input"] = [[f"{source}", f"{port.fmu_name}", f"{port.port_name}"] # 10
|
|
307
462
|
for port, source in node.input_ports.items()]
|
|
308
463
|
|
|
309
464
|
if node.output_ports:
|
|
310
|
-
json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"]
|
|
465
|
+
json_node["output"] = [[f"{port.fmu_name}", f"{port.port_name}", f"{target}"] # 11
|
|
311
466
|
for port, target in node.output_ports.items()]
|
|
312
467
|
|
|
313
468
|
if node.links:
|
|
314
|
-
json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}",
|
|
469
|
+
json_node["link"] = [[f"{link.from_port.fmu_name}", f"{link.from_port.port_name}", # 12
|
|
315
470
|
f"{link.to_port.fmu_name}", f"{link.to_port.port_name}"]
|
|
316
471
|
for link in node.links]
|
|
317
472
|
|
|
318
473
|
if node.start_values:
|
|
319
|
-
json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value]
|
|
474
|
+
json_node["start"] = [[f"{port.fmu_name}", f"{port.port_name}", value] # 13
|
|
320
475
|
for port, value in node.start_values.items()]
|
|
321
476
|
|
|
322
477
|
if node.drop_ports:
|
|
323
|
-
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
|
|
324
479
|
|
|
325
480
|
return json_node
|
|
326
481
|
|
|
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
482
|
def read_ssp(self):
|
|
376
483
|
logger.warning("This feature is ALPHA stage.")
|
|
377
484
|
|
|
@@ -388,11 +495,11 @@ class Assembly:
|
|
|
388
495
|
mt=self.default_mt, profiling=self.default_profiling,
|
|
389
496
|
auto_input=self.default_auto_input, auto_output=self.default_auto_output)
|
|
390
497
|
self.root = sdd.parse(self.description_pathname)
|
|
498
|
+
self.root.name = str(self.filename.with_suffix(".fmu"))
|
|
391
499
|
|
|
392
500
|
def make_fmu(self, dump_json=False):
|
|
393
501
|
if dump_json:
|
|
394
|
-
dump_file = (self.
|
|
395
|
-
.with_suffix(".json"))
|
|
502
|
+
dump_file = Path(self.input_pathname.stem + "-dump").with_suffix(".json")
|
|
396
503
|
logger.info(f"Dump Json '{dump_file}'")
|
|
397
504
|
self.write_json(dump_file)
|
|
398
505
|
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.dev2}/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,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,12 +209,12 @@ 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
|
|
214
|
-
except FMUContainerError as e:
|
|
217
|
+
except (FMUContainerError, AssemblyError) as e:
|
|
215
218
|
logger.fatal(f"{filename}: {e}")
|
|
216
219
|
continue
|
|
217
220
|
|