emerge 0.5.1__py3-none-any.whl → 0.5.3__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/_emerge/bc.py +14 -20
- emerge/_emerge/const.py +5 -0
- emerge/_emerge/cs.py +2 -2
- emerge/_emerge/elements/femdata.py +14 -14
- emerge/_emerge/elements/index_interp.py +1 -1
- emerge/_emerge/elements/ned2_interp.py +1 -1
- emerge/_emerge/elements/nedelec2.py +4 -4
- emerge/_emerge/elements/nedleg2.py +10 -10
- emerge/_emerge/geo/horn.py +1 -1
- emerge/_emerge/geo/modeler.py +18 -19
- emerge/_emerge/geo/operations.py +13 -10
- emerge/_emerge/geo/pcb.py +180 -82
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +47 -33
- emerge/_emerge/logsettings.py +15 -16
- emerge/_emerge/material.py +15 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/integrals.py +1 -1
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
- emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
- emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
- emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
- emerge/_emerge/physics/microwave/microwave_data.py +36 -38
- emerge/_emerge/physics/microwave/sc.py +26 -26
- emerge/_emerge/physics/microwave/simjob.py +20 -15
- emerge/_emerge/physics/microwave/sparam.py +12 -12
- emerge/_emerge/physics/microwave/touchstone.py +1 -1
- emerge/_emerge/plot/display.py +12 -6
- emerge/_emerge/plot/pyvista/display.py +44 -39
- emerge/_emerge/plot/pyvista/display_settings.py +1 -1
- emerge/_emerge/plot/simple_plots.py +15 -15
- emerge/_emerge/selection.py +35 -39
- emerge/_emerge/simmodel.py +41 -47
- emerge/_emerge/simulation_data.py +24 -15
- emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +314 -136
- emerge/cli.py +1 -1
- emerge/lib.py +245 -248
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
- emerge-0.5.3.dist-info/RECORD +83 -0
- emerge/_emerge/plot/grapher.py +0 -93
- emerge-0.5.1.dist-info/RECORD +0 -82
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/geo/pcb.py
CHANGED
|
@@ -17,10 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import numpy as np
|
|
21
|
-
from scipy.optimize import root, fmin
|
|
22
|
-
import gmsh
|
|
23
|
-
|
|
24
20
|
from ..cs import CoordinateSystem, GCS, Axis
|
|
25
21
|
from ..geometry import GeoPolygon, GeoVolume, GeoSurface
|
|
26
22
|
from ..material import Material, AIR, COPPER
|
|
@@ -29,11 +25,14 @@ from .polybased import XYPolygon
|
|
|
29
25
|
from .operations import change_coordinate_system
|
|
30
26
|
from .pcb_tools.macro import parse_macro
|
|
31
27
|
from .pcb_tools.calculator import PCBCalculator
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
32
30
|
from loguru import logger
|
|
33
|
-
from typing import Literal, Callable
|
|
31
|
+
from typing import Literal, Callable, overload
|
|
34
32
|
from dataclasses import dataclass
|
|
35
|
-
import math
|
|
36
33
|
|
|
34
|
+
import math
|
|
35
|
+
import gmsh
|
|
37
36
|
|
|
38
37
|
############################################################
|
|
39
38
|
# EXCEPTIONS #
|
|
@@ -64,7 +63,15 @@ def normalize(vector: np.ndarray) -> np.ndarray:
|
|
|
64
63
|
return vector
|
|
65
64
|
return vector / norm
|
|
66
65
|
|
|
67
|
-
def _rot_mat(angle):
|
|
66
|
+
def _rot_mat(angle: float) -> np.ndarray:
|
|
67
|
+
"""Returns a 2D rotation matrix given an angle in degrees
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
angle (float): The angle in degrees
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
np.ndarray: The rotation matrix
|
|
74
|
+
"""
|
|
68
75
|
ang = -angle * np.pi/180
|
|
69
76
|
return np.array([[np.cos(ang), -np.sin(ang)], [np.sin(ang), np.cos(ang)]])
|
|
70
77
|
|
|
@@ -107,6 +114,10 @@ class RouteElement:
|
|
|
107
114
|
self.direction: np.ndarray = None
|
|
108
115
|
self.dirright: np.ndarray = None
|
|
109
116
|
|
|
117
|
+
@property
|
|
118
|
+
def xy(self) -> tuple[float, float]:
|
|
119
|
+
return self.x, self.y
|
|
120
|
+
|
|
110
121
|
@property
|
|
111
122
|
def nr(self) -> tuple[float,float]:
|
|
112
123
|
return (self.x + self.dirright[0]*self.width/2, self.y + self.dirright[1]*self.width/2)
|
|
@@ -117,11 +128,11 @@ class RouteElement:
|
|
|
117
128
|
|
|
118
129
|
@property
|
|
119
130
|
def right(self) -> list[tuple[float, float]]:
|
|
120
|
-
|
|
131
|
+
raise NotImplementedError()
|
|
121
132
|
|
|
122
133
|
@property
|
|
123
134
|
def left(self) -> list[tuple[float, float]]:
|
|
124
|
-
|
|
135
|
+
raise NotImplementedError()
|
|
125
136
|
|
|
126
137
|
def __eq__(self, other: RouteElement) -> bool:
|
|
127
138
|
return approx(self.x, other.x) and approx(self.y, other.y) and (1-abs(np.sum(self.direction*other.direction)))<1e-8
|
|
@@ -158,7 +169,7 @@ class StripTurn(RouteElement):
|
|
|
158
169
|
direction: tuple[float, float],
|
|
159
170
|
angle: float,
|
|
160
171
|
corner_type: str = 'round',
|
|
161
|
-
champher_distance: float = None):
|
|
172
|
+
champher_distance: float | None = None):
|
|
162
173
|
self.xold: float = x
|
|
163
174
|
self.yold: float = y
|
|
164
175
|
self.width: float = width
|
|
@@ -221,7 +232,8 @@ class StripTurn(RouteElement):
|
|
|
221
232
|
y2 = yend - dist * self.direction[1]
|
|
222
233
|
|
|
223
234
|
return [(x1, y1), (x2, y2), (xend, yend)]
|
|
224
|
-
|
|
235
|
+
else:
|
|
236
|
+
raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
|
|
225
237
|
@property
|
|
226
238
|
def left(self) -> list[tuple[float, float]]:
|
|
227
239
|
if self.angle < 0:
|
|
@@ -258,7 +270,69 @@ class StripTurn(RouteElement):
|
|
|
258
270
|
y2 = yend - dist * self.direction[1]
|
|
259
271
|
|
|
260
272
|
return [(xend, yend), (x2, y2), (x1, y1)]
|
|
273
|
+
else:
|
|
274
|
+
raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
|
|
261
275
|
|
|
276
|
+
class StripCurve(StripTurn):
|
|
277
|
+
def __init__(self,
|
|
278
|
+
x: float,
|
|
279
|
+
y: float,
|
|
280
|
+
width: float,
|
|
281
|
+
direction: tuple[float, float],
|
|
282
|
+
angle: float,
|
|
283
|
+
radius: float,
|
|
284
|
+
dang: float = 10.0):
|
|
285
|
+
self.xold: float = x
|
|
286
|
+
self.yold: float = y
|
|
287
|
+
self.width: float = width
|
|
288
|
+
self.old_direction: np.ndarray = normalize(np.array(direction))
|
|
289
|
+
self.direction: np.ndarray = _rot_mat(angle) @ self.old_direction
|
|
290
|
+
self.angle: float = angle
|
|
291
|
+
self.radius: float = radius
|
|
292
|
+
self.dirright: np.ndarray = np.array([self.old_direction[1], -self.old_direction[0]])
|
|
293
|
+
self.dang: float = dang
|
|
294
|
+
|
|
295
|
+
angd = abs(angle*np.pi/180)
|
|
296
|
+
self.start = np.array([x,y])
|
|
297
|
+
self.circ_origin = self.start + radius * np.sign(angle) * self.dirright
|
|
298
|
+
|
|
299
|
+
self._xhat = -self.dirright * np.sign(angle)
|
|
300
|
+
self._yhat = self.old_direction
|
|
301
|
+
|
|
302
|
+
self.end = self.circ_origin + radius*(self._xhat*np.cos(angd)+self._yhat*np.sin(angd))
|
|
303
|
+
self.x, self.y = self.end
|
|
304
|
+
|
|
305
|
+
def __str__(self) -> str:
|
|
306
|
+
return f'StripCurve[{self.x},{self.y},w={self.width},d=({self.direction})]'
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def right(self) -> list[tuple[float, float]]:
|
|
310
|
+
points: list[tuple[float, float]] = []
|
|
311
|
+
Npts = int(np.ceil(abs(self.angle/self.dang)))
|
|
312
|
+
R = self.radius-np.sign(self.angle)*self.width/2
|
|
313
|
+
print(R, self.circ_origin, self._xhat, self._yhat)
|
|
314
|
+
for i in range(Npts):
|
|
315
|
+
ang = abs((i+1)/Npts * self.angle * np.pi/180)
|
|
316
|
+
pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
|
|
317
|
+
points.append((pnew[0], pnew[1]))
|
|
318
|
+
|
|
319
|
+
return points
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def left(self) -> list[tuple[float, float]]:
|
|
323
|
+
points: list[tuple[float, float]] = []
|
|
324
|
+
|
|
325
|
+
Npts = int(np.ceil(abs(self.angle/self.dang)))
|
|
326
|
+
R = self.radius+np.sign(self.angle)*self.width/2
|
|
327
|
+
print(R, self.circ_origin, self._xhat, self._yhat)
|
|
328
|
+
for i in range(Npts):
|
|
329
|
+
ang = abs((i+1)/Npts * self.angle * np.pi/180)
|
|
330
|
+
pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
|
|
331
|
+
points.append((pnew[0], pnew[1]))
|
|
332
|
+
|
|
333
|
+
return points[::-1]
|
|
334
|
+
|
|
335
|
+
|
|
262
336
|
############################################################
|
|
263
337
|
# THE STRIP PATH CLASS #
|
|
264
338
|
############################################################
|
|
@@ -316,7 +390,7 @@ class StripPath:
|
|
|
316
390
|
return self
|
|
317
391
|
|
|
318
392
|
def straight(self, distance:
|
|
319
|
-
float, width: float = None,
|
|
393
|
+
float, width: float | None = None,
|
|
320
394
|
dx: float = 0,
|
|
321
395
|
dy: float = 0) -> StripPath:
|
|
322
396
|
"""Add A straight section to the stripline.
|
|
@@ -381,7 +455,7 @@ class StripPath:
|
|
|
381
455
|
return self
|
|
382
456
|
|
|
383
457
|
def turn(self, angle: float,
|
|
384
|
-
width: float = None,
|
|
458
|
+
width: float | None = None,
|
|
385
459
|
corner_type: Literal['champher','square'] = 'champher') -> StripPath:
|
|
386
460
|
"""Adds a turn to the strip path.
|
|
387
461
|
|
|
@@ -399,7 +473,9 @@ class StripPath:
|
|
|
399
473
|
"""
|
|
400
474
|
x, y = self.end.x, self.end.y
|
|
401
475
|
dx, dy = self.end.direction
|
|
402
|
-
|
|
476
|
+
if abs(angle) <= 20:
|
|
477
|
+
corner_type = 'square'
|
|
478
|
+
logger.warning('Small turn detected, defaulting to rectangular corners because chamfers would add to much detail.')
|
|
403
479
|
if width is not None:
|
|
404
480
|
if width != self.end.width:
|
|
405
481
|
self._add_element(StripLine(x, y, width, (dx, dy)))
|
|
@@ -407,6 +483,37 @@ class StripPath:
|
|
|
407
483
|
width=self.end.width
|
|
408
484
|
self._add_element(StripTurn(x, y, width, (dx, dy), angle, corner_type))
|
|
409
485
|
return self
|
|
486
|
+
|
|
487
|
+
def curve(self, angle: float, radius: float,
|
|
488
|
+
width: float | None = None,
|
|
489
|
+
corner_type: Literal['champher','square'] = 'champher',
|
|
490
|
+
dang: float = 10) -> StripPath:
|
|
491
|
+
"""Adds a bend to the strip path.
|
|
492
|
+
|
|
493
|
+
The angle is specified in degrees. The width of the turn will be the same as the last segment.
|
|
494
|
+
optionally, a different width may be provided.
|
|
495
|
+
By default, all corners will be cut using the "champher" type. Other options are not yet provided.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
angle (float): The turning angle
|
|
499
|
+
width (float, optional): The stripline width. Defaults to None.
|
|
500
|
+
corner_type (str, optional): The corner type. Defaults to 'champher'.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
StripPath: The current StripPath object
|
|
504
|
+
"""
|
|
505
|
+
if angle == 0:
|
|
506
|
+
logger.trace('Zero angle turn, passing action')
|
|
507
|
+
return self
|
|
508
|
+
x, y = self.end.x, self.end.y
|
|
509
|
+
dx, dy = self.end.direction
|
|
510
|
+
if width is not None:
|
|
511
|
+
if width != self.end.width:
|
|
512
|
+
self._add_element(StripLine(x, y, width, (dx, dy)))
|
|
513
|
+
else:
|
|
514
|
+
width=self.end.width
|
|
515
|
+
self._add_element(StripCurve(x, y, width, (dx, dy), angle, radius, dang=dang))
|
|
516
|
+
return self
|
|
410
517
|
|
|
411
518
|
def store(self, name: str) -> StripPath:
|
|
412
519
|
""" Store the current x,y coordinate labeled in the PCB object.
|
|
@@ -419,13 +526,12 @@ class StripPath:
|
|
|
419
526
|
Returns:
|
|
420
527
|
StripPath: The current StripPath object.
|
|
421
528
|
"""
|
|
422
|
-
self.pcb.store(name, self.end.x, self.end.y)
|
|
423
529
|
self.pcb.stored_striplines[name] = self.end
|
|
424
530
|
return self
|
|
425
531
|
|
|
426
532
|
def split(self,
|
|
427
|
-
direction: tuple[float, float] = None,
|
|
428
|
-
width: float = None) -> StripPath:
|
|
533
|
+
direction: tuple[float, float] | None= None,
|
|
534
|
+
width: float | None= None) -> StripPath:
|
|
429
535
|
"""Split the current path in N new paths given by a new departure direction
|
|
430
536
|
|
|
431
537
|
Args:
|
|
@@ -490,7 +596,6 @@ class StripPath:
|
|
|
490
596
|
self.pcb._lumped_element(poly, impedance_function, width, length)
|
|
491
597
|
return self.pcb.new(x+dx*length, y+dy*length, self.end.width, self.end.direction, self.z)
|
|
492
598
|
|
|
493
|
-
|
|
494
599
|
def cut(self) -> StripPath:
|
|
495
600
|
"""Split the current path in N new paths given by a new departure direction
|
|
496
601
|
|
|
@@ -529,9 +634,9 @@ class StripPath:
|
|
|
529
634
|
znew: float,
|
|
530
635
|
radius: float,
|
|
531
636
|
proceed: bool = True,
|
|
532
|
-
direction: tuple[float, float] = None,
|
|
533
|
-
width: float = None,
|
|
534
|
-
extra: float = None,
|
|
637
|
+
direction: tuple[float, float] | None = None,
|
|
638
|
+
width: float | None = None,
|
|
639
|
+
extra: float | None = None,
|
|
535
640
|
segments: int = 6) -> StripPath:
|
|
536
641
|
"""Adds a via to the circuit
|
|
537
642
|
|
|
@@ -576,13 +681,13 @@ class StripPath:
|
|
|
576
681
|
return self
|
|
577
682
|
|
|
578
683
|
def jump(self,
|
|
579
|
-
dx: float = None,
|
|
580
|
-
dy: float = None,
|
|
581
|
-
width: float = None,
|
|
582
|
-
direction: tuple[float, float] = None,
|
|
583
|
-
gap: float = None,
|
|
584
|
-
side: Literal['left','right'] = None,
|
|
585
|
-
reverse: float = None) -> StripPath:
|
|
684
|
+
dx: float | None = None,
|
|
685
|
+
dy: float | None = None,
|
|
686
|
+
width: float | None = None,
|
|
687
|
+
direction: tuple[float, float] | None = None,
|
|
688
|
+
gap: float | None = None,
|
|
689
|
+
side: Literal['left','right'] | None = None,
|
|
690
|
+
reverse: float | None = None) -> StripPath:
|
|
586
691
|
"""Add an unconnected jump to the currenet stripline.
|
|
587
692
|
|
|
588
693
|
The last stripline path will be terminated and a new one will be started based on the
|
|
@@ -630,9 +735,9 @@ class StripPath:
|
|
|
630
735
|
return self.pcb.new(x, y, width, direction)
|
|
631
736
|
|
|
632
737
|
def to(self, dest: tuple[float, float],
|
|
633
|
-
arrival_dir: tuple[float, float] = None,
|
|
634
|
-
arrival_margin: float = None,
|
|
635
|
-
angle_step: float = 90):
|
|
738
|
+
arrival_dir: tuple[float, float] | None = None,
|
|
739
|
+
arrival_margin: float | None= None,
|
|
740
|
+
angle_step: float = 90) -> StripPath:
|
|
636
741
|
"""
|
|
637
742
|
Extend the path from current end point to dest (x, y).
|
|
638
743
|
Optionally ensure arrival in arrival_dir after a straight segment of arrival_margin.
|
|
@@ -672,7 +777,7 @@ class StripPath:
|
|
|
672
777
|
dt = max_t / 1000.0 # resolution of search
|
|
673
778
|
found = False
|
|
674
779
|
desired_q = None
|
|
675
|
-
cand_dx = cand_dy = 0.0
|
|
780
|
+
#cand_dx = cand_dy = 0.0
|
|
676
781
|
|
|
677
782
|
while t <= max_t:
|
|
678
783
|
# candidate intercept point
|
|
@@ -700,8 +805,8 @@ class StripPath:
|
|
|
700
805
|
raise RuntimeError("Could not find an intercept angle matching quantization")
|
|
701
806
|
|
|
702
807
|
# 1) Perform initial quantized turn
|
|
703
|
-
if abs(desired_q) > atol:
|
|
704
|
-
self.turn(-desired_q)
|
|
808
|
+
if abs(desired_q) > atol: # type: ignore
|
|
809
|
+
self.turn(-desired_q) # type: ignore
|
|
705
810
|
x0 = self.end.x
|
|
706
811
|
y0 = self.end.y
|
|
707
812
|
# compute new heading vector after turn
|
|
@@ -738,9 +843,8 @@ class StripPath:
|
|
|
738
843
|
back_ang = math.degrees(math.atan2(cross2, dot2))
|
|
739
844
|
|
|
740
845
|
backoff = math.tan(abs(back_ang)*np.pi/360)*self.end.width/2
|
|
741
|
-
self.straight(s - backoff)
|
|
742
|
-
|
|
743
846
|
|
|
847
|
+
self.straight(s - backoff)
|
|
744
848
|
self.turn(-back_ang)
|
|
745
849
|
|
|
746
850
|
x0 = self.end.x
|
|
@@ -748,11 +852,10 @@ class StripPath:
|
|
|
748
852
|
D = math.hypot(tx-x0, ty-y0)
|
|
749
853
|
# 4) Final straight into destination by arrival_margin + t
|
|
750
854
|
self.straight(D)
|
|
751
|
-
|
|
752
855
|
|
|
753
856
|
return self
|
|
754
857
|
|
|
755
|
-
def macro(self, path: str, width: float = None, start_dir: tuple[float, float] = None) -> StripPath:
|
|
858
|
+
def macro(self, path: str, width: float | None = None, start_dir: tuple[float, float] | None = None) -> StripPath:
|
|
756
859
|
r"""Parse an EMerge macro command string
|
|
757
860
|
|
|
758
861
|
The start direction by default is the abslute current heading. If a specified heading is provided
|
|
@@ -796,14 +899,14 @@ class StripPath:
|
|
|
796
899
|
return self.path[element_nr]
|
|
797
900
|
|
|
798
901
|
############################################################
|
|
799
|
-
# PCB DESIGN CLASS
|
|
902
|
+
# PCB DESIGN CLASS #
|
|
800
903
|
############################################################
|
|
801
904
|
|
|
802
905
|
class PCB:
|
|
803
906
|
def __init__(self,
|
|
804
907
|
thickness: float,
|
|
805
908
|
unit: float = 0.001,
|
|
806
|
-
cs: CoordinateSystem = None,
|
|
909
|
+
cs: CoordinateSystem | None = None,
|
|
807
910
|
material: Material = AIR,
|
|
808
911
|
layers: int = 2,
|
|
809
912
|
):
|
|
@@ -811,16 +914,16 @@ class PCB:
|
|
|
811
914
|
self.thickness: float = thickness
|
|
812
915
|
self._zs: np.ndarray = np.linspace(-self.thickness, 0, layers)
|
|
813
916
|
self.material: Material = material
|
|
814
|
-
self.width: float = None
|
|
815
|
-
self.length: float = None
|
|
816
|
-
self.origin: np.ndarray =
|
|
917
|
+
self.width: float | None = None
|
|
918
|
+
self.length: float | None = None
|
|
919
|
+
self.origin: np.ndarray = np.array([0.,0.,0.])
|
|
817
920
|
self.paths: list[StripPath] = []
|
|
818
921
|
self.polies: list[PCBPoly] = []
|
|
819
922
|
|
|
820
923
|
self.lumped_ports: list[StripLine] = []
|
|
821
924
|
self.lumped_elements: list[GeoPolygon] = []
|
|
822
925
|
|
|
823
|
-
self.unit = unit
|
|
926
|
+
self.unit: float = unit
|
|
824
927
|
|
|
825
928
|
self.cs: CoordinateSystem = cs
|
|
826
929
|
if self.cs is None:
|
|
@@ -864,7 +967,7 @@ class PCB:
|
|
|
864
967
|
"""
|
|
865
968
|
return self._zs[layer-1]
|
|
866
969
|
|
|
867
|
-
def _get_z(self, element: RouteElement) -> float:
|
|
970
|
+
def _get_z(self, element: RouteElement) -> float :
|
|
868
971
|
"""Return the z-height of a given Route Element
|
|
869
972
|
|
|
870
973
|
Args:
|
|
@@ -876,38 +979,23 @@ class PCB:
|
|
|
876
979
|
for path in self.paths:
|
|
877
980
|
if path._has(element):
|
|
878
981
|
return path.z
|
|
879
|
-
|
|
982
|
+
raise RouteException('Requesting z-height of route element that is not contained in a path.')
|
|
880
983
|
|
|
881
|
-
def
|
|
882
|
-
"""Store the x,y coordinate pair one label provided by name
|
|
883
|
-
|
|
884
|
-
Args:
|
|
885
|
-
name (str): The corodinate label name
|
|
886
|
-
x (float): The x-coordinate
|
|
887
|
-
y (float): The y-coordinate
|
|
888
|
-
"""
|
|
889
|
-
self.stored_coords[name] = (x,y)
|
|
890
|
-
|
|
891
|
-
def load(self, name: str) -> tuple[float, float] | StripLine:
|
|
984
|
+
def load(self, name: str) -> StripLine:
|
|
892
985
|
"""Acquire the x,y, coordinate associated with the label name.
|
|
893
986
|
|
|
894
987
|
Args:
|
|
895
988
|
name (str): The name of the x,y coordinate
|
|
896
989
|
|
|
897
990
|
"""
|
|
898
|
-
if name in self.stored_striplines
|
|
899
|
-
logger.warning(f'There is both a coordinate and stripline under the name {name}.')
|
|
900
|
-
return self.stored_striplines[name]
|
|
901
|
-
elif name in self.stored_striplines:
|
|
991
|
+
if name in self.stored_striplines:
|
|
902
992
|
return self.stored_striplines[name]
|
|
903
|
-
elif name in self.stored_coords:
|
|
904
|
-
return self.stored_coords[name]
|
|
905
993
|
else:
|
|
906
994
|
raise ValueError(f'There is no stripline or coordinate under the name of {name}')
|
|
907
995
|
|
|
908
996
|
def __call__(self, path_nr: int) -> StripPath:
|
|
909
997
|
if path_nr >= len(self.paths):
|
|
910
|
-
self.paths.append(StripPath())
|
|
998
|
+
self.paths.append(StripPath(self))
|
|
911
999
|
return self.paths[path_nr]
|
|
912
1000
|
|
|
913
1001
|
def determine_bounds(self,
|
|
@@ -956,9 +1044,9 @@ class PCB:
|
|
|
956
1044
|
|
|
957
1045
|
def plane(self,
|
|
958
1046
|
z: float,
|
|
959
|
-
width: float = None,
|
|
960
|
-
height: float = None,
|
|
961
|
-
origin: tuple[float, float] = None,
|
|
1047
|
+
width: float | None = None,
|
|
1048
|
+
height: float | None = None,
|
|
1049
|
+
origin: tuple[float, float] | None = None,
|
|
962
1050
|
alignment: Literal['corner','center'] = 'corner') -> GeoSurface:
|
|
963
1051
|
"""Generates a generic rectangular plate in the XY grid.
|
|
964
1052
|
If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
|
|
@@ -974,17 +1062,22 @@ class PCB:
|
|
|
974
1062
|
GeoSurface: _description_
|
|
975
1063
|
"""
|
|
976
1064
|
if width is None or height is None or origin is None:
|
|
1065
|
+
if self.width is None or self.length is None or self.origin:
|
|
1066
|
+
raise RouteException('Cannot define a plane with no possible definition of its size.')
|
|
977
1067
|
width = self.width
|
|
978
1068
|
height = self.length
|
|
979
1069
|
origin = (self.origin[0]*self.unit, self.origin[1]*self.unit)
|
|
980
|
-
|
|
1070
|
+
|
|
1071
|
+
origin: tuple[float, ...] = origin + (z*self.unit, ) # type: ignore
|
|
981
1072
|
|
|
982
1073
|
if alignment == 'center':
|
|
983
|
-
origin = (origin[0] - width*self.unit/2,
|
|
1074
|
+
origin = (origin[0] - width*self.unit/2,
|
|
1075
|
+
origin[1] - height*self.unit/2,
|
|
1076
|
+
origin[2])
|
|
984
1077
|
|
|
985
|
-
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0))
|
|
986
|
-
plane = change_coordinate_system(plane, self.cs)
|
|
987
|
-
return plane
|
|
1078
|
+
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
|
|
1079
|
+
plane = change_coordinate_system(plane, self.cs) # type: ignore
|
|
1080
|
+
return plane # type: ignore
|
|
988
1081
|
|
|
989
1082
|
def gen_pcb(self,
|
|
990
1083
|
split_z: bool = True,
|
|
@@ -1004,7 +1097,7 @@ class PCB:
|
|
|
1004
1097
|
if (z-zvalues_isolated[-1]) <= layer_tolerance:
|
|
1005
1098
|
continue
|
|
1006
1099
|
zvalues_isolated.append(z)
|
|
1007
|
-
boxes = []
|
|
1100
|
+
boxes: list[GeoVolume] = []
|
|
1008
1101
|
for z1, z2 in zip(zvalues_isolated[:-1],zvalues_isolated[1:]):
|
|
1009
1102
|
h = z2-z1
|
|
1010
1103
|
box = Box(self.width*self.unit,
|
|
@@ -1015,8 +1108,8 @@ class PCB:
|
|
|
1015
1108
|
box = change_coordinate_system(box, self.cs)
|
|
1016
1109
|
boxes.append(box)
|
|
1017
1110
|
if merge:
|
|
1018
|
-
return GeoVolume.merged(boxes)
|
|
1019
|
-
return boxes
|
|
1111
|
+
return GeoVolume.merged(boxes) # type: ignore
|
|
1112
|
+
return boxes # type: ignore
|
|
1020
1113
|
|
|
1021
1114
|
box = Box(self.width*self.unit,
|
|
1022
1115
|
self.length*self.unit,
|
|
@@ -1024,7 +1117,7 @@ class PCB:
|
|
|
1024
1117
|
position=(x0,y0,z0-self.thickness*self.unit))
|
|
1025
1118
|
box.material = self.material
|
|
1026
1119
|
box = change_coordinate_system(box, self.cs)
|
|
1027
|
-
return box
|
|
1120
|
+
return box # type: ignore
|
|
1028
1121
|
|
|
1029
1122
|
def gen_air(self, height: float) -> GeoVolume:
|
|
1030
1123
|
"""Generate the Air Block object
|
|
@@ -1041,7 +1134,7 @@ class PCB:
|
|
|
1041
1134
|
height*self.unit,
|
|
1042
1135
|
position=(x0,y0,z0))
|
|
1043
1136
|
box = change_coordinate_system(box, self.cs)
|
|
1044
|
-
return box
|
|
1137
|
+
return box # type: ignore
|
|
1045
1138
|
|
|
1046
1139
|
def new(self,
|
|
1047
1140
|
x: float,
|
|
@@ -1072,7 +1165,7 @@ class PCB:
|
|
|
1072
1165
|
self.paths.append(path)
|
|
1073
1166
|
return path
|
|
1074
1167
|
|
|
1075
|
-
def lumped_port(self, stripline: StripLine, z_ground: float = None) -> GeoPolygon:
|
|
1168
|
+
def lumped_port(self, stripline: StripLine, z_ground: float | None = None) -> GeoPolygon:
|
|
1076
1169
|
"""Generate a lumped-port object to be created.
|
|
1077
1170
|
|
|
1078
1171
|
Args:
|
|
@@ -1152,7 +1245,7 @@ class PCB:
|
|
|
1152
1245
|
|
|
1153
1246
|
plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2)
|
|
1154
1247
|
plate = change_coordinate_system(plate, self.cs)
|
|
1155
|
-
return plate
|
|
1248
|
+
return plate # type: ignore
|
|
1156
1249
|
|
|
1157
1250
|
def generate_vias(self, merge=False) -> list[Cyllinder] | Cyllinder:
|
|
1158
1251
|
"""Generates the via objects.
|
|
@@ -1175,14 +1268,14 @@ class PCB:
|
|
|
1175
1268
|
vias.append(cyl)
|
|
1176
1269
|
if merge:
|
|
1177
1270
|
|
|
1178
|
-
return GeoVolume.merged(vias)
|
|
1271
|
+
return GeoVolume.merged(vias) # type: ignore
|
|
1179
1272
|
return vias
|
|
1180
1273
|
|
|
1181
1274
|
def add_poly(self,
|
|
1182
1275
|
xs: list[float],
|
|
1183
1276
|
ys: list[float],
|
|
1184
1277
|
z: float = 0,
|
|
1185
|
-
material: Material = COPPER):
|
|
1278
|
+
material: Material = COPPER) -> None:
|
|
1186
1279
|
"""Add a custom polygon to the PCB
|
|
1187
1280
|
|
|
1188
1281
|
Args:
|
|
@@ -1210,6 +1303,12 @@ class PCB:
|
|
|
1210
1303
|
poly = GeoPolygon([planetag,])
|
|
1211
1304
|
return poly
|
|
1212
1305
|
|
|
1306
|
+
@overload
|
|
1307
|
+
def compile_paths(self, merge: Literal[True]) -> GeoSurface: ...
|
|
1308
|
+
|
|
1309
|
+
@overload
|
|
1310
|
+
def compile_paths(self, merge: Literal[False] = ...) -> list[GeoSurface]: ...
|
|
1311
|
+
|
|
1213
1312
|
def compile_paths(self, merge: bool = False) -> list[GeoPolygon] | GeoSurface:
|
|
1214
1313
|
"""Compiles the striplines and returns a list of polygons or asingle one.
|
|
1215
1314
|
|
|
@@ -1269,7 +1368,6 @@ class PCB:
|
|
|
1269
1368
|
polys.material = COPPER
|
|
1270
1369
|
return polys
|
|
1271
1370
|
|
|
1272
|
-
|
|
1273
1371
|
############################################################
|
|
1274
1372
|
# DEPRICATED #
|
|
1275
1373
|
############################################################
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from ...material import Material
|
|
3
|
+
from ...const import Z0 as n0
|
|
3
4
|
|
|
4
5
|
PI = np.pi
|
|
5
6
|
TAU = 2*PI
|
|
6
|
-
n0 = 376.73
|
|
7
7
|
|
|
8
8
|
def microstrip_z0(W: float, th: float, er: float):
|
|
9
9
|
u = W/th
|
|
@@ -14,7 +14,7 @@ def microstrip_z0(W: float, th: float, er: float):
|
|
|
14
14
|
|
|
15
15
|
class PCBCalculator:
|
|
16
16
|
|
|
17
|
-
def __init__(self, thickness: float, layers: np.
|
|
17
|
+
def __init__(self, thickness: float, layers: np.ndarray, material: Material, unit: float):
|
|
18
18
|
self.th = thickness
|
|
19
19
|
self.layers = layers
|
|
20
20
|
self.mat = material
|
|
@@ -25,8 +25,8 @@ def rotation_angle(a: tuple[float, float], b: tuple[float, float]) -> float:
|
|
|
25
25
|
@dataclass
|
|
26
26
|
class Instruction:
|
|
27
27
|
instr: str
|
|
28
|
-
args: tuple[int]
|
|
29
|
-
kwargs: dict[str,float] = None
|
|
28
|
+
args: tuple[int | float, ...]
|
|
29
|
+
kwargs: dict[str,float] | None = None
|
|
30
30
|
|
|
31
31
|
def __post_init__(self):
|
|
32
32
|
if self.kwargs is None:
|
|
@@ -37,33 +37,34 @@ char_class = ''.join(re.escape(c) for c in symbols)
|
|
|
37
37
|
|
|
38
38
|
pattern = re.compile(rf'([{char_class}])([\d\,\.\-]+)')
|
|
39
39
|
|
|
40
|
-
def parse_macro(pathstring: str, width: int, direction: tuple[float, float]) -> list[Instruction]:
|
|
40
|
+
def parse_macro(pathstring: str, width: int | float, direction: tuple[float, float]) -> list[Instruction]:
|
|
41
41
|
instructions = pattern.findall(pathstring.replace(' ',''))
|
|
42
42
|
|
|
43
43
|
oi = []
|
|
44
44
|
for com, val in instructions:
|
|
45
45
|
if ',' in val:
|
|
46
|
-
|
|
46
|
+
val_list: list[float] = [float(x) for x in val.split(',')]
|
|
47
|
+
ival, width = val_list[0], val_list[1]
|
|
47
48
|
else:
|
|
48
49
|
ival = float(val)
|
|
49
50
|
if com == '=':
|
|
50
|
-
oi.append(Instruction('straight',(ival,),{'width': width}))
|
|
51
|
+
oi.append(Instruction('straight',(ival,),{'width': width})) #type: ignore
|
|
51
52
|
elif com == '>':
|
|
52
|
-
oi.append(Instruction('turn',(rotation_angle(direction,(1
|
|
53
|
+
oi.append(Instruction('turn',(rotation_angle(direction, (1., 0.)),) ))
|
|
53
54
|
oi.append(Instruction('straight',(ival,),{'width': width}))
|
|
54
|
-
direction = (1,0)
|
|
55
|
+
direction = (1. ,0. )
|
|
55
56
|
elif com == '<':
|
|
56
|
-
oi.append(Instruction('turn',(rotation_angle(direction,(-1
|
|
57
|
+
oi.append(Instruction('turn',(rotation_angle(direction, (-1., 0.)),) ))
|
|
57
58
|
oi.append(Instruction('straight',(ival,),{'width': width}))
|
|
58
|
-
direction = (-1
|
|
59
|
+
direction = (-1.,0.)
|
|
59
60
|
elif com == 'v':
|
|
60
|
-
oi.append(Instruction('turn',(rotation_angle(direction,(0
|
|
61
|
+
oi.append(Instruction('turn',(rotation_angle(direction, (0.,-1.)),) ))
|
|
61
62
|
oi.append(Instruction('straight',(ival,),{'width': width}))
|
|
62
|
-
direction = (0
|
|
63
|
+
direction = (0.,-1.)
|
|
63
64
|
elif com == '^':
|
|
64
|
-
oi.append(Instruction('turn',(rotation_angle(direction,(0
|
|
65
|
+
oi.append(Instruction('turn',(rotation_angle(direction, (0.,1.)),) ))
|
|
65
66
|
oi.append(Instruction('straight',(ival,),{'width': width}))
|
|
66
|
-
direction = (0
|
|
67
|
+
direction = (0.,1.)
|
|
67
68
|
elif com == '\\':
|
|
68
69
|
oi.append(Instruction('turn',(90,) ))
|
|
69
70
|
oi.append(Instruction('straight',(ival,),{'width': width}))
|
emerge/_emerge/geo/pmlbox.py
CHANGED
|
@@ -106,7 +106,7 @@ def _add_pml_layer(center: tuple[float, float, float],
|
|
|
106
106
|
plate = Plate(np.array([p0x-tW/2, p0y-tD/2, p0z-dz*thickness/2 + dz*(n+1)*thl]), ax1, ax2)
|
|
107
107
|
planes.append(plate)
|
|
108
108
|
|
|
109
|
-
pml_box.material = Material(_neff=np.sqrt(material.er*material.ur), _fer=ermat, _fur=urmat)
|
|
109
|
+
pml_box.material = Material(_neff=np.sqrt(material.er*material.ur), _fer=ermat, _fur=urmat, color='#bbbbff', opacity=0.1)
|
|
110
110
|
pml_box.max_meshsize = thickness/N_mesh_layers
|
|
111
111
|
pml_box._embeddings = planes
|
|
112
112
|
|