nmag-python-3 0.0.2__py3-none-any.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.
- anisotropy/__init__.py +28 -0
- anisotropy/anisotropy.py +13 -0
- anisotropy/evaluation.py +70 -0
- anisotropy/model.py +200 -0
- anisotropy/predefined.py +202 -0
- anisotropy/py.typed +1 -0
- anisotropy/values.py +84 -0
- mag_material/__init__.py +3 -0
- mag_material/mag_material.py +231 -0
- mag_material/parameters.py +150 -0
- mag_material/py.typed +1 -0
- nmag/__init__.py +36 -0
- nmag/backends.py +493 -0
- nmag/checkpoint.py +327 -0
- nmag/config.py +174 -0
- nmag/demag/__init__.py +39 -0
- nmag/demag/bem_operator.py +149 -0
- nmag/demag/geometry.py +134 -0
- nmag/demag/lindholm.py +133 -0
- nmag/demag/lindholm_fast.py +463 -0
- nmag/demag/linear.py +489 -0
- nmag/dynamics/__init__.py +255 -0
- nmag/output.py +27 -0
- nmag/parallel.py +45 -0
- nmag/py.typed +1 -0
- nmag/resources.py +38 -0
- nmag/simulation/__init__.py +519 -0
- nmag/simulation/anisotropy/__init__.py +5 -0
- nmag/simulation/anisotropy/fields.py +56 -0
- nmag/simulation/anisotropy/materials.py +138 -0
- nmag/simulation/demag/__init__.py +1 -0
- nmag/simulation/demag/bem/__init__.py +11 -0
- nmag/simulation/demag/bem/diagnostics.py +73 -0
- nmag/simulation/demag/bem/dirichlet.py +85 -0
- nmag/simulation/demag/bem/hierarchical.py +74 -0
- nmag/simulation/demag/bem/operator.py +292 -0
- nmag/simulation/demag/fem/__init__.py +11 -0
- nmag/simulation/demag/fem/assembly.py +114 -0
- nmag/simulation/demag/fem/charges.py +38 -0
- nmag/simulation/demag/fem/geometry.py +261 -0
- nmag/simulation/demag/fields/__init__.py +11 -0
- nmag/simulation/demag/fields/auxiliary.py +186 -0
- nmag/simulation/demag/fields/probe.py +77 -0
- nmag/simulation/demag/fields/recovery.py +207 -0
- nmag/simulation/demag/solver.py +17 -0
- nmag/simulation/dynamics/__init__.py +92 -0
- nmag/simulation/dynamics/advance.py +199 -0
- nmag/simulation/dynamics/integrator.py +263 -0
- nmag/simulation/exchange/__init__.py +13 -0
- nmag/simulation/exchange/coefficients.py +185 -0
- nmag/simulation/exchange/fields.py +106 -0
- nmag/simulation/exchange/llg_rhs.py +218 -0
- nmag/simulation/fields/__init__.py +19 -0
- nmag/simulation/fields/arrays.py +111 -0
- nmag/simulation/fields/availability.py +188 -0
- nmag/simulation/fields/averages.py +293 -0
- nmag/simulation/fields/derived.py +164 -0
- nmag/simulation/fields/maxangle.py +163 -0
- nmag/simulation/fields/probes.py +106 -0
- nmag/simulation/implicit_dynamics.py +221 -0
- nmag/simulation/mesh/__init__.py +13 -0
- nmag/simulation/mesh/geometry.py +153 -0
- nmag/simulation/mesh/materials.py +299 -0
- nmag/simulation/mesh/probe.py +234 -0
- nmag/simulation/restart.py +103 -0
- nmag/simulation/support.py +224 -0
- nmag_python_3-0.0.2.dist-info/METADATA +157 -0
- nmag_python_3-0.0.2.dist-info/RECORD +147 -0
- nmag_python_3-0.0.2.dist-info/WHEEL +5 -0
- nmag_python_3-0.0.2.dist-info/licenses/LICENSE +339 -0
- nmag_python_3-0.0.2.dist-info/top_level.txt +8 -0
- nmesh/__init__.py +130 -0
- nmesh/backend.py +286 -0
- nmesh/geometry/__init__.py +52 -0
- nmesh/geometry/boolean_operations.py +157 -0
- nmesh/geometry/primitives.py +453 -0
- nmesh/geometry/transform.py +126 -0
- nmesh/io/__init__.py +50 -0
- nmesh/io/ascii.py +132 -0
- nmesh/io/legacy_nmesh_hdf5.py +318 -0
- nmesh/io/meshio_support.py +170 -0
- nmesh/mesh_generation.py +182 -0
- nmesh/mesh_io.py +227 -0
- nmesh/mesh_model.py +147 -0
- nmesh/mesh_utilities.py +79 -0
- nmesh/mesher/__init__.py +21 -0
- nmesh/mesher/driver.py +146 -0
- nmesh/mesher/meshing_defaults.py +252 -0
- nmesh/mesher/meshing_parameters.py +185 -0
- nmesh/mesher/parity.py +21 -0
- nmesh/mesher/parity_canonical.py +142 -0
- nmesh/mesher/parity_comparison.py +191 -0
- nmesh/mesher/parity_metrics.py +114 -0
- nmesh/mesher/periodic.py +97 -0
- nmesh/mesher/relaxation/__init__.py +14 -0
- nmesh/mesher/relaxation/_constants.py +20 -0
- nmesh/mesher/relaxation/_types.py +15 -0
- nmesh/mesher/relaxation/density.py +170 -0
- nmesh/mesher/relaxation/engine/__init__.py +18 -0
- nmesh/mesher/relaxation/engine/state.py +155 -0
- nmesh/mesher/relaxation/engine/steps.py +248 -0
- nmesh/mesher/relaxation/engine/topology.py +230 -0
- nmesh/mesher/relaxation/forces/__init__.py +96 -0
- nmesh/mesher/relaxation/forces/jit.py +102 -0
- nmesh/mesher/relaxation/forces/neighbors.py +186 -0
- nmesh/mesher/relaxation/forces/simplex.py +302 -0
- nmesh/mesher/relaxation/forces/summary.py +207 -0
- nmesh/mesher/relaxation/forces/types.py +92 -0
- nmesh/mesher/relaxation/geometry/__init__.py +6 -0
- nmesh/mesher/relaxation/geometry/builder.py +154 -0
- nmesh/mesher/relaxation/geometry/model.py +194 -0
- nmesh/mesher/relaxation/seeding/__init__.py +74 -0
- nmesh/mesher/relaxation/seeding/periodic.py +88 -0
- nmesh/mesher/relaxation/seeding/points.py +88 -0
- nmesh/mesher/relaxation/seeding/sampling.py +142 -0
- nmesh/mesher/relaxation/topology/__init__.py +297 -0
- nmesh/mesher/relaxation/topology/finalize.py +78 -0
- nmesh/mesher/relaxation/topology/recovery.py +310 -0
- nmesh/mesher/sectioned_config.py +70 -0
- nmesh/nmesh.py +99 -0
- nmesh/py.typed +1 -0
- nmesh/utils/__init__.py +33 -0
- nmesh/utils/array_list_utils.py +128 -0
- nmesh/utils/constants.py +22 -0
- nmesh/utils/timing_memory_utils.py +51 -0
- nmesh/utils/types.py +13 -0
- si/constants.py +49 -0
- si/physical.py +722 -0
- si/py.typed +1 -0
- simulation/__init__.py +1 -0
- simulation/clock.py +237 -0
- simulation/data_writer.py +273 -0
- simulation/data_writer_collection.py +267 -0
- simulation/hysteresis.py +74 -0
- simulation/hysteresis_runner.py +286 -0
- simulation/hysteresis_schedule.py +180 -0
- simulation/inference/__init__.py +3 -0
- simulation/inference/inference.py +95 -0
- simulation/py.typed +1 -0
- simulation/quantity.py +88 -0
- simulation/simulation_core.py +458 -0
- throttler/__init__.py +3 -0
- throttler/py.typed +1 -0
- throttler/throttler.py +55 -0
- when/__init__.py +3 -0
- when/py.typed +1 -0
- when/when.py +416 -0
nmesh/backend.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Protocol, TypeAlias, runtime_checkable
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .geometry import Body
|
|
10
|
+
|
|
11
|
+
Point: TypeAlias = list[float]
|
|
12
|
+
Simplex: TypeAlias = list[int]
|
|
13
|
+
Surface: TypeAlias = list[int]
|
|
14
|
+
BodyHandle: TypeAlias = object
|
|
15
|
+
MesherDriver: TypeAlias = Callable[..., object]
|
|
16
|
+
MesherConfig: TypeAlias = dict[str, Any]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class RawMesh:
|
|
21
|
+
points: list[Point] = field(default_factory=list[Point])
|
|
22
|
+
simplices: list[Simplex] = field(default_factory=list[Simplex])
|
|
23
|
+
regions: list[int] = field(default_factory=list[int])
|
|
24
|
+
point_regions: list[list[int]] = field(default_factory=list[list[int]])
|
|
25
|
+
surfaces: list[Surface] = field(default_factory=list[Surface])
|
|
26
|
+
links: list[tuple[int, int]] = field(default_factory=list[tuple[int, int]])
|
|
27
|
+
region_volumes: list[float] = field(default_factory=list[float])
|
|
28
|
+
periodic_point_indices: list[list[int]] = field(default_factory=list[list[int]])
|
|
29
|
+
permutation: list[int] = field(default_factory=list[int])
|
|
30
|
+
dim: int = 3
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@runtime_checkable
|
|
34
|
+
class MeshBackendProtocol(Protocol):
|
|
35
|
+
def mesh_scale_node_positions(self, raw_mesh: RawMesh, scale: float) -> None: ...
|
|
36
|
+
def mesh_writefile(self, path: str, raw_mesh: RawMesh) -> None: ...
|
|
37
|
+
def mesh_nr_simplices(self, raw_mesh: RawMesh) -> int: ...
|
|
38
|
+
def mesh_nr_points(self, raw_mesh: RawMesh) -> int: ...
|
|
39
|
+
def mesh_plotinfo(self, raw_mesh: RawMesh) -> list[object]: ...
|
|
40
|
+
def mesh_plotinfo_points(self, raw_mesh: RawMesh) -> list[list[float]]: ...
|
|
41
|
+
def mesh_plotinfo_pointsregions(self, raw_mesh: RawMesh) -> list[list[int]]: ...
|
|
42
|
+
def mesh_plotinfo_simplices(self, raw_mesh: RawMesh) -> list[list[int]]: ...
|
|
43
|
+
def mesh_plotinfo_simplicesregions(self, raw_mesh: RawMesh) -> list[int]: ...
|
|
44
|
+
def mesh_plotinfo_surfaces_and_surfacesregions(
|
|
45
|
+
self, raw_mesh: RawMesh
|
|
46
|
+
) -> tuple[list[Surface], list[int]]: ...
|
|
47
|
+
def mesh_plotinfo_links(self, raw_mesh: RawMesh) -> list[tuple[int, int]]: ...
|
|
48
|
+
def mesh_dim(self, raw_mesh: RawMesh) -> int: ...
|
|
49
|
+
def mesh_plotinfo_regionvolumes(self, raw_mesh: RawMesh) -> list[float]: ...
|
|
50
|
+
def mesh_plotinfo_periodic_points_indices(self, raw_mesh: RawMesh) -> list[list[int]]: ...
|
|
51
|
+
def mesh_set_vertex_distribution(self, raw_mesh: RawMesh, dist: object) -> None: ...
|
|
52
|
+
def mesh_get_permutation(self, raw_mesh: RawMesh) -> list[int]: ...
|
|
53
|
+
def mesh_readfile(self, filename: str, do_reorder: bool, do_distribute: bool) -> RawMesh: ...
|
|
54
|
+
def copy_mesher_defaults(self, defaults: MesherConfig) -> MesherConfig: ...
|
|
55
|
+
def mesh_bodies_raw(
|
|
56
|
+
self,
|
|
57
|
+
driver: MesherDriver,
|
|
58
|
+
mesher: MesherConfig,
|
|
59
|
+
bb_min: Point,
|
|
60
|
+
bb_max: Point,
|
|
61
|
+
mesh_ext: int,
|
|
62
|
+
objects: list[Body],
|
|
63
|
+
a0: float,
|
|
64
|
+
density: str,
|
|
65
|
+
fixed: list[Point],
|
|
66
|
+
mobile: list[Point],
|
|
67
|
+
simply: list[Point],
|
|
68
|
+
periodic: list[float],
|
|
69
|
+
cache: str,
|
|
70
|
+
hints: Sequence[Sequence[object]],
|
|
71
|
+
) -> RawMesh: ...
|
|
72
|
+
def mesh_from_points_and_simplices(
|
|
73
|
+
self,
|
|
74
|
+
dim: int,
|
|
75
|
+
points: list[list[float]],
|
|
76
|
+
simplices: list[list[int]],
|
|
77
|
+
regions: list[int],
|
|
78
|
+
periodic: list[list[int]],
|
|
79
|
+
reorder: bool,
|
|
80
|
+
distribute: bool,
|
|
81
|
+
) -> RawMesh: ...
|
|
82
|
+
def body_union(self, objs: Sequence[BodyHandle]) -> BodyHandle: ...
|
|
83
|
+
def body_difference(self, obj1: BodyHandle, objs: Sequence[BodyHandle]) -> BodyHandle: ...
|
|
84
|
+
def body_intersection(self, objs: Sequence[BodyHandle]) -> BodyHandle: ...
|
|
85
|
+
def body_shifted_sc(self, obj: BodyHandle, shift: Sequence[float]) -> BodyHandle: ...
|
|
86
|
+
def body_shifted_bc(self, obj: BodyHandle, shift: Sequence[float]) -> BodyHandle: ...
|
|
87
|
+
def body_scaled(self, obj: BodyHandle, scale: Sequence[float]) -> BodyHandle: ...
|
|
88
|
+
def body_rotated_sc(self, obj: BodyHandle, a1: int, a2: int, ang: float) -> BodyHandle: ...
|
|
89
|
+
def body_rotated_bc(self, obj: BodyHandle, a1: int, a2: int, ang: float) -> BodyHandle: ...
|
|
90
|
+
def body_rotated_axis_sc(
|
|
91
|
+
self, obj: BodyHandle, axis: Sequence[float], ang: float
|
|
92
|
+
) -> BodyHandle: ...
|
|
93
|
+
def body_rotated_axis_bc(
|
|
94
|
+
self, obj: BodyHandle, axis: Sequence[float], ang: float
|
|
95
|
+
) -> BodyHandle: ...
|
|
96
|
+
def body_box(self, p1: Sequence[float], p2: Sequence[float]) -> BodyHandle: ...
|
|
97
|
+
def body_ellipsoid(self, length: Sequence[float]) -> BodyHandle: ...
|
|
98
|
+
def body_frustum(
|
|
99
|
+
self, c1: Sequence[float], r1: float, c2: Sequence[float], r2: float
|
|
100
|
+
) -> BodyHandle: ...
|
|
101
|
+
def body_helix(
|
|
102
|
+
self, c1: Sequence[float], r1: float, c2: Sequence[float], r2: float
|
|
103
|
+
) -> BodyHandle: ...
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def mesher_defaults(self) -> MesherConfig: ...
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class StubMeshBackend:
|
|
110
|
+
"""Lightweight in-memory backend used until the Python mesher is complete."""
|
|
111
|
+
|
|
112
|
+
def mesh_scale_node_positions(self, raw_mesh: RawMesh, scale: float) -> None:
|
|
113
|
+
for point in raw_mesh.points:
|
|
114
|
+
for index, value in enumerate(point):
|
|
115
|
+
point[index] = value * scale
|
|
116
|
+
volume_scale = abs(scale) ** raw_mesh.dim
|
|
117
|
+
raw_mesh.region_volumes = [volume * volume_scale for volume in raw_mesh.region_volumes]
|
|
118
|
+
|
|
119
|
+
def mesh_writefile(self, path: str, raw_mesh: RawMesh) -> None:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def mesh_nr_simplices(self, raw_mesh: RawMesh) -> int:
|
|
123
|
+
return len(raw_mesh.simplices)
|
|
124
|
+
|
|
125
|
+
def mesh_nr_points(self, raw_mesh: RawMesh) -> int:
|
|
126
|
+
return len(raw_mesh.points)
|
|
127
|
+
|
|
128
|
+
def mesh_plotinfo(self, raw_mesh: RawMesh) -> list[object]:
|
|
129
|
+
return [
|
|
130
|
+
raw_mesh.points,
|
|
131
|
+
raw_mesh.links,
|
|
132
|
+
[raw_mesh.simplices, raw_mesh.point_regions, raw_mesh.regions],
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
def mesh_plotinfo_points(self, raw_mesh: RawMesh) -> list[Point]:
|
|
136
|
+
return raw_mesh.points
|
|
137
|
+
|
|
138
|
+
def mesh_plotinfo_pointsregions(self, raw_mesh: RawMesh) -> list[list[int]]:
|
|
139
|
+
return raw_mesh.point_regions
|
|
140
|
+
|
|
141
|
+
def mesh_plotinfo_simplices(self, raw_mesh: RawMesh) -> list[Simplex]:
|
|
142
|
+
return raw_mesh.simplices
|
|
143
|
+
|
|
144
|
+
def mesh_plotinfo_simplicesregions(self, raw_mesh: RawMesh) -> list[int]:
|
|
145
|
+
return raw_mesh.regions
|
|
146
|
+
|
|
147
|
+
def mesh_plotinfo_surfaces_and_surfacesregions(
|
|
148
|
+
self, raw_mesh: RawMesh
|
|
149
|
+
) -> tuple[list[Surface], list[int]]:
|
|
150
|
+
return raw_mesh.surfaces, []
|
|
151
|
+
|
|
152
|
+
def mesh_plotinfo_links(self, raw_mesh: RawMesh) -> list[tuple[int, int]]:
|
|
153
|
+
return raw_mesh.links
|
|
154
|
+
|
|
155
|
+
def mesh_dim(self, raw_mesh: RawMesh) -> int:
|
|
156
|
+
if raw_mesh.points:
|
|
157
|
+
return len(raw_mesh.points[0])
|
|
158
|
+
return raw_mesh.dim
|
|
159
|
+
|
|
160
|
+
def mesh_plotinfo_regionvolumes(self, raw_mesh: RawMesh) -> list[float]:
|
|
161
|
+
return raw_mesh.region_volumes
|
|
162
|
+
|
|
163
|
+
def mesh_plotinfo_periodic_points_indices(self, raw_mesh: RawMesh) -> list[list[int]]:
|
|
164
|
+
return raw_mesh.periodic_point_indices
|
|
165
|
+
|
|
166
|
+
def mesh_set_vertex_distribution(self, raw_mesh: RawMesh, dist: object) -> None:
|
|
167
|
+
raise NotImplementedError("Manual vertex distribution is not implemented.")
|
|
168
|
+
|
|
169
|
+
def mesh_get_permutation(self, raw_mesh: RawMesh) -> list[int]:
|
|
170
|
+
return raw_mesh.permutation
|
|
171
|
+
|
|
172
|
+
def mesh_readfile(self, filename: str, do_reorder: bool, do_distribute: bool) -> RawMesh:
|
|
173
|
+
return RawMesh()
|
|
174
|
+
|
|
175
|
+
def copy_mesher_defaults(self, defaults: MesherConfig) -> MesherConfig:
|
|
176
|
+
return copy.deepcopy(defaults)
|
|
177
|
+
|
|
178
|
+
def mesh_bodies_raw(
|
|
179
|
+
self,
|
|
180
|
+
driver: MesherDriver,
|
|
181
|
+
mesher: MesherConfig,
|
|
182
|
+
bb_min: Point,
|
|
183
|
+
bb_max: Point,
|
|
184
|
+
mesh_ext: int,
|
|
185
|
+
objects: list[Body],
|
|
186
|
+
a0: float,
|
|
187
|
+
density: str,
|
|
188
|
+
fixed: list[Point],
|
|
189
|
+
mobile: list[Point],
|
|
190
|
+
simply: list[Point],
|
|
191
|
+
periodic: list[float],
|
|
192
|
+
cache: str,
|
|
193
|
+
hints: Sequence[Sequence[object]],
|
|
194
|
+
) -> RawMesh:
|
|
195
|
+
from .mesher.relaxation import mesh_bodies_raw as python_mesh_bodies_raw
|
|
196
|
+
|
|
197
|
+
return python_mesh_bodies_raw(
|
|
198
|
+
driver,
|
|
199
|
+
mesher,
|
|
200
|
+
bb_min,
|
|
201
|
+
bb_max,
|
|
202
|
+
mesh_ext,
|
|
203
|
+
objects,
|
|
204
|
+
a0,
|
|
205
|
+
density,
|
|
206
|
+
fixed,
|
|
207
|
+
mobile,
|
|
208
|
+
simply,
|
|
209
|
+
periodic,
|
|
210
|
+
[list(hint) for hint in hints],
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def mesh_from_points_and_simplices(
|
|
214
|
+
self,
|
|
215
|
+
dim: int,
|
|
216
|
+
points: list[Point],
|
|
217
|
+
simplices: list[Simplex],
|
|
218
|
+
regions: list[int],
|
|
219
|
+
periodic: list[list[int]],
|
|
220
|
+
reorder: bool,
|
|
221
|
+
distribute: bool,
|
|
222
|
+
) -> RawMesh:
|
|
223
|
+
return RawMesh(
|
|
224
|
+
points=points,
|
|
225
|
+
simplices=simplices,
|
|
226
|
+
regions=regions,
|
|
227
|
+
dim=dim,
|
|
228
|
+
periodic_point_indices=periodic,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def body_union(self, objs: Sequence[BodyHandle]) -> BodyHandle:
|
|
232
|
+
return ("union", objs)
|
|
233
|
+
|
|
234
|
+
def body_difference(self, obj1: BodyHandle, objs: Sequence[BodyHandle]) -> BodyHandle:
|
|
235
|
+
return ("difference", obj1, objs)
|
|
236
|
+
|
|
237
|
+
def body_intersection(self, objs: Sequence[BodyHandle]) -> BodyHandle:
|
|
238
|
+
return ("intersection", objs)
|
|
239
|
+
|
|
240
|
+
def body_shifted_sc(self, obj: BodyHandle, shift: Sequence[float]) -> BodyHandle:
|
|
241
|
+
return ("shifted_sc", obj, shift)
|
|
242
|
+
|
|
243
|
+
def body_shifted_bc(self, obj: BodyHandle, shift: Sequence[float]) -> BodyHandle:
|
|
244
|
+
return ("shifted_bc", obj, shift)
|
|
245
|
+
|
|
246
|
+
def body_scaled(self, obj: BodyHandle, scale: Sequence[float]) -> BodyHandle:
|
|
247
|
+
return ("scaled", obj, scale)
|
|
248
|
+
|
|
249
|
+
def body_rotated_sc(self, obj: BodyHandle, a1: int, a2: int, ang: float) -> BodyHandle:
|
|
250
|
+
return ("rotated_sc", obj, a1, a2, ang)
|
|
251
|
+
|
|
252
|
+
def body_rotated_bc(self, obj: BodyHandle, a1: int, a2: int, ang: float) -> BodyHandle:
|
|
253
|
+
return ("rotated_bc", obj, a1, a2, ang)
|
|
254
|
+
|
|
255
|
+
def body_rotated_axis_sc(
|
|
256
|
+
self, obj: BodyHandle, axis: Sequence[float], ang: float
|
|
257
|
+
) -> BodyHandle:
|
|
258
|
+
return ("rotated_axis_sc", obj, axis, ang)
|
|
259
|
+
|
|
260
|
+
def body_rotated_axis_bc(
|
|
261
|
+
self, obj: BodyHandle, axis: Sequence[float], ang: float
|
|
262
|
+
) -> BodyHandle:
|
|
263
|
+
return ("rotated_axis_bc", obj, axis, ang)
|
|
264
|
+
|
|
265
|
+
def body_box(self, p1: Sequence[float], p2: Sequence[float]) -> BodyHandle:
|
|
266
|
+
return ("box", p1, p2)
|
|
267
|
+
|
|
268
|
+
def body_ellipsoid(self, length: Sequence[float]) -> BodyHandle:
|
|
269
|
+
return ("ellipsoid", length)
|
|
270
|
+
|
|
271
|
+
def body_frustum(
|
|
272
|
+
self, c1: Sequence[float], r1: float, c2: Sequence[float], r2: float
|
|
273
|
+
) -> BodyHandle:
|
|
274
|
+
return ("frustum", c1, r1, c2, r2)
|
|
275
|
+
|
|
276
|
+
def body_helix(
|
|
277
|
+
self, c1: Sequence[float], r1: float, c2: Sequence[float], r2: float
|
|
278
|
+
) -> BodyHandle:
|
|
279
|
+
return ("helix", c1, r1, c2, r2)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def mesher_defaults(self) -> MesherConfig:
|
|
283
|
+
return {"parameters": {}}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
backend: MeshBackendProtocol = StubMeshBackend()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geometry and CSG operations for the nmesh meshing library.
|
|
3
|
+
|
|
4
|
+
This package provides pure-Python geometric primitives and boolean operations.
|
|
5
|
+
|
|
6
|
+
Modules:
|
|
7
|
+
primitives: Basic geometric shapes (Box, Ellipsoid, Conic, Helix)
|
|
8
|
+
boolean_operations: CSG operations (union, difference, intersect)
|
|
9
|
+
transform: Affine transformations (shift, scale, rotate)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .boolean_operations import difference, intersect, union
|
|
13
|
+
from .primitives import (
|
|
14
|
+
Body,
|
|
15
|
+
Box,
|
|
16
|
+
Conic,
|
|
17
|
+
Ellipsoid,
|
|
18
|
+
Helix,
|
|
19
|
+
MeshObject,
|
|
20
|
+
bc_box,
|
|
21
|
+
bc_ellipsoid,
|
|
22
|
+
bc_frustum,
|
|
23
|
+
bc_helix,
|
|
24
|
+
)
|
|
25
|
+
from .transform import (
|
|
26
|
+
AffineTransform,
|
|
27
|
+
inverse_axis_rotation,
|
|
28
|
+
inverse_plane_rotation,
|
|
29
|
+
inverse_scale,
|
|
30
|
+
inverse_shift,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"AffineTransform",
|
|
35
|
+
"Body",
|
|
36
|
+
"Box",
|
|
37
|
+
"Conic",
|
|
38
|
+
"Ellipsoid",
|
|
39
|
+
"Helix",
|
|
40
|
+
"MeshObject",
|
|
41
|
+
"bc_box",
|
|
42
|
+
"bc_ellipsoid",
|
|
43
|
+
"bc_frustum",
|
|
44
|
+
"bc_helix",
|
|
45
|
+
"difference",
|
|
46
|
+
"intersect",
|
|
47
|
+
"inverse_axis_rotation",
|
|
48
|
+
"inverse_plane_rotation",
|
|
49
|
+
"inverse_scale",
|
|
50
|
+
"inverse_shift",
|
|
51
|
+
"union",
|
|
52
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constructive Solid Geometry (CSG) operations for mesh objects.
|
|
3
|
+
|
|
4
|
+
This module provides boolean operations (union, difference, intersection)
|
|
5
|
+
for combining geometric primitives into complex shapes. Operations are
|
|
6
|
+
implemented using signed distance field composition with NumPy.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from typing import cast
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from ..utils.types import FloatArray
|
|
17
|
+
from .primitives import Body, MeshObject, _make_body
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _validate_objects(objects: object, operation: str) -> tuple[MeshObject, ...]:
|
|
21
|
+
if not isinstance(objects, Sequence):
|
|
22
|
+
raise TypeError(f"{operation} expects a sequence of MeshObject instances")
|
|
23
|
+
object_sequence = cast(Sequence[object], objects)
|
|
24
|
+
if not all(isinstance(obj, MeshObject) for obj in object_sequence):
|
|
25
|
+
raise TypeError(f"{operation} expects MeshObject instances")
|
|
26
|
+
object_tuple = tuple(obj for obj in object_sequence if isinstance(obj, MeshObject))
|
|
27
|
+
if len(object_tuple) < 2:
|
|
28
|
+
raise ValueError(f"{operation} requires at least two objects")
|
|
29
|
+
dims = {obj.dim for obj in object_tuple}
|
|
30
|
+
if len(dims) != 1:
|
|
31
|
+
raise ValueError(f"{operation} requires objects with the same dimension")
|
|
32
|
+
return object_tuple
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _collect_points(objects: Sequence[MeshObject]) -> tuple[list[list[float]], list[list[float]]]:
|
|
36
|
+
fixed_points: list[list[float]] = []
|
|
37
|
+
mobile_points: list[list[float]] = []
|
|
38
|
+
for obj in objects:
|
|
39
|
+
fixed_points.extend(obj.fixed_points)
|
|
40
|
+
mobile_points.extend(obj.mobile_points)
|
|
41
|
+
return fixed_points, mobile_points
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _body_for(obj: MeshObject) -> Body:
|
|
45
|
+
if obj.obj is None:
|
|
46
|
+
raise ValueError("Boolean operations require MeshObject instances with bodies")
|
|
47
|
+
return obj.obj
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _stack_evaluations(bodies: Sequence[Body], points: FloatArray) -> FloatArray:
|
|
51
|
+
"""
|
|
52
|
+
Evaluate multiple bodies at the same points and stack results.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
bodies: Sequence of Body instances to evaluate
|
|
56
|
+
points: Array of shape (N, dim) containing query points
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Array of shape (len(bodies), N) containing signed distance values
|
|
60
|
+
|
|
61
|
+
Note:
|
|
62
|
+
Expects each body.evaluate() to return a 1D array of length N.
|
|
63
|
+
No shape validation is performed for performance reasons.
|
|
64
|
+
"""
|
|
65
|
+
return np.stack(
|
|
66
|
+
[np.asarray(body.evaluate(points), dtype=float) for body in bodies],
|
|
67
|
+
axis=0,
|
|
68
|
+
).astype(np.float64, copy=False)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def union(objects: Sequence[MeshObject]) -> MeshObject:
|
|
72
|
+
"""Return a geometry that is inside any supplied object.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
objects: At least two objects with the same dimension.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Combined mesh object.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
members = _validate_objects(objects, "Union")
|
|
82
|
+
fixed_points, mobile_points = _collect_points(members)
|
|
83
|
+
dim = members[0].dim
|
|
84
|
+
bodies = tuple(_body_for(member) for member in members)
|
|
85
|
+
body = _make_body(
|
|
86
|
+
dim,
|
|
87
|
+
lambda points: np.max(_stack_evaluations(bodies, points), axis=0),
|
|
88
|
+
)
|
|
89
|
+
return MeshObject(dim, fixed_points, mobile_points, body=body)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def difference(mother: MeshObject, subtract: Sequence[MeshObject]) -> MeshObject:
|
|
93
|
+
"""Carve each subtractor out of a mother object.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
mother: Object to retain.
|
|
97
|
+
subtract: Same-dimensional objects to remove.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Resulting mesh object.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
mother_body = _body_for(mother)
|
|
104
|
+
subtractors = tuple(subtract)
|
|
105
|
+
if not subtractors:
|
|
106
|
+
return MeshObject(
|
|
107
|
+
mother.dim,
|
|
108
|
+
mother.fixed_points[:],
|
|
109
|
+
mother.mobile_points[:],
|
|
110
|
+
body=mother_body,
|
|
111
|
+
)
|
|
112
|
+
dims = {mother.dim, *(obj.dim for obj in subtractors)}
|
|
113
|
+
if len(dims) != 1:
|
|
114
|
+
raise ValueError("Difference requires objects with the same dimension")
|
|
115
|
+
|
|
116
|
+
fixed_points = mother.fixed_points[:]
|
|
117
|
+
mobile_points = mother.mobile_points[:]
|
|
118
|
+
for obj in subtractors:
|
|
119
|
+
fixed_points.extend(obj.fixed_points)
|
|
120
|
+
mobile_points.extend(obj.mobile_points)
|
|
121
|
+
|
|
122
|
+
dim = mother.dim
|
|
123
|
+
subtractor_bodies = tuple(_body_for(obj) for obj in subtractors)
|
|
124
|
+
body = _make_body(
|
|
125
|
+
dim,
|
|
126
|
+
lambda points: np.min(
|
|
127
|
+
np.vstack(
|
|
128
|
+
(
|
|
129
|
+
np.asarray(mother_body.evaluate(points), dtype=float)[None, :],
|
|
130
|
+
-_stack_evaluations(subtractor_bodies, points),
|
|
131
|
+
)
|
|
132
|
+
),
|
|
133
|
+
axis=0,
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
return MeshObject(dim, fixed_points, mobile_points, body=body)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def intersect(objects: Sequence[MeshObject]) -> MeshObject:
|
|
140
|
+
"""Return a geometry that is inside every supplied object.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
objects: At least two objects with the same dimension.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Intersected mesh object.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
members = _validate_objects(objects, "Intersection")
|
|
150
|
+
fixed_points, mobile_points = _collect_points(members)
|
|
151
|
+
dim = members[0].dim
|
|
152
|
+
bodies = tuple(_body_for(member) for member in members)
|
|
153
|
+
body = _make_body(
|
|
154
|
+
dim,
|
|
155
|
+
lambda points: np.min(_stack_evaluations(bodies, points), axis=0),
|
|
156
|
+
)
|
|
157
|
+
return MeshObject(dim, fixed_points, mobile_points, body=body)
|