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.

Files changed (54) hide show
  1. emerge/_emerge/bc.py +14 -20
  2. emerge/_emerge/const.py +5 -0
  3. emerge/_emerge/cs.py +2 -2
  4. emerge/_emerge/elements/femdata.py +14 -14
  5. emerge/_emerge/elements/index_interp.py +1 -1
  6. emerge/_emerge/elements/ned2_interp.py +1 -1
  7. emerge/_emerge/elements/nedelec2.py +4 -4
  8. emerge/_emerge/elements/nedleg2.py +10 -10
  9. emerge/_emerge/geo/horn.py +1 -1
  10. emerge/_emerge/geo/modeler.py +18 -19
  11. emerge/_emerge/geo/operations.py +13 -10
  12. emerge/_emerge/geo/pcb.py +180 -82
  13. emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
  14. emerge/_emerge/geo/pcb_tools/macro.py +14 -13
  15. emerge/_emerge/geo/pmlbox.py +1 -1
  16. emerge/_emerge/geometry.py +47 -33
  17. emerge/_emerge/logsettings.py +15 -16
  18. emerge/_emerge/material.py +15 -11
  19. emerge/_emerge/mesh3d.py +81 -59
  20. emerge/_emerge/mesher.py +26 -21
  21. emerge/_emerge/mth/integrals.py +1 -1
  22. emerge/_emerge/mth/pairing.py +2 -2
  23. emerge/_emerge/periodic.py +34 -31
  24. emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
  25. emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
  26. emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
  27. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
  28. emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
  29. emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
  30. emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
  31. emerge/_emerge/physics/microwave/microwave_data.py +36 -38
  32. emerge/_emerge/physics/microwave/sc.py +26 -26
  33. emerge/_emerge/physics/microwave/simjob.py +20 -15
  34. emerge/_emerge/physics/microwave/sparam.py +12 -12
  35. emerge/_emerge/physics/microwave/touchstone.py +1 -1
  36. emerge/_emerge/plot/display.py +12 -6
  37. emerge/_emerge/plot/pyvista/display.py +44 -39
  38. emerge/_emerge/plot/pyvista/display_settings.py +1 -1
  39. emerge/_emerge/plot/simple_plots.py +15 -15
  40. emerge/_emerge/selection.py +35 -39
  41. emerge/_emerge/simmodel.py +41 -47
  42. emerge/_emerge/simulation_data.py +24 -15
  43. emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
  44. emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
  45. emerge/_emerge/solver.py +314 -136
  46. emerge/cli.py +1 -1
  47. emerge/lib.py +245 -248
  48. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
  49. emerge-0.5.3.dist-info/RECORD +83 -0
  50. emerge/_emerge/plot/grapher.py +0 -93
  51. emerge-0.5.1.dist-info/RECORD +0 -82
  52. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
  53. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
  54. {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
- pass
131
+ raise NotImplementedError()
121
132
 
122
133
  @property
123
134
  def left(self) -> list[tuple[float, float]]:
124
- pass
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 = None
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
- return None
982
+ raise RouteException('Requesting z-height of route element that is not contained in a path.')
880
983
 
881
- def store(self, name: str, x: float, y:float):
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 and name in self.stored_coords:
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
- origin = origin + (z*self.unit, )
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, origin[1]-height*self.unit/2, origin[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.array, material: Material, unit: float):
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
- ival, width = [float(x) for x in val.split(',')]
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,0)),) ))
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,0)),) ))
57
+ oi.append(Instruction('turn',(rotation_angle(direction, (-1., 0.)),) ))
57
58
  oi.append(Instruction('straight',(ival,),{'width': width}))
58
- direction = (-1,0)
59
+ direction = (-1.,0.)
59
60
  elif com == 'v':
60
- oi.append(Instruction('turn',(rotation_angle(direction,(0,-1)),) ))
61
+ oi.append(Instruction('turn',(rotation_angle(direction, (0.,-1.)),) ))
61
62
  oi.append(Instruction('straight',(ival,),{'width': width}))
62
- direction = (0,-1)
63
+ direction = (0.,-1.)
63
64
  elif com == '^':
64
- oi.append(Instruction('turn',(rotation_angle(direction,(0,1)),) ))
65
+ oi.append(Instruction('turn',(rotation_angle(direction, (0.,1.)),) ))
65
66
  oi.append(Instruction('straight',(ival,),{'width': width}))
66
- direction = (0,1)
67
+ direction = (0.,1.)
67
68
  elif com == '\\':
68
69
  oi.append(Instruction('turn',(90,) ))
69
70
  oi.append(Instruction('straight',(ival,),{'width': width}))
@@ -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