capytaine 2.1__cp312-cp312-win_amd64.whl

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.
Files changed (85) hide show
  1. capytaine/__about__.py +16 -0
  2. capytaine/__init__.py +48 -0
  3. capytaine/bem/__init__.py +0 -0
  4. capytaine/bem/airy_waves.py +106 -0
  5. capytaine/bem/engines.py +441 -0
  6. capytaine/bem/problems_and_results.py +540 -0
  7. capytaine/bem/solver.py +463 -0
  8. capytaine/bodies/__init__.py +4 -0
  9. capytaine/bodies/bodies.py +1084 -0
  10. capytaine/bodies/dofs.py +19 -0
  11. capytaine/bodies/predefined/__init__.py +6 -0
  12. capytaine/bodies/predefined/cylinders.py +151 -0
  13. capytaine/bodies/predefined/rectangles.py +109 -0
  14. capytaine/bodies/predefined/spheres.py +70 -0
  15. capytaine/green_functions/__init__.py +2 -0
  16. capytaine/green_functions/abstract_green_function.py +12 -0
  17. capytaine/green_functions/delhommeau.py +380 -0
  18. capytaine/green_functions/libs/Delhommeau_float32.cp312-win_amd64.dll.a +0 -0
  19. capytaine/green_functions/libs/Delhommeau_float32.cp312-win_amd64.pyd +0 -0
  20. capytaine/green_functions/libs/Delhommeau_float64.cp312-win_amd64.dll.a +0 -0
  21. capytaine/green_functions/libs/Delhommeau_float64.cp312-win_amd64.pyd +0 -0
  22. capytaine/green_functions/libs/XieDelhommeau_float32.cp312-win_amd64.dll.a +0 -0
  23. capytaine/green_functions/libs/XieDelhommeau_float32.cp312-win_amd64.pyd +0 -0
  24. capytaine/green_functions/libs/XieDelhommeau_float64.cp312-win_amd64.dll.a +0 -0
  25. capytaine/green_functions/libs/XieDelhommeau_float64.cp312-win_amd64.pyd +0 -0
  26. capytaine/green_functions/libs/__init__.py +0 -0
  27. capytaine/io/__init__.py +0 -0
  28. capytaine/io/bemio.py +141 -0
  29. capytaine/io/legacy.py +328 -0
  30. capytaine/io/mesh_loaders.py +1061 -0
  31. capytaine/io/mesh_writers.py +692 -0
  32. capytaine/io/meshio.py +35 -0
  33. capytaine/io/xarray.py +514 -0
  34. capytaine/matrices/__init__.py +16 -0
  35. capytaine/matrices/block.py +590 -0
  36. capytaine/matrices/block_toeplitz.py +325 -0
  37. capytaine/matrices/builders.py +89 -0
  38. capytaine/matrices/linear_solvers.py +233 -0
  39. capytaine/matrices/low_rank.py +393 -0
  40. capytaine/meshes/__init__.py +6 -0
  41. capytaine/meshes/clipper.py +464 -0
  42. capytaine/meshes/collections.py +313 -0
  43. capytaine/meshes/geometry.py +409 -0
  44. capytaine/meshes/meshes.py +746 -0
  45. capytaine/meshes/predefined/__init__.py +6 -0
  46. capytaine/meshes/predefined/cylinders.py +314 -0
  47. capytaine/meshes/predefined/rectangles.py +261 -0
  48. capytaine/meshes/predefined/spheres.py +62 -0
  49. capytaine/meshes/properties.py +199 -0
  50. capytaine/meshes/quadratures.py +80 -0
  51. capytaine/meshes/quality.py +448 -0
  52. capytaine/meshes/surface_integrals.py +63 -0
  53. capytaine/meshes/symmetric.py +383 -0
  54. capytaine/post_pro/__init__.py +6 -0
  55. capytaine/post_pro/free_surfaces.py +88 -0
  56. capytaine/post_pro/impedance.py +92 -0
  57. capytaine/post_pro/kochin.py +54 -0
  58. capytaine/post_pro/rao.py +60 -0
  59. capytaine/tools/__init__.py +0 -0
  60. capytaine/tools/cache_on_disk.py +24 -0
  61. capytaine/tools/deprecation_handling.py +18 -0
  62. capytaine/tools/lists_of_points.py +52 -0
  63. capytaine/tools/lru_cache.py +49 -0
  64. capytaine/tools/optional_imports.py +27 -0
  65. capytaine/tools/prony_decomposition.py +94 -0
  66. capytaine/tools/symbolic_multiplication.py +104 -0
  67. capytaine/ui/__init__.py +0 -0
  68. capytaine/ui/cli.py +28 -0
  69. capytaine/ui/rich.py +5 -0
  70. capytaine/ui/vtk/__init__.py +3 -0
  71. capytaine/ui/vtk/animation.py +329 -0
  72. capytaine/ui/vtk/body_viewer.py +26 -0
  73. capytaine/ui/vtk/helpers.py +82 -0
  74. capytaine/ui/vtk/mesh_viewer.py +461 -0
  75. capytaine-2.1.dist-info/DELVEWHEEL +2 -0
  76. capytaine-2.1.dist-info/LICENSE +674 -0
  77. capytaine-2.1.dist-info/METADATA +756 -0
  78. capytaine-2.1.dist-info/RECORD +85 -0
  79. capytaine-2.1.dist-info/WHEEL +4 -0
  80. capytaine-2.1.dist-info/entry_points.txt +3 -0
  81. capytaine.libs/libgcc_s_seh-1.dll +0 -0
  82. capytaine.libs/libgfortran-5.dll +0 -0
  83. capytaine.libs/libgomp-1.dll +0 -0
  84. capytaine.libs/libquadmath-0.dll +0 -0
  85. capytaine.libs/libwinpthread-1.dll +0 -0
@@ -0,0 +1,1061 @@
1
+ """Functions to load meshes from different file formats.
2
+ Based on meshmagick <https://github.com/LHEEA/meshmagick> by François Rongère.
3
+ """
4
+ # Copyright (C) 2017-2019 Matthieu Ancellin, based on the work of François Rongère
5
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
6
+
7
+ import os
8
+ import logging
9
+ import numpy as np
10
+
11
+ from capytaine.meshes.meshes import Mesh
12
+ from capytaine.meshes.symmetric import ReflectionSymmetricMesh
13
+ from capytaine.meshes.geometry import xOz_Plane, yOz_Plane
14
+ from capytaine.tools.optional_imports import import_optional_dependency
15
+
16
+ LOG = logging.getLogger(__name__)
17
+
18
+ real_str = r'[+-]?(?:\d+\.\d*|\d*\.\d+)(?:[Ee][+-]?\d+)?' # Regex for floats
19
+
20
+
21
+ def _check_file(filename, name=None):
22
+ if not os.path.isfile(filename):
23
+ raise IOError("file %s not found" % filename)
24
+ return
25
+
26
+
27
+ def load_mesh(filename, file_format=None, name=None):
28
+ """Driver function that loads every mesh file format known by meshmagick.
29
+ Dispatch to one of the other function depending on file_format.
30
+
31
+ Parameters
32
+ ----------
33
+ filename: str
34
+ name of the mesh file on disk
35
+ file_format: str, optional
36
+ format of the mesh defined in the extension_dict dictionary
37
+ name: str, optional
38
+ name for the created mesh object
39
+
40
+ Returns
41
+ -------
42
+ Mesh or SymmetricMesh
43
+ the loaded mesh
44
+ """
45
+ _check_file(filename)
46
+
47
+ if file_format is None:
48
+ _, file_format = os.path.splitext(filename)
49
+ file_format = file_format.strip('.').lower()
50
+
51
+ if file_format not in extension_dict:
52
+ raise IOError('Extension ".%s" is not known' % file_format)
53
+
54
+ loader = extension_dict[file_format]
55
+
56
+ if name is None: name = filename
57
+
58
+ return loader(filename, name)
59
+
60
+
61
+ def load_RAD(filename, name=None):
62
+ """Loads RADIOSS mesh files. This export file format may be chosen in ICEM meshing program.
63
+
64
+ Parameters
65
+ ----------
66
+ filename: str
67
+ name of the mesh file on disk
68
+
69
+ Returns
70
+ -------
71
+ Mesh
72
+ the loaded mesh
73
+
74
+ Note
75
+ ----
76
+ RAD files have a 1-indexing
77
+ """
78
+
79
+ import re
80
+ _check_file(filename)
81
+ ifile = open(filename, 'r')
82
+ data = ifile.read()
83
+ ifile.close()
84
+
85
+ # node_line = r'\s*\d+(?:\s*' + real_str + '){3}'
86
+ node_line = r'\s*\d+\s*(' + real_str + r')\s*(' + real_str + r')\s*(' + real_str + ')'
87
+ node_section = r'((?:' + node_line + ')+)'
88
+
89
+ elem_line = r'^\s*(?:\d+\s+){6}\d+\s*[\r\n]+'
90
+ elem_section = r'((?:' + elem_line + '){3,})'
91
+
92
+ pattern_node_line = re.compile(node_line, re.MULTILINE)
93
+ # pattern_node_line_group = re.compile(node_line, re.MULTILINE)
94
+ pattern_elem_line = re.compile(elem_line, re.MULTILINE)
95
+ pattern_node_section = re.compile(node_section, re.MULTILINE)
96
+ pattern_elem_section = re.compile(elem_section, re.MULTILINE)
97
+
98
+ vertices = []
99
+ node_section = pattern_node_section.search(data).group(1)
100
+ for node in pattern_node_line.finditer(node_section):
101
+ vertices.append(list(map(float, list(node.groups()))))
102
+ vertices = np.asarray(vertices, dtype=float)
103
+
104
+ faces = []
105
+ elem_section = pattern_elem_section.search(data).group(1)
106
+ for elem in pattern_elem_line.findall(elem_section):
107
+ faces.append(list(map(int, elem.strip().split()[3:])))
108
+ faces = np.asarray(faces, dtype=int) - 1
109
+
110
+ return Mesh(vertices, faces, name)
111
+
112
+
113
+ def load_HST(filename, name=None):
114
+ """Loads HYDROSTAR (Bureau Veritas (c)) mesh files.
115
+
116
+ Parameters
117
+ ----------
118
+ filename: str
119
+ name of the mesh file on disk
120
+
121
+ Returns
122
+ -------
123
+ Mesh
124
+ the loaded mesh
125
+
126
+ Note
127
+ ----
128
+ HST files have a 1-indexing
129
+ """
130
+
131
+ _check_file(filename)
132
+
133
+ with open(filename, 'r') as f:
134
+ lines = f.readlines()
135
+
136
+ optional_keywords = ['PROJECT', 'SYMMETRY']
137
+ not_implemented_optional_keywords = ['USER', 'REFLENGTH', 'GRAVITY', 'RHO', 'NBBODY']
138
+
139
+ vertices = []
140
+ faces = []
141
+ optional_data = {kw: None for kw in optional_keywords}
142
+ current_context = None
143
+ ignored_lines = []
144
+
145
+ for i_line, line in enumerate(lines):
146
+ line = line.lstrip()
147
+
148
+ if line == '':
149
+ continue
150
+
151
+ elif line.startswith("COORDINATES"):
152
+ current_context = 'vertices'
153
+
154
+ elif current_context == 'vertices' and line.startswith("ENDCOORDINATES"):
155
+ current_context = None
156
+
157
+ elif line.startswith("PANEL"):
158
+ panels_type = int(line[10:])
159
+ current_context = ('panels', panels_type)
160
+
161
+ elif (current_context == ('panels', 0) or current_context == ('panels', 1)) and line.startswith("ENDPANEL"):
162
+ current_context = None
163
+
164
+ elif current_context == 'vertices': # parse vertex coordinates
165
+ numbers = line.split()
166
+ if len(numbers) == 4:
167
+ i_vertex, x, y, z = numbers
168
+ if int(i_vertex) != len(vertices) + 1:
169
+ raise ValueError(
170
+ f"HST mesh reader expected the next vertex to be indexed as {len(vertices)+1}, "
171
+ f"but it was actually indexed as {i_vertex} (line {i_line+1} of {filename}).")
172
+ elif len(numbers) == 3:
173
+ x, y, z = numbers
174
+ vertices.append([x, y, z])
175
+
176
+ elif current_context == ('panels', 0): # parse face definition (no index given)
177
+ numbers = line.split()
178
+ if len(numbers) == 3:
179
+ v1, v2, v3 = numbers
180
+ v4 = v3
181
+ elif len(numbers) == 4:
182
+ v1, v2, v3, v4 = numbers
183
+ faces.append([v1, v2, v3, v4])
184
+
185
+ elif current_context == ('panels', 1): # parse face definition
186
+ numbers = line.split()
187
+ if len(numbers) == 4:
188
+ i_face, v1, v2, v3 = numbers
189
+ v4 = v3
190
+ elif len(numbers) == 5:
191
+ i_face, v1, v2, v3, v4 = numbers
192
+
193
+ if int(i_face) != len(faces) + 1:
194
+ ii = len(faces) + 1
195
+ raise ValueError(f"HST mesh reader expected the next face to be indexed {ii},\n"
196
+ f"but it was actually indexed with {i_face} (line {i_line+1} of file {filename}).")
197
+ faces.append([v1, v2, v3, v4])
198
+
199
+ elif line.startswith("ENDFILE"):
200
+ break
201
+
202
+ else:
203
+ for keyword in optional_data:
204
+ if line.startswith(keyword):
205
+ optional_data[keyword] = line[len(keyword)+1:].lstrip(':').strip()
206
+ break
207
+ else:
208
+ ignored_lines.append((i_line+1, line))
209
+
210
+ if len(ignored_lines) > 0:
211
+ formatted_ignored_lines = ["{: 4} | {}".format(i, line.strip('\n')) for (i, line) in ignored_lines]
212
+ LOG.warning(f"HST mesh reader ignored the following lines from file {filename}:\n" + "\n".join(formatted_ignored_lines))
213
+
214
+ vertices = np.array(vertices, dtype=float)
215
+ faces = np.array(faces, dtype=int) - 1
216
+
217
+ if name is None: name = optional_data['PROJECT']
218
+
219
+ if optional_data['SYMMETRY'] == '1':
220
+ return ReflectionSymmetricMesh(Mesh(vertices, faces, f"half_of_{name}"), xOz_Plane, name)
221
+ elif optional_data['SYMMETRY'] == '2':
222
+ return ReflectionSymmetricMesh(ReflectionSymmetricMesh(Mesh(vertices, faces, f"quarter_of_{name}"), yOz_Plane, f"half_of_{name}"), xOz_Plane, name)
223
+ else:
224
+ return Mesh(vertices, faces, name)
225
+
226
+
227
+ def load_DAT(filename, name=None):
228
+ """Not implemented.
229
+ Intended to load .DAT files used in DIODORE (PRINCIPIA (c))
230
+ """
231
+ _check_file(filename)
232
+ raise NotImplementedError
233
+
234
+
235
+ def load_INP(filename, name=None):
236
+ """Loads DIODORE (PRINCIPIA (c)) configuration file format.
237
+
238
+ It parses the .INP file and extracts meshes defined in subsequent .DAT files using the different information
239
+ contained in the .INP file.
240
+
241
+ Parameters
242
+ ----------
243
+ filename: str
244
+ name of the mesh file on disk
245
+
246
+ Returns
247
+ -------
248
+ Mesh
249
+ the loaded mesh
250
+
251
+ Note
252
+ ----
253
+ INP/DAT files use a 1-indexing
254
+ """
255
+ _check_file(filename)
256
+ import re
257
+
258
+ with open(filename, 'r') as f:
259
+ text = f.read()
260
+
261
+ # Retrieving frames into a dictionary frames
262
+ pattern_frame_str = r'^\s*\*FRAME,NAME=(.+)[\r\n]+(.*)'
263
+ pattern_frame = re.compile(pattern_frame_str, re.MULTILINE)
264
+
265
+ frames = {}
266
+ for match in pattern_frame.finditer(text):
267
+ frame_name = match.group(1).strip()
268
+ frame_vector = re.split(r'[, ]', match.group(2).strip())
269
+ frames[frame_name] = np.asarray(list(map(float, frame_vector)))
270
+
271
+ # Storing the inp layout into a list of dictionary
272
+ pattern_node_elements = re.compile(r'^\s*\*(NODE|ELEMENT),(.*)', re.MULTILINE)
273
+ layout = []
274
+ mesh_files = {}
275
+ for match in pattern_node_elements.finditer(text):
276
+ field_dict = dict()
277
+ field_dict['type'] = match.group(1)
278
+ if field_dict['type'] == 'NODE':
279
+ field_dict['INCREMENT'] = 'NO'
280
+ opts = match.group(2).split(',')
281
+ for opt in opts:
282
+ key, pair = opt.split('=')
283
+ field_dict[key] = pair.strip()
284
+
285
+ # Retrieving information on mesh files and their usage
286
+ file = field_dict['INPUT']
287
+ if file in mesh_files:
288
+ mesh_files[file][field_dict['type'] + '_CALL_INP'] += 1
289
+ else:
290
+ mesh_files[file] = {}
291
+ mesh_files[file]['NODE_CALL_INP'] = 0
292
+ mesh_files[file]['ELEMENT_CALL_INP'] = 0
293
+ mesh_files[file][field_dict['type'] + '_CALL_INP'] += 1
294
+
295
+ layout.append(field_dict)
296
+
297
+ # RETRIEVING DATA SECTIONS FROM MESHFILES
298
+ # patterns for recognition of sections
299
+ node_line = r'\s*\d+(?:\s+' + real_str + '){3}'
300
+ node_section = r'((?:' + node_line + ')+)'
301
+ elem_line = r'^ +\d+(?: +\d+){3,4}[\r\n]+' # 3 -> triangle, 4 -> quadrangle
302
+ elem_section = r'((?:' + elem_line + ')+)'
303
+ pattern_node_line = re.compile(node_line, re.MULTILINE)
304
+ pattern_elem_line = re.compile(elem_line, re.MULTILINE)
305
+ pattern_node_section = re.compile(node_section, re.MULTILINE)
306
+ pattern_elem_section = re.compile(elem_section, re.MULTILINE)
307
+
308
+ for file in mesh_files:
309
+ try:
310
+ meshfile = open(os.path.join(os.path.dirname(filename), file + '.DAT'), 'r')
311
+ except:
312
+ raise IOError('File {0:s} not found'.format(file + '.DAT'))
313
+ data = meshfile.read()
314
+ meshfile.close()
315
+
316
+ node_section = pattern_node_section.findall(data)
317
+ if len(node_section) > 1:
318
+ raise IOError("""Several NODE sections into a .DAT file is not supported by meshmagick
319
+ as it is considered as bad practice""")
320
+ node_array = []
321
+ idx_array = []
322
+ for node in pattern_node_line.findall(node_section[0]):
323
+ node = node.split()
324
+
325
+ node[0] = int(node[0])
326
+ idx_array.append(node[0])
327
+ node[1:] = list(map(float, node[1:]))
328
+ node_array.append(node[1:])
329
+
330
+ mesh_files[file]['NODE_SECTION'] = node_array
331
+
332
+ # Detecting renumberings to do
333
+ real_idx = 0
334
+ # renumberings = []
335
+ id_new = - np.ones(max(idx_array) + 1, dtype=int)
336
+ # FIXME: cette partie est tres buggee !!!
337
+ for i, idx in enumerate(idx_array):
338
+ id_new[idx] = i+1
339
+
340
+ mesh_files[file]['ELEM_SECTIONS'] = []
341
+ for elem_section in pattern_elem_section.findall(data):
342
+
343
+ elem_array = []
344
+ for elem in pattern_elem_line.findall(elem_section):
345
+ elem = list(map(int, elem.split()))
346
+ # for node in elem[1:]:
347
+ elem = id_new[elem[1:]].tolist()
348
+ if len(elem) == 3: # Case of a triangle, we repeat the first node at the last position
349
+ elem.append(elem[0])
350
+
351
+ elem_array.append(list(map(int, elem)))
352
+ mesh_files[file]['ELEM_SECTIONS'].append(elem_array)
353
+ mesh_files[file]['nb_elem_sections'] = len(mesh_files[file]['ELEM_SECTIONS'])
354
+
355
+ mesh_files[file]['nb_elem_sections_used'] = 0
356
+
357
+ nb_nodes = 0
358
+ nb_elems = 0
359
+ for field in layout:
360
+ file = field['INPUT']
361
+ if field['type'] == 'NODE':
362
+ nodes = np.asarray(mesh_files[file]['NODE_SECTION'], dtype=float)
363
+ # Translation of nodes according to frame option id any
364
+ nodes += frames[field['FRAME']] # TODO: s'assurer que frame est une options obligatoire...
365
+
366
+ if nb_nodes == 0:
367
+ vertices = nodes.copy()
368
+ nb_nodes = vertices.shape[0]
369
+ increment = False
370
+ continue
371
+
372
+ if field['INCREMENT'] == 'NO':
373
+ vertices[idx, :] = nodes.copy()
374
+ increment = False
375
+ else:
376
+ vertices = np.concatenate((vertices, nodes))
377
+ nb_nodes = vertices.shape[0]
378
+ increment = True
379
+ else: # this is an ELEMENT section
380
+ elem_section = np.asarray(mesh_files[file]['ELEM_SECTIONS'][mesh_files[file]['nb_elem_sections_used']],
381
+ dtype=int)
382
+
383
+ mesh_files[file]['nb_elem_sections_used'] += 1
384
+ if mesh_files[file]['nb_elem_sections_used'] == mesh_files[file]['nb_elem_sections']:
385
+ mesh_files[file]['nb_elem_sections_used'] = 0
386
+
387
+ # Updating to new id of nodes
388
+ elems = elem_section
389
+ if increment:
390
+ elems += nb_nodes
391
+
392
+ if nb_elems == 0:
393
+ faces = elems.copy()
394
+ nb_elems = faces.shape[0]
395
+ continue
396
+ else:
397
+ faces = np.concatenate((faces, elems))
398
+ nb_elems = faces.shape[0]
399
+
400
+ return Mesh(vertices, faces-1, name)
401
+
402
+
403
+ def load_TEC(filename, name=None):
404
+ """Loads TECPLOT (Tecplot (c)) mesh files.
405
+
406
+ It relies on the tecplot file reader from the VTK library.
407
+
408
+ Parameters
409
+ ----------
410
+ filename: str
411
+ name of the mesh file on disk
412
+
413
+ Returns
414
+ -------
415
+ Mesh
416
+ the loaded mesh
417
+
418
+ Note
419
+ ----
420
+ TEC files have a 1-indexing
421
+ """
422
+
423
+ import re
424
+
425
+ _check_file(filename)
426
+
427
+ data_pattern = re.compile(
428
+ r'ZONE.*\s*N\s*=\s*(\d+)\s*,\s*E=\s*(\d+)\s*,\s*F\s*=\s*FEPOINT\s*,\s*ET\s*=\s*QUADRILATERAL\s+'
429
+ + r'(^(?:\s*' + real_str + r'){3,})\s+'
430
+ + r'(^(?:\s*\d+)*)', re.MULTILINE)
431
+
432
+ with open(filename, 'r') as f:
433
+ data = f.read()
434
+
435
+ nv, nf, vertices, faces = data_pattern.search(data).groups()
436
+ nv = int(nv)
437
+ nf = int(nf)
438
+
439
+ vertices = np.asarray(list(map(float, vertices.split())), dtype=float).reshape((nv, -1))[:, :3]
440
+ faces = np.asarray(list(map(int, faces.split())), dtype=int).reshape((nf, 4))-1
441
+
442
+ return Mesh(vertices, faces, name)
443
+
444
+
445
+ def load_VTU(filename, name=None):
446
+ """Loads VTK file format in the new XML format (vtu file extension for unstructured meshes).
447
+
448
+ It relies on the reader from the VTK library.
449
+
450
+ Parameters
451
+ ----------
452
+ filename: str
453
+ name of the mesh file on disk
454
+
455
+ Returns
456
+ -------
457
+ Mesh
458
+ the loaded mesh
459
+
460
+ Note
461
+ ----
462
+ VTU files have a 0-indexing
463
+ """
464
+
465
+ _check_file(filename)
466
+
467
+ vtk = import_optional_dependency("vtk")
468
+
469
+ reader = vtk.vtkXMLUnstructuredGridReader()
470
+ reader.SetFileName(str(filename))
471
+ reader.Update()
472
+ vtk_mesh = reader.GetOutput()
473
+
474
+ vertices, faces = _dump_vtk(vtk_mesh)
475
+ return Mesh(vertices, faces, name)
476
+
477
+
478
+ def load_VTP(filename, name=None):
479
+ """Loads VTK file format in the new XML format (vtp file extension for polydata meshes).
480
+
481
+ It relies on the reader from the VTK library.
482
+
483
+ Parameters
484
+ ----------
485
+ filename: str
486
+ name of the mesh file on disk
487
+
488
+ Returns
489
+ -------
490
+ Mesh
491
+ the loaded mesh
492
+
493
+ Note
494
+ ----
495
+ VTP files have a 0-indexing
496
+ """
497
+ _check_file(filename)
498
+
499
+ vtk = import_optional_dependency("vtk")
500
+
501
+ reader = vtk.vtkXMLPolyDataReader()
502
+ reader.SetFileName(str(filename))
503
+ reader.Update()
504
+ vtk_mesh = reader.GetOutput()
505
+
506
+ vertices, faces = _dump_vtk(vtk_mesh)
507
+ return Mesh(vertices, faces, name)
508
+
509
+
510
+ def load_VTK(filename, name=None):
511
+ """Loads VTK file format in the legacy format (vtk file extension).
512
+
513
+ It relies on the reader from the VTK library.
514
+
515
+ Parameters
516
+ ----------
517
+ filename: str
518
+ name of the mesh file on disk
519
+
520
+ Returns
521
+ -------
522
+ Mesh
523
+ the loaded mesh
524
+
525
+ Note
526
+ ----
527
+ VTU files have a 0-indexing
528
+ """
529
+ _check_file(filename)
530
+
531
+ vtk = import_optional_dependency("vtk")
532
+
533
+ reader = vtk.vtkPolyDataReader()
534
+ reader.SetFileName(str(filename))
535
+ reader.Update()
536
+ vtk_mesh = reader.GetOutput()
537
+
538
+ vertices, faces = _dump_vtk(vtk_mesh)
539
+ return Mesh(vertices, faces, name)
540
+
541
+
542
+ def _dump_vtk(vtk_mesh):
543
+ """Internal driver function that uses the VTK library to read VTK polydata or vtk unstructured grid data structures
544
+
545
+ Returns
546
+ -------
547
+ vertices: ndarray
548
+ numpy array of the coordinates of the mesh's nodes
549
+ faces: ndarray
550
+ numpy array of the faces' nodes connectivities
551
+ """
552
+
553
+ nv = vtk_mesh.GetNumberOfPoints()
554
+ vertices = np.zeros((nv, 3), dtype=float)
555
+ for k in range(nv):
556
+ vertices[k] = np.array(vtk_mesh.GetPoint(k))
557
+
558
+ nf = vtk_mesh.GetNumberOfCells()
559
+ faces = np.zeros((nf, 4), dtype=int)
560
+ for k in range(nf):
561
+ cell = vtk_mesh.GetCell(k)
562
+ nv_facet = cell.GetNumberOfPoints()
563
+ for l in range(nv_facet):
564
+ faces[k][l] = cell.GetPointId(l)
565
+ if nv_facet == 3:
566
+ faces[k][3] = faces[k][0]
567
+
568
+ return vertices, faces
569
+
570
+
571
+ def load_STL(filename, name=None):
572
+ """Loads STL file format.
573
+
574
+ It relies on the reader from the VTK library. As STL file format maintains a redundant set of vertices for each
575
+ faces of the mesh, it returns a merged list of nodes and connectivity array by using the merge_duplicates function.
576
+
577
+ Parameters
578
+ ----------
579
+ filename: str
580
+ name of the mesh file on disk
581
+
582
+ Returns
583
+ -------
584
+ Mesh
585
+ the loaded mesh
586
+
587
+ Note
588
+ ----
589
+ STL files have a 0-indexing
590
+ """
591
+ vtk = import_optional_dependency("vtk")
592
+
593
+ from capytaine.meshes.quality import merge_duplicate_rows
594
+
595
+ _check_file(filename)
596
+
597
+ reader = vtk.vtkSTLReader()
598
+ reader.SetFileName(str(filename))
599
+ reader.Update()
600
+
601
+ data = reader.GetOutputDataObject(0)
602
+
603
+ nv = data.GetNumberOfPoints()
604
+ vertices = np.zeros((nv, 3), dtype=float)
605
+ for k in range(nv):
606
+ vertices[k] = np.array(data.GetPoint(k))
607
+ nf = data.GetNumberOfCells()
608
+ faces = np.zeros((nf, 4), dtype=int)
609
+ for k in range(nf):
610
+ cell = data.GetCell(k)
611
+ if cell is not None:
612
+ for l in range(3):
613
+ faces[k][l] = cell.GetPointId(l)
614
+ faces[k][3] = faces[k][0] # always repeating the first node as stl is triangle only
615
+
616
+ # Merging duplicates nodes
617
+ vertices, new_id = merge_duplicate_rows(vertices)
618
+ faces = new_id[faces]
619
+
620
+ return Mesh(vertices, faces, name)
621
+
622
+
623
+ def load_NAT(filename, name=None):
624
+ """This function loads natural file format for meshes.
625
+
626
+ Parameters
627
+ ----------
628
+ filename: str
629
+ name of the mesh file on disk
630
+
631
+ Returns
632
+ -------
633
+ Mesh
634
+ the loaded mesh
635
+
636
+ Notes
637
+ -----
638
+ The file format is as follow::
639
+
640
+ xsym ysym
641
+ n m
642
+ x1 y1 z1
643
+ .
644
+ .
645
+ .
646
+ xn yn zn
647
+ i1 j1 k1 l1
648
+ .
649
+ .
650
+ .
651
+ im jm km lm
652
+
653
+ where :
654
+ n : number of nodes
655
+ m : number of cells
656
+ x1 y1 z1 : cartesian coordinates of node 1
657
+ i1 j1 k1 l1 : counterclock wise Ids of nodes for cell 1
658
+ if cell 1 is a triangle, i1==l1
659
+
660
+ Note
661
+ ----
662
+ NAT files have a 1-indexing
663
+ """
664
+
665
+ _check_file(filename)
666
+
667
+ ifile = open(filename, 'r')
668
+ ifile.readline()
669
+ nv, nf = list(map(int, ifile.readline().split()))
670
+
671
+ vertices = []
672
+ for i in range(nv):
673
+ vertices.append(list(map(float, ifile.readline().split())))
674
+ vertices = np.array(vertices, dtype=float)
675
+
676
+ faces = []
677
+ for i in range(nf):
678
+ faces.append(list(map(int, ifile.readline().split())))
679
+ faces = np.array(faces, dtype=int)
680
+
681
+ ifile.close()
682
+ return Mesh(vertices, faces-1, name)
683
+
684
+
685
+ def load_GDF(filename, name=None):
686
+ """Loads WAMIT (Wamit INC. (c)) GDF mesh files.
687
+
688
+ As GDF file format maintains a redundant set of vertices for each faces of the mesh, it returns a merged list of
689
+ nodes and connectivity array by using the merge_duplicates function.
690
+
691
+ Parameters
692
+ ----------
693
+ filename: str
694
+ name of the mesh file on disk
695
+
696
+ Returns
697
+ -------
698
+ Mesh or ReflectionSymmetricMesh
699
+ the loaded mesh
700
+
701
+ Note
702
+ ----
703
+ GDF files have a 1-indexing
704
+ """
705
+
706
+ _check_file(filename)
707
+
708
+ with open(str(filename)) as gdf_file:
709
+ title = gdf_file.readline()
710
+ ulen, grav = map(float, gdf_file.readline().split()[:2])
711
+ isx, isy = map(int, gdf_file.readline().split()[:2])
712
+ npan = int(gdf_file.readline().split()[0])
713
+ faces_vertices = np.genfromtxt(gdf_file)
714
+
715
+ vertices, indices = np.unique(faces_vertices, axis=0, return_inverse=True)
716
+ faces = indices.reshape(-1, 4)
717
+
718
+ if faces.shape[0] != npan:
719
+ raise ValueError(
720
+ f"In {filename} npan value: {npan} is not equal to face count: \
721
+ {faces.shape[0]}."
722
+ )
723
+
724
+ if isx == 1 and isy == 1:
725
+ return ReflectionSymmetricMesh(ReflectionSymmetricMesh(Mesh(vertices, faces, f"quarter_of_{name}"), yOz_Plane, f"half_of_{name}"), xOz_Plane, name)
726
+ elif isx == 1:
727
+ return ReflectionSymmetricMesh(Mesh(vertices, faces, f"half_of_{name}"), yOz_Plane, name)
728
+ elif isy == 1:
729
+ return ReflectionSymmetricMesh(Mesh(vertices, faces, f"half_of_{name}"), xOz_Plane, name)
730
+ else:
731
+ return Mesh(vertices, faces, name)
732
+
733
+
734
+ def load_MAR(filename, name=None):
735
+ """Loads Nemoh (Ecole Centrale de Nantes) mesh files.
736
+
737
+ Parameters
738
+ ----------
739
+ filename: str
740
+ name of the mesh file on disk
741
+
742
+ Returns
743
+ -------
744
+ Mesh or ReflectionSymmetry
745
+ the loaded mesh
746
+
747
+ Note
748
+ ----
749
+ MAR files have a 1-indexing
750
+ """
751
+
752
+ _check_file(filename)
753
+
754
+ ifile = open(filename, 'r')
755
+
756
+ header = ifile.readline()
757
+ _, symmetric_mesh = header.split()
758
+
759
+ vertices = []
760
+ while 1:
761
+ line = ifile.readline()
762
+ line = line.split()
763
+ if line[0] == '0':
764
+ break
765
+ vertices.append(list(map(float, line[1:])))
766
+
767
+ vertices = np.array(vertices, dtype=float)
768
+ faces = []
769
+ while 1:
770
+ line = ifile.readline()
771
+ line = line.split()
772
+ if line[0] == '0':
773
+ break
774
+ faces.append(list(map(int, line)))
775
+
776
+ faces = np.array(faces, dtype=int)
777
+
778
+ ifile.close()
779
+
780
+ if int(symmetric_mesh) == 1:
781
+ if name is None:
782
+ half_mesh = Mesh(vertices, faces-1)
783
+ return ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane)
784
+ else:
785
+ half_mesh = Mesh(vertices, faces-1, name=f"half_of_{name}")
786
+ return ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=name)
787
+ else:
788
+ return Mesh(vertices, faces-1, name)
789
+
790
+
791
+ def load_MSH(filename, name=None):
792
+ """Loads .MSH mesh files generated by GMSH by C. Geuzaine and J.F. Remacle.
793
+
794
+ Parameters
795
+ ----------
796
+ filename: str
797
+ name of the mesh file on disk
798
+
799
+ Returns
800
+ -------
801
+ Mesh
802
+ the loaded mesh
803
+
804
+ Note
805
+ ----
806
+ MSH files have a 1-indexing
807
+ """
808
+
809
+ import re
810
+
811
+ _check_file(filename)
812
+
813
+ with open(filename, 'r') as file:
814
+ data = file.read()
815
+
816
+ nb_nodes, nodes_data = re.search(r'\$Nodes\n(\d+)\n(.+)\$EndNodes', data, re.DOTALL).groups()
817
+ nb_elts, elts_data = re.search(r'\$Elements\n(\d+)\n(.+)\$EndElements', data, re.DOTALL).groups()
818
+
819
+ vertices = np.asarray(list(map(float, nodes_data.split())), dtype=float).reshape((-1, 4))[:, 1:]
820
+ vertices = np.ascontiguousarray(vertices)
821
+ faces = []
822
+
823
+ # Triangles
824
+ for tri_elt in re.findall(r'(^\d+\s2(?:\s\d+)+?$)', elts_data, re.MULTILINE):
825
+ tri_elt = list(map(int, tri_elt.split()))
826
+ triangle = tri_elt[-3:]
827
+ triangle.append(triangle[0])
828
+ faces.append(triangle)
829
+
830
+ for quad_elt in re.findall(r'(^\d+\s3(?:\s\d+)+?$)', elts_data, re.MULTILINE):
831
+ quad_elt = list(map(int, quad_elt.split()))
832
+ quadrangle = quad_elt[-4:]
833
+ faces.append(quadrangle)
834
+
835
+ faces = np.asarray(faces, dtype=int) - 1
836
+
837
+ return Mesh(vertices, faces, name)
838
+
839
+
840
+ def load_MED(filename, name=None):
841
+ """Loads MED mesh files generated by SALOME MECA.
842
+
843
+ Parameters
844
+ ----------
845
+ filename: str
846
+ name of the mesh file on disk
847
+
848
+ Returns
849
+ -------
850
+ Mesh
851
+ the loaded mesh
852
+
853
+ Note
854
+ ----
855
+ MED files have a 1-indexing
856
+ """
857
+
858
+ try:
859
+ import h5py
860
+ except ImportError:
861
+ raise ImportError('MED file format reader needs h5py module to be installed')
862
+
863
+ _check_file(filename)
864
+
865
+ file = h5py.File(filename)
866
+
867
+ list_of_names = []
868
+ file.visit(list_of_names.append)
869
+
870
+ nb_quadrangles = nb_triangles = 0
871
+
872
+ for item in list_of_names:
873
+ if '/NOE/COO' in item:
874
+ vertices = file[item][:].reshape((3, -1)).T
875
+ nv = vertices.shape[0]
876
+ if '/MAI/TR3/NOD' in item:
877
+ triangles = file[item][:].reshape((3, -1)).T - 1
878
+ nb_triangles = triangles.shape[0]
879
+ if '/MAI/QU4/NOD' in item:
880
+ quadrangles = file[item][:].reshape((4, -1)).T - 1
881
+ nb_quadrangles = quadrangles.shape[0]
882
+
883
+ file.close()
884
+
885
+ if nb_triangles == 0:
886
+ triangles = np.zeros((0, 4), dtype=int)
887
+ else:
888
+ triangles = np.column_stack((triangles, triangles[:, 0]))
889
+ if nb_quadrangles == 0:
890
+ quadrangles = np.zeros((0, 4), dtype=int)
891
+
892
+ faces = np.row_stack([triangles, quadrangles])
893
+
894
+ return Mesh(vertices, faces, name=name)
895
+
896
+
897
+ def load_WRL(filename, name=None):
898
+ """Loads VRML 2.0 mesh files.
899
+
900
+ Parameters
901
+ ----------
902
+ filename: str
903
+ name of the mesh file on disk
904
+
905
+ Returns
906
+ -------
907
+ Mesh
908
+ the loaded mesh
909
+ """
910
+ import re
911
+
912
+ vtk = import_optional_dependency("vtk")
913
+
914
+ _check_file(filename)
915
+
916
+ # Checking version
917
+ with open(filename, 'r') as f:
918
+ line = f.readline()
919
+ ver = re.search(r'#VRML\s+V(\d.\d)', line).group(1)
920
+ if not ver == '2.0':
921
+ raise NotImplementedError('VRML loader only supports VRML 2.0 format (version %s given)' % ver)
922
+
923
+ importer = vtk.vtkVRMLImporter()
924
+ importer.SetFileName(str(filename))
925
+ importer.Update()
926
+
927
+ actors = importer.GetRenderer().GetActors()
928
+ actors.InitTraversal()
929
+ dataset = actors.GetNextActor().GetMapper().GetInput()
930
+
931
+ return _dump_vtk(dataset)
932
+
933
+
934
+ def load_NEM(filename, name=None):
935
+ """Loads mesh files that are used by the ``Mesh`` tool included in Nemoh.
936
+
937
+ Parameters
938
+ ----------
939
+ filename: str
940
+ name of the mesh file on disk
941
+
942
+ Returns
943
+ -------
944
+ Mesh
945
+ the loaded mesh
946
+
947
+ Note
948
+ ----
949
+ This format is different from that is used directly by Nemoh software. It is only dedicated to the Mesh tool.
950
+ """
951
+
952
+ _check_file(filename)
953
+
954
+ ifile = open(filename, 'r')
955
+
956
+ nv = int(ifile.readline())
957
+ nf = int(ifile.readline())
958
+
959
+ vertices = []
960
+ for ivertex in range(nv):
961
+ vertices.append(list(map(float, ifile.readline().split())))
962
+ vertices = np.asarray(vertices, dtype=float)
963
+
964
+ faces = []
965
+ for iface in range(nf):
966
+ faces.append(list(map(int, ifile.readline().split())))
967
+ faces = np.asarray(faces, dtype=int)
968
+ faces -= 1
969
+
970
+ return Mesh(vertices, faces, name)
971
+
972
+
973
+ def load_PNL(filename, name=None):
974
+ """Load mesh using HAMS file format.
975
+
976
+ Parameters
977
+ ----------
978
+ filename: str
979
+ name of the mesh file on disk
980
+
981
+ Returns
982
+ -------
983
+ Mesh or ReflectionSymmetricMesh
984
+ the loaded mesh
985
+ """
986
+
987
+ with open(filename, 'r') as f:
988
+ # Skip 3 title lines
989
+ f.readline()
990
+ f.readline()
991
+ f.readline()
992
+ # Read header data
993
+ nb_faces, nb_vertices, x_sym, y_sym = map(int, f.readline().split())
994
+ # Skip 2 more lines
995
+ f.readline()
996
+ f.readline()
997
+ vertices = np.genfromtxt((f.readline() for _ in range(nb_vertices)), usecols=(1, 2, 3))
998
+ # Skip 3 more lines
999
+ f.readline()
1000
+ f.readline()
1001
+ f.readline()
1002
+ faces = np.zeros((nb_faces, 4), dtype=int)
1003
+ for i in range(nb_faces):
1004
+ index, nb_corners, *data = map(int, f.readline().split())
1005
+ assert i+1 == index
1006
+ if nb_corners == 3: # Triangle
1007
+ assert len(data) == 3
1008
+ faces[i, 0:3] = data
1009
+ faces[i, 3] = faces[i, 2] # Convention for triangles in Capytaine: repeat last vertex
1010
+ elif int(nb_corners) == 4: # Quadrangle
1011
+ assert len(data) == 4
1012
+ faces[i, :] = data
1013
+ faces = faces - 1 # Going from Fortran 1-based indices to Numpy 0-based indices
1014
+
1015
+ if x_sym == 1 and y_sym == 0:
1016
+ half_mesh = Mesh(vertices, faces, name=(f"half_of_{name}" if name is not None else None))
1017
+ return ReflectionSymmetricMesh(half_mesh, plane=yOz_Plane, name=name)
1018
+ elif x_sym == 0 and y_sym == 1:
1019
+ half_mesh = Mesh(vertices, faces, name=(f"half_of_{name}" if name is not None else None))
1020
+ return ReflectionSymmetricMesh(half_mesh, plane=xOz_Plane, name=name)
1021
+ elif x_sym == 1 and y_sym == 1:
1022
+ quarter_mesh = Mesh(vertices, faces, name=(f"quarter_of_{name}" if name is not None else None))
1023
+ half_mesh = ReflectionSymmetricMesh(quarter_mesh, plane=xOz_Plane, name=(f"half_of_{name}" if name is not None else None))
1024
+ return ReflectionSymmetricMesh(half_mesh, plane=yOz_Plane, name=name)
1025
+ else:
1026
+ return Mesh(vertices, faces, name)
1027
+
1028
+
1029
+ extension_dict = { # keyword, reader
1030
+ 'dat': load_MAR,
1031
+ 'mar': load_MAR,
1032
+ 'nemoh': load_MAR,
1033
+ 'wamit': load_GDF,
1034
+ 'gdf': load_GDF,
1035
+ 'diodore-inp': load_INP,
1036
+ 'inp': load_INP,
1037
+ 'diodore-dat': load_DAT,
1038
+ 'hydrostar': load_HST,
1039
+ 'hst': load_HST,
1040
+ 'natural': load_NAT,
1041
+ 'nat': load_NAT,
1042
+ 'gmsh': load_MSH,
1043
+ 'msh': load_MSH,
1044
+ 'rad': load_RAD,
1045
+ 'radioss': load_RAD,
1046
+ 'stl': load_STL,
1047
+ 'vtu': load_VTU,
1048
+ 'vtp': load_VTP,
1049
+ 'paraview-legacy': load_VTK,
1050
+ 'vtk': load_VTK,
1051
+ 'tecplot': load_TEC,
1052
+ 'tec': load_TEC,
1053
+ 'med': load_MED,
1054
+ 'salome': load_MED,
1055
+ 'vrml': load_WRL,
1056
+ 'wrl': load_WRL,
1057
+ 'nem': load_NEM,
1058
+ 'nemoh_mesh': load_NEM,
1059
+ 'pnl': load_PNL,
1060
+ 'hams': load_PNL,
1061
+ }