capytaine 2.3.1__cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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 (93) hide show
  1. capytaine/__about__.py +16 -0
  2. capytaine/__init__.py +36 -0
  3. capytaine/bem/__init__.py +0 -0
  4. capytaine/bem/airy_waves.py +111 -0
  5. capytaine/bem/engines.py +441 -0
  6. capytaine/bem/problems_and_results.py +600 -0
  7. capytaine/bem/solver.py +594 -0
  8. capytaine/bodies/__init__.py +4 -0
  9. capytaine/bodies/bodies.py +1221 -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 +111 -0
  14. capytaine/bodies/predefined/spheres.py +70 -0
  15. capytaine/green_functions/FinGreen3D/.gitignore +1 -0
  16. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
  17. capytaine/green_functions/FinGreen3D/LICENSE +165 -0
  18. capytaine/green_functions/FinGreen3D/Makefile +16 -0
  19. capytaine/green_functions/FinGreen3D/README.md +24 -0
  20. capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
  21. capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
  22. capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
  23. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
  24. capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
  25. capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
  26. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
  27. capytaine/green_functions/__init__.py +2 -0
  28. capytaine/green_functions/abstract_green_function.py +64 -0
  29. capytaine/green_functions/delhommeau.py +507 -0
  30. capytaine/green_functions/hams.py +204 -0
  31. capytaine/green_functions/libs/Delhommeau_float32.cpython-310-x86_64-linux-gnu.so +0 -0
  32. capytaine/green_functions/libs/Delhommeau_float64.cpython-310-x86_64-linux-gnu.so +0 -0
  33. capytaine/green_functions/libs/__init__.py +0 -0
  34. capytaine/io/__init__.py +0 -0
  35. capytaine/io/bemio.py +153 -0
  36. capytaine/io/legacy.py +328 -0
  37. capytaine/io/mesh_loaders.py +1086 -0
  38. capytaine/io/mesh_writers.py +692 -0
  39. capytaine/io/meshio.py +38 -0
  40. capytaine/io/wamit.py +479 -0
  41. capytaine/io/xarray.py +668 -0
  42. capytaine/matrices/__init__.py +16 -0
  43. capytaine/matrices/block.py +592 -0
  44. capytaine/matrices/block_toeplitz.py +325 -0
  45. capytaine/matrices/builders.py +89 -0
  46. capytaine/matrices/linear_solvers.py +232 -0
  47. capytaine/matrices/low_rank.py +395 -0
  48. capytaine/meshes/__init__.py +6 -0
  49. capytaine/meshes/clipper.py +465 -0
  50. capytaine/meshes/collections.py +342 -0
  51. capytaine/meshes/geometry.py +409 -0
  52. capytaine/meshes/mesh_like_protocol.py +37 -0
  53. capytaine/meshes/meshes.py +890 -0
  54. capytaine/meshes/predefined/__init__.py +6 -0
  55. capytaine/meshes/predefined/cylinders.py +314 -0
  56. capytaine/meshes/predefined/rectangles.py +261 -0
  57. capytaine/meshes/predefined/spheres.py +62 -0
  58. capytaine/meshes/properties.py +276 -0
  59. capytaine/meshes/quadratures.py +80 -0
  60. capytaine/meshes/quality.py +448 -0
  61. capytaine/meshes/surface_integrals.py +63 -0
  62. capytaine/meshes/symmetric.py +462 -0
  63. capytaine/post_pro/__init__.py +6 -0
  64. capytaine/post_pro/free_surfaces.py +88 -0
  65. capytaine/post_pro/impedance.py +92 -0
  66. capytaine/post_pro/kochin.py +54 -0
  67. capytaine/post_pro/rao.py +60 -0
  68. capytaine/tools/__init__.py +0 -0
  69. capytaine/tools/cache_on_disk.py +26 -0
  70. capytaine/tools/deprecation_handling.py +18 -0
  71. capytaine/tools/lists_of_points.py +52 -0
  72. capytaine/tools/lru_cache.py +49 -0
  73. capytaine/tools/optional_imports.py +27 -0
  74. capytaine/tools/prony_decomposition.py +150 -0
  75. capytaine/tools/symbolic_multiplication.py +149 -0
  76. capytaine/tools/timer.py +66 -0
  77. capytaine/ui/__init__.py +0 -0
  78. capytaine/ui/cli.py +28 -0
  79. capytaine/ui/rich.py +5 -0
  80. capytaine/ui/vtk/__init__.py +3 -0
  81. capytaine/ui/vtk/animation.py +329 -0
  82. capytaine/ui/vtk/body_viewer.py +28 -0
  83. capytaine/ui/vtk/helpers.py +82 -0
  84. capytaine/ui/vtk/mesh_viewer.py +461 -0
  85. capytaine-2.3.1.dist-info/LICENSE +674 -0
  86. capytaine-2.3.1.dist-info/METADATA +750 -0
  87. capytaine-2.3.1.dist-info/RECORD +93 -0
  88. capytaine-2.3.1.dist-info/WHEEL +6 -0
  89. capytaine-2.3.1.dist-info/entry_points.txt +3 -0
  90. capytaine.libs/libgfortran-83c28eba.so.5.0.0 +0 -0
  91. capytaine.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
  92. capytaine.libs/libmvec-2-583a17db.28.so +0 -0
  93. capytaine.libs/libquadmath-2284e583.so.0.0.0 +0 -0
capytaine/ui/rich.py ADDED
@@ -0,0 +1,5 @@
1
+ import logging
2
+ from rich.logging import RichHandler
3
+
4
+ def set_logging(level="INFO"):
5
+ logging.basicConfig(level=level, format="%(message)s", handlers=[RichHandler(level=level, log_time_format="[%X]", show_path=False)], force=True)
@@ -0,0 +1,3 @@
1
+ from capytaine.ui.vtk.mesh_viewer import MeshViewer
2
+ from capytaine.ui.vtk.body_viewer import FloatingBodyViewer
3
+ from capytaine.ui.vtk.animation import Animation
@@ -0,0 +1,329 @@
1
+ """VTK animation for the free surface elevation."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import logging
6
+
7
+ import numpy as np
8
+ from numpy import pi
9
+
10
+ from capytaine.ui.vtk.helpers import compute_node_data, compute_vtk_polydata
11
+ from capytaine.tools.optional_imports import import_optional_dependency
12
+
13
+ vtk = import_optional_dependency("vtk")
14
+
15
+ LOG = logging.getLogger(__name__)
16
+
17
+
18
+ class Animation:
19
+ """Class to generate an animation of a result of Capytaine,
20
+ including the elevation of the free surface.
21
+
22
+ The animation is made of a short loop of a single period of the solution in frequency domain.
23
+
24
+ Parameters
25
+ ----------
26
+ loop_duration: float
27
+ Duration in the loop. For real time animation, the period of the motion.
28
+ fps: int, optional
29
+ Number of frames per second in the animation (default: 24).
30
+
31
+ Attributes
32
+ ----------
33
+ frames_per_loop: int
34
+ Number of frames in one loop
35
+ actors: list of vtk actor objects
36
+ The objects in the scene.
37
+ """
38
+
39
+ def __init__(self, loop_duration, fps=24):
40
+ self.fps = fps
41
+ self.frames_per_loop = int(fps * loop_duration)
42
+ self.actors = []
43
+
44
+ self._precomputed_polydatas = {}
45
+ self._current_frame = 0
46
+
47
+ # @classmethod
48
+ # def from_result(self, result):
49
+ # from capytaine.bodies import TRANSLATION_DOFS_DIRECTIONS, ROTATION_DOFS_AXIS
50
+ #
51
+ # if display_dof.lower() in TRANSLATION_DOFS_DIRECTIONS:
52
+ # direction = np.asarray(TRANSLATION_DOFS_DIRECTIONS[display_dof.lower()])
53
+ # def translation_motion(self, frame):
54
+ # nonlocal direction
55
+ # pos = np.asarray(self.body_actor.GetPosition())
56
+ # pos = (1 - direction) * pos + \
57
+ # direction * np.cos(2*np.pi*(frame % self.frames_per_loop)/self.frames_per_loop)
58
+ # self.body_actor.SetPosition(*pos)
59
+ # self.update_body_position = translation_motion
60
+ #
61
+ # elif display_dof.lower() in ROTATION_DOFS_AXIS:
62
+ # direction = np.asarray(ROTATION_DOFS_AXIS[display_dof.lower()])
63
+ # def rotation_motion(self, frame):
64
+ # nonlocal direction
65
+ # pos = np.asarray(self.body_actor.GetOrientation())
66
+ # pos = (1 - direction) * pos + \
67
+ # direction * np.cos(2*np.pi*(frame % self.frames_per_loop)/self.frames_per_loop)
68
+ # self.body_actor.SetOrientation(*pos)
69
+ # self.update_body_position = rotation_motion
70
+
71
+
72
+ def _add_actor(self, mesh, faces_motion=None, faces_colors=None, edges=False):
73
+ """Add an animated object to the scene."""
74
+ if faces_motion is not None:
75
+ nodes_motion = compute_node_data(mesh.merged(), faces_motion)
76
+ else:
77
+ nodes_motion = None
78
+
79
+ base_polydata = compute_vtk_polydata(mesh.merged())
80
+ mapper = vtk.vtkPolyDataMapper()
81
+ mapper.SetInputData(base_polydata)
82
+
83
+ actor = vtk.vtkActor()
84
+ if edges:
85
+ actor.GetProperty().EdgeVisibilityOn()
86
+ actor.GetProperty().SetInterpolationToGouraud()
87
+ actor.SetMapper(mapper)
88
+
89
+ if nodes_motion is not None or faces_colors is not None:
90
+ LOG.info(f"Precompute motions of {mesh.name} before animation.")
91
+ self._precomputed_polydatas[actor] = []
92
+
93
+ for i_frame in range(self.frames_per_loop):
94
+ new_polydata = vtk.vtkPolyData()
95
+ new_polydata.DeepCopy(base_polydata)
96
+
97
+ if nodes_motion is not None:
98
+ # Change points positions at frame i
99
+ current_deformation = (
100
+ np.abs(nodes_motion)*np.cos(np.angle(nodes_motion)-2*pi*i_frame/self.frames_per_loop)
101
+ )
102
+
103
+ points = new_polydata.GetPoints()
104
+ for j in range(mesh.nb_vertices):
105
+ point = points.GetPoint(j)
106
+ point = np.asarray(point) + current_deformation[j]
107
+ points.SetPoint(j, tuple(point))
108
+
109
+ if faces_colors is not None:
110
+ # Evaluate scalar field at frame i
111
+ current_colors = (
112
+ np.abs(faces_colors)*np.cos(np.angle(faces_colors)-2*pi*i_frame/self.frames_per_loop)
113
+ )
114
+ max_val = max(abs(faces_colors))
115
+ vtk_faces_colors = vtk.vtkFloatArray()
116
+ for i, color in enumerate(current_colors):
117
+ vtk_faces_colors.InsertValue(i, (color+max_val)/(2*max_val))
118
+ new_polydata.GetCellData().SetScalars(vtk_faces_colors)
119
+
120
+ self._precomputed_polydatas[actor].append(new_polydata)
121
+ else:
122
+ self._precomputed_polydatas[actor] = None
123
+
124
+ self.actors.append(actor)
125
+
126
+ return actor
127
+
128
+ def add_body(self, body, faces_motion=None, faces_colors=None, edges=False):
129
+ """Add an floating body to the scene.
130
+
131
+ Parameters
132
+ ----------
133
+ body: FloatingBody
134
+ The object to include in the scene.
135
+ faces_motion: dof, optional
136
+ The motion of the body defined at the center of the faces.
137
+ faces_colors: iterable of complex numbers, optional
138
+ Scalar field over the surface of the body that should be displayed with colors.
139
+ edges: bool, optional
140
+ Draw the edges of the mesh in the scene.
141
+
142
+ Returns
143
+ -------
144
+ vtk actor object
145
+ """
146
+ actor = self._add_actor(body.mesh.merged(), faces_motion=faces_motion,
147
+ faces_colors=faces_colors, edges=edges)
148
+ if faces_colors is None:
149
+ actor.GetProperty().SetColor((1, 1, 0))
150
+ else:
151
+ lut = vtk.vtkLookupTable()
152
+ lut.SetNumberOfColors(50)
153
+ lut.SetHueRange(0, 0.6)
154
+ lut.SetSaturationRange(0.5, 0.5)
155
+ lut.SetValueRange(0.8, 0.8)
156
+ lut.Build()
157
+ actor.GetMapper().SetLookupTable(lut)
158
+
159
+ return actor
160
+
161
+ def add_free_surface(self, free_surface, faces_elevation):
162
+ """Add the free surface to the scene.
163
+
164
+ Parameters
165
+ ----------
166
+ free_surface: FreeSurface
167
+ The free surface object
168
+ faces_elevation: array of complex numbers
169
+ The elevation of each face of the meshed free surface given as a complex number.
170
+
171
+ Returns
172
+ -------
173
+ vtk actor object
174
+ """
175
+ faces_motion = np.array([(0, 0, elevation) for elevation in faces_elevation])
176
+ actor = self._add_actor(free_surface.mesh, faces_motion=faces_motion, faces_colors=faces_motion[:, 2])
177
+
178
+ lut = vtk.vtkLookupTable()
179
+ lut.SetNumberOfColors(50)
180
+ lut.SetHueRange(0.58, 0.58)
181
+ lut.SetSaturationRange(0.5, 0.5)
182
+ lut.SetValueRange(0.4, 0.6)
183
+ lut.Build()
184
+ actor.GetMapper().SetLookupTable(lut)
185
+
186
+ return actor
187
+
188
+ def _callback(self, renderer, event):
189
+ for actor in self.actors:
190
+ if self._precomputed_polydatas[actor] is not None:
191
+ actor.GetMapper().SetInputData(self._precomputed_polydatas[actor][self._current_frame % self.frames_per_loop])
192
+ renderer.GetRenderWindow().Render()
193
+ self._current_frame += 1
194
+
195
+ def run(self, camera_position=(-10.0, -10.0, 10.0), resolution=(1280, 720), top_light_intensity=0.5):
196
+ """Run the animation.
197
+
198
+ Parameters
199
+ ----------
200
+ camera_position: 3-ple of floats, optional
201
+ The starting position of the camera in the scene.
202
+ resolution: 2-ple of ints, optional
203
+ Resolution of the video in pixels.
204
+ top_light_intensity: float between 0 and 1
205
+ Intensity of the light source at the top of the scene (default: 0.5)
206
+ """
207
+ # Setup a renderer, render window, and interactor
208
+ renderer = vtk.vtkRenderer()
209
+ renderer.SetBackground(1, 1, 1) # Background color white
210
+ for actor in self.actors:
211
+ renderer.AddActor(actor)
212
+ renderer.Modified()
213
+
214
+ camera = vtk.vtkCamera()
215
+ camera.SetPosition(*camera_position)
216
+ camera.SetFocalPoint(0, 0, 0)
217
+ camera.SetViewUp(0, 0, 1)
218
+ renderer.SetActiveCamera(camera)
219
+
220
+ light = vtk.vtkLight()
221
+ light.SetLightTypeToHeadlight()
222
+ renderer.AddLight(light)
223
+
224
+ if top_light_intensity > 0.0:
225
+ light = vtk.vtkLight()
226
+ light.SetDirectionAngle(0, 0)
227
+ light.SetLightTypeToSceneLight()
228
+ light.SetIntensity(top_light_intensity)
229
+ renderer.AddLight(light)
230
+
231
+ render_window = vtk.vtkRenderWindow()
232
+ render_window.SetSize(*resolution)
233
+ render_window.SetWindowName("Capytaine animation")
234
+ render_window.AddRenderer(renderer)
235
+
236
+ render_window_interactor = vtk.vtkRenderWindowInteractor()
237
+ render_window_interactor.SetRenderWindow(render_window)
238
+ render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
239
+
240
+ render_window.Render()
241
+
242
+ render_window_interactor.Initialize() # Initialize must be called prior to creating timer events.
243
+
244
+ render_window_interactor.AddObserver('TimerEvent', self._callback)
245
+ render_window_interactor.CreateRepeatingTimer(int(1000 / self.fps))
246
+
247
+ render_window_interactor.Start()
248
+
249
+ # Run until stopped by user.
250
+
251
+ del render_window_interactor
252
+ del render_window
253
+
254
+ def save(self, filepath, nb_loops=1, camera_position=(-10.0, -10.0, 10.0), resolution=(1280, 720), top_light_intensity=0.5):
255
+ """Save the animation in a video file.
256
+
257
+ Parameters
258
+ ----------
259
+ filepath: string
260
+ Path of the output file.
261
+ nb_loop: int, optional
262
+ Number of periods to save in the file.
263
+ camera_position: 3-ple of floats, optional
264
+ The starting position of the camera in the scene.
265
+ resolution: 2-ple of ints, optional
266
+ Resolution of the video in pixels.
267
+ top_light_intensity: float between 0 and 1
268
+ Intensity of the light source at the top of the scene (default: 0.5)
269
+ """
270
+ renderer = vtk.vtkRenderer()
271
+ renderer.SetBackground(1, 1, 1) # Background color white
272
+ for actor in self.actors:
273
+ renderer.AddActor(actor)
274
+ renderer.Modified()
275
+
276
+ camera = vtk.vtkCamera()
277
+ camera.SetPosition(*camera_position)
278
+ camera.SetFocalPoint(0, 0, 0)
279
+ camera.SetViewUp(0, 0, 1)
280
+ renderer.SetActiveCamera(camera)
281
+
282
+ light = vtk.vtkLight()
283
+ light.SetLightTypeToHeadlight()
284
+ renderer.AddLight(light)
285
+
286
+ if top_light_intensity > 0.0:
287
+ light = vtk.vtkLight()
288
+ light.SetDirectionAngle(0, 0)
289
+ light.SetLightTypeToSceneLight()
290
+ light.SetIntensity(top_light_intensity)
291
+ renderer.AddLight(light)
292
+
293
+ render_window = vtk.vtkRenderWindow()
294
+ render_window.SetSize(*resolution)
295
+ render_window.OffScreenRenderingOn()
296
+ render_window.AddRenderer(renderer)
297
+
298
+ image_filter = vtk.vtkWindowToImageFilter()
299
+ image_filter.SetInput(render_window)
300
+ image_filter.SetInputBufferTypeToRGB()
301
+ image_filter.ReadFrontBufferOff()
302
+
303
+ writer = vtk.vtkOggTheoraWriter()
304
+ writer.SetInputConnection(image_filter.GetOutputPort())
305
+ writer.SetFileName(filepath)
306
+ writer.SetRate(self.fps)
307
+
308
+ writer.Start()
309
+
310
+ for i_frame in range(nb_loops*self.frames_per_loop):
311
+ self._callback(renderer, None)
312
+ image_filter.Modified()
313
+ writer.Write()
314
+
315
+ writer.End()
316
+ render_window.Finalize()
317
+
318
+ del image_filter
319
+ del writer
320
+ del render_window
321
+
322
+ def embed_in_notebook(self, resolution=(640, 360), **kwargs):
323
+ from tempfile import mkstemp
324
+ from IPython.core.display import Video
325
+ # Requires Ipython 7.14 or higher, for this patch: https://github.com/ipython/ipython/pull/12212/
326
+
327
+ filepath = mkstemp(suffix=".ogv")[1]
328
+ self.save(filepath, nb_loops=1, resolution=resolution, **kwargs)
329
+ return Video(filepath, embed=True, width=resolution[0], html_attributes="controls loop autoplay")
@@ -0,0 +1,28 @@
1
+ """3D display of floating body with VTK."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ from capytaine.tools.optional_imports import import_optional_dependency
6
+ from capytaine.ui.vtk.mesh_viewer import MeshViewer
7
+
8
+ vtk = import_optional_dependency("vtk")
9
+
10
+ class FloatingBodyViewer(MeshViewer):
11
+
12
+ def __init__(self):
13
+ super().__init__()
14
+ self.dofs_data = {}
15
+
16
+ def add_body(self, body, **kwargs):
17
+ self.add_mesh(body.mesh, **kwargs)
18
+ if body.lid_mesh is not None:
19
+ self.add_mesh(body.lid_mesh, **kwargs)
20
+
21
+ for dof in body.dofs:
22
+ vtk_data_array = vtk.vtkFloatArray()
23
+ vtk_data_array.SetNumberOfComponents(3)
24
+ vtk_data_array.SetNumberOfTuples(body.mesh.nb_faces)
25
+ for i, vector in enumerate(body.dofs[dof]):
26
+ vtk_data_array.SetTuple3(i, *vector)
27
+ self.dofs_data[dof] = vtk_data_array
28
+ # vtk_polydata.GetCellData().SetVectors(vtk_data_array)
@@ -0,0 +1,82 @@
1
+ """Tools for 3D displays with VTK."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ from typing import Union
6
+
7
+ from capytaine.meshes.meshes import Mesh
8
+ from capytaine.meshes.collections import CollectionOfMeshes
9
+ from capytaine.tools.optional_imports import import_optional_dependency
10
+
11
+ vtk = import_optional_dependency("vtk")
12
+
13
+ def compute_vtk_polydata(mesh: Union[Mesh, CollectionOfMeshes]):
14
+ """Transform a mesh into vtkPolydata."""
15
+
16
+ # Create a vtkPoints object and store the points in it
17
+ points = vtk.vtkPoints()
18
+ for point in mesh.vertices:
19
+ points.InsertNextPoint(point)
20
+
21
+ # Create a vtkCellArray to store faces
22
+ faces = vtk.vtkCellArray()
23
+ for face_ids in mesh.faces:
24
+ if face_ids[0] == face_ids[-1]:
25
+ # Triangle
26
+ curface = face_ids[:3]
27
+ vtk_face = vtk.vtkTriangle()
28
+ else:
29
+ # Quadrangle
30
+ curface = face_ids[:4]
31
+ vtk_face = vtk.vtkQuad()
32
+
33
+ for idx, id in enumerate(curface):
34
+ vtk_face.GetPointIds().SetId(idx, id)
35
+
36
+ faces.InsertNextCell(vtk_face)
37
+
38
+ vtk_polydata = vtk.vtkPolyData()
39
+ vtk_polydata.SetPoints(points)
40
+ vtk_polydata.SetPolys(faces)
41
+
42
+ return vtk_polydata
43
+
44
+
45
+ def compute_node_data(mesh: Union[Mesh, CollectionOfMeshes],
46
+ face_data):
47
+ """Transform data defined at the center of the faces to data defined at the nodes of the mesh
48
+ by a simple averaging of the values of the neighboring faces.
49
+
50
+ Parameters
51
+ ----------
52
+ mesh: Mesh or CollectionOfMeshes
53
+ the mesh on which the face face_data are defined
54
+ face_data: numpy array of shape (mesh.nb_faces, ...)
55
+ the data defined on the center of the faces of the mesh
56
+
57
+ Returns
58
+ -------
59
+ node_data: numpy array of shape (mesh.nb_vertices, ...)
60
+ the same data averaged on the nodes
61
+ """
62
+
63
+ import numpy as np
64
+
65
+ mesh = mesh.merged()
66
+ assert face_data.shape[0] == mesh.nb_faces
67
+
68
+ # Initialize output array
69
+ node_data_shape = (mesh.vertices.shape[0], ) + face_data.shape[1:]
70
+ node_data = np.zeros(node_data_shape, dtype=complex)
71
+
72
+ # Keep track of the number of faces near each vertex
73
+ faces_near_nodes_shape = (mesh.vertices.shape[0], ) + (1, ) * len(face_data.shape[1:])
74
+ nb_faces_near_nodes = np.zeros(faces_near_nodes_shape, dtype=np.int8)
75
+
76
+ for i, vertices in enumerate(mesh.faces):
77
+ for vertex in vertices:
78
+ nb_faces_near_nodes[vertex] += 1
79
+ node_data[vertex, ...] += face_data[i, ...]
80
+
81
+ node_data /= nb_faces_near_nodes
82
+ return node_data