emerge 0.6.11__py3-none-any.whl → 1.0.1__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 +2 -2
- emerge/_emerge/bc.py +8 -3
- emerge/_emerge/cacherun.py +79 -0
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/pcb.py +160 -71
- emerge/_emerge/geo/pcb_tools/dxf.py +360 -0
- emerge/_emerge/geo/polybased.py +21 -15
- emerge/_emerge/geo/shapes.py +31 -16
- emerge/_emerge/geometry.py +147 -21
- emerge/_emerge/material.py +2 -1
- emerge/_emerge/mesh3d.py +39 -12
- emerge/_emerge/periodic.py +19 -17
- emerge/_emerge/physics/microwave/assembly/assembler.py +12 -12
- emerge/_emerge/physics/microwave/microwave_3d.py +29 -6
- emerge/_emerge/physics/microwave/microwave_bc.py +22 -9
- emerge/_emerge/physics/microwave/microwave_data.py +3 -0
- emerge/_emerge/plot/pyvista/display.py +20 -6
- emerge/_emerge/plot/pyvista/display_settings.py +2 -1
- emerge/_emerge/plot/simple_plots.py +4 -1
- emerge/_emerge/selection.py +10 -8
- emerge/_emerge/settings.py +12 -0
- emerge/_emerge/simmodel.py +182 -48
- emerge/_emerge/solver.py +9 -2
- emerge/beta/dxf.py +1 -0
- emerge/lib.py +4 -1
- emerge/materials/__init__.py +1 -0
- emerge/materials/isola.py +294 -0
- emerge/materials/rogers.py +58 -0
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/METADATA +6 -8
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/RECORD +33 -26
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/WHEEL +0 -0
- {emerge-0.6.11.dist-info → emerge-1.0.1.dist-info}/entry_points.txt +0 -0
- {emerge-0.6.11.dist-info → emerge-1.0.1.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__ = "0.
|
|
21
|
+
__version__ = "1.0.1"
|
|
22
22
|
|
|
23
23
|
############################################################
|
|
24
24
|
# HANDLE ENVIRONMENT VARIABLES #
|
|
@@ -27,7 +27,7 @@ __version__ = "0.6.11"
|
|
|
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
|
emerge/_emerge/bc.py
CHANGED
|
@@ -83,7 +83,7 @@ class BoundaryCondition:
|
|
|
83
83
|
raise ValueError(f'All tags must have the same dimension, instead its {tags}')
|
|
84
84
|
dimension = tags[0][0]
|
|
85
85
|
if self.dimension is BCDimension.ANY:
|
|
86
|
-
logger.info(f'Assigning dimension
|
|
86
|
+
logger.info(f'Assigning {self} to dimension{BCDimension(dimension)}')
|
|
87
87
|
self.dimension = BCDimension(dimension)
|
|
88
88
|
elif self.dimension != BCDimension(dimension):
|
|
89
89
|
raise ValueError(f'Current boundary condition has dimension {self.dimension}, but tags have dimension {BCDimension(dimension)}')
|
|
@@ -131,6 +131,13 @@ class BoundaryConditionSet:
|
|
|
131
131
|
self.boundary_conditions: list[BoundaryCondition] = []
|
|
132
132
|
self._initialized: bool = False
|
|
133
133
|
|
|
134
|
+
def cleanup(self) -> None:
|
|
135
|
+
""" Removes non assigned boundary conditions"""
|
|
136
|
+
logger.trace("Cleaning up boundary conditions.")
|
|
137
|
+
toremove = [bc for bc in self.boundary_conditions if len(bc.tags)==0]
|
|
138
|
+
logger.trace(f"Removing: {toremove}")
|
|
139
|
+
self.boundary_conditions = [bc for bc in self.boundary_conditions if len(bc.tags)>0]
|
|
140
|
+
|
|
134
141
|
def _construct_bc(self, constructor: type) -> type:
|
|
135
142
|
def constr(*args, **kwargs):
|
|
136
143
|
obj = constructor(*args, **kwargs)
|
|
@@ -201,8 +208,6 @@ class BoundaryConditionSet:
|
|
|
201
208
|
|
|
202
209
|
bc.add_tags(bc.selection.dimtags)
|
|
203
210
|
|
|
204
|
-
logger.info('Excluding other possible boundary conditions')
|
|
205
|
-
|
|
206
211
|
for existing_bc in self.boundary_conditions:
|
|
207
212
|
excluded = existing_bc.exclude_bc(bc)
|
|
208
213
|
if excluded:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# inside your module, e.g. utils.py
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import textwrap
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _is_plain_check_run(test: ast.AST) -> bool:
|
|
11
|
+
"""True only for a bare call to check_run() as the entire if-test."""
|
|
12
|
+
if not isinstance(test, ast.Call) or test.args or test.keywords:
|
|
13
|
+
return False
|
|
14
|
+
f = test.func
|
|
15
|
+
return (
|
|
16
|
+
isinstance(f, ast.Attribute) and f.attr == "cache_run"
|
|
17
|
+
) or (
|
|
18
|
+
isinstance(f, ast.Name) and f.id == "cache_run"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def get_run_block_str(source: str) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Return all source text before the first `if check_run():` / `if *.check_run():`.
|
|
24
|
+
Returns None if no matching if-statement exists.
|
|
25
|
+
"""
|
|
26
|
+
tree = ast.parse(source)
|
|
27
|
+
candidates = [n for n in ast.walk(tree) if isinstance(n, ast.If) and _is_plain_check_run(n.test)]
|
|
28
|
+
if not candidates:
|
|
29
|
+
return None
|
|
30
|
+
first = min(candidates, key=lambda n: n.lineno)
|
|
31
|
+
lines = source.splitlines(keepends=True)
|
|
32
|
+
return "".join(lines[: first.lineno - 1])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_build_block_str(source: str) -> str | None:
|
|
36
|
+
"""Return the code string inside the first `if *.checkrun(...):` block, or None."""
|
|
37
|
+
tree = ast.parse(source)
|
|
38
|
+
for node in ast.walk(tree):
|
|
39
|
+
if isinstance(node, ast.If) and isinstance(node.test, ast.Call):
|
|
40
|
+
f = node.test.func
|
|
41
|
+
called = (isinstance(f, ast.Attribute) and f.attr == "cache_build") or \
|
|
42
|
+
(isinstance(f, ast.Name) and f.id == "cache_build")
|
|
43
|
+
if called and node.body:
|
|
44
|
+
start = node.body[0].lineno - 1 # 0-based start line
|
|
45
|
+
end = node.body[-1].end_lineno # 1-based end line (inclusive)
|
|
46
|
+
lines = source.splitlines()
|
|
47
|
+
block = "\n".join(lines[start:end])
|
|
48
|
+
return textwrap.dedent(block)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def entry_script_path():
|
|
52
|
+
main = sys.modules.get("__main__")
|
|
53
|
+
# Most normal runs: python path/to/app.py
|
|
54
|
+
if main and hasattr(main, "__file__"):
|
|
55
|
+
return os.path.abspath(main.__file__)
|
|
56
|
+
# Fallbacks (e.g. python -m pkg.mod)
|
|
57
|
+
if sys.argv and sys.argv[0] not in ("", "-c"):
|
|
58
|
+
return os.path.abspath(sys.argv[0])
|
|
59
|
+
# Interactive sessions, notebooks, or embedded interpreters
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_build_section() -> str:
|
|
64
|
+
""" Returns the string section inside the check_build() if statement"""
|
|
65
|
+
name = entry_script_path()
|
|
66
|
+
|
|
67
|
+
lines = get_build_block_str(Path(name).read_text())
|
|
68
|
+
return lines
|
|
69
|
+
|
|
70
|
+
def get_run_section() -> str:
|
|
71
|
+
"""Return sthe string section before the check_run() if statement
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
str: _description_
|
|
75
|
+
"""
|
|
76
|
+
name = entry_script_path()
|
|
77
|
+
|
|
78
|
+
lines = get_run_block_str(Path(name).read_text())
|
|
79
|
+
return lines
|
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
|
@@ -19,8 +19,8 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
from ..cs import CoordinateSystem, GCS, Axis
|
|
21
21
|
from ..geometry import GeoPolygon, GeoVolume, GeoSurface
|
|
22
|
-
from ..material import Material, AIR, COPPER
|
|
23
|
-
from .shapes import Box, Plate, Cylinder
|
|
22
|
+
from ..material import Material, AIR, COPPER, PEC
|
|
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 = COPPER):
|
|
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,36 +933,82 @@ 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,
|
|
907
954
|
cs: CoordinateSystem | None = None,
|
|
908
955
|
material: Material = AIR,
|
|
956
|
+
trace_material: Material = PEC,
|
|
909
957
|
layers: int = 2,
|
|
958
|
+
stack: list[PCBLayer] = None,
|
|
959
|
+
name: str | None = None,
|
|
960
|
+
trace_thickness: float | None = None,
|
|
910
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
|
+
"""
|
|
911
975
|
|
|
912
976
|
self.thickness: float = thickness
|
|
913
|
-
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
|
+
|
|
914
991
|
self.material: Material = material
|
|
992
|
+
self.trace_material: Material = trace_material
|
|
915
993
|
self.width: float | None = None
|
|
916
994
|
self.length: float | None = None
|
|
917
995
|
self.origin: np.ndarray = np.array([0.,0.,0.])
|
|
996
|
+
|
|
918
997
|
self.paths: list[StripPath] = []
|
|
919
998
|
self.polies: list[PCBPoly] = []
|
|
920
999
|
|
|
921
1000
|
self.lumped_ports: list[StripLine] = []
|
|
922
1001
|
self.lumped_elements: list[GeoPolygon] = []
|
|
1002
|
+
self.trace_thickness: float | None = trace_thickness
|
|
923
1003
|
|
|
924
1004
|
self.unit: float = unit
|
|
925
1005
|
|
|
926
1006
|
self.cs: CoordinateSystem = cs
|
|
927
1007
|
if self.cs is None:
|
|
928
1008
|
self.cs = GCS
|
|
1009
|
+
|
|
1010
|
+
self.dielectric_priority: int = 11
|
|
1011
|
+
self.via_priority: int = 12
|
|
929
1012
|
|
|
930
1013
|
self.traces: list[GeoPolygon] = []
|
|
931
1014
|
self.ports: list[GeoPolygon] = []
|
|
@@ -941,6 +1024,8 @@ class PCB:
|
|
|
941
1024
|
|
|
942
1025
|
self.calc: PCBCalculator = PCBCalculator(self.thickness, self._zs, self.material, self.unit)
|
|
943
1026
|
|
|
1027
|
+
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
1028
|
+
|
|
944
1029
|
@property
|
|
945
1030
|
def trace(self) -> GeoPolygon:
|
|
946
1031
|
tags = []
|
|
@@ -1024,6 +1109,9 @@ class PCB:
|
|
|
1024
1109
|
if name in self.stored_striplines:
|
|
1025
1110
|
return self.stored_striplines[name]
|
|
1026
1111
|
else:
|
|
1112
|
+
for poly in self.polies:
|
|
1113
|
+
if poly.name==name:
|
|
1114
|
+
return poly
|
|
1027
1115
|
raise ValueError(f'There is no stripline or coordinate under the name of {name}')
|
|
1028
1116
|
|
|
1029
1117
|
def __call__(self, path_nr: int) -> StripPath:
|
|
@@ -1080,7 +1168,8 @@ class PCB:
|
|
|
1080
1168
|
width: float | None = None,
|
|
1081
1169
|
height: float | None = None,
|
|
1082
1170
|
origin: tuple[float, float] | None = None,
|
|
1083
|
-
alignment:
|
|
1171
|
+
alignment: Alignment = Alignment.CORNER,
|
|
1172
|
+
name: str | None = None) -> GeoSurface:
|
|
1084
1173
|
"""Generates a generic rectangular plate in the XY grid.
|
|
1085
1174
|
If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
|
|
1086
1175
|
|
|
@@ -1103,19 +1192,19 @@ class PCB:
|
|
|
1103
1192
|
|
|
1104
1193
|
origin: tuple[float, ...] = origin + (z*self.unit, ) # type: ignore
|
|
1105
1194
|
|
|
1106
|
-
if alignment
|
|
1195
|
+
if alignment is Alignment.CENTER:
|
|
1107
1196
|
origin = (origin[0] - width*self.unit/2,
|
|
1108
1197
|
origin[1] - height*self.unit/2,
|
|
1109
1198
|
origin[2])
|
|
1110
1199
|
|
|
1111
|
-
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)
|
|
1112
1202
|
plane = change_coordinate_system(plane, self.cs) # type: ignore
|
|
1113
|
-
plane.set_material(
|
|
1203
|
+
plane.set_material(self.trace_material)
|
|
1114
1204
|
return plane # type: ignore
|
|
1115
1205
|
|
|
1116
1206
|
def generate_pcb(self,
|
|
1117
1207
|
split_z: bool = True,
|
|
1118
|
-
layer_tolerance: float = 1e-6,
|
|
1119
1208
|
merge: bool = True) -> GeoVolume:
|
|
1120
1209
|
"""Generate the PCB Block object
|
|
1121
1210
|
|
|
@@ -1123,37 +1212,38 @@ class PCB:
|
|
|
1123
1212
|
GeoVolume: The PCB Block
|
|
1124
1213
|
"""
|
|
1125
1214
|
x0, y0, z0 = self.origin*self.unit
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (z-zvalues_isolated[-1]) <= layer_tolerance:
|
|
1132
|
-
continue
|
|
1133
|
-
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
|
+
|
|
1134
1220
|
boxes: list[GeoVolume] = []
|
|
1135
|
-
for z1, z2 in zip(
|
|
1221
|
+
for i, (z1, z2, layer) in enumerate(zip(self._zs[:-1],self._zs[1:],self._stack)):
|
|
1136
1222
|
h = z2-z1
|
|
1137
1223
|
box = Box(self.width*self.unit,
|
|
1138
1224
|
self.length*self.unit,
|
|
1139
1225
|
h*self.unit,
|
|
1140
|
-
position=(x0, y0, z0+z1*self.unit)
|
|
1141
|
-
|
|
1226
|
+
position=(x0, y0, z0+z1*self.unit),
|
|
1227
|
+
name=f'{self.name}_layer{i}')
|
|
1228
|
+
box.material = layer.mat
|
|
1142
1229
|
box = change_coordinate_system(box, self.cs)
|
|
1230
|
+
box.prio_set(self.dielectric_priority)
|
|
1143
1231
|
boxes.append(box)
|
|
1144
|
-
if merge:
|
|
1145
|
-
return GeoVolume.merged(boxes) # type: ignore
|
|
1232
|
+
if merge and Nmats == 1:
|
|
1233
|
+
return GeoVolume.merged(boxes).prio_set(self.dielectric_priority) # type: ignore
|
|
1146
1234
|
return boxes # type: ignore
|
|
1147
1235
|
|
|
1148
1236
|
box = Box(self.width*self.unit,
|
|
1149
1237
|
self.length*self.unit,
|
|
1150
1238
|
self.thickness*self.unit,
|
|
1151
|
-
position=(x0,y0,z0-self.thickness*self.unit)
|
|
1152
|
-
|
|
1239
|
+
position=(x0,y0,z0-self.thickness*self.unit),
|
|
1240
|
+
name=f'{self.name}_diel')
|
|
1241
|
+
box.material = self._stack[0].mat
|
|
1242
|
+
box.prio_set(self.dielectric_priority)
|
|
1153
1243
|
box = change_coordinate_system(box, self.cs)
|
|
1154
1244
|
return box # type: ignore
|
|
1155
1245
|
|
|
1156
|
-
def generate_air(self, height: float) -> GeoVolume:
|
|
1246
|
+
def generate_air(self, height: float, name: str = 'PCBAirbox') -> GeoVolume:
|
|
1157
1247
|
"""Generate the Air Block object
|
|
1158
1248
|
|
|
1159
1249
|
This requires that the width, depth and origin are deterimed. This
|
|
@@ -1166,7 +1256,8 @@ class PCB:
|
|
|
1166
1256
|
box = Box(self.width*self.unit,
|
|
1167
1257
|
self.length*self.unit,
|
|
1168
1258
|
height*self.unit,
|
|
1169
|
-
position=(x0,y0,z0)
|
|
1259
|
+
position=(x0,y0,z0),
|
|
1260
|
+
name=name)
|
|
1170
1261
|
box = change_coordinate_system(box, self.cs)
|
|
1171
1262
|
return box # type: ignore
|
|
1172
1263
|
|
|
@@ -1175,7 +1266,8 @@ class PCB:
|
|
|
1175
1266
|
y: float,
|
|
1176
1267
|
width: float,
|
|
1177
1268
|
direction: tuple[float, float],
|
|
1178
|
-
z: float = 0
|
|
1269
|
+
z: float = 0,
|
|
1270
|
+
name: str | None = None) -> StripPath:
|
|
1179
1271
|
"""Start a new trace
|
|
1180
1272
|
|
|
1181
1273
|
The trace is started at the provided x,y, coordinates with a width "width".
|
|
@@ -1194,12 +1286,12 @@ class PCB:
|
|
|
1194
1286
|
>>> PCB.new(...).straight(...).turn(...).straight(...) etc.
|
|
1195
1287
|
|
|
1196
1288
|
"""
|
|
1197
|
-
path = StripPath(self)
|
|
1289
|
+
path = StripPath(self, name=name)
|
|
1198
1290
|
path.init(x, y, width, direction, z=z)
|
|
1199
1291
|
self.paths.append(path)
|
|
1200
1292
|
return path
|
|
1201
1293
|
|
|
1202
|
-
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:
|
|
1203
1295
|
"""Generate a lumped-port object to be created.
|
|
1204
1296
|
|
|
1205
1297
|
Args:
|
|
@@ -1231,7 +1323,7 @@ class PCB:
|
|
|
1231
1323
|
|
|
1232
1324
|
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1233
1325
|
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1234
|
-
poly = GeoPolygon([planetag,])
|
|
1326
|
+
poly = GeoPolygon([planetag,], name='name')
|
|
1235
1327
|
poly._aux_data['width'] = stripline.width*self.unit
|
|
1236
1328
|
poly._aux_data['height'] = height*self.unit
|
|
1237
1329
|
poly._aux_data['vdir'] = self.cs.zax
|
|
@@ -1239,19 +1331,18 @@ class PCB:
|
|
|
1239
1331
|
|
|
1240
1332
|
return poly
|
|
1241
1333
|
|
|
1242
|
-
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float) -> None:
|
|
1243
|
-
|
|
1244
|
-
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)
|
|
1245
1336
|
geopoly._aux_data['func'] = function
|
|
1246
1337
|
geopoly._aux_data['width'] = width
|
|
1247
1338
|
geopoly._aux_data['height'] = length
|
|
1248
1339
|
self.lumped_elements.append(geopoly)
|
|
1249
1340
|
|
|
1250
|
-
|
|
1251
1341
|
def modal_port(self,
|
|
1252
1342
|
point: StripLine,
|
|
1253
1343
|
height: float,
|
|
1254
1344
|
width_multiplier: float = 5.0,
|
|
1345
|
+
name: str | None = 'ModalPort'
|
|
1255
1346
|
) -> GeoSurface:
|
|
1256
1347
|
"""Generate a wave-port as a GeoSurface.
|
|
1257
1348
|
|
|
@@ -1277,7 +1368,7 @@ class PCB:
|
|
|
1277
1368
|
ax1 = np.array([ds[0], ds[1], 0])*self.unit*point.width*width_multiplier
|
|
1278
1369
|
ax2 = np.array([0,0,1])*height*self.unit
|
|
1279
1370
|
|
|
1280
|
-
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)
|
|
1281
1372
|
plate = change_coordinate_system(plate, self.cs)
|
|
1282
1373
|
return plate # type: ignore
|
|
1283
1374
|
|
|
@@ -1304,7 +1395,8 @@ class PCB:
|
|
|
1304
1395
|
xg, yg, zg = self.cs.in_global_cs(x0, y0, z0)
|
|
1305
1396
|
cs = CoordinateSystem(self.cs.xax, self.cs.yax, self.cs.zax, np.array([xg, yg, zg]))
|
|
1306
1397
|
cyl = Cylinder(via.radius*self.unit, (via.z2-via.z1)*self.unit, cs, via.segments)
|
|
1307
|
-
cyl.material =
|
|
1398
|
+
cyl.material = self.trace_material
|
|
1399
|
+
cyl.prio_set(self.via_priority)
|
|
1308
1400
|
vias.append(cyl)
|
|
1309
1401
|
if merge:
|
|
1310
1402
|
|
|
@@ -1315,7 +1407,8 @@ class PCB:
|
|
|
1315
1407
|
xs: list[float],
|
|
1316
1408
|
ys: list[float],
|
|
1317
1409
|
z: float = 0,
|
|
1318
|
-
material: Material =
|
|
1410
|
+
material: Material = None,
|
|
1411
|
+
name: str | None = None) -> None:
|
|
1319
1412
|
"""Add a custom polygon to the PCB
|
|
1320
1413
|
|
|
1321
1414
|
Args:
|
|
@@ -1324,9 +1417,14 @@ class PCB:
|
|
|
1324
1417
|
z (float, optional): The z-height. Defaults to 0.
|
|
1325
1418
|
material (Material, optional): The material. Defaults to COPPER.
|
|
1326
1419
|
"""
|
|
1327
|
-
|
|
1420
|
+
if material is None:
|
|
1421
|
+
material = self.trace_material
|
|
1422
|
+
poly = PCBPoly(xs, ys, z, material,name=name)
|
|
1423
|
+
|
|
1424
|
+
self.polies.append(poly)
|
|
1425
|
+
|
|
1328
1426
|
|
|
1329
|
-
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:
|
|
1330
1428
|
""" Generates a GeoPoly out of a list of (x,y) coordinate tuples"""
|
|
1331
1429
|
ptags = []
|
|
1332
1430
|
for x,y in xys:
|
|
@@ -1340,7 +1438,8 @@ class PCB:
|
|
|
1340
1438
|
|
|
1341
1439
|
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1342
1440
|
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1343
|
-
poly = GeoPolygon([planetag,])
|
|
1441
|
+
poly = GeoPolygon([planetag,], name=name)
|
|
1442
|
+
poly._store('thickness', self.thickness)
|
|
1344
1443
|
return poly
|
|
1345
1444
|
|
|
1346
1445
|
@overload
|
|
@@ -1385,12 +1484,12 @@ class PCB:
|
|
|
1385
1484
|
ally.append(y)
|
|
1386
1485
|
|
|
1387
1486
|
poly = self._gen_poly(xys2, z)
|
|
1388
|
-
poly.material =
|
|
1487
|
+
poly.material = self.trace_material
|
|
1389
1488
|
polys.append(poly)
|
|
1390
1489
|
|
|
1391
1490
|
for pcbpoly in self.polies:
|
|
1392
1491
|
self.zs.append(pcbpoly.z)
|
|
1393
|
-
poly = self._gen_poly(pcbpoly.xys, pcbpoly.z)
|
|
1492
|
+
poly = self._gen_poly(pcbpoly.xys, pcbpoly.z, name=pcbpoly.name)
|
|
1394
1493
|
poly.material = pcbpoly.material
|
|
1395
1494
|
polys.append(poly)
|
|
1396
1495
|
xs, ys = zip(*pcbpoly.xys)
|
|
@@ -1407,14 +1506,4 @@ class PCB:
|
|
|
1407
1506
|
polys = unite(*polys)
|
|
1408
1507
|
|
|
1409
1508
|
return polys
|
|
1410
|
-
|
|
1411
|
-
############################################################
|
|
1412
|
-
# DEPRICATED #
|
|
1413
|
-
############################################################
|
|
1414
|
-
|
|
1415
|
-
class PCBLayouter(PCB):
|
|
1416
1509
|
|
|
1417
|
-
def __init__(self, *args, **kwargs):
|
|
1418
|
-
logger.warning('PCBLayouter will be depricated. Use PCB instead.')
|
|
1419
|
-
super().__init__(*args, **kwargs)
|
|
1420
|
-
|