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.

Files changed (80) hide show
  1. emerge/__init__.py +54 -0
  2. emerge/__main__.py +5 -0
  3. emerge/_emerge/__init__.py +42 -0
  4. emerge/_emerge/bc.py +197 -0
  5. emerge/_emerge/coord.py +119 -0
  6. emerge/_emerge/cs.py +523 -0
  7. emerge/_emerge/dataset.py +36 -0
  8. emerge/_emerge/elements/__init__.py +19 -0
  9. emerge/_emerge/elements/femdata.py +212 -0
  10. emerge/_emerge/elements/index_interp.py +64 -0
  11. emerge/_emerge/elements/legrange2.py +172 -0
  12. emerge/_emerge/elements/ned2_interp.py +645 -0
  13. emerge/_emerge/elements/nedelec2.py +140 -0
  14. emerge/_emerge/elements/nedleg2.py +217 -0
  15. emerge/_emerge/geo/__init__.py +24 -0
  16. emerge/_emerge/geo/horn.py +107 -0
  17. emerge/_emerge/geo/modeler.py +449 -0
  18. emerge/_emerge/geo/operations.py +254 -0
  19. emerge/_emerge/geo/pcb.py +1244 -0
  20. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  21. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  22. emerge/_emerge/geo/pmlbox.py +204 -0
  23. emerge/_emerge/geo/polybased.py +529 -0
  24. emerge/_emerge/geo/shapes.py +427 -0
  25. emerge/_emerge/geo/step.py +77 -0
  26. emerge/_emerge/geo2d.py +86 -0
  27. emerge/_emerge/geometry.py +510 -0
  28. emerge/_emerge/howto.py +214 -0
  29. emerge/_emerge/logsettings.py +5 -0
  30. emerge/_emerge/material.py +118 -0
  31. emerge/_emerge/mesh3d.py +730 -0
  32. emerge/_emerge/mesher.py +339 -0
  33. emerge/_emerge/mth/common_functions.py +33 -0
  34. emerge/_emerge/mth/integrals.py +71 -0
  35. emerge/_emerge/mth/optimized.py +357 -0
  36. emerge/_emerge/periodic.py +263 -0
  37. emerge/_emerge/physics/__init__.py +0 -0
  38. emerge/_emerge/physics/microwave/__init__.py +1 -0
  39. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  40. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  41. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  42. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  43. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  44. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  45. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  46. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  47. emerge/_emerge/physics/microwave/periodic.py +82 -0
  48. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  49. emerge/_emerge/physics/microwave/sc.py +175 -0
  50. emerge/_emerge/physics/microwave/simjob.py +147 -0
  51. emerge/_emerge/physics/microwave/sparam.py +138 -0
  52. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  53. emerge/_emerge/plot/__init__.py +0 -0
  54. emerge/_emerge/plot/display.py +394 -0
  55. emerge/_emerge/plot/grapher.py +93 -0
  56. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  57. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  58. emerge/_emerge/plot/pyvista/display.py +931 -0
  59. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  60. emerge/_emerge/plot/simple_plots.py +551 -0
  61. emerge/_emerge/plot.py +225 -0
  62. emerge/_emerge/projects/__init__.py +0 -0
  63. emerge/_emerge/projects/_gen_base.txt +32 -0
  64. emerge/_emerge/projects/_load_base.txt +24 -0
  65. emerge/_emerge/projects/generate_project.py +40 -0
  66. emerge/_emerge/selection.py +596 -0
  67. emerge/_emerge/simmodel.py +444 -0
  68. emerge/_emerge/simulation_data.py +411 -0
  69. emerge/_emerge/solver.py +993 -0
  70. emerge/_emerge/system.py +54 -0
  71. emerge/cli.py +19 -0
  72. emerge/lib.py +57 -0
  73. emerge/plot.py +1 -0
  74. emerge/pyvista.py +1 -0
  75. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
  76. emerge-0.4.8.dist-info/RECORD +78 -0
  77. emerge-0.4.8.dist-info/entry_points.txt +2 -0
  78. emerge-0.4.6.dist-info/RECORD +0 -4
  79. emerge-0.4.6.dist-info/entry_points.txt +0 -2
  80. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
@@ -0,0 +1,140 @@
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
+ from __future__ import annotations
19
+ import numpy as np
20
+ from ..mesh3d import Mesh3D
21
+ from .femdata import FEMBasis
22
+ from .ned2_interp import ned2_tet_interp, ned2_tet_interp_curl
23
+ from ..mth.optimized import local_mapping
24
+ from .index_interp import index_interp
25
+
26
+ ############### Nedelec2 Class
27
+
28
+ class Nedelec2(FEMBasis):
29
+
30
+
31
+ def __init__(self, mesh: Mesh3D):
32
+ super().__init__(mesh)
33
+
34
+ self.nedges: int = self.mesh.n_edges
35
+ self.ntris: int = self.mesh.n_tris
36
+ self.ntets: int = self.mesh.n_tets
37
+
38
+ self.nfield: int = 2*self.nedges + 2*self.ntris
39
+
40
+ ######## MESH Derived
41
+
42
+ nedges = self.mesh.n_edges
43
+ ntris = self.mesh.n_tris
44
+
45
+ self.tet_to_field: np.ndarray = np.zeros((20, self.mesh.tets.shape[1]), dtype=int)
46
+ self.tet_to_field[:6,:] = self.mesh.tet_to_edge
47
+ self.tet_to_field[6:10,:] = self.mesh.tet_to_tri + nedges
48
+ self.tet_to_field[10:16,:] = self.mesh.tet_to_edge + (ntris+nedges)
49
+ self.tet_to_field[16:20,:] = self.mesh.tet_to_tri + (ntris+2*nedges)
50
+
51
+ self.edge_to_field: np.ndarray = np.zeros((2,nedges), dtype=int)
52
+
53
+ self.edge_to_field[0,:] = np.arange(nedges)
54
+ self.edge_to_field[1,:] = np.arange(nedges) + ntris + nedges
55
+
56
+ self.tri_to_field: np.ndarray = np.zeros((8,ntris), dtype=int)
57
+
58
+ self.tri_to_field[:3,:] = self.mesh.tri_to_edge
59
+ self.tri_to_field[3,:] = np.arange(ntris) + nedges
60
+ self.tri_to_field[4:7,:] = self.mesh.tri_to_edge + nedges + ntris
61
+ self.tri_to_field[7,:] = np.arange(ntris) + 2*nedges + ntris
62
+
63
+ ##
64
+ self._field: np.ndarray = None
65
+ self.n_tet_dofs = 20
66
+ self.n_tri_dofs = 8
67
+ self._all_tet_ids = np.arange(self.ntets)
68
+
69
+ self.empty_tri_rowcol()
70
+
71
+ def interpolate(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, tetids: np.ndarray = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
72
+ '''
73
+ Interpolate the provided field data array at the given xs, ys and zs coordinates
74
+ '''
75
+ if tetids is None:
76
+ tetids = self._all_tet_ids
77
+ return ned2_tet_interp(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, tetids)
78
+
79
+ def interpolate_curl(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, c: np.ndarray, tetids: np.ndarray = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
80
+ """
81
+ Interpolates the curl of the field at the given points.
82
+ """
83
+ if tetids is None:
84
+ tetids = self._all_tet_ids
85
+ return ned2_tet_interp_curl(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, c, tetids)
86
+
87
+ def interpolate_index(self, xs: np.ndarray,
88
+ ys: np.ndarray,
89
+ zs: np.ndarray,
90
+ tetids: np.ndarray = None) -> np.ndarray:
91
+ if tetids is None:
92
+ tetids = self._all_tet_ids
93
+
94
+ return index_interp(np.array([xs, ys, zs]), self.mesh.tets, self.mesh.nodes, tetids)
95
+ ###### INDEX MAPPINGS
96
+
97
+ def local_tet_to_triid(self, itet: int) -> np.ndarray:
98
+ tri_ids = self.tet_to_field[6:10, itet] - self.n_edges
99
+ global_tri_map = self.mesh.tris[:, tri_ids]
100
+ return local_mapping(self.mesh.tets[:, itet], global_tri_map)
101
+
102
+ def local_tet_to_edgeid(self, itet: int) -> np.ndarray:
103
+ global_edge_map = self.mesh.edges[:, self.tet_to_field[:6,itet]]
104
+ return local_mapping(self.mesh.tets[:, itet], global_edge_map)
105
+
106
+ def local_tri_to_edgeid(self, itri: int) -> np.ndarray:
107
+ global_edge_map = self.mesh.edges[:, self.tri_to_field[:3,itri]]
108
+ return local_mapping(self.mesh.tris[:, itri], global_edge_map)
109
+
110
+ def map_edge_to_field(self, edge_ids: np.ndarray) -> np.ndarray:
111
+ """
112
+ Returns the field ids for the edges.
113
+ """
114
+ # Concatinate the edges with the edges + ntris + nedges
115
+ edge_ids = np.array(edge_ids)
116
+ return np.concatenate((edge_ids, edge_ids + self.ntris + self.nedges))
117
+
118
+ ########
119
+ # @staticmethod
120
+ # def tet_stiff_mass_submatrix(tet_vertices: np.ndarray,
121
+ # edge_lengths: np.ndarray,
122
+ # local_edge_map: np.ndarray,
123
+ # local_tri_map: np.ndarray,
124
+ # C_stiffness: float,
125
+ # C_mass: float) -> tuple[np.ndarray, np.ndarray]:
126
+ # return ned2_tet_stiff_mass(tet_vertices, edge_lengths, local_edge_map, local_tri_map, C_stiffness, C_mass)
127
+
128
+ # @staticmethod
129
+ # def tri_stiff_vec_matrix(lcs_vertices: np.ndarray,
130
+ # gamma: complex,
131
+ # lcs_Uinc: np.ndarray,
132
+ # DPTs: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
133
+ # return ned2_tri_stiff_force(lcs_vertices, gamma, lcs_Uinc, DPTs)
134
+
135
+ # @staticmethod
136
+ # def tri_surf_integral(lcs_vertices: np.ndarray,
137
+ # edge_lengths: np.ndarray,
138
+ # lcs_Uinc: np.ndarray,
139
+ # DPTs: np.ndarray) -> complex:
140
+ # return ned2_tri_surface_integral(lcs_vertices, edge_lengths, lcs_Uinc, DPTs)
@@ -0,0 +1,217 @@
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
+ from __future__ import annotations
19
+ import numpy as np
20
+ from ..mesh3d import SurfaceMesh
21
+ from .femdata import FEMBasis
22
+ from .ned2_interp import ned2_tri_interp_full, ned2_tri_interp_curl
23
+ from ..mth.optimized import matinv
24
+ from ..cs import CoordinateSystem
25
+
26
+
27
+ ## TODO: TEMPORARY SOLUTION FIX THIS
28
+
29
+ class FieldFunctionClass:
30
+ """ This Class serves as a picklable class so that ModalPort boundary conditions
31
+ can actually be stored with the Simulation3D class. Functions aren't picklable in
32
+ Python.
33
+
34
+ I am not happy with the existence of this class, it feels too ad-hoc but for now it
35
+ is the simplest way. It stores all actually required information needed to do a
36
+ surface field interpolation without needing to store the Mesh3D and SurfaceMesh class
37
+ objects plus the NedelecLegrange2 classes with the Simulation3D.
38
+
39
+ As it stands currently, only the GMSH mesh is stored plus the geometry objects. The
40
+ mesh is reconstructed as it is deterministic.
41
+ """
42
+ def __init__(self,
43
+ field: np.ndarray,
44
+ cs: CoordinateSystem,
45
+ nodes: np.ndarray,
46
+ tris: np.ndarray,
47
+ tri_to_field: np.ndarray,
48
+ EH: str = 'E',
49
+ diadic: np.ndarray = None,
50
+ beta: float = None,
51
+ constant: float = 1.0):
52
+ self.field: np.ndarray = field
53
+ self.cs: CoordinateSystem = cs
54
+ self.nodes: np.ndarray = nodes
55
+ self.tris: np.ndarray = tris
56
+ self.tri_to_field: np.ndarray = tri_to_field
57
+ self.eh: str = EH
58
+ self.diadic: np.ndarray = diadic
59
+ self.beta: float = beta
60
+ self.constant: float = constant
61
+ if EH == 'H':
62
+ if diadic is None:
63
+ self.diadic = np.eye(3)[:,:,np.newaxis()] * np.ones((self.tris.shape[1]))
64
+
65
+ def __call__(self, xs: np.ndarray,
66
+ ys: np.ndarray,
67
+ zs: np.ndarray):
68
+ xl, yl, zl = self.cs.in_local_cs(xs, ys, zs)
69
+ if self.eh == 'E':
70
+ Fxl, Fyl, Fzl = self.calcE(xl, yl)
71
+ else:
72
+ Fxl, Fyl, Fzl = self.calcH(xl, yl)
73
+ Fx, Fy, Fz = self.cs.in_global_basis(Fxl, Fyl, Fzl)
74
+ return np.array([Fx, Fy, Fz])*self.constant
75
+
76
+ def calcE(self, xs: np.ndarray, ys: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
77
+ coordinates = np.array([xs, ys])
78
+ return ned2_tri_interp_full(coordinates,
79
+ self.field,
80
+ self.tris,
81
+ self.nodes,
82
+ self.tri_to_field)
83
+
84
+ def calcH(self, xs: np.ndarray, ys: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
85
+ coordinates = np.array([xs, ys])
86
+
87
+ return ned2_tri_interp_curl(coordinates,
88
+ self.field,
89
+ self.tris,
90
+ self.nodes,
91
+ self.tri_to_field,
92
+ self.diadic,
93
+ self.beta)
94
+
95
+ ############### Nedelec2 Class
96
+
97
+ class NedelecLegrange2(FEMBasis):
98
+
99
+
100
+ def __init__(self, mesh: SurfaceMesh, cs: CoordinateSystem):
101
+
102
+ self.mesh: SurfaceMesh = mesh
103
+
104
+ self.cs: CoordinateSystem = cs
105
+
106
+ ##
107
+ nodes = self.mesh.nodes
108
+ self.local_nodes: np.ndarray = np.array(self.cs.in_local_cs(nodes[0,:], nodes[1,:], nodes[2,:]))
109
+
110
+ ## Counters
111
+ self.n_nodes: int = self.mesh.n_nodes
112
+ self.n_edges: int = self.mesh.n_edges
113
+ self.n_tris: int = self.mesh.n_tris
114
+ self.n_tri_dofs: int = None
115
+
116
+ self.n_field: int = 2*self.n_edges + 2*self.n_tris + self.n_nodes + self.n_edges
117
+ self.n_xy: int = 2*self.n_edges + 2*self.n_tris
118
+
119
+ ######## MESH Derived
120
+ Nn = self.mesh.n_nodes
121
+ Ne = self.mesh.n_edges
122
+ Nt = self.mesh.n_tris
123
+
124
+ self.tri_to_field: np.ndarray = np.zeros((8 + 6, self.n_tris), dtype=int)
125
+
126
+ self.tri_to_field[:3,:] = self.mesh.tri_to_edge
127
+ self.tri_to_field[3,:] = np.arange(Nt) + Ne
128
+ self.tri_to_field[4:7,:] = self.mesh.tri_to_edge + Ne + Nt
129
+ self.tri_to_field[7,:] = np.arange(Nt) + 2*Ne + Nt
130
+ self.tri_to_field[8:11,:] = self.mesh.tris + (2*Ne + 2*Nt) # + E + T + E + T
131
+ self.tri_to_field[11:14,:] = self.mesh.tri_to_edge + (2*Ne + 2*Nt + Nn)
132
+
133
+ self.edge_to_field: np.ndarray = np.zeros((5,Ne), dtype=int) #edge mode 1, edge mode 2, edge legrande mode, edge vertex mode 1, edge vertex mode 2
134
+
135
+ self.edge_to_field[0,:] = np.arange(Ne)
136
+ self.edge_to_field[1,:] = np.arange(Ne) + Nt + Ne
137
+ self.edge_to_field[2,:] = np.arange(Ne) + Ne*2 + Nt*2 + Nn
138
+ self.edge_to_field[3:,:] = self.mesh.edges + Ne*2 + Nt*2
139
+
140
+ ##
141
+ self._field: np.ndarray = None
142
+ self._rows: np.ndarray = None
143
+ self._cols: np.ndarray = None
144
+
145
+ def __call__(self, **kwargs) -> NedelecLegrange2:
146
+ self._field = self.fielddata(**kwargs)
147
+ return self
148
+
149
+ def interpolate_Ef(self, field: np.ndarray) -> FieldFunctionClass:
150
+ '''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
151
+
152
+ # def func(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray:
153
+ # xl, yl, zl = self.cs.in_local_cs(xs, ys, zs)
154
+ # Exl, Eyl, Ezl = self.tri_interpolate(field, xl, yl)
155
+ # Ex, Ey, Ez = self.cs.in_global_basis(Exl, Eyl, Ezl)
156
+ # return np.array([Ex, Ey, Ez])
157
+ return FieldFunctionClass(field, self.cs, self.local_nodes, self.mesh.tris, self.tri_to_field, 'E')
158
+
159
+ def interpolate_Hf(self, field: np.ndarray, k0: float, ur: np.ndarray, beta: float) -> FieldFunctionClass:
160
+ '''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
161
+ constant = 1j/ ((k0*299792458)*(4*np.pi*1e-7))
162
+ urinv = np.zeros_like(ur)
163
+
164
+ for i in range(ur.shape[2]):
165
+ urinv[:,:,i] = matinv(ur[:,:,i])
166
+
167
+ # def func(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray:
168
+ # xl, yl, _ = self.cs.in_local_cs(xs, ys, zs)
169
+ # Exl, Eyl, Ezl = self.tri_interpolate_curl(field, xl, yl, urinv, beta)
170
+ # Ex, Ey, Ez = self.cs.in_global_basis(Exl, Eyl, Ezl)
171
+ # return np.array([Ex, Ey, Ez])*constant
172
+ return FieldFunctionClass(field, self.cs, self.local_nodes, self.mesh.tris, self.tri_to_field, 'H', urinv, beta, constant)
173
+
174
+ def tri_interpolate(self, field, xs: np.ndarray, ys: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
175
+ coordinates = np.array([xs, ys])
176
+ return ned2_tri_interp_full(coordinates,
177
+ field,
178
+ self.mesh.tris,
179
+ self.local_nodes,
180
+ self.tri_to_field)
181
+
182
+ def tri_interpolate_curl(self, field, xs: np.ndarray, ys: np.ndarray, diadic: np.ndarray = None, beta: float = 0) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
183
+ coordinates = np.array([xs, ys])
184
+ if diadic is None:
185
+ diadic = np.eye(3)[:,:,np.newaxis()] * np.ones((self.mesh.n_tris))
186
+ return ned2_tri_interp_curl(coordinates,
187
+ field,
188
+ self.mesh.tris,
189
+ self.local_nodes,
190
+ self.tri_to_field,
191
+ diadic,
192
+ beta)
193
+
194
+
195
+ # def interpolate_curl(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, c: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
196
+ # """
197
+ # Interpolates the curl of the field at the given points.
198
+ # """
199
+ # return ned2_tet_interp_curl(np.array([xs, ys,zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, c)
200
+
201
+ # def fieldf(self, field: np.ndarray, basis: np.ndarray = None, origin: np.ndarray = None) -> Callable:
202
+ # if basis is None:
203
+ # basis = np.eye(3)
204
+
205
+ # if origin is None:
206
+ # origin = np.zeros(3)
207
+
208
+ # ibasis = np.linalg.pinv(basis)
209
+ # def func(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> np.ndarray:
210
+ # xyz = np.array([xs, ys, zs]) + origin[:, np.newaxis]
211
+ # xyzg = basis @ xyz
212
+ # return ibasis @ np.array(self.interpolate(field, xyzg[0,:], xyzg[1,:], xyzg[2,:]))
213
+ # return func
214
+
215
+ ###### INDEX MAPPINGS
216
+
217
+
@@ -0,0 +1,24 @@
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
+ from .pcb import PCBLayouter
19
+ from .pmlbox import pmlbox
20
+ from .horn import Horn
21
+ from .shapes import Cyllinder, CoaxCyllinder, Box, XYPlate, HalfSphere, Sphere, Plate, OldBox, Alignment
22
+ from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect
23
+ from .polybased import XYPolygon, GeoPrism
24
+ from .step import STEPItems
@@ -0,0 +1,107 @@
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
+ from ..geometry import GeoVolume
19
+ from ..cs import CoordinateSystem
20
+ from ..selection import FaceSelection
21
+
22
+ import gmsh
23
+
24
+ class Horn(GeoVolume):
25
+
26
+ def __init__(self,
27
+ start_aperture: tuple[float, float],
28
+ end_aperture: tuple[float, float],
29
+ height: float,
30
+ cs: CoordinateSystem):
31
+ """Generate a Horn geometry. The horn is defined by a start and ending rectangle.
32
+
33
+ Start aperture is always defined at z=0 and the end at z=height.
34
+ The horn can be reoriented by choosing a different coordinate system.
35
+
36
+ Args:
37
+ start_aperture (tuple[float, float]): The width/height of the start aperture
38
+ end_aperture (tuple[float, float]): The width/height of the end aperture
39
+ height (float): The height of the horn
40
+ cs (CoordinateSystem): The coordinate systme to place the horn in.
41
+ """
42
+ super().__init__([])
43
+ p0 = cs.origin
44
+ p1 = p0 + cs.zax.np * height
45
+
46
+ wax = cs.xax.np
47
+ hax = cs.yax.np
48
+ dax = cs.zax.np
49
+
50
+ w1, h1 = start_aperture
51
+ w2, h2 = end_aperture
52
+
53
+ p11 = p0 + wax *w1/2 + hax * h1/2
54
+ p12 = p0 + wax *w1/2 - hax * h1/2
55
+ p13 = p0 - wax *w1/2 - hax * h1/2
56
+ p14 = p0 - wax *w1/2 + hax * h1/2
57
+
58
+ p21 = p1 + wax *w2/2 + hax * h2/2
59
+ p22 = p1 + wax *w2/2 - hax * h2/2
60
+ p23 = p1 - wax *w2/2 - hax * h2/2
61
+ p24 = p1 - wax *w2/2 + hax * h2/2
62
+
63
+ pt11 = gmsh.model.occ.addPoint(*p11)
64
+ pt12 = gmsh.model.occ.addPoint(*p12)
65
+ pt13 = gmsh.model.occ.addPoint(*p13)
66
+ pt14 = gmsh.model.occ.addPoint(*p14)
67
+ pt21 = gmsh.model.occ.addPoint(*p21)
68
+ pt22 = gmsh.model.occ.addPoint(*p22)
69
+ pt23 = gmsh.model.occ.addPoint(*p23)
70
+ pt24 = gmsh.model.occ.addPoint(*p24)
71
+
72
+ l1r = gmsh.model.occ.addLine(pt11, pt12)
73
+ l1b = gmsh.model.occ.addLine(pt12, pt13)
74
+ l1l = gmsh.model.occ.addLine(pt13, pt14)
75
+ l1t = gmsh.model.occ.addLine(pt14, pt11)
76
+
77
+ l2r = gmsh.model.occ.addLine(pt21, pt22)
78
+ l2b = gmsh.model.occ.addLine(pt22, pt23)
79
+ l2l = gmsh.model.occ.addLine(pt23, pt24)
80
+ l2t = gmsh.model.occ.addLine(pt24, pt21)
81
+
82
+ dtr = gmsh.model.occ.addLine(pt11, pt21)
83
+ dbr = gmsh.model.occ.addLine(pt12, pt22)
84
+ dbl = gmsh.model.occ.addLine(pt13, pt23)
85
+ dtl = gmsh.model.occ.addLine(pt14, pt24)
86
+
87
+ wbot = gmsh.model.occ.addWire([l1r, l1b, l1l, l1t])
88
+ wtop = gmsh.model.occ.addWire([l2r, l2b, l2l, l2t])
89
+ wright = gmsh.model.occ.addWire([l1r, dbr, l2r, dtr])
90
+ wleft = gmsh.model.occ.addWire([l1l, dbl, l2l, dtl])
91
+ wfront = gmsh.model.occ.addWire([l1b, dbl, l2b, dbr])
92
+ wback = gmsh.model.occ.addWire([l1t, dtr, l2t, dtl])
93
+
94
+ s1 = gmsh.model.occ.addSurfaceFilling(wbot)
95
+ s2 = gmsh.model.occ.addSurfaceFilling(wtop)
96
+ s3 = gmsh.model.occ.addSurfaceFilling(wright)
97
+ s4 = gmsh.model.occ.addSurfaceFilling(wleft)
98
+ s5 = gmsh.model.occ.addSurfaceFilling(wfront)
99
+ s6 = gmsh.model.occ.addSurfaceFilling(wback)
100
+
101
+ sv = gmsh.model.occ.addSurfaceLoop([s1, s2, s3, s4, s5, s6])
102
+
103
+ self.tags: list[int] = [gmsh.model.occ.addVolume([sv,]),]
104
+
105
+ pc = p0 + dax * height/2
106
+ self._add_face_pointer('front', pc - height/2*dax, -dax)
107
+ self._add_face_pointer('back', pc + height/2*dax, dax)