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.
Files changed (109) hide show
  1. mmgpy/__init__.py +296 -0
  2. mmgpy/__main__.py +13 -0
  3. mmgpy/_io.py +535 -0
  4. mmgpy/_logging.py +290 -0
  5. mmgpy/_mesh.py +2286 -0
  6. mmgpy/_mmgpy.cpython-311-x86_64-linux-gnu.so +0 -0
  7. mmgpy/_mmgpy.pyi +2140 -0
  8. mmgpy/_options.py +304 -0
  9. mmgpy/_progress.py +850 -0
  10. mmgpy/_pyvista.py +410 -0
  11. mmgpy/_result.py +143 -0
  12. mmgpy/_transfer.py +273 -0
  13. mmgpy/_validation.py +669 -0
  14. mmgpy/_version.py +3 -0
  15. mmgpy/_version.py.in +3 -0
  16. mmgpy/bin/mmg2d_O3 +0 -0
  17. mmgpy/bin/mmg3d_O3 +0 -0
  18. mmgpy/bin/mmgs_O3 +0 -0
  19. mmgpy/interactive/__init__.py +24 -0
  20. mmgpy/interactive/sizing_editor.py +790 -0
  21. mmgpy/lagrangian.py +394 -0
  22. mmgpy/lib/libmmg2d.so +0 -0
  23. mmgpy/lib/libmmg2d.so.5 +0 -0
  24. mmgpy/lib/libmmg2d.so.5.8.0 +0 -0
  25. mmgpy/lib/libmmg3d.so +0 -0
  26. mmgpy/lib/libmmg3d.so.5 +0 -0
  27. mmgpy/lib/libmmg3d.so.5.8.0 +0 -0
  28. mmgpy/lib/libmmgs.so +0 -0
  29. mmgpy/lib/libmmgs.so.5 +0 -0
  30. mmgpy/lib/libmmgs.so.5.8.0 +0 -0
  31. mmgpy/lib/libvtkCommonColor-9.5.so.1 +0 -0
  32. mmgpy/lib/libvtkCommonComputationalGeometry-9.5.so.1 +0 -0
  33. mmgpy/lib/libvtkCommonCore-9.5.so.1 +0 -0
  34. mmgpy/lib/libvtkCommonDataModel-9.5.so.1 +0 -0
  35. mmgpy/lib/libvtkCommonExecutionModel-9.5.so.1 +0 -0
  36. mmgpy/lib/libvtkCommonMath-9.5.so.1 +0 -0
  37. mmgpy/lib/libvtkCommonMisc-9.5.so.1 +0 -0
  38. mmgpy/lib/libvtkCommonSystem-9.5.so.1 +0 -0
  39. mmgpy/lib/libvtkCommonTransforms-9.5.so.1 +0 -0
  40. mmgpy/lib/libvtkDICOMParser-9.5.so.1 +0 -0
  41. mmgpy/lib/libvtkFiltersCellGrid-9.5.so.1 +0 -0
  42. mmgpy/lib/libvtkFiltersCore-9.5.so.1 +0 -0
  43. mmgpy/lib/libvtkFiltersExtraction-9.5.so.1 +0 -0
  44. mmgpy/lib/libvtkFiltersGeneral-9.5.so.1 +0 -0
  45. mmgpy/lib/libvtkFiltersGeometry-9.5.so.1 +0 -0
  46. mmgpy/lib/libvtkFiltersHybrid-9.5.so.1 +0 -0
  47. mmgpy/lib/libvtkFiltersHyperTree-9.5.so.1 +0 -0
  48. mmgpy/lib/libvtkFiltersModeling-9.5.so.1 +0 -0
  49. mmgpy/lib/libvtkFiltersParallel-9.5.so.1 +0 -0
  50. mmgpy/lib/libvtkFiltersReduction-9.5.so.1 +0 -0
  51. mmgpy/lib/libvtkFiltersSources-9.5.so.1 +0 -0
  52. mmgpy/lib/libvtkFiltersStatistics-9.5.so.1 +0 -0
  53. mmgpy/lib/libvtkFiltersTexture-9.5.so.1 +0 -0
  54. mmgpy/lib/libvtkFiltersVerdict-9.5.so.1 +0 -0
  55. mmgpy/lib/libvtkIOCellGrid-9.5.so.1 +0 -0
  56. mmgpy/lib/libvtkIOCore-9.5.so.1 +0 -0
  57. mmgpy/lib/libvtkIOGeometry-9.5.so.1 +0 -0
  58. mmgpy/lib/libvtkIOImage-9.5.so.1 +0 -0
  59. mmgpy/lib/libvtkIOLegacy-9.5.so.1 +0 -0
  60. mmgpy/lib/libvtkIOParallel-9.5.so.1 +0 -0
  61. mmgpy/lib/libvtkIOParallelXML-9.5.so.1 +0 -0
  62. mmgpy/lib/libvtkIOXML-9.5.so.1 +0 -0
  63. mmgpy/lib/libvtkIOXMLParser-9.5.so.1 +0 -0
  64. mmgpy/lib/libvtkImagingCore-9.5.so.1 +0 -0
  65. mmgpy/lib/libvtkImagingSources-9.5.so.1 +0 -0
  66. mmgpy/lib/libvtkParallelCore-9.5.so.1 +0 -0
  67. mmgpy/lib/libvtkParallelDIY-9.5.so.1 +0 -0
  68. mmgpy/lib/libvtkRenderingCore-9.5.so.1 +0 -0
  69. mmgpy/lib/libvtkdoubleconversion-9.5.so.1 +0 -0
  70. mmgpy/lib/libvtkexpat-9.5.so.1 +0 -0
  71. mmgpy/lib/libvtkfmt-9.5.so.1 +0 -0
  72. mmgpy/lib/libvtkjpeg-9.5.so.1 +0 -0
  73. mmgpy/lib/libvtkjsoncpp-9.5.so.1 +0 -0
  74. mmgpy/lib/libvtkkissfft-9.5.so.1 +0 -0
  75. mmgpy/lib/libvtkloguru-9.5.so.1 +0 -0
  76. mmgpy/lib/libvtklz4-9.5.so.1 +0 -0
  77. mmgpy/lib/libvtklzma-9.5.so.1 +0 -0
  78. mmgpy/lib/libvtkmetaio-9.5.so.1 +0 -0
  79. mmgpy/lib/libvtkpng-9.5.so.1 +0 -0
  80. mmgpy/lib/libvtkpugixml-9.5.so.1 +0 -0
  81. mmgpy/lib/libvtksys-9.5.so.1 +0 -0
  82. mmgpy/lib/libvtktiff-9.5.so.1 +0 -0
  83. mmgpy/lib/libvtktoken-9.5.so.1 +0 -0
  84. mmgpy/lib/libvtkverdict-9.5.so.1 +0 -0
  85. mmgpy/lib/libvtkzlib-9.5.so.1 +0 -0
  86. mmgpy/metrics.py +596 -0
  87. mmgpy/progress.py +69 -0
  88. mmgpy/py.typed +0 -0
  89. mmgpy/repair/__init__.py +37 -0
  90. mmgpy/repair/_core.py +226 -0
  91. mmgpy/repair/_elements.py +241 -0
  92. mmgpy/repair/_vertices.py +219 -0
  93. mmgpy/sizing.py +370 -0
  94. mmgpy/ui/__init__.py +97 -0
  95. mmgpy/ui/__main__.py +87 -0
  96. mmgpy/ui/app.py +1837 -0
  97. mmgpy/ui/parsers.py +501 -0
  98. mmgpy/ui/remeshing.py +448 -0
  99. mmgpy/ui/samples.py +249 -0
  100. mmgpy/ui/utils.py +280 -0
  101. mmgpy/ui/viewer.py +587 -0
  102. mmgpy-0.5.0.dist-info/METADATA +186 -0
  103. mmgpy-0.5.0.dist-info/RECORD +109 -0
  104. mmgpy-0.5.0.dist-info/WHEEL +6 -0
  105. mmgpy-0.5.0.dist-info/entry_points.txt +13 -0
  106. mmgpy-0.5.0.dist-info/licenses/LICENSE +38 -0
  107. share/man/man1/mmg2d.1.gz +0 -0
  108. share/man/man1/mmg3d.1.gz +0 -0
  109. 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
Binary file
Binary file
mmgpy/lib/libmmg3d.so ADDED
Binary file
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