emerge 0.4.7__py3-none-any.whl → 0.4.8__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.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/__init__.py +14 -14
- emerge/_emerge/__init__.py +42 -0
- emerge/_emerge/bc.py +197 -0
- emerge/_emerge/coord.py +119 -0
- emerge/_emerge/cs.py +523 -0
- emerge/_emerge/dataset.py +36 -0
- emerge/_emerge/elements/__init__.py +19 -0
- emerge/_emerge/elements/femdata.py +212 -0
- emerge/_emerge/elements/index_interp.py +64 -0
- emerge/_emerge/elements/legrange2.py +172 -0
- emerge/_emerge/elements/ned2_interp.py +645 -0
- emerge/_emerge/elements/nedelec2.py +140 -0
- emerge/_emerge/elements/nedleg2.py +217 -0
- emerge/_emerge/geo/__init__.py +24 -0
- emerge/_emerge/geo/horn.py +107 -0
- emerge/_emerge/geo/modeler.py +449 -0
- emerge/_emerge/geo/operations.py +254 -0
- emerge/_emerge/geo/pcb.py +1244 -0
- emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
- emerge/_emerge/geo/pcb_tools/macro.py +79 -0
- emerge/_emerge/geo/pmlbox.py +204 -0
- emerge/_emerge/geo/polybased.py +529 -0
- emerge/_emerge/geo/shapes.py +427 -0
- emerge/_emerge/geo/step.py +77 -0
- emerge/_emerge/geo2d.py +86 -0
- emerge/_emerge/geometry.py +510 -0
- emerge/_emerge/howto.py +214 -0
- emerge/_emerge/logsettings.py +5 -0
- emerge/_emerge/material.py +118 -0
- emerge/_emerge/mesh3d.py +730 -0
- emerge/_emerge/mesher.py +339 -0
- emerge/_emerge/mth/common_functions.py +33 -0
- emerge/_emerge/mth/integrals.py +71 -0
- emerge/_emerge/mth/optimized.py +357 -0
- emerge/_emerge/periodic.py +263 -0
- emerge/_emerge/physics/__init__.py +0 -0
- emerge/_emerge/physics/microwave/__init__.py +1 -0
- emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
- emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
- emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
- emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
- emerge/_emerge/physics/microwave/periodic.py +82 -0
- emerge/_emerge/physics/microwave/port_functions.py +53 -0
- emerge/_emerge/physics/microwave/sc.py +175 -0
- emerge/_emerge/physics/microwave/simjob.py +147 -0
- emerge/_emerge/physics/microwave/sparam.py +138 -0
- emerge/_emerge/physics/microwave/touchstone.py +140 -0
- emerge/_emerge/plot/__init__.py +0 -0
- emerge/_emerge/plot/display.py +394 -0
- emerge/_emerge/plot/grapher.py +93 -0
- emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
- emerge/_emerge/plot/pyvista/__init__.py +1 -0
- emerge/_emerge/plot/pyvista/display.py +931 -0
- emerge/_emerge/plot/pyvista/display_settings.py +24 -0
- emerge/_emerge/plot/simple_plots.py +551 -0
- emerge/_emerge/plot.py +225 -0
- emerge/_emerge/projects/__init__.py +0 -0
- emerge/_emerge/projects/_gen_base.txt +32 -0
- emerge/_emerge/projects/_load_base.txt +24 -0
- emerge/_emerge/projects/generate_project.py +40 -0
- emerge/_emerge/selection.py +596 -0
- emerge/_emerge/simmodel.py +444 -0
- emerge/_emerge/simulation_data.py +411 -0
- emerge/_emerge/solver.py +993 -0
- emerge/_emerge/system.py +54 -0
- emerge/cli.py +19 -0
- emerge/lib.py +1 -1
- emerge/plot.py +1 -1
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
- emerge-0.4.8.dist-info/RECORD +78 -0
- emerge-0.4.8.dist-info/entry_points.txt +2 -0
- emerge-0.4.7.dist-info/RECORD +0 -9
- emerge-0.4.7.dist-info/entry_points.txt +0 -2
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# EMerge is an open source Python based FEM EM simulation module.
|
|
2
|
+
# Copyright (C) 2025 Robert Fennis.
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, see
|
|
16
|
+
# <https://www.gnu.org/licenses/>.
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
import numpy as np
|
|
19
|
+
from ..cs import CoordinateSystem, GCS
|
|
20
|
+
from ..geometry import GeoVolume, GeoPolygon
|
|
21
|
+
from .shapes import Alignment
|
|
22
|
+
import gmsh
|
|
23
|
+
from typing import Generator, Callable
|
|
24
|
+
from ..selection import FaceSelection
|
|
25
|
+
from typing import Literal
|
|
26
|
+
from functools import reduce
|
|
27
|
+
from numba import njit
|
|
28
|
+
|
|
29
|
+
@njit(cache=True)
|
|
30
|
+
def _subsample_coordinates(xs: np.ndarray, ys: np.ndarray, tolerance: float, xmin: float) -> tuple[np.ndarray, np.ndarray]:
|
|
31
|
+
"""This function takes a set of x and y coordinates in a finely sampled set and returns a reduced
|
|
32
|
+
set of numbers that traces the input curve within a provided tolerance.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
xs (np.ndarray): The set of X-coordinates
|
|
36
|
+
ys (np.ndarray): The set of Y-coordinates
|
|
37
|
+
tolerance (float): The maximum deviation of the curve in meters
|
|
38
|
+
xmin (float): The minimal distance to the next point.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
np.ndarray: The output X-coordinates
|
|
42
|
+
np.ndarray: The output Y-coordinates
|
|
43
|
+
"""
|
|
44
|
+
N = xs.shape[0]
|
|
45
|
+
ids = np.zeros((N,), dtype=np.int32)
|
|
46
|
+
store_index = 1
|
|
47
|
+
start_index = 0
|
|
48
|
+
final_index = 0
|
|
49
|
+
for iteration in range(N):
|
|
50
|
+
i1 = start_index
|
|
51
|
+
done = 0
|
|
52
|
+
for i2 in range(i1+1,N):
|
|
53
|
+
x_true = xs[i1:i2+1]
|
|
54
|
+
y_true = ys[i1:i2+1]
|
|
55
|
+
|
|
56
|
+
x_f = np.linspace(xs[i1],xs[i2], i2-i1+1)
|
|
57
|
+
y_f = np.linspace(ys[i1],ys[i2], i2-i1+1)
|
|
58
|
+
error = np.max(np.sqrt((x_f-x_true)**2 + (y_f-y_true)**2))
|
|
59
|
+
ds = np.sqrt((xs[i2]-xs[i1])**2 + (ys[i2]-ys[i1])**2)
|
|
60
|
+
# If at the end
|
|
61
|
+
if i2==N-1:
|
|
62
|
+
ids[store_index] = i2-1
|
|
63
|
+
final_index = store_index + 1
|
|
64
|
+
done = 1
|
|
65
|
+
break
|
|
66
|
+
# If not yet past the minimum distance, accumulate more
|
|
67
|
+
if ds < xmin:
|
|
68
|
+
continue
|
|
69
|
+
# If the end is less than a minimum distance
|
|
70
|
+
if np.sqrt((ys[-1]-ys[i2])**2 + (xs[-1]-xs[i2])**2) < xmin:
|
|
71
|
+
imid = i1 + (N-1-i1)//2
|
|
72
|
+
ids[store_index] = imid
|
|
73
|
+
ids[store_index+1] = N-1
|
|
74
|
+
final_index = store_index + 2
|
|
75
|
+
done = 1
|
|
76
|
+
break
|
|
77
|
+
if error < tolerance:
|
|
78
|
+
continue
|
|
79
|
+
else:
|
|
80
|
+
ids[store_index] = i2-1
|
|
81
|
+
start_index = i2
|
|
82
|
+
store_index = store_index + 1
|
|
83
|
+
break
|
|
84
|
+
if done==1:
|
|
85
|
+
break
|
|
86
|
+
return xs[ids[0:final_index]], ys[ids[0:final_index]]
|
|
87
|
+
|
|
88
|
+
def _discretize_curve(xfunc: Callable, yfunc: Callable,
|
|
89
|
+
t0: float, t1: float, xmin: float, tol: float=1e-4) -> tuple[np.ndarray, np.ndarray]:
|
|
90
|
+
"""Computes a discreteized curve in X/Y coordinates based on the input parametric coordinates.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
xfunc (Callable): The X-coordinate function fx(t)
|
|
94
|
+
yfunc (Callable): The Y-coordinate function fy(t)
|
|
95
|
+
t0 (float): The minimum value for the t-prameter
|
|
96
|
+
t1 (float): The maximum value for the t-parameter
|
|
97
|
+
xmin (float): The minimum distance for subsequent points
|
|
98
|
+
tol (float, optional): The curve matching tolerance. Defaults to 1e-4.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
tuple[np.ndarray, np.ndarray]: _description_
|
|
102
|
+
"""
|
|
103
|
+
td = np.linspace(t0, t1, 10001)
|
|
104
|
+
xs = xfunc(td)
|
|
105
|
+
ys = yfunc(td)
|
|
106
|
+
XS, YS = _subsample_coordinates(xs, ys, tol, xmin)
|
|
107
|
+
return XS, YS
|
|
108
|
+
|
|
109
|
+
def rotate_point(point: tuple[float, float, float],
|
|
110
|
+
axis: tuple[float, float, float],
|
|
111
|
+
ang: float,
|
|
112
|
+
origin: tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
113
|
+
degrees: bool = False) -> tuple[float, float, float]:
|
|
114
|
+
"""
|
|
115
|
+
Rotate a 3‑D point around an arbitrary axis that passes through `origin`.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
point : (x, y, z) coordinate of the point to rotate.
|
|
120
|
+
axis : (ux, uy, uz) direction vector of the rotation axis (need not be unit length).
|
|
121
|
+
ang : rotation angle. Positive values follow the right‑hand rule.
|
|
122
|
+
origin : (ox, oy, oz) point through which the axis passes. Defaults to global origin.
|
|
123
|
+
degrees : If True, `ang` is interpreted in degrees; otherwise in radians.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
(x,y,z) : tuple with the rotated coordinates.
|
|
128
|
+
"""
|
|
129
|
+
# Convert inputs to numpy arrays
|
|
130
|
+
p = np.asarray(point, dtype=float)
|
|
131
|
+
o = np.asarray(origin, dtype=float)
|
|
132
|
+
u = np.asarray(axis, dtype=float)
|
|
133
|
+
|
|
134
|
+
# Shift so the axis passes through the global origin
|
|
135
|
+
p_shifted = p - o
|
|
136
|
+
|
|
137
|
+
# Normalise the axis direction
|
|
138
|
+
norm = np.linalg.norm(u)
|
|
139
|
+
if norm == 0:
|
|
140
|
+
raise ValueError("Axis direction vector must be non‑zero.")
|
|
141
|
+
u = u / norm
|
|
142
|
+
|
|
143
|
+
# Convert angle to radians if necessary
|
|
144
|
+
if degrees:
|
|
145
|
+
ang = np.radians(ang)
|
|
146
|
+
|
|
147
|
+
# Rodrigues’ rotation formula components
|
|
148
|
+
cos_a = np.cos(ang)
|
|
149
|
+
sin_a = np.sin(ang)
|
|
150
|
+
cross = np.cross(u, p_shifted)
|
|
151
|
+
dot = np.dot(u, p_shifted)
|
|
152
|
+
|
|
153
|
+
rotated = (p_shifted * cos_a
|
|
154
|
+
+ cross * sin_a
|
|
155
|
+
+ u * dot * (1 - cos_a))
|
|
156
|
+
|
|
157
|
+
# Shift back to original reference frame
|
|
158
|
+
rotated += o
|
|
159
|
+
return tuple(rotated)
|
|
160
|
+
|
|
161
|
+
class GeoPrism(GeoVolume):
|
|
162
|
+
"""The GepPrism class generalizes the GeoVolume for extruded convex polygons.
|
|
163
|
+
Besides having a volumetric definitions, the class offers a .front_face
|
|
164
|
+
and .back_face property that selects the respective faces.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
GeoVolume (_type_): _description_
|
|
168
|
+
"""
|
|
169
|
+
def __init__(self,
|
|
170
|
+
volume_tag: int,
|
|
171
|
+
front_tag: int,
|
|
172
|
+
side_tags: list[int],):
|
|
173
|
+
super().__init__(volume_tag)
|
|
174
|
+
self.front_tag: int = front_tag
|
|
175
|
+
self.back_tag: int = None
|
|
176
|
+
|
|
177
|
+
gmsh.model.occ.synchronize()
|
|
178
|
+
o1 = gmsh.model.occ.get_center_of_mass(2, self.front_tag)
|
|
179
|
+
n1 = gmsh.model.get_normal(self.front_tag, (0,0))
|
|
180
|
+
self._add_face_pointer('back', o1, n1)
|
|
181
|
+
|
|
182
|
+
tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
|
|
183
|
+
|
|
184
|
+
for dim, tag in tags:
|
|
185
|
+
if (dim,tag) in side_tags:
|
|
186
|
+
continue
|
|
187
|
+
o2 = gmsh.model.occ.get_center_of_mass(2, tag)
|
|
188
|
+
n2 = gmsh.model.get_normal(tag, (0,0))
|
|
189
|
+
self._add_face_pointer('front', o2, n2)
|
|
190
|
+
self.back_tag = tag
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
self.side_tags: list[int] = [dt[1] for dt in tags if dt[1]!=self.front_tag and dt[1]!=self.back_tag]
|
|
194
|
+
|
|
195
|
+
for tag in self.side_tags:
|
|
196
|
+
o2 = gmsh.model.occ.get_center_of_mass(2, tag)
|
|
197
|
+
n2 = gmsh.model.get_normal(tag, (0,0))
|
|
198
|
+
self._add_face_pointer(f'side{tag}', o2, n2)
|
|
199
|
+
self.back_tag = tag
|
|
200
|
+
|
|
201
|
+
def outside(self, *exclude: Literal['front','back']) -> FaceSelection:
|
|
202
|
+
"""Select all outside faces except for the once specified by outside
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
FaceSelection: The resultant face selection
|
|
206
|
+
"""
|
|
207
|
+
tagslist = [self._face_tags(name) for name in self._face_pointers.keys() if name not in exclude]
|
|
208
|
+
|
|
209
|
+
tags = list(reduce(lambda a,b: a+b, tagslist))
|
|
210
|
+
return FaceSelection(tags)
|
|
211
|
+
|
|
212
|
+
class XYPolygon:
|
|
213
|
+
"""This class generalizes a polygon in an un-embedded XY space that can be embedded in 3D space.
|
|
214
|
+
"""
|
|
215
|
+
def __init__(self,
|
|
216
|
+
xs: np.ndarray | list | tuple = None,
|
|
217
|
+
ys: np.ndarray | list | tuple = None):
|
|
218
|
+
"""Constructs an XY-plane placed polygon.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
xs (np.ndarray): The X-points
|
|
222
|
+
ys (np.ndarray): The Y-points
|
|
223
|
+
"""
|
|
224
|
+
if xs is None:
|
|
225
|
+
xs = []
|
|
226
|
+
if ys is None:
|
|
227
|
+
ys = []
|
|
228
|
+
|
|
229
|
+
self.x: np.ndarray = np.asarray(xs)
|
|
230
|
+
self.y: np.ndarray = np.asarray(ys)
|
|
231
|
+
|
|
232
|
+
self.fillets: list[tuple[float, int]] = []
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def N(self) -> int:
|
|
236
|
+
"""The number of polygon points
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
int: The number of points
|
|
240
|
+
"""
|
|
241
|
+
return len(self.xs)
|
|
242
|
+
|
|
243
|
+
def _check(self) -> None:
|
|
244
|
+
"""Checks if the last point is the same as the first point.
|
|
245
|
+
The XYPolygon does not store redundant points p[0]==p[N] so if these are
|
|
246
|
+
the same, this function will remove the last point.
|
|
247
|
+
"""
|
|
248
|
+
if np.sqrt((self.x[-1]-self.x[0])**2 + (self.y[-1]-self.y[0])**2) < 1e-6:
|
|
249
|
+
self.x = self.x[:-1]
|
|
250
|
+
self.y = self.y[:-1]
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def area(self) -> float:
|
|
254
|
+
"""The Area of the polygon
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
float: The area in square meters
|
|
258
|
+
"""
|
|
259
|
+
return 0.5*np.abs(np.dot(self.x,np.roll(self.y,1))-np.dot(self.y,np.roll(self.x,1)))
|
|
260
|
+
|
|
261
|
+
def extend(self, xpts: list[float], ypts: list[float]) -> XYPolygon:
|
|
262
|
+
"""Adds a series for x and y coordinates to the existing polygon.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
xpts (list[float]): The list of x-coordinates
|
|
266
|
+
ypts (list[float]): The list of y-coordinates
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
XYPolygon: The same XYpolygon object
|
|
270
|
+
"""
|
|
271
|
+
self.x = np.hstack([self.x, np.array(xpts)])
|
|
272
|
+
self.y = np.hstack([self.y, np.array(ypts)])
|
|
273
|
+
return self
|
|
274
|
+
|
|
275
|
+
def iterate(self) -> Generator[tuple[float, float],None, None]:
|
|
276
|
+
""" Iterates over the x,y coordinates as a tuple."""
|
|
277
|
+
for i in range(self.N):
|
|
278
|
+
yield (self.x[i], self.y[i])
|
|
279
|
+
|
|
280
|
+
def fillet(self, radius: float, *indices: int) -> None:
|
|
281
|
+
"""Add a fillet rounding with a given radius to the provided nodes.
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
>>> my_polygon.fillet(0.05, 2, 3, 4, 6)
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
radius (float): The radius
|
|
288
|
+
*indices (int): The indices for which to apply the fillet.
|
|
289
|
+
"""
|
|
290
|
+
for i in indices:
|
|
291
|
+
self.fillets.append((radius, i))
|
|
292
|
+
|
|
293
|
+
def _finalize(self, cs: CoordinateSystem = None) -> GeoPolygon:
|
|
294
|
+
"""Turns the XYPolygon object into a GeoPolygon that is embedded in 3D space.
|
|
295
|
+
|
|
296
|
+
The polygon will be placed in the XY-plane of the provided coordinate center.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
cs (CoordinateSystem, optional): The coordinate system in which to put the polygon. Defaults to None.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
GeoPolygon: The resultant 3D GeoPolygon object.
|
|
303
|
+
"""
|
|
304
|
+
self._check()
|
|
305
|
+
|
|
306
|
+
ptags = []
|
|
307
|
+
xg, yg, zg = cs.in_global_cs(self.x, self.y, 0*self.x)
|
|
308
|
+
|
|
309
|
+
for x,y,z in zip(xg, yg, zg):
|
|
310
|
+
ptags.append(gmsh.model.occ.add_point(x,y,z))
|
|
311
|
+
|
|
312
|
+
lines = []
|
|
313
|
+
for i1, p1 in enumerate(ptags):
|
|
314
|
+
p2 = ptags[(i1+1) % len(ptags)]
|
|
315
|
+
lines.append(gmsh.model.occ.add_line(p1, p2))
|
|
316
|
+
|
|
317
|
+
add = 0
|
|
318
|
+
for radius, index in self.fillets:
|
|
319
|
+
t1 = lines[index + add]
|
|
320
|
+
t2 = lines[(index+add-1) % len(lines)]
|
|
321
|
+
tag = gmsh.model.occ.fillet2_d(t1, t2, radius)
|
|
322
|
+
lines.insert(index, tag)
|
|
323
|
+
add += 1
|
|
324
|
+
|
|
325
|
+
wiretag = gmsh.model.occ.add_wire(lines)
|
|
326
|
+
surftag = gmsh.model.occ.add_plane_surface([wiretag,])
|
|
327
|
+
poly = GeoPolygon([surftag,])
|
|
328
|
+
poly.points = ptags
|
|
329
|
+
poly.lines = lines
|
|
330
|
+
return poly
|
|
331
|
+
|
|
332
|
+
def extrude(self, length: float, cs: CoordinateSystem = None) -> GeoPrism:
|
|
333
|
+
"""Extrues the polygon along the Z-axis.
|
|
334
|
+
The z-coordinates go from z1 to z2 (in meters). Then the extrusion
|
|
335
|
+
is either provided by a maximum dz distance (in meters) or a number
|
|
336
|
+
of sections N.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
length (length): The length of the extrusion.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
GeoVolume: The resultant Volumetric object.
|
|
343
|
+
"""
|
|
344
|
+
if cs is None:
|
|
345
|
+
cs = GCS
|
|
346
|
+
poly_fin = self._finalize(cs)
|
|
347
|
+
zax = length*cs.zax.np
|
|
348
|
+
poly_fin._exists = False
|
|
349
|
+
volume = gmsh.model.occ.extrude(poly_fin.dimtags, zax[0], zax[1], zax[2])
|
|
350
|
+
tags = [t for d,t in volume if d==3]
|
|
351
|
+
surftags = [t for d,t in volume if d==2]
|
|
352
|
+
return GeoPrism(tags, surftags[0], surftags)
|
|
353
|
+
|
|
354
|
+
def geo(self, cs: CoordinateSystem = None) -> GeoPolygon:
|
|
355
|
+
"""Returns a GeoPolygon object for the current polygon.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
cs (CoordinateSystem, optional): The Coordinate system of which the XY plane will be used. Defaults to None.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
GeoPolygon: The resultant object.
|
|
362
|
+
"""
|
|
363
|
+
if cs is None:
|
|
364
|
+
cs = GCS
|
|
365
|
+
return self._finalize(cs)
|
|
366
|
+
|
|
367
|
+
def revolve(self, cs: CoordinateSystem, origin: tuple[float, float, float], axis: tuple[float, float,float], angle: float = 360.0) -> GeoPrism:
|
|
368
|
+
"""Applies a revolution to the XYPolygon along the provided rotation ais
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
cs (CoordinateSystem, optional): _description_. Defaults to None.
|
|
372
|
+
angle (float, optional): _description_. Defaults to 360.0.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Prism: The resultant
|
|
376
|
+
"""
|
|
377
|
+
if cs is None:
|
|
378
|
+
cs = GCS
|
|
379
|
+
poly_fin = self._finalize(cs)
|
|
380
|
+
|
|
381
|
+
x,y,z = origin
|
|
382
|
+
ax, ay, az = axis
|
|
383
|
+
|
|
384
|
+
volume = gmsh.model.occ.revolve(poly_fin.dimtags, x,y,z, ax, ay, az, angle*np.pi/180)
|
|
385
|
+
tags = [t for d,t in volume if d==3]
|
|
386
|
+
surftags = [t for d,t in volume if d==2]
|
|
387
|
+
return GeoPrism(tags, surftags[0], surftags)
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def circle(radius: float,
|
|
391
|
+
dsmax: float = None,
|
|
392
|
+
tolerance: float = None,
|
|
393
|
+
Nsections: int = None):
|
|
394
|
+
"""This method generates a segmented circle.
|
|
395
|
+
|
|
396
|
+
The number of points along the circumpherence can be specified in 3 ways. By a maximum
|
|
397
|
+
circumpherential length (dsmax), by a radial tolerance (tolerance) or by a number of
|
|
398
|
+
sections (Nsections).
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
radius (float): The circle radius
|
|
402
|
+
dsmax (float, optional): The maximum circumpherential angle. Defaults to None.
|
|
403
|
+
tolerance (float, optional): The maximum radial error. Defaults to None.
|
|
404
|
+
Nsections (int, optional): The number of sections. Defaults to None.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
XYPolygon: The XYPolygon object.
|
|
408
|
+
"""
|
|
409
|
+
if Nsections is not None:
|
|
410
|
+
N = Nsections+1
|
|
411
|
+
elif dsmax is not None:
|
|
412
|
+
N = int(np.ceil((2*np.pi*radius)/dsmax))
|
|
413
|
+
elif tolerance is not None:
|
|
414
|
+
N = int(np.ceil(2*np.pi/np.arccos(1-tolerance)))
|
|
415
|
+
|
|
416
|
+
angs = np.linspace(0,2*np.pi,N)
|
|
417
|
+
|
|
418
|
+
xs = radius*np.cos(angs[:-1])
|
|
419
|
+
ys = radius*np.sin(angs[:-1])
|
|
420
|
+
return XYPolygon(xs, ys)
|
|
421
|
+
|
|
422
|
+
@staticmethod
|
|
423
|
+
def rect(width: float,
|
|
424
|
+
height: float,
|
|
425
|
+
origin: tuple[float, float],
|
|
426
|
+
alignment: Alignment = Alignment.CORNER) -> XYPolygon:
|
|
427
|
+
"""Create a rectangle in the XY-plane as polygon
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
width (float): The width (X)
|
|
431
|
+
height (float): The height (Y)
|
|
432
|
+
origin (tuple[float, float]): The origin (x,y)
|
|
433
|
+
alignment (Alignment, optional): What point the origin describes. Defaults to Alignment.CORNER.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
XYPolygon: A new XYpolygon object
|
|
437
|
+
"""
|
|
438
|
+
if alignment is Alignment.CORNER:
|
|
439
|
+
x0, y0 = origin
|
|
440
|
+
else:
|
|
441
|
+
x0 = origin[0]-width/2
|
|
442
|
+
y0 = origin[1]-height/2
|
|
443
|
+
xs = np.array([x0, x0, x0 + width, x0+width])
|
|
444
|
+
ys = np.array([y0, y0+height, y0+height, y0])
|
|
445
|
+
return XYPolygon(xs, ys)
|
|
446
|
+
|
|
447
|
+
def parametric(self, xfunc: Callable,
|
|
448
|
+
yfunc: Callable,
|
|
449
|
+
xmin: float = 1e-3,
|
|
450
|
+
tolerance: float = 1e-5,
|
|
451
|
+
tmin: float = 0,
|
|
452
|
+
tmax: float = 1,
|
|
453
|
+
reverse: bool = False) -> XYPolygon:
|
|
454
|
+
"""Adds the points of a parametric curve to the polygon.
|
|
455
|
+
The parametric curve is defined by two parametric functions of a parameter t that (by default) lives in the interval from [0,1].
|
|
456
|
+
thus the curve x(t) = xfunc(t), and y(t) = yfunc(t).
|
|
457
|
+
|
|
458
|
+
The tolerance indicates a maximum deviation from the true path.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
xfunc (Callable): The x-coordinate function.
|
|
462
|
+
yfunc (Callable): The y-coordinate function
|
|
463
|
+
tolerance (float): A maximum distance tolerance. Defaults to 10um.
|
|
464
|
+
tmin (float, optional): The start value of the t-parameter. Defaults to 0.
|
|
465
|
+
tmax (float, optional): The end value of the t-parameter. Defaults to 1.
|
|
466
|
+
reverse (bool, optional): Reverses the curve.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
XYPolygon: _description_
|
|
470
|
+
"""
|
|
471
|
+
xs, ys = _discretize_curve(xfunc, yfunc, tmin, tmax, xmin, tolerance)
|
|
472
|
+
|
|
473
|
+
if reverse:
|
|
474
|
+
xs = xs[::-1]
|
|
475
|
+
ys = ys[::-1]
|
|
476
|
+
self.extend(xs, ys)
|
|
477
|
+
return self
|
|
478
|
+
|
|
479
|
+
# def discrete_revolve(self, cs: CoordinateSystem, origin: tuple[float, float, float], axis: tuple[float, float,float], angle: float = 360.0, nsteps: int = 12) -> GeoPrism:
|
|
480
|
+
# """Applies a revolution to the XYPolygon along the coordinate system Z-axis
|
|
481
|
+
|
|
482
|
+
# Args:
|
|
483
|
+
# cs (CoordinateSystem, optional): _description_. Defaults to None.
|
|
484
|
+
# angle (float, optional): _description_. Defaults to 360.0.
|
|
485
|
+
|
|
486
|
+
# Returns:
|
|
487
|
+
# Prism: The resultant
|
|
488
|
+
# """
|
|
489
|
+
# if cs is None:
|
|
490
|
+
# cs = GCS
|
|
491
|
+
|
|
492
|
+
# x,y,z = origin
|
|
493
|
+
# ax, ay, az = axis
|
|
494
|
+
# loops = []
|
|
495
|
+
# loops_edges = []
|
|
496
|
+
|
|
497
|
+
# closed = False
|
|
498
|
+
# if angle == 360:
|
|
499
|
+
# angs = np.linspace(0, 2*np.pi, nsteps+1)[:-1]
|
|
500
|
+
# closed = True
|
|
501
|
+
# else:
|
|
502
|
+
# angs = np.linspace(0, angle*np.pi/180, nsteps)
|
|
503
|
+
|
|
504
|
+
# for x0, y0 in zip(self.x, self.y):
|
|
505
|
+
# #print([rotate_point((x0, y0, 0), axis, ang, origin, degrees=False) for ang in angs])
|
|
506
|
+
# points = [gmsh.model.occ.add_point(*rotate_point((x0, y0, 0), axis, ang, origin, degrees=False)) for ang in angs]
|
|
507
|
+
# points = points + [points[0],]
|
|
508
|
+
# loops.append(points)
|
|
509
|
+
|
|
510
|
+
# edges = [gmsh.model.occ.add_line(p1, p2) for p1, p2 in zip(points[:-1],points[1:])]
|
|
511
|
+
# loops_edges.append(edges)
|
|
512
|
+
|
|
513
|
+
# face1loop = gmsh.model.occ.add_curve_loop(loops_edges[0])
|
|
514
|
+
# face_front = gmsh.model.occ.add_plane_surface([face1loop,])
|
|
515
|
+
|
|
516
|
+
# face2loop = gmsh.model.occ.add_curve_loop(loops_edges[-1])
|
|
517
|
+
# face_back = gmsh.model.occ.add_plane_surface([face2loop,])
|
|
518
|
+
|
|
519
|
+
# faces = []
|
|
520
|
+
# for loop1, loop2 in zip(loops_edges[:-1], loops_edges[1:]):
|
|
521
|
+
# for p1, p2, p3, p4 in zip(loop1[:-1], loop1[1:], loop2[1:], loop2[:0]):
|
|
522
|
+
# curve = gmsh.model.occ.add_curve_loop([p1, p2, p3, p4])
|
|
523
|
+
# face = gmsh.model.occ.add_plane_surface(curve)
|
|
524
|
+
# faces.append(face)
|
|
525
|
+
|
|
526
|
+
# surface_loop = gmsh.model.occ.add_surface_loop(faces + [face_front, face_back])
|
|
527
|
+
# vol = gmsh.model.occ.add_volume([surface_loop,])
|
|
528
|
+
|
|
529
|
+
# return GeoVolume(vol)
|