witwin-maxwell 0.3.0__py3-none-win_amd64.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.
- witwin/maxwell/__init__.py +164 -0
- witwin/maxwell/adapters/__init__.py +1 -0
- witwin/maxwell/adapters/gds.py +203 -0
- witwin/maxwell/adapters/tidy3d.py +1669 -0
- witwin/maxwell/adjoint_inputs.py +70 -0
- witwin/maxwell/compiler/__init__.py +11 -0
- witwin/maxwell/compiler/materials.py +1355 -0
- witwin/maxwell/compiler/monitors.py +164 -0
- witwin/maxwell/compiler/sources.py +222 -0
- witwin/maxwell/fdfd/__init__.py +3 -0
- witwin/maxwell/fdfd/adjoint/__init__.py +3 -0
- witwin/maxwell/fdfd/adjoint/bridge.py +180 -0
- witwin/maxwell/fdfd/ams.py +300 -0
- witwin/maxwell/fdfd/krylov.py +280 -0
- witwin/maxwell/fdfd/plotting.py +110 -0
- witwin/maxwell/fdfd/postprocess.py +33 -0
- witwin/maxwell/fdfd/solver.py +1057 -0
- witwin/maxwell/fdtd/__init__.py +4 -0
- witwin/maxwell/fdtd/adjoint/__init__.py +21 -0
- witwin/maxwell/fdtd/adjoint/bridge.py +746 -0
- witwin/maxwell/fdtd/adjoint/capabilities.py +29 -0
- witwin/maxwell/fdtd/adjoint/core.py +3614 -0
- witwin/maxwell/fdtd/adjoint/dispatch.py +684 -0
- witwin/maxwell/fdtd/adjoint/native.py +2561 -0
- witwin/maxwell/fdtd/adjoint/profiler.py +195 -0
- witwin/maxwell/fdtd/adjoint/reverse_common.py +207 -0
- witwin/maxwell/fdtd/adjoint/seeds.py +524 -0
- witwin/maxwell/fdtd/boundary/__init__.py +37 -0
- witwin/maxwell/fdtd/boundary/bloch.py +90 -0
- witwin/maxwell/fdtd/boundary/common.py +41 -0
- witwin/maxwell/fdtd/boundary/cpml.py +527 -0
- witwin/maxwell/fdtd/boundary/runtime.py +209 -0
- witwin/maxwell/fdtd/checkpoint.py +227 -0
- witwin/maxwell/fdtd/coords.py +32 -0
- witwin/maxwell/fdtd/cuda/__init__.py +11 -0
- witwin/maxwell/fdtd/cuda/backend.py +3532 -0
- witwin/maxwell/fdtd/cuda/build.py +292 -0
- witwin/maxwell/fdtd/cuda/extension.cpp +2053 -0
- witwin/maxwell/fdtd/cuda/kernels/adjoint.cu +5290 -0
- witwin/maxwell/fdtd/cuda/kernels/aniso.cu +1704 -0
- witwin/maxwell/fdtd/cuda/kernels/boundary.cu +424 -0
- witwin/maxwell/fdtd/cuda/kernels/common.cu +74 -0
- witwin/maxwell/fdtd/cuda/kernels/common.cuh +82 -0
- witwin/maxwell/fdtd/cuda/kernels/dispersive.cu +782 -0
- witwin/maxwell/fdtd/cuda/kernels/electric.cu +3613 -0
- witwin/maxwell/fdtd/cuda/kernels/magnetic.cu +781 -0
- witwin/maxwell/fdtd/cuda/kernels/observers.cu +347 -0
- witwin/maxwell/fdtd/cuda/kernels/projection.cu +211 -0
- witwin/maxwell/fdtd/cuda/kernels/sources.cu +1482 -0
- witwin/maxwell/fdtd/cuda/kernels/spectral.cu +297 -0
- witwin/maxwell/fdtd/cuda/launch.h +23 -0
- witwin/maxwell/fdtd/cuda/prebuilt/witwin_maxwell_fdtd_cuda.pyd +0 -0
- witwin/maxwell/fdtd/cuda/runtime/__init__.py +6 -0
- witwin/maxwell/fdtd/cuda/runtime/dispatcher.py +6 -0
- witwin/maxwell/fdtd/cuda/runtime/graph.py +33 -0
- witwin/maxwell/fdtd/cuda/tensors.h +35 -0
- witwin/maxwell/fdtd/dispersion.py +48 -0
- witwin/maxwell/fdtd/evaluation.py +290 -0
- witwin/maxwell/fdtd/excitation/__init__.py +27 -0
- witwin/maxwell/fdtd/excitation/injection.py +1102 -0
- witwin/maxwell/fdtd/excitation/modes.py +1675 -0
- witwin/maxwell/fdtd/excitation/spatial.py +469 -0
- witwin/maxwell/fdtd/excitation/temporal.py +510 -0
- witwin/maxwell/fdtd/excitation/tfsf_apply.py +264 -0
- witwin/maxwell/fdtd/excitation/tfsf_common.py +421 -0
- witwin/maxwell/fdtd/excitation/tfsf_specs.py +490 -0
- witwin/maxwell/fdtd/excitation/tfsf_state.py +778 -0
- witwin/maxwell/fdtd/io.py +31 -0
- witwin/maxwell/fdtd/material_pullback.py +371 -0
- witwin/maxwell/fdtd/meshing/__init__.py +3 -0
- witwin/maxwell/fdtd/meshing/autogrid.py +335 -0
- witwin/maxwell/fdtd/observers.py +1453 -0
- witwin/maxwell/fdtd/plotting.py +121 -0
- witwin/maxwell/fdtd/postprocess.py +205 -0
- witwin/maxwell/fdtd/runtime/__init__.py +119 -0
- witwin/maxwell/fdtd/runtime/initialization.py +236 -0
- witwin/maxwell/fdtd/runtime/materials.py +1563 -0
- witwin/maxwell/fdtd/runtime/module_cache.py +31 -0
- witwin/maxwell/fdtd/runtime/spectral.py +581 -0
- witwin/maxwell/fdtd/runtime/stepping.py +1987 -0
- witwin/maxwell/fdtd/solver.py +502 -0
- witwin/maxwell/geometry/__init__.py +5 -0
- witwin/maxwell/geometry/polyslab.py +299 -0
- witwin/maxwell/materials.py +3 -0
- witwin/maxwell/media.py +1467 -0
- witwin/maxwell/monitors.py +743 -0
- witwin/maxwell/ports.py +113 -0
- witwin/maxwell/postprocess/RCS.py +139 -0
- witwin/maxwell/postprocess/__init__.py +42 -0
- witwin/maxwell/postprocess/diffraction.py +219 -0
- witwin/maxwell/postprocess/directivity.py +152 -0
- witwin/maxwell/postprocess/emission.py +67 -0
- witwin/maxwell/postprocess/modal.py +324 -0
- witwin/maxwell/postprocess/nfft.py +266 -0
- witwin/maxwell/postprocess/scattering_parameters.py +358 -0
- witwin/maxwell/postprocess/stratton_chu.py +1193 -0
- witwin/maxwell/result.py +967 -0
- witwin/maxwell/scene.py +1338 -0
- witwin/maxwell/simulation.py +659 -0
- witwin/maxwell/sources.py +917 -0
- witwin/maxwell/visualization/__init__.py +28 -0
- witwin/maxwell/visualization/interactive.py +484 -0
- witwin/maxwell/visualization/plots.py +568 -0
- witwin_maxwell-0.3.0.dist-info/METADATA +184 -0
- witwin_maxwell-0.3.0.dist-info/RECORD +107 -0
- witwin_maxwell-0.3.0.dist-info/WHEEL +4 -0
- witwin_maxwell-0.3.0.dist-info/licenses/LICENSE +13 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Maxwell-specific extruded polygon geometries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from witwin.core import GeometryBase
|
|
9
|
+
from witwin.core.geometry.polygon import polygon_loops_signed_distance_2d
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _as_scalar(value, *, device=None) -> torch.Tensor:
|
|
13
|
+
if isinstance(value, torch.Tensor):
|
|
14
|
+
return value.to(device=device, dtype=torch.float32).reshape(())
|
|
15
|
+
return torch.tensor(float(value), dtype=torch.float32, device=device)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _axial_split(dx, dy, dz, axis: str):
|
|
19
|
+
if axis == "z":
|
|
20
|
+
return dz, dx, dy
|
|
21
|
+
if axis == "y":
|
|
22
|
+
return dy, dx, dz
|
|
23
|
+
return dx, dy, dz
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _constant_tensor(data, *, device, dtype=torch.float32) -> torch.Tensor:
|
|
27
|
+
return torch.tensor(data, dtype=dtype, device=device)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _faces_tensor(data, *, device) -> torch.Tensor:
|
|
31
|
+
return _constant_tensor(data, device=device, dtype=torch.int64)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _as_polygon_vertices(value, *, device=None) -> torch.Tensor:
|
|
35
|
+
if isinstance(value, torch.Tensor):
|
|
36
|
+
vertices = value.to(device=device, dtype=torch.float32)
|
|
37
|
+
else:
|
|
38
|
+
vertices = torch.tensor(
|
|
39
|
+
[[float(u), float(v)] for u, v in value], dtype=torch.float32, device=device
|
|
40
|
+
)
|
|
41
|
+
if vertices.ndim != 2 or vertices.shape[0] < 3 or vertices.shape[1] != 2:
|
|
42
|
+
raise ValueError("vertices must have shape (N, 2) with N >= 3.")
|
|
43
|
+
return vertices
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _as_axis_bounds(value, *, device=None) -> torch.Tensor:
|
|
47
|
+
if isinstance(value, torch.Tensor):
|
|
48
|
+
bounds = value.to(device=device, dtype=torch.float32)
|
|
49
|
+
else:
|
|
50
|
+
bounds = torch.tensor(
|
|
51
|
+
[float(value[0]), float(value[1])], dtype=torch.float32, device=device
|
|
52
|
+
)
|
|
53
|
+
if bounds.shape != (2,):
|
|
54
|
+
raise ValueError("bounds must contain (min, max) along the extrusion axis.")
|
|
55
|
+
return bounds
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _cross_2d_scalar(origin, first, second) -> float:
|
|
59
|
+
return float(
|
|
60
|
+
(first[0] - origin[0]) * (second[1] - origin[1])
|
|
61
|
+
- (first[1] - origin[1]) * (second[0] - origin[0])
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _point_in_triangle_2d(point, a, b, c, eps=1.0e-12) -> bool:
|
|
66
|
+
"""Inclusive point-in-triangle test for a CCW triangle (a, b, c)."""
|
|
67
|
+
return (
|
|
68
|
+
_cross_2d_scalar(a, b, point) >= -eps
|
|
69
|
+
and _cross_2d_scalar(b, c, point) >= -eps
|
|
70
|
+
and _cross_2d_scalar(c, a, point) >= -eps
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _ear_clip_faces_2d(polygon: np.ndarray) -> list[list[int]]:
|
|
75
|
+
"""Triangulate a simple CCW (N, 2) polygon into CCW index triangles via ear clipping."""
|
|
76
|
+
remaining = list(range(polygon.shape[0]))
|
|
77
|
+
faces: list[list[int]] = []
|
|
78
|
+
while len(remaining) > 3:
|
|
79
|
+
clipped = False
|
|
80
|
+
for slot in range(len(remaining)):
|
|
81
|
+
i0 = remaining[slot - 1]
|
|
82
|
+
i1 = remaining[slot]
|
|
83
|
+
i2 = remaining[(slot + 1) % len(remaining)]
|
|
84
|
+
a, b, c = polygon[i0], polygon[i1], polygon[i2]
|
|
85
|
+
if _cross_2d_scalar(a, b, c) <= 1.0e-12:
|
|
86
|
+
continue
|
|
87
|
+
others = (polygon[j] for j in remaining if j not in (i0, i1, i2))
|
|
88
|
+
if any(_point_in_triangle_2d(p, a, b, c) for p in others):
|
|
89
|
+
continue
|
|
90
|
+
faces.append([i0, i1, i2])
|
|
91
|
+
remaining.pop(slot)
|
|
92
|
+
clipped = True
|
|
93
|
+
break
|
|
94
|
+
if not clipped:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"Failed to triangulate polygon; vertices must describe a simple polygon."
|
|
97
|
+
)
|
|
98
|
+
faces.append(list(remaining))
|
|
99
|
+
return faces
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _offset_polygon_2d(polygon: np.ndarray, offset: float) -> np.ndarray:
|
|
103
|
+
"""Approximate outward miter offset of a CCW (N, 2) polygon (negative offset shrinks)."""
|
|
104
|
+
if offset == 0.0:
|
|
105
|
+
return polygon
|
|
106
|
+
previous_edges = polygon - np.roll(polygon, 1, axis=0)
|
|
107
|
+
next_edges = np.roll(polygon, -1, axis=0) - polygon
|
|
108
|
+
|
|
109
|
+
def _outward_normals(edges: np.ndarray) -> np.ndarray:
|
|
110
|
+
normals = np.stack([edges[:, 1], -edges[:, 0]], axis=1)
|
|
111
|
+
return normals / np.maximum(
|
|
112
|
+
np.linalg.norm(normals, axis=1, keepdims=True), 1.0e-12
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
normal_previous = _outward_normals(previous_edges)
|
|
116
|
+
normal_next = _outward_normals(next_edges)
|
|
117
|
+
denom = np.maximum(
|
|
118
|
+
1.0 + np.sum(normal_previous * normal_next, axis=1, keepdims=True), 1.0e-3
|
|
119
|
+
)
|
|
120
|
+
return polygon + offset * (normal_previous + normal_next) / denom
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class PolySlab(GeometryBase):
|
|
124
|
+
"""Extruded 2D polygon with optional linear sidewall taper (Tidy3D semantics).
|
|
125
|
+
|
|
126
|
+
``vertices`` is the (N, 2) cross-section in the plane perpendicular to ``axis``
|
|
127
|
+
(plane coordinates follow ``_axial_split``: axis z -> (x, y), y -> (x, z),
|
|
128
|
+
x -> (y, z)), ``bounds`` is (min, max) along the axis, and a positive
|
|
129
|
+
``sidewall_angle`` (radians) shrinks the cross-section as the axis coordinate
|
|
130
|
+
increases. ``reference_plane`` selects the axis position where the drawn
|
|
131
|
+
polygon is exact. The interior uses the even-odd rule, so non-convex polygons
|
|
132
|
+
are supported and vertex winding is irrelevant.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
kind = "poly_slab"
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
vertices,
|
|
140
|
+
bounds,
|
|
141
|
+
axis="z",
|
|
142
|
+
sidewall_angle=0.0,
|
|
143
|
+
reference_plane="middle",
|
|
144
|
+
position=(0, 0, 0),
|
|
145
|
+
rotation=None,
|
|
146
|
+
*,
|
|
147
|
+
device=None,
|
|
148
|
+
):
|
|
149
|
+
super().__init__(position=position, rotation=rotation, device=device)
|
|
150
|
+
self.vertices: torch.Tensor = _as_polygon_vertices(vertices, device=device)
|
|
151
|
+
self.bounds: torch.Tensor = _as_axis_bounds(bounds, device=device)
|
|
152
|
+
if isinstance(axis, int) and not isinstance(axis, bool):
|
|
153
|
+
if axis not in (0, 1, 2):
|
|
154
|
+
raise ValueError("integer axis must be 0, 1, or 2.")
|
|
155
|
+
axis = ("x", "y", "z")[axis]
|
|
156
|
+
self.axis: str = self._validate_axis(axis)
|
|
157
|
+
self.sidewall_angle: torch.Tensor = _as_scalar(sidewall_angle, device=device)
|
|
158
|
+
if reference_plane not in ("bottom", "middle", "top"):
|
|
159
|
+
raise ValueError("reference_plane must be 'bottom', 'middle', or 'top'.")
|
|
160
|
+
self.reference_plane: str = reference_plane
|
|
161
|
+
self.loop_sizes: tuple[int, ...] = (int(self.vertices.shape[0]),)
|
|
162
|
+
|
|
163
|
+
def _reference_position(self, bounds: torch.Tensor) -> torch.Tensor:
|
|
164
|
+
if self.reference_plane == "bottom":
|
|
165
|
+
return bounds[0]
|
|
166
|
+
if self.reference_plane == "top":
|
|
167
|
+
return bounds[1]
|
|
168
|
+
return 0.5 * (bounds[0] + bounds[1])
|
|
169
|
+
|
|
170
|
+
def signed_distance(self, xx, yy, zz):
|
|
171
|
+
dx, dy, dz = self._local_coords(xx, yy, zz)
|
|
172
|
+
axial, pu, pv = _axial_split(dx, dy, dz, self.axis)
|
|
173
|
+
vertices = self.vertices.to(device=pu.device, dtype=pu.dtype)
|
|
174
|
+
loops = torch.split(vertices, list(self.loop_sizes), dim=0)
|
|
175
|
+
points = torch.stack([pu, pv], dim=-1).reshape(-1, 2)
|
|
176
|
+
polygon_distance = polygon_loops_signed_distance_2d(points, loops).reshape(
|
|
177
|
+
pu.shape
|
|
178
|
+
)
|
|
179
|
+
bounds = self.bounds.to(device=axial.device, dtype=axial.dtype)
|
|
180
|
+
tan_angle = torch.tan(
|
|
181
|
+
self.sidewall_angle.to(device=axial.device, dtype=axial.dtype)
|
|
182
|
+
)
|
|
183
|
+
# Signed distance minus an offset is exact morphological erosion/dilation, so
|
|
184
|
+
# the taper is a z-dependent inward offset of the cross-section.
|
|
185
|
+
tapered_distance = polygon_distance + tan_angle * (
|
|
186
|
+
axial - self._reference_position(bounds)
|
|
187
|
+
)
|
|
188
|
+
axial_distance = torch.abs(axial - 0.5 * (bounds[0] + bounds[1])) - 0.5 * (
|
|
189
|
+
bounds[1] - bounds[0]
|
|
190
|
+
)
|
|
191
|
+
outside = torch.sqrt(
|
|
192
|
+
torch.clamp(tapered_distance, min=0.0) ** 2
|
|
193
|
+
+ torch.clamp(axial_distance, min=0.0) ** 2
|
|
194
|
+
)
|
|
195
|
+
inside = torch.clamp(torch.maximum(tapered_distance, axial_distance), max=0.0)
|
|
196
|
+
return outside + inside
|
|
197
|
+
|
|
198
|
+
def to_mesh(self, segments=16):
|
|
199
|
+
del segments
|
|
200
|
+
device = self.device
|
|
201
|
+
polygon = self.vertices.detach().cpu().to(torch.float64).numpy()
|
|
202
|
+
# Drop consecutive duplicate vertices (including a closed-ring wrap-around
|
|
203
|
+
# repeat); coincident points would block every candidate ear below.
|
|
204
|
+
keep = np.linalg.norm(polygon - np.roll(polygon, 1, axis=0), axis=1) > 1.0e-12
|
|
205
|
+
polygon = polygon[keep]
|
|
206
|
+
if polygon.shape[0] < 3:
|
|
207
|
+
raise ValueError("vertices must contain at least 3 distinct points.")
|
|
208
|
+
signed_area = 0.5 * float(
|
|
209
|
+
np.sum(
|
|
210
|
+
polygon[:, 0] * np.roll(polygon[:, 1], -1)
|
|
211
|
+
- np.roll(polygon[:, 0], -1) * polygon[:, 1]
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
if signed_area < 0.0:
|
|
215
|
+
polygon = polygon[::-1].copy()
|
|
216
|
+
bounds = self.bounds.detach().cpu().to(torch.float64).numpy()
|
|
217
|
+
reference = {
|
|
218
|
+
"bottom": bounds[0],
|
|
219
|
+
"top": bounds[1],
|
|
220
|
+
"middle": 0.5 * (bounds[0] + bounds[1]),
|
|
221
|
+
}[self.reference_plane]
|
|
222
|
+
tan_angle = float(
|
|
223
|
+
np.tan(self.sidewall_angle.detach().cpu().to(torch.float64).item())
|
|
224
|
+
)
|
|
225
|
+
bottom_ring = _offset_polygon_2d(polygon, -tan_angle * (bounds[0] - reference))
|
|
226
|
+
top_ring = _offset_polygon_2d(polygon, -tan_angle * (bounds[1] - reference))
|
|
227
|
+
count = polygon.shape[0]
|
|
228
|
+
bottom = np.concatenate([bottom_ring, np.full((count, 1), bounds[0])], axis=1)
|
|
229
|
+
top = np.concatenate([top_ring, np.full((count, 1), bounds[1])], axis=1)
|
|
230
|
+
vertices = _constant_tensor(
|
|
231
|
+
np.concatenate([bottom, top], axis=0), device=device
|
|
232
|
+
)
|
|
233
|
+
faces = []
|
|
234
|
+
for index in range(count):
|
|
235
|
+
next_index = (index + 1) % count
|
|
236
|
+
faces.extend(
|
|
237
|
+
[
|
|
238
|
+
[index, next_index, count + next_index],
|
|
239
|
+
[index, count + next_index, count + index],
|
|
240
|
+
]
|
|
241
|
+
)
|
|
242
|
+
for i0, i1, i2 in _ear_clip_faces_2d(polygon):
|
|
243
|
+
faces.append([i0, i2, i1])
|
|
244
|
+
faces.append([count + i0, count + i1, count + i2])
|
|
245
|
+
# Map local (u, v, axial) columns to world axes following _axial_split
|
|
246
|
+
# (axis z -> (x, y), y -> (x, z), x -> (y, z)); _remap_axis_torch would
|
|
247
|
+
# mirror u/v for axis "x" and disagree with signed_distance/to_mask.
|
|
248
|
+
vertices = vertices[
|
|
249
|
+
:, {"z": [0, 1, 2], "y": [0, 2, 1], "x": [2, 0, 1]}[self.axis]
|
|
250
|
+
]
|
|
251
|
+
faces_tensor = _faces_tensor(faces, device=device)
|
|
252
|
+
if self.axis == "y":
|
|
253
|
+
# Odd column permutation reflects orientation; flip winding to keep
|
|
254
|
+
# face normals outward.
|
|
255
|
+
faces_tensor = torch.flip(faces_tensor, dims=(1,))
|
|
256
|
+
vertices = self._transform_mesh_verts(vertices)
|
|
257
|
+
return vertices, faces_tensor
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class ComplexPolySlab(PolySlab):
|
|
261
|
+
"""PolySlab variant for non-simple cross-sections.
|
|
262
|
+
|
|
263
|
+
Accepts a list of vertex loops; self-intersecting loops (e.g. bowties) and
|
|
264
|
+
multiple loops are allowed. The interior is the even-odd rule over the combined
|
|
265
|
+
crossing count of all loops, so loop winding is irrelevant and extra loops carve
|
|
266
|
+
holes. The distance magnitude is the minimum distance over all edges of all loops.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
kind = "complex_poly_slab"
|
|
270
|
+
|
|
271
|
+
def __init__(
|
|
272
|
+
self,
|
|
273
|
+
loops,
|
|
274
|
+
bounds,
|
|
275
|
+
axis="z",
|
|
276
|
+
sidewall_angle=0.0,
|
|
277
|
+
reference_plane="middle",
|
|
278
|
+
position=(0, 0, 0),
|
|
279
|
+
rotation=None,
|
|
280
|
+
*,
|
|
281
|
+
device=None,
|
|
282
|
+
):
|
|
283
|
+
loop_tensors = [_as_polygon_vertices(loop, device=device) for loop in loops]
|
|
284
|
+
if not loop_tensors:
|
|
285
|
+
raise ValueError("loops must contain at least one polygon loop.")
|
|
286
|
+
super().__init__(
|
|
287
|
+
torch.cat(loop_tensors, dim=0),
|
|
288
|
+
bounds,
|
|
289
|
+
axis=axis,
|
|
290
|
+
sidewall_angle=sidewall_angle,
|
|
291
|
+
reference_plane=reference_plane,
|
|
292
|
+
position=position,
|
|
293
|
+
rotation=rotation,
|
|
294
|
+
device=device,
|
|
295
|
+
)
|
|
296
|
+
self.loop_sizes = tuple(int(loop.shape[0]) for loop in loop_tensors)
|
|
297
|
+
|
|
298
|
+
def to_mesh(self, segments=16):
|
|
299
|
+
raise NotImplementedError("ComplexPolySlab does not support mesh export.")
|