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