emerge 1.0.0__tar.gz → 1.0.1__tar.gz
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-1.0.0 → emerge-1.0.1}/.bumpversion.toml +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/PKG-INFO +3 -1
- {emerge-1.0.0 → emerge-1.0.1}/emerge/__init__.py +2 -2
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/__init__.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/pcb.py +145 -66
- emerge-1.0.1/emerge/_emerge/geo/pcb_tools/dxf.py +360 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/polybased.py +21 -15
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/shapes.py +31 -16
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geometry.py +120 -21
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/mesh3d.py +39 -12
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/periodic.py +19 -17
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/microwave_bc.py +17 -4
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/microwave_data.py +3 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/pyvista/display.py +9 -1
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/simple_plots.py +4 -1
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/selection.py +10 -8
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/simmodel.py +67 -33
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/solver.py +9 -2
- emerge-1.0.1/emerge/beta/dxf.py +1 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/lib.py +3 -0
- emerge-1.0.1/emerge/materials/__init__.py +1 -0
- emerge-1.0.1/emerge/materials/isola.py +294 -0
- emerge-1.0.1/emerge/materials/rogers.py +58 -0
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo10_sgh.py +3 -3
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo11_lumped_element_filter.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo12_mode_alignment.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo13_helix_antenna.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo14_boundary_selection.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo1_stepped_imp_filter.py +1 -3
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo2_combline_filter.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo3_coupled_line_filter.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo4_patch_antenna.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo5_revolve.py +4 -4
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo6_striplines_with_vias.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo7_periodic_cells.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo8_waveguide_bpf_synthesis.py +2 -2
- {emerge-1.0.0 → emerge-1.0.1}/examples/demo9_dielectric_resonator.py +1 -1
- {emerge-1.0.0 → emerge-1.0.1}/pyproject.toml +5 -1
- {emerge-1.0.0 → emerge-1.0.1}/uv.lock +64 -2
- {emerge-1.0.0 → emerge-1.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/.gitignore +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/.python-version +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/LICENSE +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/README.md +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/THIRD_PARTY_LICENSES.md +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/UMFPACK_Install_windows.md +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/UMFPACK_installer_windows.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/__main__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/_cache_check.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/bc.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/cacherun.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/const.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/coord.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/cs.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/dataset.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/elements/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/elements/femdata.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/elements/index_interp.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/elements/ned2_interp.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/elements/nedelec2.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/elements/nedleg2.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/horn.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/modeler.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/operations.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/pmlbox.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo/step.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/geo2d.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/howto.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/logsettings.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/material.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/mesher.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/mth/common_functions.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/mth/integrals.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/mth/optimized.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/mth/pairing.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/assembly/assembler.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/microwave_3d.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/periodic.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/sc.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/simjob.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/sparam.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/display.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/plot.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/projects/__init__.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/projects/_gen_base.txt +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/projects/_load_base.txt +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/projects/generate_project.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/settings.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/simulation_data.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/_emerge/system.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/cli.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/ext.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/plot.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/emerge/pyvista.py +0 -0
- {emerge-1.0.0 → emerge-1.0.1}/src/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emerge
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: An open source EM FEM simulator in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/FennisRobert/EMerge
|
|
6
6
|
Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
|
|
@@ -20,6 +20,8 @@ Provides-Extra: cudss
|
|
|
20
20
|
Requires-Dist: cupy-cuda12x; extra == 'cudss'
|
|
21
21
|
Requires-Dist: nvidia-cudss-cu12; extra == 'cudss'
|
|
22
22
|
Requires-Dist: nvmath-python[cu12]; extra == 'cudss'
|
|
23
|
+
Provides-Extra: dxf
|
|
24
|
+
Requires-Dist: ezdxf; extra == 'dxf'
|
|
23
25
|
Provides-Extra: umfpack
|
|
24
26
|
Requires-Dist: scikit-umfpack; (sys_platform != 'win32') and extra == 'umfpack'
|
|
25
27
|
Description-Content-Type: text/markdown
|
|
@@ -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.1"
|
|
22
22
|
|
|
23
23
|
############################################################
|
|
24
24
|
# HANDLE ENVIRONMENT VARIABLES #
|
|
@@ -27,7 +27,7 @@ __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
|
|
@@ -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
|
|
@@ -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'
|
|
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)])
|
|
333
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,51 @@ 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,
|
|
911
961
|
):
|
|
962
|
+
"""Creates a new PCB layout class instance
|
|
963
|
+
|
|
964
|
+
Args:
|
|
965
|
+
thickness (float): The total PCB thickness
|
|
966
|
+
unit (float, optional): The units used for all dimensions. Defaults to 0.001 (mm).
|
|
967
|
+
cs (CoordinateSystem | None, optional): The coordinate system to place the PCB in (XY). Defaults to None.
|
|
968
|
+
material (Material, optional): The dielectric material. Defaults to AIR.
|
|
969
|
+
trace_material (Material, optional): The trace material. Defaults to PEC.
|
|
970
|
+
layers (int, optional): The number of copper layers. Defaults to 2.
|
|
971
|
+
stack (list[PCBLayer], optional): Optional list of PCBLayer classes for multilayer PCB with different dielectrics. Defaults to None.
|
|
972
|
+
name (str | None, optional): The PCB object name. Defaults to None.
|
|
973
|
+
trace_thickness (float | None, optional): The conductor trace thickness if important. Defaults to None.
|
|
974
|
+
"""
|
|
912
975
|
|
|
913
976
|
self.thickness: float = thickness
|
|
914
|
-
self.
|
|
977
|
+
self._stack: list[PCBLayer] = []
|
|
978
|
+
|
|
979
|
+
if stack is not None:
|
|
980
|
+
self._stack = stack
|
|
981
|
+
ths = [ly.th for ly in stack]
|
|
982
|
+
zbot = -sum(ths)
|
|
983
|
+
self._zs = np.concatenate([np.array([zbot,]), zbot + np.cumsum(np.array(ths))])
|
|
984
|
+
self.thickness = sum(ths)
|
|
985
|
+
else:
|
|
986
|
+
self._zs: np.ndarray = np.linspace(-self.thickness, 0, layers)
|
|
987
|
+
ths = np.diff(self._zs)
|
|
988
|
+
self._stack = [PCBLayer(th, material) for th in ths]
|
|
989
|
+
|
|
990
|
+
|
|
915
991
|
self.material: Material = material
|
|
916
992
|
self.trace_material: Material = trace_material
|
|
917
993
|
self.width: float | None = None
|
|
918
994
|
self.length: float | None = None
|
|
919
995
|
self.origin: np.ndarray = np.array([0.,0.,0.])
|
|
996
|
+
|
|
920
997
|
self.paths: list[StripPath] = []
|
|
921
998
|
self.polies: list[PCBPoly] = []
|
|
922
999
|
|
|
923
1000
|
self.lumped_ports: list[StripLine] = []
|
|
924
1001
|
self.lumped_elements: list[GeoPolygon] = []
|
|
1002
|
+
self.trace_thickness: float | None = trace_thickness
|
|
925
1003
|
|
|
926
1004
|
self.unit: float = unit
|
|
927
1005
|
|
|
@@ -946,6 +1024,8 @@ class PCB:
|
|
|
946
1024
|
|
|
947
1025
|
self.calc: PCBCalculator = PCBCalculator(self.thickness, self._zs, self.material, self.unit)
|
|
948
1026
|
|
|
1027
|
+
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
1028
|
+
|
|
949
1029
|
@property
|
|
950
1030
|
def trace(self) -> GeoPolygon:
|
|
951
1031
|
tags = []
|
|
@@ -1029,6 +1109,9 @@ class PCB:
|
|
|
1029
1109
|
if name in self.stored_striplines:
|
|
1030
1110
|
return self.stored_striplines[name]
|
|
1031
1111
|
else:
|
|
1112
|
+
for poly in self.polies:
|
|
1113
|
+
if poly.name==name:
|
|
1114
|
+
return poly
|
|
1032
1115
|
raise ValueError(f'There is no stripline or coordinate under the name of {name}')
|
|
1033
1116
|
|
|
1034
1117
|
def __call__(self, path_nr: int) -> StripPath:
|
|
@@ -1085,7 +1168,8 @@ class PCB:
|
|
|
1085
1168
|
width: float | None = None,
|
|
1086
1169
|
height: float | None = None,
|
|
1087
1170
|
origin: tuple[float, float] | None = None,
|
|
1088
|
-
alignment:
|
|
1171
|
+
alignment: Alignment = Alignment.CORNER,
|
|
1172
|
+
name: str | None = None) -> GeoSurface:
|
|
1089
1173
|
"""Generates a generic rectangular plate in the XY grid.
|
|
1090
1174
|
If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
|
|
1091
1175
|
|
|
@@ -1108,19 +1192,19 @@ class PCB:
|
|
|
1108
1192
|
|
|
1109
1193
|
origin: tuple[float, ...] = origin + (z*self.unit, ) # type: ignore
|
|
1110
1194
|
|
|
1111
|
-
if alignment
|
|
1195
|
+
if alignment is Alignment.CENTER:
|
|
1112
1196
|
origin = (origin[0] - width*self.unit/2,
|
|
1113
1197
|
origin[1] - height*self.unit/2,
|
|
1114
1198
|
origin[2])
|
|
1115
1199
|
|
|
1116
|
-
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
|
|
1200
|
+
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0), name=name) # type: ignore
|
|
1201
|
+
plane._store('thickness', self.thickness)
|
|
1117
1202
|
plane = change_coordinate_system(plane, self.cs) # type: ignore
|
|
1118
1203
|
plane.set_material(self.trace_material)
|
|
1119
1204
|
return plane # type: ignore
|
|
1120
1205
|
|
|
1121
1206
|
def generate_pcb(self,
|
|
1122
1207
|
split_z: bool = True,
|
|
1123
|
-
layer_tolerance: float = 1e-6,
|
|
1124
1208
|
merge: bool = True) -> GeoVolume:
|
|
1125
1209
|
"""Generate the PCB Block object
|
|
1126
1210
|
|
|
@@ -1128,39 +1212,38 @@ class PCB:
|
|
|
1128
1212
|
GeoVolume: The PCB Block
|
|
1129
1213
|
"""
|
|
1130
1214
|
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)
|
|
1215
|
+
|
|
1216
|
+
Nmats = len(set([layer.mat.name for layer in self._stack]))
|
|
1217
|
+
|
|
1218
|
+
if split_z and self._zs.shape[0]>2 or Nmats > 1:
|
|
1219
|
+
|
|
1139
1220
|
boxes: list[GeoVolume] = []
|
|
1140
|
-
for z1, z2 in zip(
|
|
1221
|
+
for i, (z1, z2, layer) in enumerate(zip(self._zs[:-1],self._zs[1:],self._stack)):
|
|
1141
1222
|
h = z2-z1
|
|
1142
1223
|
box = Box(self.width*self.unit,
|
|
1143
1224
|
self.length*self.unit,
|
|
1144
1225
|
h*self.unit,
|
|
1145
|
-
position=(x0, y0, z0+z1*self.unit)
|
|
1146
|
-
|
|
1226
|
+
position=(x0, y0, z0+z1*self.unit),
|
|
1227
|
+
name=f'{self.name}_layer{i}')
|
|
1228
|
+
box.material = layer.mat
|
|
1147
1229
|
box = change_coordinate_system(box, self.cs)
|
|
1148
1230
|
box.prio_set(self.dielectric_priority)
|
|
1149
1231
|
boxes.append(box)
|
|
1150
|
-
if merge:
|
|
1232
|
+
if merge and Nmats == 1:
|
|
1151
1233
|
return GeoVolume.merged(boxes).prio_set(self.dielectric_priority) # type: ignore
|
|
1152
1234
|
return boxes # type: ignore
|
|
1153
1235
|
|
|
1154
1236
|
box = Box(self.width*self.unit,
|
|
1155
1237
|
self.length*self.unit,
|
|
1156
1238
|
self.thickness*self.unit,
|
|
1157
|
-
position=(x0,y0,z0-self.thickness*self.unit)
|
|
1158
|
-
|
|
1239
|
+
position=(x0,y0,z0-self.thickness*self.unit),
|
|
1240
|
+
name=f'{self.name}_diel')
|
|
1241
|
+
box.material = self._stack[0].mat
|
|
1159
1242
|
box.prio_set(self.dielectric_priority)
|
|
1160
1243
|
box = change_coordinate_system(box, self.cs)
|
|
1161
1244
|
return box # type: ignore
|
|
1162
1245
|
|
|
1163
|
-
def generate_air(self, height: float) -> GeoVolume:
|
|
1246
|
+
def generate_air(self, height: float, name: str = 'PCBAirbox') -> GeoVolume:
|
|
1164
1247
|
"""Generate the Air Block object
|
|
1165
1248
|
|
|
1166
1249
|
This requires that the width, depth and origin are deterimed. This
|
|
@@ -1173,7 +1256,8 @@ class PCB:
|
|
|
1173
1256
|
box = Box(self.width*self.unit,
|
|
1174
1257
|
self.length*self.unit,
|
|
1175
1258
|
height*self.unit,
|
|
1176
|
-
position=(x0,y0,z0)
|
|
1259
|
+
position=(x0,y0,z0),
|
|
1260
|
+
name=name)
|
|
1177
1261
|
box = change_coordinate_system(box, self.cs)
|
|
1178
1262
|
return box # type: ignore
|
|
1179
1263
|
|
|
@@ -1182,7 +1266,8 @@ class PCB:
|
|
|
1182
1266
|
y: float,
|
|
1183
1267
|
width: float,
|
|
1184
1268
|
direction: tuple[float, float],
|
|
1185
|
-
z: float = 0
|
|
1269
|
+
z: float = 0,
|
|
1270
|
+
name: str | None = None) -> StripPath:
|
|
1186
1271
|
"""Start a new trace
|
|
1187
1272
|
|
|
1188
1273
|
The trace is started at the provided x,y, coordinates with a width "width".
|
|
@@ -1201,12 +1286,12 @@ class PCB:
|
|
|
1201
1286
|
>>> PCB.new(...).straight(...).turn(...).straight(...) etc.
|
|
1202
1287
|
|
|
1203
1288
|
"""
|
|
1204
|
-
path = StripPath(self)
|
|
1289
|
+
path = StripPath(self, name=name)
|
|
1205
1290
|
path.init(x, y, width, direction, z=z)
|
|
1206
1291
|
self.paths.append(path)
|
|
1207
1292
|
return path
|
|
1208
1293
|
|
|
1209
|
-
def lumped_port(self, stripline: StripLine, z_ground: float | None = None) -> GeoPolygon:
|
|
1294
|
+
def lumped_port(self, stripline: StripLine, z_ground: float | None = None, name: str | None = 'LumpedPort') -> GeoPolygon:
|
|
1210
1295
|
"""Generate a lumped-port object to be created.
|
|
1211
1296
|
|
|
1212
1297
|
Args:
|
|
@@ -1238,7 +1323,7 @@ class PCB:
|
|
|
1238
1323
|
|
|
1239
1324
|
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1240
1325
|
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1241
|
-
poly = GeoPolygon([planetag,])
|
|
1326
|
+
poly = GeoPolygon([planetag,], name='name')
|
|
1242
1327
|
poly._aux_data['width'] = stripline.width*self.unit
|
|
1243
1328
|
poly._aux_data['height'] = height*self.unit
|
|
1244
1329
|
poly._aux_data['vdir'] = self.cs.zax
|
|
@@ -1246,19 +1331,18 @@ class PCB:
|
|
|
1246
1331
|
|
|
1247
1332
|
return poly
|
|
1248
1333
|
|
|
1249
|
-
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float) -> None:
|
|
1250
|
-
|
|
1251
|
-
geopoly = poly._finalize(self.cs)
|
|
1334
|
+
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float, name: str | None = 'LumpedElement') -> None:
|
|
1335
|
+
geopoly = poly._finalize(self.cs, name=name)
|
|
1252
1336
|
geopoly._aux_data['func'] = function
|
|
1253
1337
|
geopoly._aux_data['width'] = width
|
|
1254
1338
|
geopoly._aux_data['height'] = length
|
|
1255
1339
|
self.lumped_elements.append(geopoly)
|
|
1256
1340
|
|
|
1257
|
-
|
|
1258
1341
|
def modal_port(self,
|
|
1259
1342
|
point: StripLine,
|
|
1260
1343
|
height: float,
|
|
1261
1344
|
width_multiplier: float = 5.0,
|
|
1345
|
+
name: str | None = 'ModalPort'
|
|
1262
1346
|
) -> GeoSurface:
|
|
1263
1347
|
"""Generate a wave-port as a GeoSurface.
|
|
1264
1348
|
|
|
@@ -1284,7 +1368,7 @@ class PCB:
|
|
|
1284
1368
|
ax1 = np.array([ds[0], ds[1], 0])*self.unit*point.width*width_multiplier
|
|
1285
1369
|
ax2 = np.array([0,0,1])*height*self.unit
|
|
1286
1370
|
|
|
1287
|
-
plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2)
|
|
1371
|
+
plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2, name=name)
|
|
1288
1372
|
plate = change_coordinate_system(plate, self.cs)
|
|
1289
1373
|
return plate # type: ignore
|
|
1290
1374
|
|
|
@@ -1323,7 +1407,8 @@ class PCB:
|
|
|
1323
1407
|
xs: list[float],
|
|
1324
1408
|
ys: list[float],
|
|
1325
1409
|
z: float = 0,
|
|
1326
|
-
material: Material = None
|
|
1410
|
+
material: Material = None,
|
|
1411
|
+
name: str | None = None) -> None:
|
|
1327
1412
|
"""Add a custom polygon to the PCB
|
|
1328
1413
|
|
|
1329
1414
|
Args:
|
|
@@ -1334,9 +1419,12 @@ class PCB:
|
|
|
1334
1419
|
"""
|
|
1335
1420
|
if material is None:
|
|
1336
1421
|
material = self.trace_material
|
|
1337
|
-
|
|
1422
|
+
poly = PCBPoly(xs, ys, z, material,name=name)
|
|
1423
|
+
|
|
1424
|
+
self.polies.append(poly)
|
|
1425
|
+
|
|
1338
1426
|
|
|
1339
|
-
def _gen_poly(self, xys: list[tuple[float, float]], z: float) -> GeoPolygon:
|
|
1427
|
+
def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon:
|
|
1340
1428
|
""" Generates a GeoPoly out of a list of (x,y) coordinate tuples"""
|
|
1341
1429
|
ptags = []
|
|
1342
1430
|
for x,y in xys:
|
|
@@ -1350,7 +1438,8 @@ class PCB:
|
|
|
1350
1438
|
|
|
1351
1439
|
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1352
1440
|
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1353
|
-
poly = GeoPolygon([planetag,])
|
|
1441
|
+
poly = GeoPolygon([planetag,], name=name)
|
|
1442
|
+
poly._store('thickness', self.thickness)
|
|
1354
1443
|
return poly
|
|
1355
1444
|
|
|
1356
1445
|
@overload
|
|
@@ -1400,7 +1489,7 @@ class PCB:
|
|
|
1400
1489
|
|
|
1401
1490
|
for pcbpoly in self.polies:
|
|
1402
1491
|
self.zs.append(pcbpoly.z)
|
|
1403
|
-
poly = self._gen_poly(pcbpoly.xys, pcbpoly.z)
|
|
1492
|
+
poly = self._gen_poly(pcbpoly.xys, pcbpoly.z, name=pcbpoly.name)
|
|
1404
1493
|
poly.material = pcbpoly.material
|
|
1405
1494
|
polys.append(poly)
|
|
1406
1495
|
xs, ys = zip(*pcbpoly.xys)
|
|
@@ -1417,14 +1506,4 @@ class PCB:
|
|
|
1417
1506
|
polys = unite(*polys)
|
|
1418
1507
|
|
|
1419
1508
|
return polys
|
|
1420
|
-
|
|
1421
|
-
############################################################
|
|
1422
|
-
# DEPRICATED #
|
|
1423
|
-
############################################################
|
|
1424
|
-
|
|
1425
|
-
class PCBLayouter(PCB):
|
|
1426
1509
|
|
|
1427
|
-
def __init__(self, *args, **kwargs):
|
|
1428
|
-
logger.warning('PCBLayouter will be depricated. Use PCB instead.')
|
|
1429
|
-
super().__init__(*args, **kwargs)
|
|
1430
|
-
|