mmgpy 0.5.0__cp311-cp311-manylinux2014_x86_64.manylinux_2_17_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.
- mmgpy/__init__.py +296 -0
- mmgpy/__main__.py +13 -0
- mmgpy/_io.py +535 -0
- mmgpy/_logging.py +290 -0
- mmgpy/_mesh.py +2286 -0
- mmgpy/_mmgpy.cpython-311-x86_64-linux-gnu.so +0 -0
- mmgpy/_mmgpy.pyi +2140 -0
- mmgpy/_options.py +304 -0
- mmgpy/_progress.py +850 -0
- mmgpy/_pyvista.py +410 -0
- mmgpy/_result.py +143 -0
- mmgpy/_transfer.py +273 -0
- mmgpy/_validation.py +669 -0
- mmgpy/_version.py +3 -0
- mmgpy/_version.py.in +3 -0
- mmgpy/bin/mmg2d_O3 +0 -0
- mmgpy/bin/mmg3d_O3 +0 -0
- mmgpy/bin/mmgs_O3 +0 -0
- mmgpy/interactive/__init__.py +24 -0
- mmgpy/interactive/sizing_editor.py +790 -0
- mmgpy/lagrangian.py +394 -0
- mmgpy/lib/libmmg2d.so +0 -0
- mmgpy/lib/libmmg2d.so.5 +0 -0
- mmgpy/lib/libmmg2d.so.5.8.0 +0 -0
- mmgpy/lib/libmmg3d.so +0 -0
- mmgpy/lib/libmmg3d.so.5 +0 -0
- mmgpy/lib/libmmg3d.so.5.8.0 +0 -0
- mmgpy/lib/libmmgs.so +0 -0
- mmgpy/lib/libmmgs.so.5 +0 -0
- mmgpy/lib/libmmgs.so.5.8.0 +0 -0
- mmgpy/lib/libvtkCommonColor-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonComputationalGeometry-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonCore-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonDataModel-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonExecutionModel-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonMath-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonMisc-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonSystem-9.5.so.1 +0 -0
- mmgpy/lib/libvtkCommonTransforms-9.5.so.1 +0 -0
- mmgpy/lib/libvtkDICOMParser-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersCellGrid-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersCore-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersExtraction-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersGeneral-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersGeometry-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersHybrid-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersHyperTree-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersModeling-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersParallel-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersReduction-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersSources-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersStatistics-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersTexture-9.5.so.1 +0 -0
- mmgpy/lib/libvtkFiltersVerdict-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOCellGrid-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOCore-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOGeometry-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOImage-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOLegacy-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOParallel-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOParallelXML-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOXML-9.5.so.1 +0 -0
- mmgpy/lib/libvtkIOXMLParser-9.5.so.1 +0 -0
- mmgpy/lib/libvtkImagingCore-9.5.so.1 +0 -0
- mmgpy/lib/libvtkImagingSources-9.5.so.1 +0 -0
- mmgpy/lib/libvtkParallelCore-9.5.so.1 +0 -0
- mmgpy/lib/libvtkParallelDIY-9.5.so.1 +0 -0
- mmgpy/lib/libvtkRenderingCore-9.5.so.1 +0 -0
- mmgpy/lib/libvtkdoubleconversion-9.5.so.1 +0 -0
- mmgpy/lib/libvtkexpat-9.5.so.1 +0 -0
- mmgpy/lib/libvtkfmt-9.5.so.1 +0 -0
- mmgpy/lib/libvtkjpeg-9.5.so.1 +0 -0
- mmgpy/lib/libvtkjsoncpp-9.5.so.1 +0 -0
- mmgpy/lib/libvtkkissfft-9.5.so.1 +0 -0
- mmgpy/lib/libvtkloguru-9.5.so.1 +0 -0
- mmgpy/lib/libvtklz4-9.5.so.1 +0 -0
- mmgpy/lib/libvtklzma-9.5.so.1 +0 -0
- mmgpy/lib/libvtkmetaio-9.5.so.1 +0 -0
- mmgpy/lib/libvtkpng-9.5.so.1 +0 -0
- mmgpy/lib/libvtkpugixml-9.5.so.1 +0 -0
- mmgpy/lib/libvtksys-9.5.so.1 +0 -0
- mmgpy/lib/libvtktiff-9.5.so.1 +0 -0
- mmgpy/lib/libvtktoken-9.5.so.1 +0 -0
- mmgpy/lib/libvtkverdict-9.5.so.1 +0 -0
- mmgpy/lib/libvtkzlib-9.5.so.1 +0 -0
- mmgpy/metrics.py +596 -0
- mmgpy/progress.py +69 -0
- mmgpy/py.typed +0 -0
- mmgpy/repair/__init__.py +37 -0
- mmgpy/repair/_core.py +226 -0
- mmgpy/repair/_elements.py +241 -0
- mmgpy/repair/_vertices.py +219 -0
- mmgpy/sizing.py +370 -0
- mmgpy/ui/__init__.py +97 -0
- mmgpy/ui/__main__.py +87 -0
- mmgpy/ui/app.py +1837 -0
- mmgpy/ui/parsers.py +501 -0
- mmgpy/ui/remeshing.py +448 -0
- mmgpy/ui/samples.py +249 -0
- mmgpy/ui/utils.py +280 -0
- mmgpy/ui/viewer.py +587 -0
- mmgpy-0.5.0.dist-info/METADATA +186 -0
- mmgpy-0.5.0.dist-info/RECORD +109 -0
- mmgpy-0.5.0.dist-info/WHEEL +6 -0
- mmgpy-0.5.0.dist-info/entry_points.txt +13 -0
- mmgpy-0.5.0.dist-info/licenses/LICENSE +38 -0
- share/man/man1/mmg2d.1.gz +0 -0
- share/man/man1/mmg3d.1.gz +0 -0
- share/man/man1/mmgs.1.gz +0 -0
mmgpy/lagrangian.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Pure Python Lagrangian motion implementation using Laplacian smoothing.
|
|
2
|
+
|
|
3
|
+
This module provides a Python-only implementation of mesh motion that works
|
|
4
|
+
without the ELAS library. It uses Laplacian smoothing to propagate boundary
|
|
5
|
+
displacements smoothly into the mesh interior, then remeshes to maintain
|
|
6
|
+
mesh quality.
|
|
7
|
+
|
|
8
|
+
Key functions:
|
|
9
|
+
- propagate_displacement: Propagate boundary displacement to interior nodes
|
|
10
|
+
- move_mesh: Apply displacement and remesh
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from scipy import sparse
|
|
19
|
+
from scipy.sparse.linalg import spsolve
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from numpy.typing import NDArray
|
|
23
|
+
|
|
24
|
+
from ._mmgpy import MmgMesh2D, MmgMesh3D, MmgMeshS
|
|
25
|
+
|
|
26
|
+
# Type alias for mesh union
|
|
27
|
+
MeshType = "MmgMesh2D | MmgMesh3D | MmgMeshS"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build_adjacency_from_elements(
|
|
31
|
+
n_vertices: int,
|
|
32
|
+
elements: NDArray[np.int32],
|
|
33
|
+
) -> list[list[int]]:
|
|
34
|
+
"""Build adjacency list from element connectivity.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
n_vertices: Number of vertices in the mesh.
|
|
38
|
+
elements: Element connectivity array (M x nodes_per_element).
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of neighbor indices for each vertex.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
adjacency: list[set[int]] = [set() for _ in range(n_vertices)]
|
|
45
|
+
nodes_per_elem = elements.shape[1]
|
|
46
|
+
|
|
47
|
+
for elem in elements:
|
|
48
|
+
for i in range(nodes_per_elem):
|
|
49
|
+
for j in range(i + 1, nodes_per_elem):
|
|
50
|
+
v_i, v_j = elem[i], elem[j]
|
|
51
|
+
adjacency[v_i].add(v_j)
|
|
52
|
+
adjacency[v_j].add(v_i)
|
|
53
|
+
|
|
54
|
+
return [list(neighbors) for neighbors in adjacency]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _build_laplacian_system(
|
|
58
|
+
adjacency: list[list[int]],
|
|
59
|
+
boundary_mask: NDArray[np.bool_],
|
|
60
|
+
) -> tuple[sparse.csr_matrix, sparse.csr_matrix, NDArray[np.intp], NDArray[np.intp]]:
|
|
61
|
+
"""Build sparse Laplacian matrices for interior-interior and interior-boundary.
|
|
62
|
+
|
|
63
|
+
Constructs the system L_II @ u_I = -L_IB @ u_B where:
|
|
64
|
+
- L_II: Laplacian submatrix for interior-to-interior connections
|
|
65
|
+
- L_IB: Laplacian submatrix for interior-to-boundary connections
|
|
66
|
+
- u_I: Unknown interior displacements
|
|
67
|
+
- u_B: Known boundary displacements
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
adjacency: Adjacency list for each vertex.
|
|
71
|
+
boundary_mask: Boolean array, True for boundary vertices.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Tuple of (L_II, L_IB, interior_indices, boundary_indices).
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
interior_mask = ~boundary_mask
|
|
78
|
+
interior_indices = np.where(interior_mask)[0]
|
|
79
|
+
boundary_indices = np.where(boundary_mask)[0]
|
|
80
|
+
|
|
81
|
+
n_interior = len(interior_indices)
|
|
82
|
+
n_boundary = len(boundary_indices)
|
|
83
|
+
|
|
84
|
+
if n_interior == 0:
|
|
85
|
+
# All vertices are boundary - return empty matrices
|
|
86
|
+
return (
|
|
87
|
+
sparse.csr_matrix((0, 0)),
|
|
88
|
+
sparse.csr_matrix((0, n_boundary)),
|
|
89
|
+
interior_indices,
|
|
90
|
+
boundary_indices,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Create mappings from global to local indices
|
|
94
|
+
interior_map = {idx: i for i, idx in enumerate(interior_indices)}
|
|
95
|
+
boundary_map = {idx: i for i, idx in enumerate(boundary_indices)}
|
|
96
|
+
|
|
97
|
+
# Build sparse matrix data
|
|
98
|
+
rows_ii: list[int] = []
|
|
99
|
+
cols_ii: list[int] = []
|
|
100
|
+
vals_ii: list[float] = []
|
|
101
|
+
|
|
102
|
+
rows_ib: list[int] = []
|
|
103
|
+
cols_ib: list[int] = []
|
|
104
|
+
vals_ib: list[float] = []
|
|
105
|
+
|
|
106
|
+
for idx in interior_indices:
|
|
107
|
+
local_i = interior_map[idx]
|
|
108
|
+
neighbors = adjacency[idx]
|
|
109
|
+
degree = len(neighbors)
|
|
110
|
+
|
|
111
|
+
# Diagonal entry (degree of vertex)
|
|
112
|
+
rows_ii.append(local_i)
|
|
113
|
+
cols_ii.append(local_i)
|
|
114
|
+
vals_ii.append(float(degree))
|
|
115
|
+
|
|
116
|
+
# Off-diagonal entries
|
|
117
|
+
for neighbor in neighbors:
|
|
118
|
+
if interior_mask[neighbor]:
|
|
119
|
+
# Neighbor is interior
|
|
120
|
+
local_j = interior_map[neighbor]
|
|
121
|
+
rows_ii.append(local_i)
|
|
122
|
+
cols_ii.append(local_j)
|
|
123
|
+
vals_ii.append(-1.0)
|
|
124
|
+
else:
|
|
125
|
+
# Neighbor is boundary
|
|
126
|
+
local_j = boundary_map[neighbor]
|
|
127
|
+
rows_ib.append(local_i)
|
|
128
|
+
cols_ib.append(local_j)
|
|
129
|
+
vals_ib.append(-1.0)
|
|
130
|
+
|
|
131
|
+
l_ii = sparse.csr_matrix(
|
|
132
|
+
(vals_ii, (rows_ii, cols_ii)),
|
|
133
|
+
shape=(n_interior, n_interior),
|
|
134
|
+
)
|
|
135
|
+
l_ib = sparse.csr_matrix(
|
|
136
|
+
(vals_ib, (rows_ib, cols_ib)),
|
|
137
|
+
shape=(n_interior, n_boundary),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return l_ii, l_ib, interior_indices, boundary_indices
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def propagate_displacement(
|
|
144
|
+
vertices: NDArray[np.float64],
|
|
145
|
+
elements: NDArray[np.int32],
|
|
146
|
+
boundary_mask: NDArray[np.bool_],
|
|
147
|
+
boundary_displacement: NDArray[np.float64],
|
|
148
|
+
) -> NDArray[np.float64]:
|
|
149
|
+
"""Propagate displacement from boundary to interior using Laplacian smoothing.
|
|
150
|
+
|
|
151
|
+
Solves the Laplace equation nabla^2 u = 0 with Dirichlet boundary conditions
|
|
152
|
+
u = boundary_displacement on the boundary. This produces a smooth displacement
|
|
153
|
+
field that transitions from boundary values to interior.
|
|
154
|
+
|
|
155
|
+
The complexity is O(n) for building the matrix and typically O(n^1.5) for
|
|
156
|
+
solving due to the sparse structure.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
vertices: Nx2 or Nx3 array of vertex coordinates.
|
|
160
|
+
elements: Mx(nodes_per_element) array of element connectivity.
|
|
161
|
+
boundary_mask: N boolean array, True for vertices with prescribed displacement.
|
|
162
|
+
boundary_displacement: Nxdim array of displacement vectors.
|
|
163
|
+
Only values at boundary vertices (where boundary_mask is True) are used.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Nxdim array of displacement for all vertices.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ValueError: If array dimensions don't match.
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
n_vertices = len(vertices)
|
|
173
|
+
n_dims = vertices.shape[1]
|
|
174
|
+
|
|
175
|
+
if len(boundary_mask) != n_vertices:
|
|
176
|
+
msg = f"boundary_mask length {len(boundary_mask)} != n_vertices {n_vertices}"
|
|
177
|
+
raise ValueError(msg)
|
|
178
|
+
|
|
179
|
+
if boundary_displacement.shape[0] != n_vertices:
|
|
180
|
+
msg = (
|
|
181
|
+
f"boundary_displacement rows {boundary_displacement.shape[0]} "
|
|
182
|
+
f"!= n_vertices {n_vertices}"
|
|
183
|
+
)
|
|
184
|
+
raise ValueError(msg)
|
|
185
|
+
|
|
186
|
+
if boundary_displacement.shape[1] != n_dims:
|
|
187
|
+
msg = (
|
|
188
|
+
f"boundary_displacement columns {boundary_displacement.shape[1]} "
|
|
189
|
+
f"!= n_dims {n_dims}"
|
|
190
|
+
)
|
|
191
|
+
raise ValueError(msg)
|
|
192
|
+
|
|
193
|
+
n_boundary = np.sum(boundary_mask)
|
|
194
|
+
if n_boundary == 0:
|
|
195
|
+
# No boundary vertices - return zero displacement
|
|
196
|
+
return np.zeros_like(vertices)
|
|
197
|
+
|
|
198
|
+
if n_boundary == n_vertices:
|
|
199
|
+
# All vertices are boundary - return boundary displacement directly
|
|
200
|
+
return boundary_displacement.copy()
|
|
201
|
+
|
|
202
|
+
# Build adjacency and Laplacian system
|
|
203
|
+
adjacency = _build_adjacency_from_elements(n_vertices, elements)
|
|
204
|
+
l_ii, l_ib, interior_indices, boundary_indices = _build_laplacian_system(
|
|
205
|
+
adjacency,
|
|
206
|
+
boundary_mask,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Initialize result with boundary values
|
|
210
|
+
result = np.zeros((n_vertices, n_dims), dtype=np.float64)
|
|
211
|
+
result[boundary_mask] = boundary_displacement[boundary_mask]
|
|
212
|
+
|
|
213
|
+
# Solve for each dimension independently
|
|
214
|
+
u_b = boundary_displacement[boundary_indices]
|
|
215
|
+
|
|
216
|
+
for dim in range(n_dims):
|
|
217
|
+
rhs = -l_ib @ u_b[:, dim]
|
|
218
|
+
u_i = spsolve(l_ii, rhs)
|
|
219
|
+
result[interior_indices, dim] = u_i
|
|
220
|
+
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _get_elements(
|
|
225
|
+
mesh: MmgMesh2D | MmgMesh3D | MmgMeshS,
|
|
226
|
+
*,
|
|
227
|
+
is_3d: bool,
|
|
228
|
+
) -> NDArray[np.int32]:
|
|
229
|
+
"""Get elements from mesh based on mesh type."""
|
|
230
|
+
if is_3d:
|
|
231
|
+
# Use cast since we've verified is_3d means mesh has get_tetrahedra
|
|
232
|
+
return cast("Any", mesh).get_tetrahedra()
|
|
233
|
+
return mesh.get_triangles()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _set_mesh_data(
|
|
237
|
+
mesh: MmgMesh2D | MmgMesh3D | MmgMeshS,
|
|
238
|
+
vertices: NDArray[np.float64],
|
|
239
|
+
elements: NDArray[np.int32],
|
|
240
|
+
*,
|
|
241
|
+
is_3d: bool,
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Set vertices and elements on mesh, reinitializing internal structures."""
|
|
244
|
+
# Ensure vertices is float64
|
|
245
|
+
verts = np.asarray(vertices, dtype=np.float64)
|
|
246
|
+
if is_3d:
|
|
247
|
+
# MmgMesh3D has set_vertices_and_elements
|
|
248
|
+
cast("Any", mesh).set_vertices_and_elements(verts, elements)
|
|
249
|
+
else:
|
|
250
|
+
# MmgMesh2D uses separate methods
|
|
251
|
+
mesh.set_mesh_size(vertices=len(verts), triangles=len(elements))
|
|
252
|
+
mesh.set_vertices(verts)
|
|
253
|
+
mesh.set_triangles(elements)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _validate_displacement(
|
|
257
|
+
displacement: NDArray[np.float64],
|
|
258
|
+
n_vertices: int,
|
|
259
|
+
n_dims: int,
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Validate displacement array dimensions."""
|
|
262
|
+
if displacement.shape[0] != n_vertices:
|
|
263
|
+
msg = f"Displacement rows {displacement.shape[0]} != n_vertices {n_vertices}"
|
|
264
|
+
raise ValueError(msg)
|
|
265
|
+
|
|
266
|
+
if displacement.shape[1] != n_dims:
|
|
267
|
+
msg = f"Displacement columns {displacement.shape[1]} != n_dims {n_dims}"
|
|
268
|
+
raise ValueError(msg)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def move_mesh(
|
|
272
|
+
mesh: MmgMesh2D | MmgMesh3D | MmgMeshS,
|
|
273
|
+
displacement: NDArray[np.float64],
|
|
274
|
+
*,
|
|
275
|
+
boundary_mask: NDArray[np.bool_] | None = None,
|
|
276
|
+
propagate: bool = True,
|
|
277
|
+
n_steps: int = 1,
|
|
278
|
+
**remesh_options: float | bool | None,
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Move mesh vertices by displacement and remesh to maintain quality.
|
|
281
|
+
|
|
282
|
+
This is a pure Python implementation of Lagrangian motion that works
|
|
283
|
+
without the ELAS library. For large displacements, consider using
|
|
284
|
+
multiple steps (n_steps > 1) to avoid mesh inversion.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
mesh: MmgMesh2D, MmgMesh3D, or MmgMeshS mesh object.
|
|
288
|
+
displacement: Nxdim array of displacement vectors for each vertex.
|
|
289
|
+
If boundary_mask is provided and propagate=True, only boundary
|
|
290
|
+
values need to be correct; interior values will be computed.
|
|
291
|
+
boundary_mask: Optional boolean array indicating which vertices have
|
|
292
|
+
prescribed displacement. If None, all vertices are treated as
|
|
293
|
+
having prescribed displacement (no propagation needed).
|
|
294
|
+
propagate: If True and boundary_mask is provided, propagate boundary
|
|
295
|
+
displacement to interior using Laplacian smoothing.
|
|
296
|
+
n_steps: Number of incremental steps to apply the displacement.
|
|
297
|
+
Use more steps for large displacements to avoid mesh inversion.
|
|
298
|
+
**remesh_options: Options passed to mesh.remesh() (hmax, hmin, etc.).
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
ValueError: If displacement dimensions don't match mesh.
|
|
302
|
+
RuntimeError: If remeshing fails.
|
|
303
|
+
|
|
304
|
+
"""
|
|
305
|
+
vertices = mesh.get_vertices()
|
|
306
|
+
n_vertices = len(vertices)
|
|
307
|
+
n_dims = vertices.shape[1]
|
|
308
|
+
is_3d = hasattr(mesh, "get_tetrahedra")
|
|
309
|
+
|
|
310
|
+
_validate_displacement(displacement, n_vertices, n_dims)
|
|
311
|
+
|
|
312
|
+
elements = _get_elements(mesh, is_3d=is_3d)
|
|
313
|
+
|
|
314
|
+
# Propagate displacement if needed
|
|
315
|
+
if boundary_mask is not None and propagate:
|
|
316
|
+
full_displacement = propagate_displacement(
|
|
317
|
+
vertices,
|
|
318
|
+
elements,
|
|
319
|
+
boundary_mask,
|
|
320
|
+
displacement,
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
full_displacement = displacement.copy()
|
|
324
|
+
|
|
325
|
+
# Apply displacement in steps
|
|
326
|
+
step_displacement = full_displacement / n_steps
|
|
327
|
+
|
|
328
|
+
# Filter out None values from remesh options
|
|
329
|
+
filtered_options: dict[str, float | int | bool] = {
|
|
330
|
+
k: v for k, v in remesh_options.items() if v is not None
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for _ in range(n_steps):
|
|
334
|
+
current_vertices = mesh.get_vertices()
|
|
335
|
+
new_vertices = np.asarray(
|
|
336
|
+
current_vertices + step_displacement,
|
|
337
|
+
dtype=np.float64,
|
|
338
|
+
)
|
|
339
|
+
current_elements = _get_elements(mesh, is_3d=is_3d)
|
|
340
|
+
|
|
341
|
+
_set_mesh_data(mesh, new_vertices, current_elements, is_3d=is_3d)
|
|
342
|
+
mesh.remesh(**filtered_options) # type: ignore[arg-type]
|
|
343
|
+
|
|
344
|
+
# Break if topology changed (can't continue incremental steps)
|
|
345
|
+
if len(mesh.get_vertices()) != len(current_vertices):
|
|
346
|
+
break
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def detect_boundary_vertices(
|
|
350
|
+
mesh: MmgMesh2D | MmgMesh3D | MmgMeshS,
|
|
351
|
+
) -> NDArray[np.bool_]:
|
|
352
|
+
"""Detect boundary vertices in a mesh.
|
|
353
|
+
|
|
354
|
+
Boundary vertices are those that lie on the exterior surface of the mesh.
|
|
355
|
+
For 3D meshes, these are vertices on surface triangles.
|
|
356
|
+
For 2D/surface meshes, these are vertices on boundary edges.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
mesh: MmgMesh2D, MmgMesh3D, or MmgMeshS mesh object.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Boolean array of length n_vertices, True for boundary vertices.
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
n_vertices = len(mesh.get_vertices())
|
|
366
|
+
boundary_mask = np.zeros(n_vertices, dtype=bool)
|
|
367
|
+
|
|
368
|
+
# Check if mesh has edges (2D/surface meshes)
|
|
369
|
+
try:
|
|
370
|
+
edges = mesh.get_edges()
|
|
371
|
+
if len(edges) > 0:
|
|
372
|
+
boundary_mask[edges.ravel()] = True
|
|
373
|
+
return boundary_mask
|
|
374
|
+
except (AttributeError, RuntimeError):
|
|
375
|
+
pass
|
|
376
|
+
|
|
377
|
+
# For 3D meshes, use surface triangles
|
|
378
|
+
try:
|
|
379
|
+
triangles = mesh.get_triangles()
|
|
380
|
+
if len(triangles) > 0:
|
|
381
|
+
boundary_mask[triangles.ravel()] = True
|
|
382
|
+
return boundary_mask
|
|
383
|
+
except (AttributeError, RuntimeError):
|
|
384
|
+
pass
|
|
385
|
+
|
|
386
|
+
# Fallback: treat all vertices as interior (no boundary)
|
|
387
|
+
return boundary_mask
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
__all__ = [
|
|
391
|
+
"detect_boundary_vertices",
|
|
392
|
+
"move_mesh",
|
|
393
|
+
"propagate_displacement",
|
|
394
|
+
]
|
mmgpy/lib/libmmg2d.so
ADDED
|
Binary file
|
mmgpy/lib/libmmg2d.so.5
ADDED
|
Binary file
|
|
Binary file
|
mmgpy/lib/libmmg3d.so
ADDED
|
Binary file
|
mmgpy/lib/libmmg3d.so.5
ADDED
|
Binary file
|
|
Binary file
|
mmgpy/lib/libmmgs.so
ADDED
|
Binary file
|
mmgpy/lib/libmmgs.so.5
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|