emerge 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/__init__.py +7 -8
- emerge/_emerge/elements/femdata.py +4 -3
- emerge/_emerge/elements/nedelec2.py +8 -4
- emerge/_emerge/elements/nedleg2.py +6 -2
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/pcb.py +149 -66
- emerge/_emerge/geo/pcb_tools/dxf.py +361 -0
- emerge/_emerge/geo/polybased.py +23 -74
- emerge/_emerge/geo/shapes.py +31 -16
- emerge/_emerge/geometry.py +120 -21
- emerge/_emerge/mesh3d.py +62 -43
- emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
- emerge/_emerge/mth/optimized.py +69 -3
- emerge/_emerge/periodic.py +19 -17
- emerge/_emerge/physics/microwave/__init__.py +0 -1
- emerge/_emerge/physics/microwave/assembly/assembler.py +27 -5
- emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +2 -3
- emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -1
- emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +375 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +37 -38
- emerge/_emerge/physics/microwave/microwave_3d.py +11 -19
- emerge/_emerge/physics/microwave/microwave_bc.py +38 -21
- emerge/_emerge/physics/microwave/microwave_data.py +3 -26
- emerge/_emerge/physics/microwave/port_functions.py +4 -4
- emerge/_emerge/plot/pyvista/display.py +13 -2
- emerge/_emerge/plot/simple_plots.py +4 -1
- emerge/_emerge/selection.py +12 -9
- emerge/_emerge/simmodel.py +68 -34
- emerge/_emerge/solver.py +28 -16
- emerge/beta/dxf.py +1 -0
- emerge/lib.py +1 -0
- emerge/materials/__init__.py +1 -0
- emerge/materials/isola.py +294 -0
- emerge/materials/rogers.py +58 -0
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/METADATA +18 -4
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/RECORD +39 -33
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/WHEEL +0 -0
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/licenses/LICENSE +0 -0
emerge/__init__.py
CHANGED
|
@@ -18,7 +18,7 @@ along with this program; if not, see
|
|
|
18
18
|
"""
|
|
19
19
|
import os
|
|
20
20
|
|
|
21
|
-
__version__ = "1.0.
|
|
21
|
+
__version__ = "1.0.2"
|
|
22
22
|
|
|
23
23
|
############################################################
|
|
24
24
|
# HANDLE ENVIRONMENT VARIABLES #
|
|
@@ -27,17 +27,16 @@ __version__ = "1.0.0"
|
|
|
27
27
|
NTHREADS = "1"
|
|
28
28
|
os.environ["EMERGE_STD_LOGLEVEL"] = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
|
|
29
29
|
os.environ["EMERGE_FILE_LOGLEVEL"] = os.getenv("EMERGE_FILE_LOGLEVEL", default="DEBUG")
|
|
30
|
-
os.environ["OMP_NUM_THREADS"] = os.getenv("OMP_NUM_THREADS", default="
|
|
30
|
+
os.environ["OMP_NUM_THREADS"] = os.getenv("OMP_NUM_THREADS", default="1")
|
|
31
31
|
os.environ["MKL_NUM_THREADS"] = os.getenv("MKL_NUM_THREADS", default="4")
|
|
32
32
|
os.environ["OPENBLAS_NUM_THREADS"] = NTHREADS
|
|
33
33
|
os.environ["VECLIB_MAXIMUM_THREADS"] = NTHREADS
|
|
34
34
|
os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
|
|
35
35
|
|
|
36
|
-
|
|
37
36
|
############################################################
|
|
38
37
|
# IMPORT MODULES #
|
|
39
38
|
############################################################
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
from ._emerge.logsettings import LOG_CONTROLLER
|
|
42
41
|
from loguru import logger
|
|
43
42
|
|
|
@@ -54,8 +53,7 @@ from ._emerge.coord import Line
|
|
|
54
53
|
from ._emerge import geo
|
|
55
54
|
from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
56
55
|
from ._emerge.geometry import select
|
|
57
|
-
from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
58
|
-
from ._emerge.physics.microwave.sc import stratton_chu
|
|
56
|
+
#from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
59
57
|
from ._emerge.periodic import RectCell, HexCell
|
|
60
58
|
from ._emerge.mesher import Algorithm2D, Algorithm3D
|
|
61
59
|
from . import lib
|
|
@@ -65,10 +63,11 @@ howto = _HowtoClass()
|
|
|
65
63
|
|
|
66
64
|
logger.debug('Importing complete!')
|
|
67
65
|
|
|
68
|
-
|
|
69
66
|
############################################################
|
|
70
67
|
# CONSTANTS #
|
|
71
68
|
############################################################
|
|
72
69
|
|
|
73
70
|
CENTER = geo.Alignment.CENTER
|
|
74
|
-
CORNER = geo.Alignment.CORNER
|
|
71
|
+
CORNER = geo.Alignment.CORNER
|
|
72
|
+
EISO = lib.EISO
|
|
73
|
+
EOMNI = lib.EOMNI
|
|
@@ -19,9 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
from ..mesh3d import Mesh3D
|
|
20
20
|
import numpy as np
|
|
21
21
|
from typing import Callable
|
|
22
|
-
from scipy.sparse import csr_matrix # type: ignore
|
|
23
22
|
|
|
24
|
-
from ..mth.optimized import matmul
|
|
25
23
|
|
|
26
24
|
class FEMBasis:
|
|
27
25
|
|
|
@@ -48,6 +46,8 @@ class FEMBasis:
|
|
|
48
46
|
|
|
49
47
|
def interpolate_Ef(self, field: np.ndarray, basis: np.ndarray | None = None, origin: np.ndarray | None = None, tetids: np.ndarray | None = None) -> Callable:
|
|
50
48
|
'''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
|
|
49
|
+
from ..mth.optimized import matmul
|
|
50
|
+
|
|
51
51
|
if basis is None:
|
|
52
52
|
basis = np.eye(3)
|
|
53
53
|
|
|
@@ -124,7 +124,8 @@ class FEMBasis:
|
|
|
124
124
|
N = self.n_tri_dofs**2
|
|
125
125
|
return slice(itri*N,(itri+1)*N)
|
|
126
126
|
|
|
127
|
-
def generate_csr(self, data: np.ndarray)
|
|
127
|
+
def generate_csr(self, data: np.ndarray):
|
|
128
|
+
from scipy.sparse import csr_matrix # type: ignore
|
|
128
129
|
ids = np.argwhere(data!=0)[:,0]
|
|
129
130
|
return csr_matrix((data[ids], (self._rows[ids], self._cols[ids])), shape=(self.n_field, self.n_field))
|
|
130
131
|
### QUANTITIES
|
|
@@ -19,9 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
from ..mesh3d import Mesh3D
|
|
21
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
22
|
|
|
26
23
|
############### Nedelec2 Class
|
|
27
24
|
|
|
@@ -72,6 +69,7 @@ class Nedelec2(FEMBasis):
|
|
|
72
69
|
'''
|
|
73
70
|
Interpolate the provided field data array at the given xs, ys and zs coordinates
|
|
74
71
|
'''
|
|
72
|
+
from .ned2_interp import ned2_tet_interp
|
|
75
73
|
if tetids is None:
|
|
76
74
|
tetids = self._all_tet_ids
|
|
77
75
|
vals = 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)
|
|
@@ -83,8 +81,11 @@ class Nedelec2(FEMBasis):
|
|
|
83
81
|
"""
|
|
84
82
|
Interpolates the curl of the field at the given points.
|
|
85
83
|
"""
|
|
84
|
+
from .ned2_interp import ned2_tet_interp_curl
|
|
85
|
+
|
|
86
86
|
if tetids is None:
|
|
87
87
|
tetids = self._all_tet_ids
|
|
88
|
+
|
|
88
89
|
vals = 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)
|
|
89
90
|
if not usenan:
|
|
90
91
|
vals = np.nan_to_num(vals)
|
|
@@ -97,7 +98,7 @@ class Nedelec2(FEMBasis):
|
|
|
97
98
|
usenan: bool = True) -> np.ndarray:
|
|
98
99
|
if tetids is None:
|
|
99
100
|
tetids = self._all_tet_ids
|
|
100
|
-
|
|
101
|
+
from .index_interp import index_interp
|
|
101
102
|
vals = index_interp(np.array([xs, ys, zs]), self.mesh.tets, self.mesh.nodes, tetids)
|
|
102
103
|
if not usenan:
|
|
103
104
|
vals[vals==-1]==0
|
|
@@ -106,15 +107,18 @@ class Nedelec2(FEMBasis):
|
|
|
106
107
|
###### INDEX MAPPINGS
|
|
107
108
|
|
|
108
109
|
def local_tet_to_triid(self, itet: int) -> np.ndarray:
|
|
110
|
+
from ..mth.optimized import local_mapping
|
|
109
111
|
tri_ids = self.tet_to_field[6:10, itet] - self.n_edges
|
|
110
112
|
global_tri_map = self.mesh.tris[:, tri_ids]
|
|
111
113
|
return local_mapping(self.mesh.tets[:, itet], global_tri_map)
|
|
112
114
|
|
|
113
115
|
def local_tet_to_edgeid(self, itet: int) -> np.ndarray:
|
|
116
|
+
from ..mth.optimized import local_mapping
|
|
114
117
|
global_edge_map = self.mesh.edges[:, self.tet_to_field[:6,itet]]
|
|
115
118
|
return local_mapping(self.mesh.tets[:, itet], global_edge_map)
|
|
116
119
|
|
|
117
120
|
def local_tri_to_edgeid(self, itri: int) -> np.ndarray:
|
|
121
|
+
from ..mth.optimized import local_mapping
|
|
118
122
|
global_edge_map = self.mesh.edges[:, self.tri_to_field[:3,itri]]
|
|
119
123
|
return local_mapping(self.mesh.tris[:, itri], global_edge_map)
|
|
120
124
|
|
|
@@ -19,8 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
from ..mesh3d import SurfaceMesh
|
|
21
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
22
|
from ..cs import CoordinateSystem
|
|
25
23
|
from ..const import MU0, C0
|
|
26
24
|
|
|
@@ -74,6 +72,8 @@ class FieldFunctionClass:
|
|
|
74
72
|
return np.array([Fx, Fy, Fz])*self.constant
|
|
75
73
|
|
|
76
74
|
def calcE(self, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
75
|
+
from .ned2_interp import ned2_tri_interp_full
|
|
76
|
+
|
|
77
77
|
coordinates = np.array([xs, ys])
|
|
78
78
|
vals = ned2_tri_interp_full(coordinates,
|
|
79
79
|
self.field,
|
|
@@ -85,6 +85,7 @@ class FieldFunctionClass:
|
|
|
85
85
|
return vals
|
|
86
86
|
|
|
87
87
|
def calcH(self, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
88
|
+
from .ned2_interp import ned2_tri_interp_curl
|
|
88
89
|
coordinates = np.array([xs, ys])
|
|
89
90
|
|
|
90
91
|
vals = ned2_tri_interp_curl(coordinates,
|
|
@@ -176,6 +177,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
176
177
|
|
|
177
178
|
def interpolate_Hf(self, field: np.ndarray, k0: float, ur: np.ndarray, beta: float) -> FieldFunctionClass:
|
|
178
179
|
'''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
|
|
180
|
+
from ..mth.optimized import matinv
|
|
179
181
|
constant = 1j / ((k0*C0)*MU0)
|
|
180
182
|
urinv = np.zeros_like(ur)
|
|
181
183
|
|
|
@@ -185,6 +187,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
185
187
|
return FieldFunctionClass(field, self.cs, self.local_nodes, self.mesh.tris, self.tri_to_field, 'H', urinv, beta, constant)
|
|
186
188
|
|
|
187
189
|
def tri_interpolate(self, field, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
190
|
+
from .ned2_interp import ned2_tri_interp_full
|
|
188
191
|
coordinates = np.array([xs, ys])
|
|
189
192
|
vals = ned2_tri_interp_full(coordinates,
|
|
190
193
|
field,
|
|
@@ -196,6 +199,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
196
199
|
return vals
|
|
197
200
|
|
|
198
201
|
def tri_interpolate_curl(self, field, xs: np.ndarray, ys: np.ndarray, diadic: np.ndarray | None = None, beta: float = 0.0, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
202
|
+
from .ned2_interp import ned2_tri_interp_curl
|
|
199
203
|
coordinates = np.array([xs, ys])
|
|
200
204
|
if diadic is None:
|
|
201
205
|
diadic = np.eye(3)[:,:,np.newaxis()] * np.ones((self.mesh.n_tris)) # type: ignore
|
emerge/_emerge/geo/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# along with this program; if not, see
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
from .pcb import PCB
|
|
18
|
+
from .pcb import PCB, PCBLayer
|
|
19
19
|
from .pmlbox import pmlbox
|
|
20
20
|
from .horn import Horn
|
|
21
21
|
from .shapes import Cylinder, CoaxCylinder, Box, XYPlate, HalfSphere, Sphere, Plate, OldBox, Alignment, Cone
|
emerge/_emerge/geo/pcb.py
CHANGED
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
from ..cs import CoordinateSystem, GCS, Axis
|
|
21
21
|
from ..geometry import GeoPolygon, GeoVolume, GeoSurface
|
|
22
22
|
from ..material import Material, AIR, COPPER, PEC
|
|
23
|
-
from .shapes import Box, Plate, Cylinder
|
|
23
|
+
from .shapes import Box, Plate, Cylinder, Alignment
|
|
24
24
|
from .polybased import XYPolygon
|
|
25
25
|
from .operations import change_coordinate_system, unite
|
|
26
26
|
from .pcb_tools.macro import parse_macro
|
|
@@ -49,6 +49,26 @@ SIZE_NAMES = Literal['0402','0603','1005','1608','2012','3216','3225','4532','50
|
|
|
49
49
|
_SMD_SIZE_DICT = {x: (float(x[:2])*0.05, float(x[2:])*0.1) for x in ['0402','0603','1005','1608','2012','3216','3225','4532','5025','6332']}
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
class _PCB_NAME_MANAGER:
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.names: set[str] = set()
|
|
56
|
+
|
|
57
|
+
def __call__(self, name: str | None, classname: str | None = None) -> str:
|
|
58
|
+
if name is None:
|
|
59
|
+
return self(classname)
|
|
60
|
+
|
|
61
|
+
if name not in self.names:
|
|
62
|
+
self.names.add(name)
|
|
63
|
+
return name
|
|
64
|
+
for i in range(1_000_000):
|
|
65
|
+
newname = f'{name}_{i}'
|
|
66
|
+
if newname not in self.names:
|
|
67
|
+
self.names.add(newname)
|
|
68
|
+
return newname
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_NAME_MANAGER = _PCB_NAME_MANAGER()
|
|
52
72
|
############################################################
|
|
53
73
|
# FUNCTIONS #
|
|
54
74
|
############################################################
|
|
@@ -80,21 +100,6 @@ def _rot_mat(angle: float) -> np.ndarray:
|
|
|
80
100
|
############################################################
|
|
81
101
|
|
|
82
102
|
|
|
83
|
-
class PCBPoly:
|
|
84
|
-
|
|
85
|
-
def __init__(self,
|
|
86
|
-
xs: list[float],
|
|
87
|
-
ys: list[float],
|
|
88
|
-
z: float = 0,
|
|
89
|
-
material: Material = PEC):
|
|
90
|
-
self.xs: list[float] = xs
|
|
91
|
-
self.ys: list[float] = ys
|
|
92
|
-
self.z: float = z
|
|
93
|
-
self.material: Material = material
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def xys(self) -> list[tuple[float, float]]:
|
|
97
|
-
return list([(x,y) for x,y in zip(self.xs, self.ys)])
|
|
98
103
|
|
|
99
104
|
@dataclass
|
|
100
105
|
class Via:
|
|
@@ -106,7 +111,7 @@ class Via:
|
|
|
106
111
|
segments: int
|
|
107
112
|
|
|
108
113
|
class RouteElement:
|
|
109
|
-
|
|
114
|
+
_DEFNAME: str = 'RouteElement'
|
|
110
115
|
def __init__(self):
|
|
111
116
|
self.width: float = None
|
|
112
117
|
self.x: float = None
|
|
@@ -138,7 +143,7 @@ class RouteElement:
|
|
|
138
143
|
return approx(self.x, other.x) and approx(self.y, other.y) and (1-abs(np.sum(self.direction*other.direction)))<1e-8
|
|
139
144
|
|
|
140
145
|
class StripLine(RouteElement):
|
|
141
|
-
|
|
146
|
+
_DEFNAME: str = 'StripLine'
|
|
142
147
|
def __init__(self,
|
|
143
148
|
x: float,
|
|
144
149
|
y: float,
|
|
@@ -161,7 +166,7 @@ class StripLine(RouteElement):
|
|
|
161
166
|
return [(self.x - self.width/2 * self.dirright[0], self.y - self.width/2 * self.dirright[1])]
|
|
162
167
|
|
|
163
168
|
class StripTurn(RouteElement):
|
|
164
|
-
|
|
169
|
+
_DEFNAME: str = 'StripTurn'
|
|
165
170
|
def __init__(self,
|
|
166
171
|
x: float,
|
|
167
172
|
y: float,
|
|
@@ -234,6 +239,7 @@ class StripTurn(RouteElement):
|
|
|
234
239
|
return [(x1, y1), (x2, y2), (xend, yend)]
|
|
235
240
|
else:
|
|
236
241
|
raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
|
|
242
|
+
|
|
237
243
|
@property
|
|
238
244
|
def left(self) -> list[tuple[float, float]]:
|
|
239
245
|
if self.angle < 0:
|
|
@@ -330,17 +336,48 @@ class StripCurve(StripTurn):
|
|
|
330
336
|
|
|
331
337
|
return points[::-1]
|
|
332
338
|
|
|
339
|
+
class PCBPoly:
|
|
340
|
+
_DEFNAME: str = 'Poly'
|
|
333
341
|
|
|
342
|
+
def __init__(self,
|
|
343
|
+
xs: list[float],
|
|
344
|
+
ys: list[float],
|
|
345
|
+
z: float = 0,
|
|
346
|
+
material: Material = PEC,
|
|
347
|
+
name: str | None = None):
|
|
348
|
+
self.xs: list[float] = xs
|
|
349
|
+
self.ys: list[float] = ys
|
|
350
|
+
self.z: float = z
|
|
351
|
+
self.material: Material = material
|
|
352
|
+
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def xys(self) -> list[tuple[float, float]]:
|
|
356
|
+
return list([(x,y) for x,y in zip(self.xs, self.ys)])
|
|
357
|
+
|
|
358
|
+
def segment(self, index: int) -> StripLine:
|
|
359
|
+
N = len(self.xs)
|
|
360
|
+
x1 = self.xs[index%N]
|
|
361
|
+
x2 = self.xs[(index+1)%N]
|
|
362
|
+
y1 = self.ys[index%N]
|
|
363
|
+
y2 = self.ys[(index+1)%N]
|
|
364
|
+
z = self.z
|
|
365
|
+
W = ((x2-x1)**2 + (y2-y1)**2)**(0.5)
|
|
366
|
+
wdir = ((y2-y1)/W, -(x2-x1)/W)
|
|
367
|
+
|
|
368
|
+
return StripLine((x2+x1)/2, (y1+y2)/2, W, wdir)
|
|
334
369
|
############################################################
|
|
335
370
|
# THE STRIP PATH CLASS #
|
|
336
371
|
############################################################
|
|
337
372
|
|
|
338
373
|
class StripPath:
|
|
339
|
-
|
|
340
|
-
|
|
374
|
+
_DEFNAME: str = 'Path'
|
|
375
|
+
|
|
376
|
+
def __init__(self, pcb: PCB, name: str | None = None):
|
|
341
377
|
self.pcb: PCB = pcb
|
|
342
378
|
self.path: list[RouteElement] = []
|
|
343
379
|
self.z: float = 0
|
|
380
|
+
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
344
381
|
|
|
345
382
|
def _has(self, element: RouteElement) -> bool:
|
|
346
383
|
if element in self.path:
|
|
@@ -896,11 +933,21 @@ class StripPath:
|
|
|
896
933
|
self.path.append(RouteElement())
|
|
897
934
|
return self.path[element_nr]
|
|
898
935
|
|
|
936
|
+
class PCBLayer:
|
|
937
|
+
|
|
938
|
+
def __init__(self,
|
|
939
|
+
thickness: float,
|
|
940
|
+
material: Material):
|
|
941
|
+
self.th: float = thickness
|
|
942
|
+
self.mat: Material = material
|
|
943
|
+
|
|
899
944
|
############################################################
|
|
900
945
|
# PCB DESIGN CLASS #
|
|
901
946
|
############################################################
|
|
902
947
|
|
|
903
948
|
class PCB:
|
|
949
|
+
_DEFNAME: str = 'PCB'
|
|
950
|
+
|
|
904
951
|
def __init__(self,
|
|
905
952
|
thickness: float,
|
|
906
953
|
unit: float = 0.001,
|
|
@@ -908,20 +955,55 @@ class PCB:
|
|
|
908
955
|
material: Material = AIR,
|
|
909
956
|
trace_material: Material = PEC,
|
|
910
957
|
layers: int = 2,
|
|
958
|
+
stack: list[PCBLayer] = None,
|
|
959
|
+
name: str | None = None,
|
|
960
|
+
trace_thickness: float | None = None,
|
|
961
|
+
zs: np.ndarray | None = None
|
|
911
962
|
):
|
|
963
|
+
"""Creates a new PCB layout class instance
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
thickness (float): The total PCB thickness
|
|
967
|
+
unit (float, optional): The units used for all dimensions. Defaults to 0.001 (mm).
|
|
968
|
+
cs (CoordinateSystem | None, optional): The coordinate system to place the PCB in (XY). Defaults to None.
|
|
969
|
+
material (Material, optional): The dielectric material. Defaults to AIR.
|
|
970
|
+
trace_material (Material, optional): The trace material. Defaults to PEC.
|
|
971
|
+
layers (int, optional): The number of copper layers. Defaults to 2.
|
|
972
|
+
stack (list[PCBLayer], optional): Optional list of PCBLayer classes for multilayer PCB with different dielectrics. Defaults to None.
|
|
973
|
+
name (str | None, optional): The PCB object name. Defaults to None.
|
|
974
|
+
trace_thickness (float | None, optional): The conductor trace thickness if important. Defaults to None.
|
|
975
|
+
"""
|
|
912
976
|
|
|
913
977
|
self.thickness: float = thickness
|
|
914
|
-
self.
|
|
978
|
+
self._stack: list[PCBLayer] = []
|
|
979
|
+
if zs is not None:
|
|
980
|
+
self._zs = zs
|
|
981
|
+
self.thickness = np.max(zs)-np.min(zs)
|
|
982
|
+
self._stack = [PCBLayer(th, material) for th in np.diff(self._zs)]
|
|
983
|
+
elif stack is not None:
|
|
984
|
+
self._stack = stack
|
|
985
|
+
ths = [ly.th for ly in stack]
|
|
986
|
+
zbot = -sum(ths)
|
|
987
|
+
self._zs = np.concatenate([np.array([zbot,]), zbot + np.cumsum(np.array(ths))])
|
|
988
|
+
self.thickness = sum(ths)
|
|
989
|
+
else:
|
|
990
|
+
self._zs: np.ndarray = np.linspace(-self.thickness, 0, layers)
|
|
991
|
+
ths = np.diff(self._zs)
|
|
992
|
+
self._stack = [PCBLayer(th, material) for th in ths]
|
|
993
|
+
|
|
994
|
+
|
|
915
995
|
self.material: Material = material
|
|
916
996
|
self.trace_material: Material = trace_material
|
|
917
997
|
self.width: float | None = None
|
|
918
998
|
self.length: float | None = None
|
|
919
999
|
self.origin: np.ndarray = np.array([0.,0.,0.])
|
|
1000
|
+
|
|
920
1001
|
self.paths: list[StripPath] = []
|
|
921
1002
|
self.polies: list[PCBPoly] = []
|
|
922
1003
|
|
|
923
1004
|
self.lumped_ports: list[StripLine] = []
|
|
924
1005
|
self.lumped_elements: list[GeoPolygon] = []
|
|
1006
|
+
self.trace_thickness: float | None = trace_thickness
|
|
925
1007
|
|
|
926
1008
|
self.unit: float = unit
|
|
927
1009
|
|
|
@@ -946,6 +1028,8 @@ class PCB:
|
|
|
946
1028
|
|
|
947
1029
|
self.calc: PCBCalculator = PCBCalculator(self.thickness, self._zs, self.material, self.unit)
|
|
948
1030
|
|
|
1031
|
+
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
1032
|
+
|
|
949
1033
|
@property
|
|
950
1034
|
def trace(self) -> GeoPolygon:
|
|
951
1035
|
tags = []
|
|
@@ -1029,6 +1113,9 @@ class PCB:
|
|
|
1029
1113
|
if name in self.stored_striplines:
|
|
1030
1114
|
return self.stored_striplines[name]
|
|
1031
1115
|
else:
|
|
1116
|
+
for poly in self.polies:
|
|
1117
|
+
if poly.name==name:
|
|
1118
|
+
return poly
|
|
1032
1119
|
raise ValueError(f'There is no stripline or coordinate under the name of {name}')
|
|
1033
1120
|
|
|
1034
1121
|
def __call__(self, path_nr: int) -> StripPath:
|
|
@@ -1085,7 +1172,8 @@ class PCB:
|
|
|
1085
1172
|
width: float | None = None,
|
|
1086
1173
|
height: float | None = None,
|
|
1087
1174
|
origin: tuple[float, float] | None = None,
|
|
1088
|
-
alignment:
|
|
1175
|
+
alignment: Alignment = Alignment.CORNER,
|
|
1176
|
+
name: str | None = None) -> GeoSurface:
|
|
1089
1177
|
"""Generates a generic rectangular plate in the XY grid.
|
|
1090
1178
|
If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
|
|
1091
1179
|
|
|
@@ -1108,19 +1196,19 @@ class PCB:
|
|
|
1108
1196
|
|
|
1109
1197
|
origin: tuple[float, ...] = origin + (z*self.unit, ) # type: ignore
|
|
1110
1198
|
|
|
1111
|
-
if alignment
|
|
1199
|
+
if alignment is Alignment.CENTER:
|
|
1112
1200
|
origin = (origin[0] - width*self.unit/2,
|
|
1113
1201
|
origin[1] - height*self.unit/2,
|
|
1114
1202
|
origin[2])
|
|
1115
1203
|
|
|
1116
|
-
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
|
|
1204
|
+
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0), name=name) # type: ignore
|
|
1205
|
+
plane._store('thickness', self.thickness)
|
|
1117
1206
|
plane = change_coordinate_system(plane, self.cs) # type: ignore
|
|
1118
1207
|
plane.set_material(self.trace_material)
|
|
1119
1208
|
return plane # type: ignore
|
|
1120
1209
|
|
|
1121
1210
|
def generate_pcb(self,
|
|
1122
1211
|
split_z: bool = True,
|
|
1123
|
-
layer_tolerance: float = 1e-6,
|
|
1124
1212
|
merge: bool = True) -> GeoVolume:
|
|
1125
1213
|
"""Generate the PCB Block object
|
|
1126
1214
|
|
|
@@ -1128,39 +1216,38 @@ class PCB:
|
|
|
1128
1216
|
GeoVolume: The PCB Block
|
|
1129
1217
|
"""
|
|
1130
1218
|
x0, y0, z0 = self.origin*self.unit
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
if (z-zvalues_isolated[-1]) <= layer_tolerance:
|
|
1137
|
-
continue
|
|
1138
|
-
zvalues_isolated.append(z)
|
|
1219
|
+
|
|
1220
|
+
Nmats = len(set([layer.mat.name for layer in self._stack]))
|
|
1221
|
+
|
|
1222
|
+
if split_z and self._zs.shape[0]>2 or Nmats > 1:
|
|
1223
|
+
|
|
1139
1224
|
boxes: list[GeoVolume] = []
|
|
1140
|
-
for z1, z2 in zip(
|
|
1225
|
+
for i, (z1, z2, layer) in enumerate(zip(self._zs[:-1],self._zs[1:],self._stack)):
|
|
1141
1226
|
h = z2-z1
|
|
1142
1227
|
box = Box(self.width*self.unit,
|
|
1143
1228
|
self.length*self.unit,
|
|
1144
1229
|
h*self.unit,
|
|
1145
|
-
position=(x0, y0, z0+z1*self.unit)
|
|
1146
|
-
|
|
1230
|
+
position=(x0, y0, z0+z1*self.unit),
|
|
1231
|
+
name=f'{self.name}_layer{i}')
|
|
1232
|
+
box.material = layer.mat
|
|
1147
1233
|
box = change_coordinate_system(box, self.cs)
|
|
1148
1234
|
box.prio_set(self.dielectric_priority)
|
|
1149
1235
|
boxes.append(box)
|
|
1150
|
-
if merge:
|
|
1236
|
+
if merge and Nmats == 1:
|
|
1151
1237
|
return GeoVolume.merged(boxes).prio_set(self.dielectric_priority) # type: ignore
|
|
1152
1238
|
return boxes # type: ignore
|
|
1153
1239
|
|
|
1154
1240
|
box = Box(self.width*self.unit,
|
|
1155
1241
|
self.length*self.unit,
|
|
1156
1242
|
self.thickness*self.unit,
|
|
1157
|
-
position=(x0,y0,z0-self.thickness*self.unit)
|
|
1158
|
-
|
|
1243
|
+
position=(x0,y0,z0-self.thickness*self.unit),
|
|
1244
|
+
name=f'{self.name}_diel')
|
|
1245
|
+
box.material = self._stack[0].mat
|
|
1159
1246
|
box.prio_set(self.dielectric_priority)
|
|
1160
1247
|
box = change_coordinate_system(box, self.cs)
|
|
1161
1248
|
return box # type: ignore
|
|
1162
1249
|
|
|
1163
|
-
def generate_air(self, height: float) -> GeoVolume:
|
|
1250
|
+
def generate_air(self, height: float, name: str = 'PCBAirbox') -> GeoVolume:
|
|
1164
1251
|
"""Generate the Air Block object
|
|
1165
1252
|
|
|
1166
1253
|
This requires that the width, depth and origin are deterimed. This
|
|
@@ -1173,7 +1260,8 @@ class PCB:
|
|
|
1173
1260
|
box = Box(self.width*self.unit,
|
|
1174
1261
|
self.length*self.unit,
|
|
1175
1262
|
height*self.unit,
|
|
1176
|
-
position=(x0,y0,z0)
|
|
1263
|
+
position=(x0,y0,z0),
|
|
1264
|
+
name=name)
|
|
1177
1265
|
box = change_coordinate_system(box, self.cs)
|
|
1178
1266
|
return box # type: ignore
|
|
1179
1267
|
|
|
@@ -1182,7 +1270,8 @@ class PCB:
|
|
|
1182
1270
|
y: float,
|
|
1183
1271
|
width: float,
|
|
1184
1272
|
direction: tuple[float, float],
|
|
1185
|
-
z: float = 0
|
|
1273
|
+
z: float = 0,
|
|
1274
|
+
name: str | None = None) -> StripPath:
|
|
1186
1275
|
"""Start a new trace
|
|
1187
1276
|
|
|
1188
1277
|
The trace is started at the provided x,y, coordinates with a width "width".
|
|
@@ -1201,12 +1290,12 @@ class PCB:
|
|
|
1201
1290
|
>>> PCB.new(...).straight(...).turn(...).straight(...) etc.
|
|
1202
1291
|
|
|
1203
1292
|
"""
|
|
1204
|
-
path = StripPath(self)
|
|
1293
|
+
path = StripPath(self, name=name)
|
|
1205
1294
|
path.init(x, y, width, direction, z=z)
|
|
1206
1295
|
self.paths.append(path)
|
|
1207
1296
|
return path
|
|
1208
1297
|
|
|
1209
|
-
def lumped_port(self, stripline: StripLine, z_ground: float | None = None) -> GeoPolygon:
|
|
1298
|
+
def lumped_port(self, stripline: StripLine, z_ground: float | None = None, name: str | None = 'LumpedPort') -> GeoPolygon:
|
|
1210
1299
|
"""Generate a lumped-port object to be created.
|
|
1211
1300
|
|
|
1212
1301
|
Args:
|
|
@@ -1238,7 +1327,7 @@ class PCB:
|
|
|
1238
1327
|
|
|
1239
1328
|
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1240
1329
|
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1241
|
-
poly = GeoPolygon([planetag,])
|
|
1330
|
+
poly = GeoPolygon([planetag,], name='name')
|
|
1242
1331
|
poly._aux_data['width'] = stripline.width*self.unit
|
|
1243
1332
|
poly._aux_data['height'] = height*self.unit
|
|
1244
1333
|
poly._aux_data['vdir'] = self.cs.zax
|
|
@@ -1246,19 +1335,18 @@ class PCB:
|
|
|
1246
1335
|
|
|
1247
1336
|
return poly
|
|
1248
1337
|
|
|
1249
|
-
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float) -> None:
|
|
1250
|
-
|
|
1251
|
-
geopoly = poly._finalize(self.cs)
|
|
1338
|
+
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float, name: str | None = 'LumpedElement') -> None:
|
|
1339
|
+
geopoly = poly._finalize(self.cs, name=name)
|
|
1252
1340
|
geopoly._aux_data['func'] = function
|
|
1253
1341
|
geopoly._aux_data['width'] = width
|
|
1254
1342
|
geopoly._aux_data['height'] = length
|
|
1255
1343
|
self.lumped_elements.append(geopoly)
|
|
1256
1344
|
|
|
1257
|
-
|
|
1258
1345
|
def modal_port(self,
|
|
1259
1346
|
point: StripLine,
|
|
1260
1347
|
height: float,
|
|
1261
1348
|
width_multiplier: float = 5.0,
|
|
1349
|
+
name: str | None = 'ModalPort'
|
|
1262
1350
|
) -> GeoSurface:
|
|
1263
1351
|
"""Generate a wave-port as a GeoSurface.
|
|
1264
1352
|
|
|
@@ -1284,7 +1372,7 @@ class PCB:
|
|
|
1284
1372
|
ax1 = np.array([ds[0], ds[1], 0])*self.unit*point.width*width_multiplier
|
|
1285
1373
|
ax2 = np.array([0,0,1])*height*self.unit
|
|
1286
1374
|
|
|
1287
|
-
plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2)
|
|
1375
|
+
plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2, name=name)
|
|
1288
1376
|
plate = change_coordinate_system(plate, self.cs)
|
|
1289
1377
|
return plate # type: ignore
|
|
1290
1378
|
|
|
@@ -1323,7 +1411,8 @@ class PCB:
|
|
|
1323
1411
|
xs: list[float],
|
|
1324
1412
|
ys: list[float],
|
|
1325
1413
|
z: float = 0,
|
|
1326
|
-
material: Material = None
|
|
1414
|
+
material: Material = None,
|
|
1415
|
+
name: str | None = None) -> None:
|
|
1327
1416
|
"""Add a custom polygon to the PCB
|
|
1328
1417
|
|
|
1329
1418
|
Args:
|
|
@@ -1334,9 +1423,12 @@ class PCB:
|
|
|
1334
1423
|
"""
|
|
1335
1424
|
if material is None:
|
|
1336
1425
|
material = self.trace_material
|
|
1337
|
-
|
|
1426
|
+
poly = PCBPoly(xs, ys, z, material,name=name)
|
|
1427
|
+
|
|
1428
|
+
self.polies.append(poly)
|
|
1429
|
+
|
|
1338
1430
|
|
|
1339
|
-
def _gen_poly(self, xys: list[tuple[float, float]], z: float) -> GeoPolygon:
|
|
1431
|
+
def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon:
|
|
1340
1432
|
""" Generates a GeoPoly out of a list of (x,y) coordinate tuples"""
|
|
1341
1433
|
ptags = []
|
|
1342
1434
|
for x,y in xys:
|
|
@@ -1350,7 +1442,8 @@ class PCB:
|
|
|
1350
1442
|
|
|
1351
1443
|
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1352
1444
|
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1353
|
-
poly = GeoPolygon([planetag,])
|
|
1445
|
+
poly = GeoPolygon([planetag,], name=name)
|
|
1446
|
+
poly._store('thickness', self.thickness)
|
|
1354
1447
|
return poly
|
|
1355
1448
|
|
|
1356
1449
|
@overload
|
|
@@ -1400,7 +1493,7 @@ class PCB:
|
|
|
1400
1493
|
|
|
1401
1494
|
for pcbpoly in self.polies:
|
|
1402
1495
|
self.zs.append(pcbpoly.z)
|
|
1403
|
-
poly = self._gen_poly(pcbpoly.xys, pcbpoly.z)
|
|
1496
|
+
poly = self._gen_poly(pcbpoly.xys, pcbpoly.z, name=pcbpoly.name)
|
|
1404
1497
|
poly.material = pcbpoly.material
|
|
1405
1498
|
polys.append(poly)
|
|
1406
1499
|
xs, ys = zip(*pcbpoly.xys)
|
|
@@ -1417,14 +1510,4 @@ class PCB:
|
|
|
1417
1510
|
polys = unite(*polys)
|
|
1418
1511
|
|
|
1419
1512
|
return polys
|
|
1420
|
-
|
|
1421
|
-
############################################################
|
|
1422
|
-
# DEPRICATED #
|
|
1423
|
-
############################################################
|
|
1424
|
-
|
|
1425
|
-
class PCBLayouter(PCB):
|
|
1426
1513
|
|
|
1427
|
-
def __init__(self, *args, **kwargs):
|
|
1428
|
-
logger.warning('PCBLayouter will be depricated. Use PCB instead.')
|
|
1429
|
-
super().__init__(*args, **kwargs)
|
|
1430
|
-
|