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.

Files changed (39) hide show
  1. emerge/__init__.py +7 -8
  2. emerge/_emerge/elements/femdata.py +4 -3
  3. emerge/_emerge/elements/nedelec2.py +8 -4
  4. emerge/_emerge/elements/nedleg2.py +6 -2
  5. emerge/_emerge/geo/__init__.py +1 -1
  6. emerge/_emerge/geo/pcb.py +149 -66
  7. emerge/_emerge/geo/pcb_tools/dxf.py +361 -0
  8. emerge/_emerge/geo/polybased.py +23 -74
  9. emerge/_emerge/geo/shapes.py +31 -16
  10. emerge/_emerge/geometry.py +120 -21
  11. emerge/_emerge/mesh3d.py +62 -43
  12. emerge/_emerge/{_cache_check.py → mth/_cache_check.py} +2 -2
  13. emerge/_emerge/mth/optimized.py +69 -3
  14. emerge/_emerge/periodic.py +19 -17
  15. emerge/_emerge/physics/microwave/__init__.py +0 -1
  16. emerge/_emerge/physics/microwave/assembly/assembler.py +27 -5
  17. emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +2 -3
  18. emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -1
  19. emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +375 -0
  20. emerge/_emerge/physics/microwave/assembly/robinbc.py +37 -38
  21. emerge/_emerge/physics/microwave/microwave_3d.py +11 -19
  22. emerge/_emerge/physics/microwave/microwave_bc.py +38 -21
  23. emerge/_emerge/physics/microwave/microwave_data.py +3 -26
  24. emerge/_emerge/physics/microwave/port_functions.py +4 -4
  25. emerge/_emerge/plot/pyvista/display.py +13 -2
  26. emerge/_emerge/plot/simple_plots.py +4 -1
  27. emerge/_emerge/selection.py +12 -9
  28. emerge/_emerge/simmodel.py +68 -34
  29. emerge/_emerge/solver.py +28 -16
  30. emerge/beta/dxf.py +1 -0
  31. emerge/lib.py +1 -0
  32. emerge/materials/__init__.py +1 -0
  33. emerge/materials/isola.py +294 -0
  34. emerge/materials/rogers.py +58 -0
  35. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/METADATA +18 -4
  36. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/RECORD +39 -33
  37. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/WHEEL +0 -0
  38. {emerge-1.0.0.dist-info → emerge-1.0.2.dist-info}/entry_points.txt +0 -0
  39. {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.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="4")
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
- from ._emerge import _cache_check
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) -> csr_matrix:
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
@@ -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
- def __init__(self, pcb: PCB):
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._zs: np.ndarray = np.linspace(-self.thickness, 0, layers)
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: Literal['corner','center'] = 'corner') -> GeoSurface:
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 == 'center':
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
- if split_z:
1133
- zvalues = sorted(list(set(self.zs + [-self.thickness, 0.0])))
1134
- zvalues_isolated = [zvalues[0],]
1135
- for z in zvalues[1:]:
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(zvalues_isolated[:-1],zvalues_isolated[1:]):
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
- box.material = self.material
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
- box.material = self.material
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) -> StripPath:
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) -> 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
- self.polies.append(PCBPoly(xs, ys, z, material))
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
-