emerge 0.4.6__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 +54 -0
- emerge/__main__.py +5 -0
- 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 +57 -0
- emerge/plot.py +1 -0
- emerge/pyvista.py +1 -0
- {emerge-0.4.6.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.6.dist-info/RECORD +0 -4
- emerge-0.4.6.dist-info/entry_points.txt +0 -2
- {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,427 @@
|
|
|
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
|
+
|
|
18
|
+
import gmsh
|
|
19
|
+
from ..geometry import GeoObject, GeoSurface, GeoVolume
|
|
20
|
+
from ..cs import CoordinateSystem, GCS
|
|
21
|
+
import numpy as np
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from .operations import subtract
|
|
24
|
+
from ..selection import FaceSelection, Selector, SELECTOR_OBJ
|
|
25
|
+
|
|
26
|
+
from typing import Literal
|
|
27
|
+
from functools import reduce
|
|
28
|
+
|
|
29
|
+
class Alignment(Enum):
|
|
30
|
+
CENTER = 1
|
|
31
|
+
CORNER = 2
|
|
32
|
+
|
|
33
|
+
class Box(GeoVolume):
|
|
34
|
+
""" A class that represents a box shaped volume
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self,
|
|
39
|
+
width: float,
|
|
40
|
+
depth: float,
|
|
41
|
+
height: float,
|
|
42
|
+
position: tuple = (0,0,0),
|
|
43
|
+
alignment: Alignment = Alignment.CORNER,
|
|
44
|
+
cs: CoordinateSystem = GCS):
|
|
45
|
+
"""Creates a box volume object.
|
|
46
|
+
Specify the alignment of the box with the provided position. The options are CORNER (default)
|
|
47
|
+
for the front-left-bottom node of the box or CENTER for the center of the box.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
width (float): The x-size
|
|
51
|
+
depth (float): The y-size
|
|
52
|
+
height (float): The z-size
|
|
53
|
+
position (tuple, optional): The position of the box. Defaults to (0,0,0).
|
|
54
|
+
alignment (Alignment, optional): Which point of the box is placed at the position.
|
|
55
|
+
Defaults to Alignment.CORNER.
|
|
56
|
+
"""
|
|
57
|
+
if alignment is Alignment.CENTER:
|
|
58
|
+
position = (position[0]-width/2, position[1]-depth/2, position[2]-height/2)
|
|
59
|
+
|
|
60
|
+
x,y,z = position
|
|
61
|
+
|
|
62
|
+
tag = gmsh.model.occ.addBox(x,y,z,width,depth,height)
|
|
63
|
+
super().__init__(tag)
|
|
64
|
+
|
|
65
|
+
self.center = (x+width/2, y+depth/2, z+height/2)
|
|
66
|
+
self.width = width
|
|
67
|
+
self.height = height
|
|
68
|
+
self.depth = depth
|
|
69
|
+
|
|
70
|
+
wax = cs.xax.np
|
|
71
|
+
dax = cs.yax.np
|
|
72
|
+
hax = cs.zax.np
|
|
73
|
+
|
|
74
|
+
p0 = self.center
|
|
75
|
+
pc = p0
|
|
76
|
+
self._add_face_pointer('front', pc - depth/2*dax, -dax)
|
|
77
|
+
self._add_face_pointer('back', pc + depth/2*dax, dax)
|
|
78
|
+
self._add_face_pointer('left', pc - width/2*wax, -wax)
|
|
79
|
+
self._add_face_pointer('right', pc + width/2*wax, wax)
|
|
80
|
+
self._add_face_pointer('top', pc + height/2*hax, hax)
|
|
81
|
+
self._add_face_pointer('bottom', pc - height/2*hax, -hax)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
|
|
85
|
+
"""Select all outside faces except for the once specified by outside
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
FaceSelection: The resultant face selection
|
|
89
|
+
"""
|
|
90
|
+
tagslist = [self._face_tags(name) for name in ['bottom','top','right','left','front','back'] if name not in exclude]
|
|
91
|
+
|
|
92
|
+
tags = list(reduce(lambda a,b: a+b, tagslist))
|
|
93
|
+
return FaceSelection(tags)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Sphere(GeoVolume):
|
|
97
|
+
|
|
98
|
+
def __init__(self,
|
|
99
|
+
radius: float,
|
|
100
|
+
position: tuple = (0,0,0)):
|
|
101
|
+
"""Generates a sphere objected centered ont he position with the given radius
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
radius (float): The sphere radius
|
|
105
|
+
position (tuple, optional): The center position. Defaults to (0,0,0).
|
|
106
|
+
"""
|
|
107
|
+
super().__init__([])
|
|
108
|
+
x,y,z = position
|
|
109
|
+
self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
|
|
110
|
+
|
|
111
|
+
class XYPlate(GeoSurface):
|
|
112
|
+
def __init__(self,
|
|
113
|
+
width: float,
|
|
114
|
+
depth: float,
|
|
115
|
+
position: tuple = (0,0,0),
|
|
116
|
+
alignment: Alignment = Alignment.CORNER):
|
|
117
|
+
"""Generates and XY-plane oriented plate
|
|
118
|
+
|
|
119
|
+
Specify the alignment of the plate with the provided position. The options are CORNER (default)
|
|
120
|
+
for the front-left node of the plate or CENTER for the center of the plate.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
width (float): The x-size of the plate
|
|
124
|
+
depth (float): The y-size of the plate
|
|
125
|
+
position (tuple, optional): The position of the alignment node. Defaults to (0,0,0).
|
|
126
|
+
alignment (Alignment, optional): Which node to align to. Defaults to Alignment.CORNER.
|
|
127
|
+
"""
|
|
128
|
+
super().__init__([])
|
|
129
|
+
if alignment is Alignment.CENTER:
|
|
130
|
+
position = (position[0]-width/2, position[1]-depth/2, position[2])
|
|
131
|
+
|
|
132
|
+
x,y,z = position
|
|
133
|
+
self.tags: list[int] = [gmsh.model.occ.addRectangle(x,y,z,width,depth),]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Plate(GeoSurface):
|
|
137
|
+
|
|
138
|
+
def __init__(self,
|
|
139
|
+
origin: tuple[float, float, float],
|
|
140
|
+
u: tuple[float, float, float],
|
|
141
|
+
v: tuple[float, float, float]):
|
|
142
|
+
"""A generalized 2D rectangular plate in XYZ-space.
|
|
143
|
+
|
|
144
|
+
The plate is specified by an origin (o) in meters coordinate plus two vectors (u,v) in meters
|
|
145
|
+
that span two of the sides such that all points of the plate are defined by:
|
|
146
|
+
p1 = o
|
|
147
|
+
p2 = o+u
|
|
148
|
+
p3 = o+v
|
|
149
|
+
p4 = o+u+v
|
|
150
|
+
Args:
|
|
151
|
+
origin (tuple[float, float, float]): The origin of the plate in meters
|
|
152
|
+
u (tuple[float, float, float]): The u-axis of the plate
|
|
153
|
+
v (tuple[float, float, float]): The v-axis of the plate
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
origin = np.array(origin)
|
|
157
|
+
u = np.array(u)
|
|
158
|
+
v = np.array(v)
|
|
159
|
+
|
|
160
|
+
tagp1 = gmsh.model.occ.addPoint(*origin)
|
|
161
|
+
tagp2 = gmsh.model.occ.addPoint(*(origin+u))
|
|
162
|
+
tagp3 = gmsh.model.occ.addPoint(*(origin+v))
|
|
163
|
+
tagp4 = gmsh.model.occ.addPoint(*(origin+u+v))
|
|
164
|
+
|
|
165
|
+
tagl1 = gmsh.model.occ.addLine(tagp1, tagp2)
|
|
166
|
+
tagl2 = gmsh.model.occ.addLine(tagp2, tagp4)
|
|
167
|
+
tagl3 = gmsh.model.occ.addLine(tagp4, tagp3)
|
|
168
|
+
tagl4 = gmsh.model.occ.addLine(tagp3, tagp1)
|
|
169
|
+
|
|
170
|
+
tag_wire = gmsh.model.occ.addWire([tagl1,tagl2, tagl3, tagl4])
|
|
171
|
+
|
|
172
|
+
tags: list[int] = [gmsh.model.occ.addPlaneSurface([tag_wire,]),]
|
|
173
|
+
super().__init__(tags)
|
|
174
|
+
|
|
175
|
+
class Cyllinder(GeoVolume):
|
|
176
|
+
|
|
177
|
+
def __init__(self,
|
|
178
|
+
radius: float,
|
|
179
|
+
height: float,
|
|
180
|
+
cs: CoordinateSystem = None,
|
|
181
|
+
Nsections: int = None):
|
|
182
|
+
"""Generates a Cyllinder object in 3D space.
|
|
183
|
+
The cyllinder will always be placed in the origin of the provided CoordinateSystem.
|
|
184
|
+
The bottom cyllinder plane is always placed in the XY-plane. The lenth of the cyllinder is
|
|
185
|
+
oriented along the Z-axis.
|
|
186
|
+
|
|
187
|
+
By default the cyllinder uses the Open Cascade modeling for a cyllinder. In this representation
|
|
188
|
+
the surface of the cyllinder is approximated with a tolerance thay may be irregular.
|
|
189
|
+
As an alternative, the argument Nsections may be provided in which case the Cyllinder is replaced
|
|
190
|
+
by an extrusion of a regular N-sided polygon.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
radius (float): The radius of the Cyllinder
|
|
194
|
+
height (float): The height of the Cyllinder
|
|
195
|
+
cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
|
|
196
|
+
Nsections (int, optional): The number of sections. Defaults to None.
|
|
197
|
+
"""
|
|
198
|
+
ax = cs.zax.np
|
|
199
|
+
|
|
200
|
+
if Nsections:
|
|
201
|
+
from .polybased import XYPolygon
|
|
202
|
+
cyl = XYPolygon.circle(radius, Nsections=Nsections).extrude(height, cs)
|
|
203
|
+
cyl._exists = False
|
|
204
|
+
self._face_pointers = cyl._face_pointers
|
|
205
|
+
super().__init__(cyl.tags)
|
|
206
|
+
else:
|
|
207
|
+
cyl = gmsh.model.occ.addCylinder(cs.origin[0], cs.origin[1], cs.origin[2],
|
|
208
|
+
height*ax[0], height*ax[1], height*ax[2],
|
|
209
|
+
radius)
|
|
210
|
+
super().__init__(cyl)
|
|
211
|
+
self._add_face_pointer('front', cs.origin, -cs.zax.np)
|
|
212
|
+
self._add_face_pointer('back', cs.origin+height*cs.zax.np, cs.zax.np)
|
|
213
|
+
|
|
214
|
+
self.cs: CoordinateSystem = cs
|
|
215
|
+
self.radius = radius
|
|
216
|
+
self.height = height
|
|
217
|
+
|
|
218
|
+
def face_points(self, nRadius: int = 10, Angle: int = 10, face_number: int = 1) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
219
|
+
"""Returns the points of the cylinder."""
|
|
220
|
+
rs = np.linspace(0, self.radius, nRadius)
|
|
221
|
+
angles = np.linspace(0, 2 * np.pi, int(360 / Angle), endpoint=False)
|
|
222
|
+
R, A = np.meshgrid(rs, angles)
|
|
223
|
+
x = R * np.cos(A)
|
|
224
|
+
y = R * np.sin(A)
|
|
225
|
+
z = np.zeros_like(x)
|
|
226
|
+
if face_number == 2:
|
|
227
|
+
z = z + self.height
|
|
228
|
+
|
|
229
|
+
xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
|
|
230
|
+
return xo, yo, zo
|
|
231
|
+
|
|
232
|
+
class CoaxCyllinder(GeoVolume):
|
|
233
|
+
"""A coaxial cylinder with an inner and outer radius."""
|
|
234
|
+
|
|
235
|
+
def __init__(self,
|
|
236
|
+
rout: float,
|
|
237
|
+
rin: float,
|
|
238
|
+
height: float,
|
|
239
|
+
cs: CoordinateSystem = None,
|
|
240
|
+
Nsections: int = None):
|
|
241
|
+
"""Generates a Coaxial cyllinder object in 3D space.
|
|
242
|
+
The coaxial cyllinder will always be placed in the origin of the provided CoordinateSystem.
|
|
243
|
+
The bottom coax plane is always placed in the XY-plane. The lenth of the coax is
|
|
244
|
+
oriented along the Z-axis.
|
|
245
|
+
|
|
246
|
+
By default the coax uses the Open Cascade modeling for a cyllinder. In this representation
|
|
247
|
+
the surface of the cyllinder is approximated with a tolerance thay may be irregular.
|
|
248
|
+
As an alternative, the argument Nsections may be provided in which case the Cyllinder is replaced
|
|
249
|
+
by an extrusion of a regular N-sided polygon.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
radius (float): The radius of the Cyllinder
|
|
253
|
+
height (float): The height of the Cyllinder
|
|
254
|
+
cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
|
|
255
|
+
Nsections (int, optional): The number of sections. Defaults to None.
|
|
256
|
+
"""
|
|
257
|
+
if cs is None:
|
|
258
|
+
cs = GCS
|
|
259
|
+
if rout <= rin:
|
|
260
|
+
raise ValueError("Outer radius must be greater than inner radius.")
|
|
261
|
+
|
|
262
|
+
self.rout = rout
|
|
263
|
+
self.rin = rin
|
|
264
|
+
self.height = height
|
|
265
|
+
|
|
266
|
+
self.cyl_out = Cyllinder(rout, height, cs, Nsections=Nsections)
|
|
267
|
+
self.cyl_in = Cyllinder(rin, height, cs, Nsections=Nsections)
|
|
268
|
+
self.cyl_in._exists = False
|
|
269
|
+
self.cyl_out._exists = False
|
|
270
|
+
cyltags, _ = gmsh.model.occ.cut(self.cyl_out.dimtags, self.cyl_in.dimtags)
|
|
271
|
+
|
|
272
|
+
super().__init__([dt[1] for dt in cyltags])
|
|
273
|
+
|
|
274
|
+
self._add_face_pointer('front', cs.origin, -cs.zax.np)
|
|
275
|
+
self._add_face_pointer('back', cs.origin+height*cs.zax.np, cs.zax.np)
|
|
276
|
+
|
|
277
|
+
self.cs = cs
|
|
278
|
+
|
|
279
|
+
def face_points(self, nRadius: int = 10, Angle: int = 10, face_number: int = 1) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
280
|
+
"""Returns the points of the coaxial cylinder."""
|
|
281
|
+
rs = np.linspace(self.rin, self.rout, nRadius)
|
|
282
|
+
angles = np.linspace(0, 2 * np.pi, int(360 / Angle), endpoint=False)
|
|
283
|
+
R, A = np.meshgrid(rs, angles)
|
|
284
|
+
x = R * np.cos(A)
|
|
285
|
+
y = R * np.sin(A)
|
|
286
|
+
z = np.zeros_like(x)
|
|
287
|
+
if face_number == 2:
|
|
288
|
+
z = z + self.height
|
|
289
|
+
|
|
290
|
+
xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
|
|
291
|
+
return xo, yo, zo
|
|
292
|
+
return super().boundary()
|
|
293
|
+
|
|
294
|
+
class HalfSphere(GeoVolume):
|
|
295
|
+
|
|
296
|
+
def __init__(self,
|
|
297
|
+
radius: float,
|
|
298
|
+
position: tuple = (0,0,0),
|
|
299
|
+
direction: tuple = (1,0,0)):
|
|
300
|
+
super().__init__([])
|
|
301
|
+
sphere = Sphere(radius, position=position)
|
|
302
|
+
cx, cy, cz = position
|
|
303
|
+
|
|
304
|
+
box = Box(1.1*radius, 2.2*radius, 2.2*radius, position=(cx-radius*1.1,cy-radius*1.1, cz-radius*1.1))
|
|
305
|
+
|
|
306
|
+
self.tag = subtract(sphere, box)[0].tag
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class OldBox(GeoVolume):
|
|
311
|
+
'''The sided box class creates a box just like the Box class but with selectable face tags.
|
|
312
|
+
This class is more convenient in use when defining radiation boundaries.'''
|
|
313
|
+
def __init__(self,
|
|
314
|
+
width: float,
|
|
315
|
+
depth: float,
|
|
316
|
+
height: float,
|
|
317
|
+
position: tuple = (0,0,0),
|
|
318
|
+
cs: CoordinateSystem = GCS,
|
|
319
|
+
alignment: Alignment = Alignment.CORNER):
|
|
320
|
+
"""Creates a box volume object with selectable sides.
|
|
321
|
+
|
|
322
|
+
Specify the alignment of the box with the provided position. The options are CORNER (default)
|
|
323
|
+
for the front-left-bottom node of the box or CENTER for the center of the box.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
width (float): The x-size
|
|
327
|
+
depth (float): The y-size
|
|
328
|
+
height (float): The z-size
|
|
329
|
+
position (tuple, optional): The position of the box. Defaults to (0,0,0).
|
|
330
|
+
alignment (Alignment, optional): Which point of the box is placed at the position.
|
|
331
|
+
Defaults to Alignment.CORNER.
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if alignment is Alignment.CORNER:
|
|
336
|
+
position = (position[0]+width/2, position[1]+depth/2, position[2])
|
|
337
|
+
elif alignment is Alignment.CENTER:
|
|
338
|
+
position = (position[0], position[1], position[2] - height/2)
|
|
339
|
+
p0 = np.array(position)
|
|
340
|
+
p1 = p0 + cs.zax.np * height
|
|
341
|
+
|
|
342
|
+
wax = cs.xax.np
|
|
343
|
+
dax = cs.yax.np
|
|
344
|
+
hax = cs.zax.np
|
|
345
|
+
|
|
346
|
+
w = width
|
|
347
|
+
d = depth
|
|
348
|
+
|
|
349
|
+
p11 = p0 + wax * w/2 + dax * d/2 # right back
|
|
350
|
+
p12 = p0 + wax * w/2 - dax * d/2 # right front
|
|
351
|
+
p13 = p0 - wax * w/2 - dax * d/2 # Left front
|
|
352
|
+
p14 = p0 - wax * w/2 + dax * d/2 # left back
|
|
353
|
+
|
|
354
|
+
p21 = p1 + wax * w/2 + dax * d/2
|
|
355
|
+
p22 = p1 + wax * w/2 - dax * d/2
|
|
356
|
+
p23 = p1 - wax * w/2 - dax * d/2
|
|
357
|
+
p24 = p1 - wax * w/2 + dax * d/2
|
|
358
|
+
|
|
359
|
+
pt11 = gmsh.model.occ.addPoint(*p11) # right back
|
|
360
|
+
pt12 = gmsh.model.occ.addPoint(*p12) # Right front
|
|
361
|
+
pt13 = gmsh.model.occ.addPoint(*p13) # left front
|
|
362
|
+
pt14 = gmsh.model.occ.addPoint(*p14) # Left back
|
|
363
|
+
pt21 = gmsh.model.occ.addPoint(*p21)
|
|
364
|
+
pt22 = gmsh.model.occ.addPoint(*p22)
|
|
365
|
+
pt23 = gmsh.model.occ.addPoint(*p23)
|
|
366
|
+
pt24 = gmsh.model.occ.addPoint(*p24)
|
|
367
|
+
|
|
368
|
+
l1r = gmsh.model.occ.addLine(pt11, pt12) #Right
|
|
369
|
+
l1f = gmsh.model.occ.addLine(pt12, pt13) #Front
|
|
370
|
+
l1l = gmsh.model.occ.addLine(pt13, pt14) #Left
|
|
371
|
+
l1b = gmsh.model.occ.addLine(pt14, pt11) #Back
|
|
372
|
+
|
|
373
|
+
l2r = gmsh.model.occ.addLine(pt21, pt22)
|
|
374
|
+
l2f = gmsh.model.occ.addLine(pt22, pt23)
|
|
375
|
+
l2l = gmsh.model.occ.addLine(pt23, pt24)
|
|
376
|
+
l2b = gmsh.model.occ.addLine(pt24, pt21)
|
|
377
|
+
|
|
378
|
+
dbr = gmsh.model.occ.addLine(pt11, pt21)
|
|
379
|
+
dfr = gmsh.model.occ.addLine(pt12, pt22)
|
|
380
|
+
dfl = gmsh.model.occ.addLine(pt13, pt23)
|
|
381
|
+
dbl = gmsh.model.occ.addLine(pt14, pt24)
|
|
382
|
+
|
|
383
|
+
wbot = gmsh.model.occ.addWire([l1r, l1b, l1l, l1f])
|
|
384
|
+
wtop = gmsh.model.occ.addWire([l2r, l2b, l2l, l2f])
|
|
385
|
+
wright = gmsh.model.occ.addWire([l1r, dbr, l2r, dfr])
|
|
386
|
+
wleft = gmsh.model.occ.addWire([l1l, dbl, l2l, dfl])
|
|
387
|
+
wback = gmsh.model.occ.addWire([l1b, dbl, l2b, dbr])
|
|
388
|
+
wfront = gmsh.model.occ.addWire([l1f, dfr, l2f, dfl])
|
|
389
|
+
|
|
390
|
+
bottom_tag = gmsh.model.occ.addSurfaceFilling(wbot)
|
|
391
|
+
top_tag = gmsh.model.occ.addSurfaceFilling(wtop)
|
|
392
|
+
front_tag = gmsh.model.occ.addSurfaceFilling(wfront)
|
|
393
|
+
back_tag = gmsh.model.occ.addSurfaceFilling(wback)
|
|
394
|
+
left_tag = gmsh.model.occ.addSurfaceFilling(wleft)
|
|
395
|
+
right_tag = gmsh.model.occ.addSurfaceFilling(wright)
|
|
396
|
+
|
|
397
|
+
sv = gmsh.model.occ.addSurfaceLoop([bottom_tag,
|
|
398
|
+
top_tag,
|
|
399
|
+
right_tag,
|
|
400
|
+
left_tag,
|
|
401
|
+
front_tag,
|
|
402
|
+
back_tag])
|
|
403
|
+
|
|
404
|
+
volume_tag: int = gmsh.model.occ.addVolume([sv,])
|
|
405
|
+
|
|
406
|
+
super().__init__(volume_tag)
|
|
407
|
+
#self.tags: list[int] = [volume_tag,]
|
|
408
|
+
|
|
409
|
+
pc = p0 + height/2*hax
|
|
410
|
+
self._add_face_pointer('front', pc - depth/2*dax, -dax)
|
|
411
|
+
self._add_face_pointer('back', pc + depth/2*dax, dax)
|
|
412
|
+
self._add_face_pointer('left', pc - width/2*wax, -wax)
|
|
413
|
+
self._add_face_pointer('right', pc + width/2*wax, wax)
|
|
414
|
+
self._add_face_pointer('top', pc + height/2*hax, hax)
|
|
415
|
+
self._add_face_pointer('bottom', pc - height/2*hax, -hax)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
|
|
419
|
+
"""Select all outside faces except for the once specified by outside
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
FaceSelection: The resultant face selection
|
|
423
|
+
"""
|
|
424
|
+
tagslist = [self._face_tags(name) for name in ['bottom','top','right','left','front','back'] if name not in exclude]
|
|
425
|
+
|
|
426
|
+
tags = list(reduce(lambda a,b: a+b, tagslist))
|
|
427
|
+
return FaceSelection(tags)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import gmsh
|
|
2
|
+
from ..geometry import GeoPoint, GeoEdge, GeoVolume, GeoSurface, GeoObject
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
class STEPItems:
|
|
7
|
+
|
|
8
|
+
def __init__(self, filename: str, unit: float = 1.0):
|
|
9
|
+
"""Imports the provided STEP file.
|
|
10
|
+
Specify the unit in case of scaling issues where mm units are not taken into consideration.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
filename (str): The filename
|
|
14
|
+
unit (float, optional): The STEP file size unit. Defaults to 1.0.
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
FileNotFoundError: If a file does not exist
|
|
18
|
+
"""
|
|
19
|
+
stl_path = Path(filename)
|
|
20
|
+
gmsh.option.setNumber("Geometry.OCCScaling", 1)
|
|
21
|
+
gmsh.option.setNumber("Geometry.OCCImportLabels", 2)
|
|
22
|
+
|
|
23
|
+
if not stl_path.exists:
|
|
24
|
+
raise FileNotFoundError(f'File with name {stl_path} does not exist.')
|
|
25
|
+
|
|
26
|
+
dimtags = gmsh.model.occ.import_shapes(filename, format='step')
|
|
27
|
+
|
|
28
|
+
gmsh.model.occ.affine_transform(dimtags, np.array([unit, 0, 0, 0,
|
|
29
|
+
0, unit, 0, 0,
|
|
30
|
+
0, 0, unit, 0,
|
|
31
|
+
0, 0, 0, 1]))
|
|
32
|
+
#dimtags = gmsh.model.occ.heal_shapes(dimtags, tolerance=1e-6)
|
|
33
|
+
|
|
34
|
+
self.points: dict[str, GeoPoint] = dict()
|
|
35
|
+
self.edges: dict[str, GeoEdge] = dict()
|
|
36
|
+
self.surfaces: dict[str, GeoSurface] = dict()
|
|
37
|
+
self.volumes: dict[str, GeoVolume] = dict()
|
|
38
|
+
|
|
39
|
+
i = 0
|
|
40
|
+
for dim, tag in dimtags:
|
|
41
|
+
name = gmsh.model.getPhysicalName(dim, tag)
|
|
42
|
+
if name == '':
|
|
43
|
+
name = f'Obj{i}'
|
|
44
|
+
i+=1
|
|
45
|
+
if dim == 0:
|
|
46
|
+
self.points[name] = GeoPoint(tag)
|
|
47
|
+
elif dim == 1:
|
|
48
|
+
self.edges[name] = GeoEdge(tag)
|
|
49
|
+
elif dim == 2:
|
|
50
|
+
self.surfaces[name] = GeoSurface(tag)
|
|
51
|
+
elif dim == 3:
|
|
52
|
+
self.volumes[name] = GeoVolume(tag)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def _dicts(self):
|
|
56
|
+
yield self.points
|
|
57
|
+
yield self.edges
|
|
58
|
+
yield self.surfaces
|
|
59
|
+
yield self.volumes
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def objects(self) -> tuple[GeoObject,...]:
|
|
63
|
+
objects = tuple()
|
|
64
|
+
for dct in self._dicts:
|
|
65
|
+
objects = objects + tuple(dct.values())
|
|
66
|
+
return objects
|
|
67
|
+
|
|
68
|
+
def __getitem__(self, name: str) -> GeoObject:
|
|
69
|
+
if name in self.points:
|
|
70
|
+
return self.points[name]
|
|
71
|
+
elif name in self.edges:
|
|
72
|
+
return self.edges[name]
|
|
73
|
+
elif name in self.surfaces:
|
|
74
|
+
return self.surfaces[name]
|
|
75
|
+
elif name in self.volumes:
|
|
76
|
+
return self.volumes[name]
|
|
77
|
+
|
emerge/_emerge/geo2d.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# import shapely as shp
|
|
2
|
+
# import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# def rectangle(p1: tuple, p2: tuple) -> shp.Polygon:
|
|
6
|
+
# x1, y1 = p1
|
|
7
|
+
# x2, y2 = p2
|
|
8
|
+
# x1, x2 = sorted([x1, x2])
|
|
9
|
+
# y1, y2 = sorted([y1, y2])
|
|
10
|
+
# return shp.Polygon(((x1, y1), (x2, y1), (x2, y2), (x1, y2)))
|
|
11
|
+
|
|
12
|
+
# def rect_center(center: tuple, width: float, height: float):
|
|
13
|
+
# x0, y0 = center
|
|
14
|
+
# w, h = width, height
|
|
15
|
+
# return shp.Polygon(((x0-w/2,y0-h/2),(x0+w/2,y0-h/2),(x0+w/2,y0+h/2),(x0-w/2,y0+h/2)))
|
|
16
|
+
|
|
17
|
+
# def polygon(points) -> shp.Polygon:
|
|
18
|
+
# return shp.Polygon(points)
|
|
19
|
+
|
|
20
|
+
# def circle(center: tuple, radius: float, segments: int, ang_range: tuple = (0,360)):
|
|
21
|
+
# x,y = center
|
|
22
|
+
# points = []
|
|
23
|
+
# path = []
|
|
24
|
+
# angs = np.linspace(ang_range[0],ang_range[1],segments+1)
|
|
25
|
+
|
|
26
|
+
# angs = angs*np.pi/180
|
|
27
|
+
# for i in range(segments+1):
|
|
28
|
+
# ang = angs[i]
|
|
29
|
+
# points.append((x+radius*np.cos(ang),y+radius*np.sin(ang)))
|
|
30
|
+
# path.append((x+radius*np.cos(ang),y+radius*np.sin(ang)))
|
|
31
|
+
|
|
32
|
+
# if ang_range[0] != ang_range[1]%360:
|
|
33
|
+
# points = [center,] + points
|
|
34
|
+
# else:
|
|
35
|
+
# points = points[:-1]
|
|
36
|
+
# path = path + [path[0],]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# return shp.Polygon(points), shp.LineString(path)
|
|
40
|
+
|
|
41
|
+
# def transform(polygon: shp.Polygon, transformation: callable, reverse=False) -> shp.Polygon:
|
|
42
|
+
# ''' Transforms the provided shapely polygon by applying the transformation f(x,y) to each coordinate'''
|
|
43
|
+
# ex, ey = polygon.exterior.xy
|
|
44
|
+
# interiors = [interior.xy for interior in polygon.interiors]
|
|
45
|
+
# new_poly = shp.Polygon(transformation(x,y) for x,y in zip(ex, ey))
|
|
46
|
+
# for interior in interiors:
|
|
47
|
+
# print(f'Type of interior = {type(interior)}, {interior}')
|
|
48
|
+
# ix, iy = interior
|
|
49
|
+
# new_poly = new_poly.difference(shp.Polygon(transformation(x,y) for x,y in zip(ix, iy)))
|
|
50
|
+
# if reverse:
|
|
51
|
+
# new_poly = new_poly.reverse()
|
|
52
|
+
# return new_poly
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# def move(polygon: shp.Polygon, dx: float, dy: float) -> shp.Polygon:
|
|
56
|
+
# func = lambda x,y: (x+dx, y+dy)
|
|
57
|
+
# return transform(polygon, func)
|
|
58
|
+
|
|
59
|
+
# def array(polygon: shp.Polygon, direction: tuple, N: int, include_original: bool = True) -> list[shp.Polygon]:
|
|
60
|
+
# polys = []
|
|
61
|
+
# for i in range(N+1):
|
|
62
|
+
# polys.append(move(polygon, direction[0]*i, direction[1]*i))
|
|
63
|
+
# if not include_original:
|
|
64
|
+
# polys = polys[1:]
|
|
65
|
+
# return polys
|
|
66
|
+
|
|
67
|
+
# def mirror(polygon: shp.Polygon, origin: tuple, axis: tuple) -> shp.Polygon:
|
|
68
|
+
# ax, ay = axis[0]/np.sqrt(axis[0]**2 + axis[1]**2), axis[1]/np.sqrt(axis[0]**2 + axis[1]**2)
|
|
69
|
+
# def _mir_transform(x, y):
|
|
70
|
+
# dotprod = (x-origin[0])*ax + (y-origin[1])*ay
|
|
71
|
+
# x2 = x - dotprod*ax*2
|
|
72
|
+
# y2 = y - dotprod*ay*2
|
|
73
|
+
# return x2, y2
|
|
74
|
+
# return transform(polygon, _mir_transform, reverse=True)
|
|
75
|
+
|
|
76
|
+
# def rasterize(polygons: list[shp.Polygon], gridsize: float) -> list[shp.Polygon]:
|
|
77
|
+
# output_polygons = []
|
|
78
|
+
# grid = lambda x: np.round(np.array(x)/gridsize)*gridsize
|
|
79
|
+
# for poly in polygons:
|
|
80
|
+
# ex, ey = poly.exterior.xy
|
|
81
|
+
# interiors = [interior.xy for interior in poly.interiors]
|
|
82
|
+
# new_poly = shp.Polygon((x,y) for x,y in zip(grid(ex), grid(ey)))
|
|
83
|
+
# for ix, iy in interiors:
|
|
84
|
+
# new_poly.difference(shp.Polygon((x,y) for x,y in zip(grid(ix), grid(iy))))
|
|
85
|
+
# output_polygons.append(new_poly)
|
|
86
|
+
# return output_polygons
|