forked from SAP-archive/fedem-solvers
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathexporter.py
More file actions
501 lines (432 loc) · 16.8 KB
/
Copy pathexporter.py
File metadata and controls
501 lines (432 loc) · 16.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# SPDX-FileCopyrightText: 2023 SAP SE
#
# SPDX-License-Identifier: Apache-2.0
#
# This file is part of FEDEM - https://openfedem.org
"""
Python wrapper for the native VTFX exporter.
"""
from ctypes import byref, c_bool, c_char_p, c_double, c_float, c_int, cdll
from os import path, remove
from subprocess import run
class ExporterException(Exception):
"""
General exception-type for exporter exceptions.
Parameters
----------
rc : int, default=0
Return code
"""
def __init__(self, rc=0):
"""
Constructor.
"""
super().__init__({"Failure": f"Return code {rc}"})
class Exporter:
"""
This class provides functionality for exporting fedem animations
to Ceetron CUG format.
Parameters
----------
fe_parts : dict
Dictionary of finite element parts to include in visualization
vis_parts : dict
Dictionary of visualization/vrml parts to include in visualization
lib_path : str
Absolute path to the shared object library vtfxExporter.so
vtfx_path : str, default=None
Absolute path to vtfx-file, use a temporary file if None
case_name : str, default="Case"
Case identifier
Methods
-------
do_step:
Executes a step of visualization export with the provided input data
clean:
Converts temporary generated vtfx-file to CUG database and cleans up
"""
def __init__(self, fe_parts, vis_parts, lib_path, vtfx_path=None, case_name="Case"):
"""
Constructor.
Initializes the internal datastructure of the shared object library,
and loads the finite element and visualization parts into memory.
"""
self._initialize(lib_path, vtfx_path, None, case_name)
self._fem_parts = []
self._vis_parts = []
# Add FE parts
for fe_part in fe_parts:
if path.isfile(fe_part["path"]):
self._add_fe_part(
str.encode(fe_part["path"]),
str.encode(fe_part["name"]),
fe_part["base_id"],
fe_part.get("surface_only", True),
fe_part.get("include_spiders", False),
)
if fe_part["recovery"]:
self._fem_parts.append(fe_part["base_id"])
else:
self._vis_parts.append(fe_part["base_id"])
# Add visualization parts
for vis_part in vis_parts:
if path.isfile(vis_part["path"]):
self._add_visualization_part(
str.encode(vis_part["path"]),
str.encode(vis_part["name"]),
vis_part["base_id"],
)
self._vis_parts.append(vis_part["base_id"])
def _get_part_object(self, part_id):
elements = self._get_elements(part_id)
types = self._get_element_types(part_id)
nodes = self._get_nodes(part_id)
t_matrix = self._get_transformation_matrix(part_id)
return {
"nodes": nodes,
"edges": elements,
"elementTypes": types,
"transformation": t_matrix,
}
def _export_geometry(self):
parts = []
for part_id in self._fem_parts:
parts.append(self._get_part_object(part_id))
for part_id in self._vis_parts:
parts.append(self._get_part_object(part_id))
return {"parts": parts}
def do_step(self, time, transformation_in, deformation_in, stress_in):
"""
Execute a step of visualization export with the provided input data.
Parameters
----------
time : float
Simulation time
transformation_in : list of c_double
Array of transformation data from the fedem solver
deformation_in : dict
Arrays of part deformation data from the fedem solver
stress_in : dict
Arrays of part stress data from the fedem solver
"""
self._set_transformation_data(transformation_in)
for base_id in self._fem_parts:
self._set_part_deformation_data(base_id, deformation_in[base_id])
self._set_part_stress_data(base_id, stress_in[base_id])
rc = self.lib_exporter.doStep(c_double(time))
if rc != 0:
raise ExporterException(rc)
def clean(self, time_step, lib_dir=None, out_dir=None, fmax=-1.0):
"""
Clears all data about included FE-parts and frs-files,
and removes all transformation, stress and deformation results.
Closes the vtfx-file and converts it to a CUG database.
Parameters
----------
time_step : float
Time step to use for animation speed
lib_dir : str, default None
Absolute path for folder where to write CUG log-file
If None, current working directory will be used
out_dir : str, default=None
Absolute path for output folder of the CUG database
If None, CUG data is not generated and the vtfx-file is retained
fmax : float, default=-1
Set fringe range to [0,fmax] if fmax is larger than 0.
If less than zero, the range will be set automatically.
"""
print(" * Writing VTFX-file", self.vtfx_path, flush=True)
rc = self.lib_exporter.finish(c_double(time_step), c_double(0), c_double(fmax))
if rc == 0 and out_dir and path.isfile(self.cug_path):
logfile = lib_dir + "/Cug.log" if path.isdir(lib_dir) else "./Cug.log"
with open(logfile, "w") as outfile:
print(" * Creating CUG database in", out_dir, flush=True)
rc = run(
[self.cug_path, self.vtfx_path, out_dir], check=True, stdout=outfile
).returncode
if rc != 0:
print(f" *** Failed ({rc})")
elif out_dir:
remove(self.vtfx_path)
def _initialize(self, lib_path, vtfx_path, cug_path, case_name):
"""
Initialization
"""
self.lib_exporter = cdll.LoadLibrary(lib_path)
self.lib_exporter.initialize(c_bool(False))
self.vtfx_path = "/tmp/temp.vtfx" if vtfx_path is None else vtfx_path
self.cug_path = "/usr/bin/CugComposer" if cug_path is None else cug_path
rc = self.lib_exporter.setVtfxPath(
c_char_p(self.vtfx_path.encode("utf-8")),
c_char_p(case_name.encode("utf-8")),
)
if rc != 0:
raise ExporterException(rc)
def _add_fe_part(
self, ftl_path, part_name, base_id, surface_only=True, include_spiders=False
):
"""
Adds the FE-Part defined in the FEDEM ftl-file at ftl_path,
with the specified part_name, and set up remapping of elements
to the formats required by the Ceetron vtfx format.
Parameters
----------
ftl_path : str
Absolute path to the ftl-file containing the node
and element definitions for the FE-part.
part_name : str
Name given to the FE-part in the exported vtfx-file.
base_id : int
The base_id of the part in the FEDEM model.
surface_only : bool, default=True
Set to True if all geometry should be converted to
surface quad and triangle elements.
include_spiders : bool, default=False
Set to True to also include spider elements and beams
"""
if not path.isfile(ftl_path):
raise ExporterException(1)
rc = self.lib_exporter.addFEPart(
c_char_p(ftl_path),
c_char_p(part_name),
c_int(base_id),
c_bool(surface_only),
c_bool(include_spiders),
c_float(1.0),
c_float(1.0),
None,
)
if rc != 0:
raise ExporterException(rc)
def _add_visualization_part(self, file_path, part_name, base_id):
"""
Adds the visualization part defined in the specified vrml-file,
with the specified part_name, and set up remapping of elements
to the formats required by the Ceetron vtfx format.
Parameters
----------
file_path : str
Absolute path to the visualization file containing the node
and element definitions.
part_name : str
Name given to the FE-part in the exported vtfx-file.
base_id : int
The base_id of the part in the FEDEM model.
"""
if not path.isfile(file_path):
raise ExporterException(1)
rc = self.lib_exporter.addVisualizationPart(
c_char_p(file_path),
c_char_p(part_name),
c_int(base_id),
c_float(1.0),
c_float(1.0),
None,
)
if rc != 0:
raise ExporterException(rc)
def _get_number_of_elements(self, base_id):
"""
Returns the total number of finite elements for the FE-part in the ftl-file.
Parameters
----------
baseId : int
The FEDEM BaseID of the FE-part.
Returns
-------
int
The number of elements.
"""
return self.lib_exporter.getNumberOfElements(c_int(base_id))
def _get_number_of_element_nodes(self, base_id):
"""
Returns the total number of element-nodes for the FE-part
with the specified baseId.
Parameters
----------
baseId : int
The FEDEM BaseID of the FE-part.
Returns
-------
int
The number of element nodes.
"""
return self.lib_exporter.getNumberOfElementNodes(c_int(base_id))
def _get_number_of_nodes(self, base_id):
"""
Returns the total number of unique nodes for the FE-part
with the specified baseId.
Parameters
----------
baseId : int
The FEDEM BaseID of the FE-part.
Returns
-------
int
The number of nodes.
"""
return self.lib_exporter.getNumberOfNodes(c_int(base_id))
def _get_element_types(self, base_id, element_types=None):
"""
Fills the array given as input with the element types for all elements
of the FE-part with the specified baseId.
Each type is an integer-value corresponding to the number of nodes
in the element(3 for triangles, 4 for quads)
Parameters
----------
baseId : int
The FEDEM BaseId of the FE-Part.
elementTypes : list of c_int, default=None
The array of integers that will be filled with the element-types.
If none, a list will be allocated inside the function.
Size of list must be equal to value from _get_number_of_elements().
Returns
-------
list of ints
List of element types
"""
if not element_types:
element_types = (c_int * self._get_number_of_elements(base_id))()
rc = self.lib_exporter.getElementTypes(c_int(base_id), byref(element_types))
if rc != 0:
raise ExporterException(rc)
return element_types[0:]
def _get_elements(self, base_id, elements=None):
"""
Fills the array given as input with the node-indices of all
elements of the FE-part with the specified baseId.
Parameters
----------
baseId : int
The FEDEM BaseID of the FE-part.
elements : list of c_int, default=None
List of integers that will be filled with the element-node indices.
If None, a list will be allocated inside the function.
The size must be equal to the sum of all nodes for the different elements.
Returns
-------
List of ints
List of node indices for the elements
"""
if not elements:
elements = (c_int * self._get_number_of_element_nodes(base_id))()
rc = self.lib_exporter.getElements(
c_int(base_id), byref(elements), c_bool(False)
)
if rc != 0:
raise ExporterException(rc)
return elements[0:]
def _get_nodes(self, base_id, nodes=None):
"""
Fills the array given as input with the x,y and z values
for all the nodes of the FE-part with the specified baseId.
Parameters
----------
baseId : int
The FEDEM BaseID of the FE-part.
nodes : list of c_double, default=None
List of doubles that will be filled with the node coordinates.
If None, a list will be allocated inside the function.
The size must be equal to three times the value from _get_number_of_nodes().
Returns
-------
List of doubles
List of node coordinates
"""
if not nodes:
nodes = (c_double * (self._get_number_of_nodes(base_id) * 3))()
rc = self.lib_exporter.getNodes(c_int(base_id), byref(nodes))
if rc != 0:
raise ExporterException(rc)
return nodes[0:]
def _get_transformation_matrix(self, base_id, transformation=None):
"""
Fills the array given as input with the 16 components of the
transformation matrix of the FE-part with the
specified baseId, for the current time-step.
Row dominant. Final row is translation
Parameters
----------
baseId : int
The FEDEM BaseID of the part to get results from.
transformation : list of c_double, default=None
List of doubles that will be filled with the transformation matrix.
If none, a list will be allocated inside the function.
Size of array must be equal to 16.
Returns
-------
List of doubles
List containing the transformation matrix
"""
if not transformation:
transformation = (c_double * 16)()
rc = self.lib_exporter.getTransformationMatrix(
c_int(base_id), byref(transformation)
)
if rc != 0:
raise ExporterException(rc)
return transformation[0:]
def _get_deformation_vector(self, base_id, deformation=None):
"""
Fills the array given as input with the updated x,y and z values in the
current time-step, for all the nodes of
the FE-part with the specified baseId.
Parameters
----------
baseId : int
The FEDEM BaseID of the part to get results from.
deformation : List of c_double, default=None
List of doubles that will be filled with
x,y,z coordinates of all nodes of the deformed part.
If None, a list will be allocated inside the function.
The size must be equal to three times the value from _get_number_of_nodes().
Returns
-------
List of doubles
List containing the nodal displacements
"""
if not deformation:
deformation = (c_double * (self._get_number_of_nodes(base_id) * 3))()
rc = self.lib_exporter.getDeformationVector(c_int(base_id), byref(deformation))
if rc != 0:
raise ExporterException(rc)
return deformation[0:]
def _get_stress_vector(self, base_id, stress=None):
"""
Fills the array given as input with the von Mises stress values in the
current time-step, for all the nodes of the FE-part in the ftl-file.
Parameters
----------
baseId : int
The FEDEM BaseID of the part to get results from.
stress : List of c_double, default=None
List of doubles that will be filled with
von Mises stress value of all nodes of the deformed part.
If none, a list will be allocated inside the function.
The size must be equal to the value from _get_number_of_element_nodes().
Returns
-------
List of doubles
List containing the stress values for each node
"""
if not stress:
stress = (c_double * self._get_number_of_nodes(base_id))()
rc = self.lib_exporter.getStressVector(c_int(base_id), byref(stress))
if rc != 0:
raise ExporterException(rc)
return stress[0:]
def _set_transformation_data(self, data):
sz = len(data)
rc = self.lib_exporter.setTransformationData(c_int(sz), data)
if rc != 0:
raise ExporterException(rc)
def _set_part_deformation_data(self, base_id, data):
sz = len(data)
rc = self.lib_exporter.setPartDeformationData(c_int(base_id), c_int(sz), data)
if rc != 0:
raise ExporterException(rc)
def _set_part_stress_data(self, base_id, data):
sz = len(data)
rc = self.lib_exporter.setPartStressData(c_int(base_id), c_int(sz), data)
if rc != 0:
raise ExporterException(rc)