capytaine 2.3.1__cp314-cp314-macosx_14_0_arm64.whl → 3.0.0a1__cp314-cp314-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.
- capytaine/__about__.py +7 -2
- capytaine/__init__.py +8 -12
- capytaine/bem/engines.py +234 -354
- capytaine/bem/problems_and_results.py +14 -13
- capytaine/bem/solver.py +204 -80
- capytaine/bodies/bodies.py +278 -869
- capytaine/bodies/dofs.py +136 -9
- capytaine/bodies/hydrostatics.py +540 -0
- capytaine/bodies/multibodies.py +216 -0
- capytaine/green_functions/{libs/Delhommeau_float32.cpython-314-darwin.so → Delhommeau_float32.cpython-314-darwin.so} +0 -0
- capytaine/green_functions/{libs/Delhommeau_float64.cpython-314-darwin.so → Delhommeau_float64.cpython-314-darwin.so} +0 -0
- capytaine/green_functions/abstract_green_function.py +2 -2
- capytaine/green_functions/delhommeau.py +31 -16
- capytaine/green_functions/hams.py +19 -13
- capytaine/io/legacy.py +3 -103
- capytaine/io/xarray.py +11 -6
- capytaine/meshes/__init__.py +2 -6
- capytaine/meshes/abstract_meshes.py +375 -0
- capytaine/meshes/clean.py +302 -0
- capytaine/meshes/clip.py +347 -0
- capytaine/meshes/export.py +89 -0
- capytaine/meshes/geometry.py +244 -394
- capytaine/meshes/io.py +433 -0
- capytaine/meshes/meshes.py +617 -681
- capytaine/meshes/predefined/cylinders.py +22 -56
- capytaine/meshes/predefined/rectangles.py +26 -85
- capytaine/meshes/predefined/spheres.py +4 -11
- capytaine/meshes/quality.py +118 -407
- capytaine/meshes/surface_integrals.py +48 -29
- capytaine/meshes/symmetric_meshes.py +641 -0
- capytaine/meshes/visualization.py +353 -0
- capytaine/post_pro/free_surfaces.py +1 -4
- capytaine/post_pro/kochin.py +10 -10
- capytaine/tools/block_circulant_matrices.py +275 -0
- capytaine/tools/lists_of_points.py +2 -2
- capytaine/tools/memory_monitor.py +45 -0
- capytaine/tools/symbolic_multiplication.py +13 -1
- capytaine/tools/timer.py +58 -34
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
- capytaine-3.0.0a1.dist-info/RECORD +65 -0
- capytaine/bodies/predefined/__init__.py +0 -6
- capytaine/bodies/predefined/cylinders.py +0 -151
- capytaine/bodies/predefined/rectangles.py +0 -111
- capytaine/bodies/predefined/spheres.py +0 -70
- capytaine/green_functions/FinGreen3D/.gitignore +0 -1
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
- capytaine/green_functions/FinGreen3D/LICENSE +0 -165
- capytaine/green_functions/FinGreen3D/Makefile +0 -16
- capytaine/green_functions/FinGreen3D/README.md +0 -24
- capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
- capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
- capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
- capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
- capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/mesh_loaders.py +0 -1086
- capytaine/io/mesh_writers.py +0 -692
- capytaine/io/meshio.py +0 -38
- capytaine/matrices/__init__.py +0 -16
- capytaine/matrices/block.py +0 -592
- capytaine/matrices/block_toeplitz.py +0 -325
- capytaine/matrices/builders.py +0 -89
- capytaine/matrices/linear_solvers.py +0 -232
- capytaine/matrices/low_rank.py +0 -395
- capytaine/meshes/clipper.py +0 -465
- capytaine/meshes/collections.py +0 -342
- capytaine/meshes/mesh_like_protocol.py +0 -37
- capytaine/meshes/properties.py +0 -276
- capytaine/meshes/quadratures.py +0 -80
- capytaine/meshes/symmetric.py +0 -462
- capytaine/tools/lru_cache.py +0 -49
- capytaine/ui/vtk/__init__.py +0 -3
- capytaine/ui/vtk/animation.py +0 -329
- capytaine/ui/vtk/body_viewer.py +0 -28
- capytaine/ui/vtk/helpers.py +0 -82
- capytaine/ui/vtk/mesh_viewer.py +0 -461
- capytaine-2.3.1.dist-info/RECORD +0 -92
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
capytaine/meshes/collections.py
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
"""A set of meshes that can be used as a Mesh."""
|
|
2
|
-
# Copyright (C) 2017-2019 Matthieu Ancellin
|
|
3
|
-
# See LICENSE file at <https://github.com/mancellin/capytaine>
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from itertools import chain, accumulate
|
|
7
|
-
from functools import lru_cache
|
|
8
|
-
from typing import Iterable, Union
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
|
|
12
|
-
from capytaine.meshes.geometry import Abstract3DObject, ClippableMixin, inplace_transformation
|
|
13
|
-
from capytaine.meshes.surface_integrals import SurfaceIntegralsMixin
|
|
14
|
-
from capytaine.meshes.meshes import Mesh
|
|
15
|
-
|
|
16
|
-
LOG = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class CollectionOfMeshes(ClippableMixin, SurfaceIntegralsMixin, Abstract3DObject):
|
|
20
|
-
"""A tuple of meshes.
|
|
21
|
-
It gives access to all the vertices of all the sub-meshes as if it were a mesh itself.
|
|
22
|
-
Collections can be nested to store meshes in a tree structure.
|
|
23
|
-
|
|
24
|
-
Parameters
|
|
25
|
-
----------
|
|
26
|
-
meshes: Iterable of Mesh or CollectionOfMeshes
|
|
27
|
-
meshes in the collection
|
|
28
|
-
name : str, optional
|
|
29
|
-
a name for the collection
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def __init__(self, meshes: Iterable[Union[Mesh, 'CollectionOfMeshes']], name=None):
|
|
33
|
-
|
|
34
|
-
self._meshes = tuple(meshes)
|
|
35
|
-
|
|
36
|
-
for mesh in self._meshes:
|
|
37
|
-
assert isinstance(mesh, Mesh) or isinstance(mesh, CollectionOfMeshes)
|
|
38
|
-
|
|
39
|
-
if name is None:
|
|
40
|
-
self.name = f'collection_of_meshes_{next(Mesh._ids)}'
|
|
41
|
-
else:
|
|
42
|
-
self.name = str(name)
|
|
43
|
-
|
|
44
|
-
LOG.debug(f"New collection of meshes: {repr(self)}")
|
|
45
|
-
|
|
46
|
-
def __short_str__(self):
|
|
47
|
-
return (f"{self.__class__.__name__}(..., name=\"{self.name}\")")
|
|
48
|
-
|
|
49
|
-
def __str__(self):
|
|
50
|
-
if len(self._meshes) < 3:
|
|
51
|
-
meshes_str = ', '.join(m.__short_str__() for m in self._meshes)
|
|
52
|
-
else:
|
|
53
|
-
meshes_str = self._meshes[0].__short_str__() + ", ..., " + self._meshes[-1].__short_str__()
|
|
54
|
-
return f"{self.__class__.__name__}([{meshes_str}], name=\"{self.name}\")"
|
|
55
|
-
|
|
56
|
-
def __repr__(self):
|
|
57
|
-
return f"{self.__class__.__name__}({', '.join(str(m) for m in self._meshes)}, name=\"{self.name}\")"
|
|
58
|
-
|
|
59
|
-
def _repr_pretty_(self, p, cycle):
|
|
60
|
-
p.text(self.__str__())
|
|
61
|
-
|
|
62
|
-
def __rich_repr__(self):
|
|
63
|
-
class WrappedString:
|
|
64
|
-
def __init__(self, s):
|
|
65
|
-
self.s = s
|
|
66
|
-
def __repr__(self):
|
|
67
|
-
return self.s
|
|
68
|
-
for m in self._meshes:
|
|
69
|
-
yield m
|
|
70
|
-
yield "name", self.name
|
|
71
|
-
|
|
72
|
-
def __iter__(self):
|
|
73
|
-
return iter(self._meshes)
|
|
74
|
-
|
|
75
|
-
def __len__(self):
|
|
76
|
-
return len(self._meshes)
|
|
77
|
-
|
|
78
|
-
def __getitem__(self, item):
|
|
79
|
-
return self._meshes.__getitem__(item)
|
|
80
|
-
|
|
81
|
-
def __eq__(self, other):
|
|
82
|
-
if isinstance(other, CollectionOfMeshes):
|
|
83
|
-
return self._meshes == other._meshes
|
|
84
|
-
else:
|
|
85
|
-
return NotImplemented
|
|
86
|
-
|
|
87
|
-
def __hash__(self):
|
|
88
|
-
return hash(self._meshes)
|
|
89
|
-
|
|
90
|
-
def tree_view(self, **kwargs):
|
|
91
|
-
body_tree_views = []
|
|
92
|
-
for i, mesh in enumerate(self):
|
|
93
|
-
tree_view = mesh.tree_view(**kwargs)
|
|
94
|
-
if i == len(self)-1:
|
|
95
|
-
prefix = ' └─'
|
|
96
|
-
shift = ' '
|
|
97
|
-
else:
|
|
98
|
-
prefix = ' ├─'
|
|
99
|
-
shift = ' │ '
|
|
100
|
-
body_tree_views.append(prefix + tree_view.replace('\n', '\n' + shift))
|
|
101
|
-
|
|
102
|
-
return self.__short_str__() + '\n' + '\n'.join(body_tree_views)
|
|
103
|
-
|
|
104
|
-
def path_to_leaf(self):
|
|
105
|
-
"""
|
|
106
|
-
Builds a list of lists of paths from the collection corresponding to the
|
|
107
|
-
root of the tree to the submeshes corresponding to the leaves
|
|
108
|
-
"""
|
|
109
|
-
ptl = []
|
|
110
|
-
for i, mesh in enumerate(self):
|
|
111
|
-
for path in mesh.path_to_leaf():
|
|
112
|
-
ptl.append([i] + path)
|
|
113
|
-
return ptl
|
|
114
|
-
|
|
115
|
-
def copy(self, name=None):
|
|
116
|
-
from copy import deepcopy
|
|
117
|
-
new_mesh = deepcopy(self)
|
|
118
|
-
if name is not None:
|
|
119
|
-
new_mesh.name = name
|
|
120
|
-
return new_mesh
|
|
121
|
-
|
|
122
|
-
@inplace_transformation
|
|
123
|
-
def heal_mesh(self, closed_mesh=False):
|
|
124
|
-
for mesh in self:
|
|
125
|
-
mesh.heal_mesh(closed_mesh=closed_mesh)
|
|
126
|
-
|
|
127
|
-
@inplace_transformation
|
|
128
|
-
def with_normal_vector_going_down(self):
|
|
129
|
-
for mesh in self:
|
|
130
|
-
mesh.with_normal_vector_going_down()
|
|
131
|
-
|
|
132
|
-
##############
|
|
133
|
-
# Properties #
|
|
134
|
-
##############
|
|
135
|
-
|
|
136
|
-
@property
|
|
137
|
-
def nb_submeshes(self):
|
|
138
|
-
return len(self)
|
|
139
|
-
|
|
140
|
-
@property
|
|
141
|
-
def nb_vertices(self):
|
|
142
|
-
return sum(mesh.nb_vertices for mesh in self)
|
|
143
|
-
|
|
144
|
-
@property
|
|
145
|
-
def nb_faces(self):
|
|
146
|
-
return sum(mesh.nb_faces for mesh in self)
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def volume(self):
|
|
150
|
-
return sum(mesh.volume for mesh in self)
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def vertices(self):
|
|
154
|
-
return np.concatenate([mesh.vertices for mesh in self])
|
|
155
|
-
|
|
156
|
-
@property
|
|
157
|
-
def faces(self):
|
|
158
|
-
"""Return the indices of the vertices forming each of the faces. For the
|
|
159
|
-
later submeshes, the indices of the vertices has to be shifted to
|
|
160
|
-
correspond to their index in the concatenated array self.vertices.
|
|
161
|
-
"""
|
|
162
|
-
nb_vertices = accumulate(chain([0], (mesh.nb_vertices for mesh in self[:-1])))
|
|
163
|
-
return np.concatenate([mesh.faces + nbv for mesh, nbv in zip(self, nb_vertices)])
|
|
164
|
-
|
|
165
|
-
@property
|
|
166
|
-
def faces_normals(self):
|
|
167
|
-
return np.concatenate([mesh.faces_normals for mesh in self])
|
|
168
|
-
|
|
169
|
-
@property
|
|
170
|
-
def faces_areas(self):
|
|
171
|
-
return np.concatenate([mesh.faces_areas for mesh in self])
|
|
172
|
-
|
|
173
|
-
@property
|
|
174
|
-
def faces_centers(self):
|
|
175
|
-
return np.concatenate([mesh.faces_centers for mesh in self])
|
|
176
|
-
|
|
177
|
-
@property
|
|
178
|
-
def faces_radiuses(self):
|
|
179
|
-
return np.concatenate([mesh.faces_radiuses for mesh in self])
|
|
180
|
-
|
|
181
|
-
@property
|
|
182
|
-
def quadrature_points(self):
|
|
183
|
-
quad_submeshes = [mesh.quadrature_points for mesh in self]
|
|
184
|
-
return (
|
|
185
|
-
np.concatenate([quad[0] for quad in quad_submeshes]), # Points
|
|
186
|
-
np.concatenate([quad[1] for quad in quad_submeshes]) # Weights
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def quadrature_method(self):
|
|
191
|
-
methods_submeshes = [mesh.quadrature_method for mesh in self]
|
|
192
|
-
if len(set(methods_submeshes)) == 1:
|
|
193
|
-
return methods_submeshes[0] # All the same methods
|
|
194
|
-
else:
|
|
195
|
-
return "Mixed quadrature method"
|
|
196
|
-
|
|
197
|
-
def compute_quadrature(self, method):
|
|
198
|
-
for mesh in self:
|
|
199
|
-
mesh.compute_quadrature(method)
|
|
200
|
-
|
|
201
|
-
@property
|
|
202
|
-
def center_of_mass_of_nodes(self):
|
|
203
|
-
return sum([mesh.nb_vertices*mesh.center_of_mass_of_nodes for mesh in self])/self.nb_vertices
|
|
204
|
-
|
|
205
|
-
@property
|
|
206
|
-
@lru_cache(maxsize=1024)
|
|
207
|
-
def diameter_of_nodes(self):
|
|
208
|
-
return self.merged().diameter_of_nodes # TODO: improve implementation
|
|
209
|
-
|
|
210
|
-
def indices_of_mesh(self, mesh_index: int) -> slice:
|
|
211
|
-
"""Return the indices of the faces for the sub-mesh given as argument."""
|
|
212
|
-
start = sum((mesh.nb_faces for mesh in self[:mesh_index])) # Number of faces in previous meshes
|
|
213
|
-
return slice(start, start + self[mesh_index].nb_faces)
|
|
214
|
-
|
|
215
|
-
def submesh_containing_face(self, id_face):
|
|
216
|
-
total_faces = 0
|
|
217
|
-
for id_mesh in range(self.nb_submeshes):
|
|
218
|
-
total_faces += self[id_mesh].nb_faces
|
|
219
|
-
if id_face < total_faces:
|
|
220
|
-
return id_mesh, id_face - (total_faces - self[id_mesh].nb_faces)
|
|
221
|
-
|
|
222
|
-
##################
|
|
223
|
-
# Transformation #
|
|
224
|
-
##################
|
|
225
|
-
|
|
226
|
-
def join_meshes(*meshes, name=None, return_masks=False):
|
|
227
|
-
coll = CollectionOfMeshes(meshes, name=name)
|
|
228
|
-
if return_masks:
|
|
229
|
-
masks = []
|
|
230
|
-
for i_mesh in range(len(meshes)):
|
|
231
|
-
mask = np.full((coll.nb_faces,), False)
|
|
232
|
-
mask[coll.indices_of_mesh(i_mesh)] = True
|
|
233
|
-
masks.append(mask)
|
|
234
|
-
return coll, masks
|
|
235
|
-
return coll
|
|
236
|
-
|
|
237
|
-
def __add__(self, mesh_to_add):
|
|
238
|
-
return self.join_meshes(mesh_to_add)
|
|
239
|
-
|
|
240
|
-
def merged(self, name=None) -> Mesh:
|
|
241
|
-
"""Merge the sub-meshes and return a full mesh.
|
|
242
|
-
If the collection contains other collections, they are merged recursively.
|
|
243
|
-
Optionally, a new name can be given to the resulting mesh."""
|
|
244
|
-
if name is None:
|
|
245
|
-
name = self.name
|
|
246
|
-
merged = Mesh(self.vertices, self.faces, name=name)
|
|
247
|
-
merged.merge_duplicates()
|
|
248
|
-
merged.heal_triangles()
|
|
249
|
-
return merged
|
|
250
|
-
|
|
251
|
-
def extract_one_face(self, id_face):
|
|
252
|
-
id_mesh, relative_id_face = self.submesh_containing_face(id_face)
|
|
253
|
-
mesh = self[id_mesh]
|
|
254
|
-
|
|
255
|
-
extracted_mesh = mesh.extract_one_face(relative_id_face)
|
|
256
|
-
|
|
257
|
-
if hasattr(mesh, '__internals__'):
|
|
258
|
-
for prop in mesh.__internals__:
|
|
259
|
-
if prop[:4] == "face":
|
|
260
|
-
extracted_mesh.__internals__[prop] = mesh.__internals__[prop][[relative_id_face]]
|
|
261
|
-
|
|
262
|
-
return extracted_mesh
|
|
263
|
-
|
|
264
|
-
def extract_faces(self, *args, **kwargs):
|
|
265
|
-
return self.merged().extract_faces(*args, **kwargs)
|
|
266
|
-
|
|
267
|
-
def sliced_by_plane(self, plane):
|
|
268
|
-
return CollectionOfMeshes([mesh.sliced_by_plane(plane) for mesh in self], name=self.name)
|
|
269
|
-
|
|
270
|
-
@inplace_transformation
|
|
271
|
-
def translate(self, vector):
|
|
272
|
-
for mesh in self:
|
|
273
|
-
mesh.translate(vector)
|
|
274
|
-
|
|
275
|
-
@inplace_transformation
|
|
276
|
-
def rotate(self, axis, angle):
|
|
277
|
-
for mesh in self:
|
|
278
|
-
mesh.rotate(axis, angle)
|
|
279
|
-
|
|
280
|
-
@inplace_transformation
|
|
281
|
-
def mirror(self, plane):
|
|
282
|
-
for mesh in self:
|
|
283
|
-
mesh.mirror(plane)
|
|
284
|
-
|
|
285
|
-
@inplace_transformation
|
|
286
|
-
def clip(self, plane):
|
|
287
|
-
self._clipping_data = {'faces_ids': []}
|
|
288
|
-
faces_shifts = list(accumulate(chain([0], (mesh.nb_faces for mesh in self[:-1]))))
|
|
289
|
-
for mesh, faces_shift in zip(self, faces_shifts):
|
|
290
|
-
mesh.clip(plane)
|
|
291
|
-
self._clipping_data['faces_ids'].extend([i + faces_shift for i in mesh._clipping_data['faces_ids']])
|
|
292
|
-
self._clipping_data['faces_ids'] = np.asarray(self._clipping_data['faces_ids'])
|
|
293
|
-
self.prune_empty_meshes()
|
|
294
|
-
|
|
295
|
-
def symmetrized(self, plane):
|
|
296
|
-
from capytaine.meshes.symmetric import ReflectionSymmetricMesh
|
|
297
|
-
half = self.clipped(plane, name=f"{self.name}_half")
|
|
298
|
-
return ReflectionSymmetricMesh(half, plane=plane, name=f"symmetrized_of_{self.name}")
|
|
299
|
-
|
|
300
|
-
@inplace_transformation
|
|
301
|
-
def prune_empty_meshes(self):
|
|
302
|
-
"""Remove empty meshes from the collection."""
|
|
303
|
-
remaining_meshes = tuple(mesh for mesh in self if mesh.nb_faces > 0 and mesh.nb_vertices > 0)
|
|
304
|
-
if len(remaining_meshes) == 0:
|
|
305
|
-
self._meshes = (Mesh(name="empty_mesh"),)
|
|
306
|
-
else:
|
|
307
|
-
self._meshes = remaining_meshes
|
|
308
|
-
|
|
309
|
-
@property
|
|
310
|
-
def axis_aligned_bbox(self):
|
|
311
|
-
"""Get the axis aligned bounding box of the mesh.
|
|
312
|
-
|
|
313
|
-
Returns
|
|
314
|
-
-------
|
|
315
|
-
tuple
|
|
316
|
-
(xmin, xmax, ymin, ymax, zmin, zmax)
|
|
317
|
-
"""
|
|
318
|
-
if self.nb_vertices > 0:
|
|
319
|
-
x, y, z = self.vertices.T
|
|
320
|
-
return (x.min(), x.max(),
|
|
321
|
-
y.min(), y.max(),
|
|
322
|
-
z.min(), z.max())
|
|
323
|
-
else:
|
|
324
|
-
return tuple(np.zeros(6))
|
|
325
|
-
|
|
326
|
-
def show(self, **kwargs):
|
|
327
|
-
from capytaine.ui.vtk.mesh_viewer import MeshViewer
|
|
328
|
-
|
|
329
|
-
viewer = MeshViewer()
|
|
330
|
-
for mesh in self:
|
|
331
|
-
viewer.add_mesh(mesh.merged(), **kwargs)
|
|
332
|
-
viewer.show()
|
|
333
|
-
viewer.finalize()
|
|
334
|
-
|
|
335
|
-
def show_matplotlib(self, *args, **kwargs):
|
|
336
|
-
self.merged().show_matplotlib(*args, **kwargs)
|
|
337
|
-
|
|
338
|
-
def lowest_lid_position(self, *args, **kwargs):
|
|
339
|
-
return self.merged().lowest_lid_position(*args, **kwargs)
|
|
340
|
-
|
|
341
|
-
def generate_lid(self, *args, **kwargs):
|
|
342
|
-
return self.merged().generate_lid(*args, **kwargs)
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from typing import Tuple, Protocol, runtime_checkable
|
|
2
|
-
from numpy.typing import ArrayLike
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@runtime_checkable
|
|
6
|
-
class MeshLike(Protocol):
|
|
7
|
-
"""Minimal API that a class describing a mesh should implement to be
|
|
8
|
-
usable with the rest of Capytaine.
|
|
9
|
-
|
|
10
|
-
The goal is two-fold:
|
|
11
|
-
1. Use at runtime to identify a mesh for functions that behaves
|
|
12
|
-
differently depending on the type of the input (e.g. Delhommeau().evaluate).
|
|
13
|
-
2. Use as documentation for third-party mesh implementation.
|
|
14
|
-
|
|
15
|
-
In the future, it could also be used for static typing.
|
|
16
|
-
"""
|
|
17
|
-
vertices: ArrayLike
|
|
18
|
-
faces: ArrayLike
|
|
19
|
-
nb_vertices: int
|
|
20
|
-
nb_faces: int
|
|
21
|
-
faces_centers: ArrayLike
|
|
22
|
-
faces_normals: ArrayLike
|
|
23
|
-
faces_areas: ArrayLike
|
|
24
|
-
faces_radiuses: ArrayLike
|
|
25
|
-
quadrature_points: Tuple[ArrayLike, ArrayLike]
|
|
26
|
-
|
|
27
|
-
def __short_str__(self) -> str:
|
|
28
|
-
...
|
|
29
|
-
|
|
30
|
-
def extract_faces(self, faces_id):
|
|
31
|
-
...
|
|
32
|
-
|
|
33
|
-
def join_meshes(*meshes, return_mask):
|
|
34
|
-
...
|
|
35
|
-
|
|
36
|
-
def with_normal_vector_going_down(self, **kwargs):
|
|
37
|
-
...
|
capytaine/meshes/properties.py
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
"""Helper functions to compute some properties of the mesh.
|
|
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
|
-
from functools import reduce
|
|
8
|
-
from itertools import chain
|
|
9
|
-
import numpy as np
|
|
10
|
-
from typing import List
|
|
11
|
-
from numpy.typing import NDArray
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def compute_faces_properties(mesh):
|
|
15
|
-
"""Compute the faces properties of the mesh"""
|
|
16
|
-
|
|
17
|
-
# faces_areas, faces_normals, faces_centers = mm.get_all_faces_properties(mesh._vertices, mesh._faces)
|
|
18
|
-
nf = mesh.nb_faces
|
|
19
|
-
|
|
20
|
-
# triangle_mask = _faces[:, 0] == _faces[:, -1]
|
|
21
|
-
# nb_triangles = np.sum(triangle_mask)
|
|
22
|
-
# quads_mask = np.invert(triangle_mask)
|
|
23
|
-
# nb_quads = nf - nb_triangles
|
|
24
|
-
|
|
25
|
-
faces_areas = np.zeros(nf, dtype=float)
|
|
26
|
-
faces_normals = np.zeros((nf, 3), dtype=float)
|
|
27
|
-
faces_centers = np.zeros((nf, 3), dtype=float)
|
|
28
|
-
|
|
29
|
-
# Collectively dealing with triangles
|
|
30
|
-
# triangles = _faces[triangle_mask]
|
|
31
|
-
triangles_id = mesh.triangles_ids
|
|
32
|
-
triangles = mesh._faces[triangles_id]
|
|
33
|
-
|
|
34
|
-
triangles_normals = np.cross(mesh._vertices[triangles[:, 1]] - mesh._vertices[triangles[:, 0]],
|
|
35
|
-
mesh._vertices[triangles[:, 2]] - mesh._vertices[triangles[:, 0]])
|
|
36
|
-
triangles_normals_norm = np.linalg.norm(triangles_normals, axis=1)
|
|
37
|
-
|
|
38
|
-
degenerate_triangle = np.abs(triangles_normals_norm) < 1e-12
|
|
39
|
-
triangles_id = triangles_id[~degenerate_triangle]
|
|
40
|
-
triangles_normals = triangles_normals[~degenerate_triangle, :]
|
|
41
|
-
triangles_normals_norm = triangles_normals_norm[~degenerate_triangle]
|
|
42
|
-
triangles = triangles[~degenerate_triangle, :]
|
|
43
|
-
# Now, continue the computations without the degenerate triangles
|
|
44
|
-
|
|
45
|
-
faces_normals[triangles_id] = triangles_normals / triangles_normals_norm[:, np.newaxis]
|
|
46
|
-
faces_areas[triangles_id] = triangles_normals_norm / 2.
|
|
47
|
-
faces_centers[triangles_id] = np.sum(mesh._vertices[triangles[:, :3]], axis=1) / 3.
|
|
48
|
-
|
|
49
|
-
# Collectively dealing with quads
|
|
50
|
-
quads_id = mesh.quadrangles_ids
|
|
51
|
-
quads = mesh._faces[quads_id]
|
|
52
|
-
# quads = _faces[quads_mask]
|
|
53
|
-
|
|
54
|
-
quads_normals = np.cross(mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]],
|
|
55
|
-
mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 1]])
|
|
56
|
-
|
|
57
|
-
quads_normals_norm = np.linalg.norm(quads_normals, axis=1)
|
|
58
|
-
|
|
59
|
-
degenerate_quad = np.abs(quads_normals_norm) < 1e-12
|
|
60
|
-
quads_id = quads_id[~degenerate_quad]
|
|
61
|
-
quads_normals = quads_normals[~degenerate_quad]
|
|
62
|
-
quads_normals_norm = quads_normals_norm[~degenerate_quad]
|
|
63
|
-
quads = quads[~degenerate_quad, :]
|
|
64
|
-
# Now, continue the computations without the degenerate quads
|
|
65
|
-
|
|
66
|
-
faces_normals[quads_id] = quads_normals / quads_normals_norm[:, np.newaxis]
|
|
67
|
-
|
|
68
|
-
a1 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 1]] - mesh._vertices[quads[:, 0]],
|
|
69
|
-
mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
|
|
70
|
-
a2 = np.linalg.norm(np.cross(mesh._vertices[quads[:, 3]] - mesh._vertices[quads[:, 0]],
|
|
71
|
-
mesh._vertices[quads[:, 2]] - mesh._vertices[quads[:, 0]]), axis=1) * 0.5
|
|
72
|
-
faces_areas[quads_id] = a1 + a2
|
|
73
|
-
|
|
74
|
-
c1 = np.sum(mesh._vertices[quads[:, :3]], axis=1) / 3.
|
|
75
|
-
c2 = (np.sum(mesh._vertices[quads[:, 2:4]], axis=1) + mesh._vertices[quads[:, 0]]) / 3.
|
|
76
|
-
|
|
77
|
-
faces_centers[quads_id] = (np.array(([a1, ] * 3)).T * c1 + np.array(([a2, ] * 3)).T * c2)
|
|
78
|
-
faces_centers[quads_id] /= np.array(([faces_areas[quads_id], ] * 3)).T
|
|
79
|
-
|
|
80
|
-
faces_radiuses = compute_radiuses(mesh, faces_centers)
|
|
81
|
-
|
|
82
|
-
return {'faces_areas': faces_areas,
|
|
83
|
-
'faces_normals': faces_normals,
|
|
84
|
-
'faces_centers': faces_centers,
|
|
85
|
-
'faces_radiuses': faces_radiuses,
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def compute_radiuses(mesh, faces_centers):
|
|
90
|
-
"""Compute the radiuses of the faces of the mesh.
|
|
91
|
-
|
|
92
|
-
The radius is defined here as the maximal distance between the center
|
|
93
|
-
of mass of a cell and one of its points."""
|
|
94
|
-
|
|
95
|
-
# Coordinates of all the vertices grouped by face
|
|
96
|
-
faces_vertices = mesh.vertices[mesh.faces, :]
|
|
97
|
-
# faces_vertices.shape == (nb_faces, 4, 3)
|
|
98
|
-
|
|
99
|
-
# Reorder the axes for array broadcasting below
|
|
100
|
-
faces_vertices = np.moveaxis(faces_vertices, 0, 1)
|
|
101
|
-
# faces_vertices.shape == (4, nb_faces, 3)
|
|
102
|
-
|
|
103
|
-
# Get all the vectors between the center of faces and their vertices.
|
|
104
|
-
radial_vector = faces_centers - faces_vertices
|
|
105
|
-
# radial_vector.shape == (4, nb_faces, 3)
|
|
106
|
-
|
|
107
|
-
# Keep the maximum length
|
|
108
|
-
faces_radiuses = np.max(np.linalg.norm(radial_vector, axis=2), axis=0)
|
|
109
|
-
# faces_radiuses.shape = (nb_faces)
|
|
110
|
-
|
|
111
|
-
return faces_radiuses
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def compute_connectivity(mesh):
|
|
115
|
-
"""Compute the connectivities of the mesh.
|
|
116
|
-
|
|
117
|
-
It concerns further connectivity than simple faces/vertices connectivities. It computes the vertices / vertices, vertices / faces and faces / faces connectivities.
|
|
118
|
-
|
|
119
|
-
Note
|
|
120
|
-
----
|
|
121
|
-
* Note that if the mesh is not conformal, the algorithm may not perform correctly
|
|
122
|
-
|
|
123
|
-
TODO: The computation of boundaries should be in the future computed with help of VTK
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
nv = mesh.nb_vertices
|
|
127
|
-
nf = mesh.nb_faces
|
|
128
|
-
|
|
129
|
-
mesh_closed = True
|
|
130
|
-
|
|
131
|
-
# Building connectivities
|
|
132
|
-
|
|
133
|
-
# Establishing v_v and v_f connectivities
|
|
134
|
-
v_v = dict([(i, set()) for i in range(nv)])
|
|
135
|
-
v_f = dict([(i, set()) for i in range(nv)])
|
|
136
|
-
for (iface, face) in enumerate(mesh._faces):
|
|
137
|
-
if face[0] == face[-1]:
|
|
138
|
-
face_w = face[:3]
|
|
139
|
-
else:
|
|
140
|
-
face_w = face
|
|
141
|
-
for (index, iV) in enumerate(face_w):
|
|
142
|
-
v_f[iV].add(iface)
|
|
143
|
-
v_v[face_w[index - 1]].add(iV)
|
|
144
|
-
v_v[iV].add(face_w[index - 1])
|
|
145
|
-
|
|
146
|
-
# Connectivity f_f
|
|
147
|
-
boundary_edges = dict()
|
|
148
|
-
|
|
149
|
-
f_f = dict([(i, set()) for i in range(nf)])
|
|
150
|
-
for ivertex in range(nv):
|
|
151
|
-
set1 = v_f[ivertex]
|
|
152
|
-
for iadj_v in v_v[ivertex]:
|
|
153
|
-
set2 = v_f[iadj_v]
|
|
154
|
-
intersection = list(set1 & set2)
|
|
155
|
-
if len(intersection) == 2:
|
|
156
|
-
f_f[intersection[0]].add(intersection[1])
|
|
157
|
-
f_f[intersection[1]].add(intersection[0])
|
|
158
|
-
|
|
159
|
-
elif len(intersection) == 1:
|
|
160
|
-
boundary_face = mesh._faces[intersection[0]]
|
|
161
|
-
|
|
162
|
-
if boundary_face[0] == boundary_face[-1]:
|
|
163
|
-
boundary_face = boundary_face[:3]
|
|
164
|
-
ids = np.where((boundary_face == ivertex) + (boundary_face == iadj_v))[0]
|
|
165
|
-
|
|
166
|
-
if ids[1] != ids[0]+1:
|
|
167
|
-
i_v_orig, i_v_target = boundary_face[ids]
|
|
168
|
-
else:
|
|
169
|
-
i_v_target, i_v_orig = boundary_face[ids]
|
|
170
|
-
|
|
171
|
-
boundary_edges[i_v_orig] = i_v_target
|
|
172
|
-
else:
|
|
173
|
-
raise RuntimeError('Unexpected error while computing mesh connectivities')
|
|
174
|
-
|
|
175
|
-
# Computing boundaries
|
|
176
|
-
boundaries = list()
|
|
177
|
-
# TODO: calculer des boundaries fermees et ouvertes (closed_boundaries et open_boundaries) et mettre dans dict
|
|
178
|
-
while True:
|
|
179
|
-
try:
|
|
180
|
-
boundary = list()
|
|
181
|
-
i_v0_init, i_v1 = boundary_edges.popitem()
|
|
182
|
-
boundary.append(i_v0_init)
|
|
183
|
-
boundary.append(i_v1)
|
|
184
|
-
i_v0 = i_v1
|
|
185
|
-
|
|
186
|
-
while True:
|
|
187
|
-
try:
|
|
188
|
-
i_v1 = boundary_edges.pop(i_v0)
|
|
189
|
-
boundary.append(i_v1)
|
|
190
|
-
i_v0 = i_v1
|
|
191
|
-
except KeyError:
|
|
192
|
-
if boundary[0] != boundary[-1]:
|
|
193
|
-
print('Boundary is not closed !!!')
|
|
194
|
-
else:
|
|
195
|
-
boundaries.append(boundary)
|
|
196
|
-
break
|
|
197
|
-
except KeyError:
|
|
198
|
-
break
|
|
199
|
-
|
|
200
|
-
return {'v_v': v_v,
|
|
201
|
-
'v_f': v_f,
|
|
202
|
-
'f_f': f_f,
|
|
203
|
-
'boundaries': boundaries}
|
|
204
|
-
|
|
205
|
-
def faces_in_group(faces: NDArray[np.integer], group: NDArray[np.integer]) -> NDArray[np.bool_]:
|
|
206
|
-
"""Identification of faces with vertices within group.
|
|
207
|
-
|
|
208
|
-
Parameters
|
|
209
|
-
----------
|
|
210
|
-
faces : NDArray[np.integer]
|
|
211
|
-
Mesh faces. Expecting a numpy array of shape N_faces x N_vertices_per_face.
|
|
212
|
-
group : NDArray[np.integer]
|
|
213
|
-
Group of connected vertices
|
|
214
|
-
|
|
215
|
-
Returns
|
|
216
|
-
-------
|
|
217
|
-
NDArray[np.bool]
|
|
218
|
-
Mask of faces containing vertices from the group
|
|
219
|
-
"""
|
|
220
|
-
return np.any(np.isin(faces, group), axis=1)
|
|
221
|
-
|
|
222
|
-
def clustering(faces: NDArray[np.integer]) -> List[NDArray[np.integer]]:
|
|
223
|
-
"""Clustering of vertices per connected faces.
|
|
224
|
-
|
|
225
|
-
Parameters
|
|
226
|
-
----------
|
|
227
|
-
faces : NDArray[np.integer]
|
|
228
|
-
Mesh faces. Expecting a numpy array of shape N_faces x N_vertices_per_face.
|
|
229
|
-
|
|
230
|
-
Returns
|
|
231
|
-
-------
|
|
232
|
-
list[NDArray[np.integer]]
|
|
233
|
-
Groups of connected vertices.
|
|
234
|
-
"""
|
|
235
|
-
vert_groups: list[NDArray[np.integer]] = []
|
|
236
|
-
mask = np.ones(faces.shape[0], dtype=bool)
|
|
237
|
-
while np.any(mask):
|
|
238
|
-
# Consider faces whose vertices are not already identified in a group.
|
|
239
|
-
# Start new group by considering first face
|
|
240
|
-
remaining_faces = faces[mask]
|
|
241
|
-
group = remaining_faces[0]
|
|
242
|
-
rem_mask = np.ones(remaining_faces.shape[0], dtype=bool)
|
|
243
|
-
# Iterative update of vertices group. Output final result to frozenset
|
|
244
|
-
while not np.allclose(new:=faces_in_group(remaining_faces, group), rem_mask):
|
|
245
|
-
group = np.unique(remaining_faces[new])
|
|
246
|
-
rem_mask = new
|
|
247
|
-
else:
|
|
248
|
-
group = np.unique(remaining_faces[new])
|
|
249
|
-
vert_groups.append(group)
|
|
250
|
-
# Identify faces that have no vertices in current groups
|
|
251
|
-
mask = ~reduce(np.logical_or, [faces_in_group(faces, group) for group in vert_groups])
|
|
252
|
-
return vert_groups
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def connected_components(mesh):
|
|
256
|
-
"""Returns a list of meshes that each corresponds to the a connected component in the original mesh.
|
|
257
|
-
Assumes the mesh is mostly conformal without duplicate vertices.
|
|
258
|
-
"""
|
|
259
|
-
# Get connected vertices
|
|
260
|
-
vertices_components = clustering(mesh.faces)
|
|
261
|
-
# Verification
|
|
262
|
-
if sum(len(group) for group in vertices_components) != len(set(chain.from_iterable(vertices_components))):
|
|
263
|
-
raise ValueError("Error in connected components clustering. Some elements are duplicated")
|
|
264
|
-
# The components are found. The rest is just about retrieving the faces in each components.
|
|
265
|
-
faces_components = [np.argwhere(faces_in_group(mesh.faces, group)) for group in vertices_components]
|
|
266
|
-
components = [mesh.extract_faces(f) for f in faces_components]
|
|
267
|
-
return components
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def connected_components_of_waterline(mesh, z=0.0):
|
|
271
|
-
if np.any(mesh.vertices[:, 2] > z + 1e-8):
|
|
272
|
-
mesh = mesh.immersed_part(free_surface=z)
|
|
273
|
-
fs_vertices_indices = np.where(np.isclose(mesh.vertices[:, 2], z))[0]
|
|
274
|
-
fs_faces_indices = np.where(np.any(np.isin(mesh.faces, fs_vertices_indices), axis=1))[0]
|
|
275
|
-
crown_mesh = mesh.extract_faces(fs_faces_indices)
|
|
276
|
-
return connected_components(crown_mesh)
|