emerge 0.5.1__py3-none-any.whl → 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/_emerge/bc.py +11 -8
- 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 +9 -9
- 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 +70 -69
- emerge/_emerge/geo/pcb_tools/macro.py +14 -13
- emerge/_emerge/geo/pmlbox.py +1 -1
- emerge/_emerge/geometry.py +46 -32
- emerge/_emerge/logsettings.py +3 -3
- emerge/_emerge/material.py +11 -11
- emerge/_emerge/mesh3d.py +81 -59
- emerge/_emerge/mesher.py +26 -21
- emerge/_emerge/mth/pairing.py +2 -2
- emerge/_emerge/periodic.py +34 -31
- emerge/_emerge/physics/microwave/adaptive_freq.py +14 -11
- emerge/_emerge/physics/microwave/assembly/assembler.py +61 -57
- 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 +40 -20
- emerge/_emerge/physics/microwave/microwave_bc.py +114 -95
- emerge/_emerge/physics/microwave/microwave_data.py +33 -33
- emerge/_emerge/physics/microwave/simjob.py +12 -12
- 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 +29 -39
- emerge/_emerge/simulation_data.py +19 -14
- emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
- emerge/_emerge/solver.py +52 -52
- emerge/lib.py +243 -243
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/METADATA +1 -1
- emerge-0.5.2.dist-info/RECORD +81 -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.2.dist-info}/WHEEL +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.1.dist-info → emerge-0.5.2.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/geo/pcb.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
|
-
from scipy.optimize import root, fmin
|
|
22
21
|
import gmsh
|
|
23
22
|
|
|
24
23
|
from ..cs import CoordinateSystem, GCS, Axis
|
|
@@ -30,7 +29,7 @@ from .operations import change_coordinate_system
|
|
|
30
29
|
from .pcb_tools.macro import parse_macro
|
|
31
30
|
from .pcb_tools.calculator import PCBCalculator
|
|
32
31
|
from loguru import logger
|
|
33
|
-
from typing import Literal, Callable
|
|
32
|
+
from typing import Literal, Callable, overload
|
|
34
33
|
from dataclasses import dataclass
|
|
35
34
|
import math
|
|
36
35
|
|
|
@@ -107,6 +106,10 @@ class RouteElement:
|
|
|
107
106
|
self.direction: np.ndarray = None
|
|
108
107
|
self.dirright: np.ndarray = None
|
|
109
108
|
|
|
109
|
+
@property
|
|
110
|
+
def xy(self) -> tuple[float, float]:
|
|
111
|
+
return self.x, self.y
|
|
112
|
+
|
|
110
113
|
@property
|
|
111
114
|
def nr(self) -> tuple[float,float]:
|
|
112
115
|
return (self.x + self.dirright[0]*self.width/2, self.y + self.dirright[1]*self.width/2)
|
|
@@ -117,11 +120,11 @@ class RouteElement:
|
|
|
117
120
|
|
|
118
121
|
@property
|
|
119
122
|
def right(self) -> list[tuple[float, float]]:
|
|
120
|
-
|
|
123
|
+
raise NotImplementedError()
|
|
121
124
|
|
|
122
125
|
@property
|
|
123
126
|
def left(self) -> list[tuple[float, float]]:
|
|
124
|
-
|
|
127
|
+
raise NotImplementedError()
|
|
125
128
|
|
|
126
129
|
def __eq__(self, other: RouteElement) -> bool:
|
|
127
130
|
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 +161,7 @@ class StripTurn(RouteElement):
|
|
|
158
161
|
direction: tuple[float, float],
|
|
159
162
|
angle: float,
|
|
160
163
|
corner_type: str = 'round',
|
|
161
|
-
champher_distance: float = None):
|
|
164
|
+
champher_distance: float | None = None):
|
|
162
165
|
self.xold: float = x
|
|
163
166
|
self.yold: float = y
|
|
164
167
|
self.width: float = width
|
|
@@ -221,7 +224,8 @@ class StripTurn(RouteElement):
|
|
|
221
224
|
y2 = yend - dist * self.direction[1]
|
|
222
225
|
|
|
223
226
|
return [(x1, y1), (x2, y2), (xend, yend)]
|
|
224
|
-
|
|
227
|
+
else:
|
|
228
|
+
raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
|
|
225
229
|
@property
|
|
226
230
|
def left(self) -> list[tuple[float, float]]:
|
|
227
231
|
if self.angle < 0:
|
|
@@ -258,6 +262,8 @@ class StripTurn(RouteElement):
|
|
|
258
262
|
y2 = yend - dist * self.direction[1]
|
|
259
263
|
|
|
260
264
|
return [(xend, yend), (x2, y2), (x1, y1)]
|
|
265
|
+
else:
|
|
266
|
+
raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
|
|
261
267
|
|
|
262
268
|
############################################################
|
|
263
269
|
# THE STRIP PATH CLASS #
|
|
@@ -316,7 +322,7 @@ class StripPath:
|
|
|
316
322
|
return self
|
|
317
323
|
|
|
318
324
|
def straight(self, distance:
|
|
319
|
-
float, width: float = None,
|
|
325
|
+
float, width: float | None = None,
|
|
320
326
|
dx: float = 0,
|
|
321
327
|
dy: float = 0) -> StripPath:
|
|
322
328
|
"""Add A straight section to the stripline.
|
|
@@ -381,7 +387,7 @@ class StripPath:
|
|
|
381
387
|
return self
|
|
382
388
|
|
|
383
389
|
def turn(self, angle: float,
|
|
384
|
-
width: float = None,
|
|
390
|
+
width: float | None = None,
|
|
385
391
|
corner_type: Literal['champher','square'] = 'champher') -> StripPath:
|
|
386
392
|
"""Adds a turn to the strip path.
|
|
387
393
|
|
|
@@ -419,13 +425,12 @@ class StripPath:
|
|
|
419
425
|
Returns:
|
|
420
426
|
StripPath: The current StripPath object.
|
|
421
427
|
"""
|
|
422
|
-
self.pcb.store(name, self.end.x, self.end.y)
|
|
423
428
|
self.pcb.stored_striplines[name] = self.end
|
|
424
429
|
return self
|
|
425
430
|
|
|
426
431
|
def split(self,
|
|
427
|
-
direction: tuple[float, float] = None,
|
|
428
|
-
width: float = None) -> StripPath:
|
|
432
|
+
direction: tuple[float, float] | None= None,
|
|
433
|
+
width: float | None= None) -> StripPath:
|
|
429
434
|
"""Split the current path in N new paths given by a new departure direction
|
|
430
435
|
|
|
431
436
|
Args:
|
|
@@ -529,9 +534,9 @@ class StripPath:
|
|
|
529
534
|
znew: float,
|
|
530
535
|
radius: float,
|
|
531
536
|
proceed: bool = True,
|
|
532
|
-
direction: tuple[float, float] = None,
|
|
533
|
-
width: float = None,
|
|
534
|
-
extra: float = None,
|
|
537
|
+
direction: tuple[float, float] | None = None,
|
|
538
|
+
width: float | None = None,
|
|
539
|
+
extra: float | None = None,
|
|
535
540
|
segments: int = 6) -> StripPath:
|
|
536
541
|
"""Adds a via to the circuit
|
|
537
542
|
|
|
@@ -576,13 +581,13 @@ class StripPath:
|
|
|
576
581
|
return self
|
|
577
582
|
|
|
578
583
|
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:
|
|
584
|
+
dx: float | None = None,
|
|
585
|
+
dy: float | None = None,
|
|
586
|
+
width: float | None = None,
|
|
587
|
+
direction: tuple[float, float] | None = None,
|
|
588
|
+
gap: float | None = None,
|
|
589
|
+
side: Literal['left','right'] | None = None,
|
|
590
|
+
reverse: float | None = None) -> StripPath:
|
|
586
591
|
"""Add an unconnected jump to the currenet stripline.
|
|
587
592
|
|
|
588
593
|
The last stripline path will be terminated and a new one will be started based on the
|
|
@@ -630,8 +635,8 @@ class StripPath:
|
|
|
630
635
|
return self.pcb.new(x, y, width, direction)
|
|
631
636
|
|
|
632
637
|
def to(self, dest: tuple[float, float],
|
|
633
|
-
arrival_dir: tuple[float, float] = None,
|
|
634
|
-
arrival_margin: float = None,
|
|
638
|
+
arrival_dir: tuple[float, float] | None = None,
|
|
639
|
+
arrival_margin: float | None= None,
|
|
635
640
|
angle_step: float = 90):
|
|
636
641
|
"""
|
|
637
642
|
Extend the path from current end point to dest (x, y).
|
|
@@ -672,7 +677,7 @@ class StripPath:
|
|
|
672
677
|
dt = max_t / 1000.0 # resolution of search
|
|
673
678
|
found = False
|
|
674
679
|
desired_q = None
|
|
675
|
-
cand_dx = cand_dy = 0.0
|
|
680
|
+
#cand_dx = cand_dy = 0.0
|
|
676
681
|
|
|
677
682
|
while t <= max_t:
|
|
678
683
|
# candidate intercept point
|
|
@@ -700,8 +705,8 @@ class StripPath:
|
|
|
700
705
|
raise RuntimeError("Could not find an intercept angle matching quantization")
|
|
701
706
|
|
|
702
707
|
# 1) Perform initial quantized turn
|
|
703
|
-
if abs(desired_q) > atol:
|
|
704
|
-
self.turn(-desired_q)
|
|
708
|
+
if abs(desired_q) > atol: # type: ignore
|
|
709
|
+
self.turn(-desired_q) # type: ignore
|
|
705
710
|
x0 = self.end.x
|
|
706
711
|
y0 = self.end.y
|
|
707
712
|
# compute new heading vector after turn
|
|
@@ -752,7 +757,7 @@ class StripPath:
|
|
|
752
757
|
|
|
753
758
|
return self
|
|
754
759
|
|
|
755
|
-
def macro(self, path: str, width: float = None, start_dir: tuple[float, float] = None) -> StripPath:
|
|
760
|
+
def macro(self, path: str, width: float | None = None, start_dir: tuple[float, float] | None = None) -> StripPath:
|
|
756
761
|
r"""Parse an EMerge macro command string
|
|
757
762
|
|
|
758
763
|
The start direction by default is the abslute current heading. If a specified heading is provided
|
|
@@ -803,7 +808,7 @@ class PCB:
|
|
|
803
808
|
def __init__(self,
|
|
804
809
|
thickness: float,
|
|
805
810
|
unit: float = 0.001,
|
|
806
|
-
cs: CoordinateSystem = None,
|
|
811
|
+
cs: CoordinateSystem | None = None,
|
|
807
812
|
material: Material = AIR,
|
|
808
813
|
layers: int = 2,
|
|
809
814
|
):
|
|
@@ -811,16 +816,16 @@ class PCB:
|
|
|
811
816
|
self.thickness: float = thickness
|
|
812
817
|
self._zs: np.ndarray = np.linspace(-self.thickness, 0, layers)
|
|
813
818
|
self.material: Material = material
|
|
814
|
-
self.width: float = None
|
|
815
|
-
self.length: float = None
|
|
816
|
-
self.origin: np.ndarray =
|
|
819
|
+
self.width: float | None = None
|
|
820
|
+
self.length: float | None = None
|
|
821
|
+
self.origin: np.ndarray = np.array([0.,0.,0.])
|
|
817
822
|
self.paths: list[StripPath] = []
|
|
818
823
|
self.polies: list[PCBPoly] = []
|
|
819
824
|
|
|
820
825
|
self.lumped_ports: list[StripLine] = []
|
|
821
826
|
self.lumped_elements: list[GeoPolygon] = []
|
|
822
827
|
|
|
823
|
-
self.unit = unit
|
|
828
|
+
self.unit: float = unit
|
|
824
829
|
|
|
825
830
|
self.cs: CoordinateSystem = cs
|
|
826
831
|
if self.cs is None:
|
|
@@ -864,7 +869,7 @@ class PCB:
|
|
|
864
869
|
"""
|
|
865
870
|
return self._zs[layer-1]
|
|
866
871
|
|
|
867
|
-
def _get_z(self, element: RouteElement) -> float:
|
|
872
|
+
def _get_z(self, element: RouteElement) -> float :
|
|
868
873
|
"""Return the z-height of a given Route Element
|
|
869
874
|
|
|
870
875
|
Args:
|
|
@@ -876,38 +881,23 @@ class PCB:
|
|
|
876
881
|
for path in self.paths:
|
|
877
882
|
if path._has(element):
|
|
878
883
|
return path.z
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
def store(self, name: str, x: float, y:float):
|
|
882
|
-
"""Store the x,y coordinate pair one label provided by name
|
|
884
|
+
raise RouteException('Requesting z-height of route element that is not contained in a path.')
|
|
883
885
|
|
|
884
|
-
|
|
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:
|
|
886
|
+
def load(self, name: str) -> StripLine:
|
|
892
887
|
"""Acquire the x,y, coordinate associated with the label name.
|
|
893
888
|
|
|
894
889
|
Args:
|
|
895
890
|
name (str): The name of the x,y coordinate
|
|
896
891
|
|
|
897
892
|
"""
|
|
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:
|
|
893
|
+
if name in self.stored_striplines:
|
|
902
894
|
return self.stored_striplines[name]
|
|
903
|
-
elif name in self.stored_coords:
|
|
904
|
-
return self.stored_coords[name]
|
|
905
895
|
else:
|
|
906
896
|
raise ValueError(f'There is no stripline or coordinate under the name of {name}')
|
|
907
897
|
|
|
908
898
|
def __call__(self, path_nr: int) -> StripPath:
|
|
909
899
|
if path_nr >= len(self.paths):
|
|
910
|
-
self.paths.append(StripPath())
|
|
900
|
+
self.paths.append(StripPath(self))
|
|
911
901
|
return self.paths[path_nr]
|
|
912
902
|
|
|
913
903
|
def determine_bounds(self,
|
|
@@ -956,9 +946,9 @@ class PCB:
|
|
|
956
946
|
|
|
957
947
|
def plane(self,
|
|
958
948
|
z: float,
|
|
959
|
-
width: float = None,
|
|
960
|
-
height: float = None,
|
|
961
|
-
origin: tuple[float, float] = None,
|
|
949
|
+
width: float | None = None,
|
|
950
|
+
height: float | None = None,
|
|
951
|
+
origin: tuple[float, float] | None = None,
|
|
962
952
|
alignment: Literal['corner','center'] = 'corner') -> GeoSurface:
|
|
963
953
|
"""Generates a generic rectangular plate in the XY grid.
|
|
964
954
|
If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
|
|
@@ -974,17 +964,22 @@ class PCB:
|
|
|
974
964
|
GeoSurface: _description_
|
|
975
965
|
"""
|
|
976
966
|
if width is None or height is None or origin is None:
|
|
967
|
+
if self.width is None or self.length is None or self.origin:
|
|
968
|
+
raise RouteException('Cannot define a plane with no possible definition of its size.')
|
|
977
969
|
width = self.width
|
|
978
970
|
height = self.length
|
|
979
971
|
origin = (self.origin[0]*self.unit, self.origin[1]*self.unit)
|
|
980
|
-
|
|
972
|
+
|
|
973
|
+
origin: tuple[float, ...] = origin + (z*self.unit, ) # type: ignore
|
|
981
974
|
|
|
982
975
|
if alignment == 'center':
|
|
983
|
-
origin = (origin[0] - width*self.unit/2,
|
|
976
|
+
origin = (origin[0] - width*self.unit/2,
|
|
977
|
+
origin[1] - height*self.unit/2,
|
|
978
|
+
origin[2])
|
|
984
979
|
|
|
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
|
|
980
|
+
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
|
|
981
|
+
plane = change_coordinate_system(plane, self.cs) # type: ignore
|
|
982
|
+
return plane # type: ignore
|
|
988
983
|
|
|
989
984
|
def gen_pcb(self,
|
|
990
985
|
split_z: bool = True,
|
|
@@ -1004,7 +999,7 @@ class PCB:
|
|
|
1004
999
|
if (z-zvalues_isolated[-1]) <= layer_tolerance:
|
|
1005
1000
|
continue
|
|
1006
1001
|
zvalues_isolated.append(z)
|
|
1007
|
-
boxes = []
|
|
1002
|
+
boxes: list[GeoVolume] = []
|
|
1008
1003
|
for z1, z2 in zip(zvalues_isolated[:-1],zvalues_isolated[1:]):
|
|
1009
1004
|
h = z2-z1
|
|
1010
1005
|
box = Box(self.width*self.unit,
|
|
@@ -1015,8 +1010,8 @@ class PCB:
|
|
|
1015
1010
|
box = change_coordinate_system(box, self.cs)
|
|
1016
1011
|
boxes.append(box)
|
|
1017
1012
|
if merge:
|
|
1018
|
-
return GeoVolume.merged(boxes)
|
|
1019
|
-
return boxes
|
|
1013
|
+
return GeoVolume.merged(boxes) # type: ignore
|
|
1014
|
+
return boxes # type: ignore
|
|
1020
1015
|
|
|
1021
1016
|
box = Box(self.width*self.unit,
|
|
1022
1017
|
self.length*self.unit,
|
|
@@ -1024,7 +1019,7 @@ class PCB:
|
|
|
1024
1019
|
position=(x0,y0,z0-self.thickness*self.unit))
|
|
1025
1020
|
box.material = self.material
|
|
1026
1021
|
box = change_coordinate_system(box, self.cs)
|
|
1027
|
-
return box
|
|
1022
|
+
return box # type: ignore
|
|
1028
1023
|
|
|
1029
1024
|
def gen_air(self, height: float) -> GeoVolume:
|
|
1030
1025
|
"""Generate the Air Block object
|
|
@@ -1041,7 +1036,7 @@ class PCB:
|
|
|
1041
1036
|
height*self.unit,
|
|
1042
1037
|
position=(x0,y0,z0))
|
|
1043
1038
|
box = change_coordinate_system(box, self.cs)
|
|
1044
|
-
return box
|
|
1039
|
+
return box # type: ignore
|
|
1045
1040
|
|
|
1046
1041
|
def new(self,
|
|
1047
1042
|
x: float,
|
|
@@ -1072,7 +1067,7 @@ class PCB:
|
|
|
1072
1067
|
self.paths.append(path)
|
|
1073
1068
|
return path
|
|
1074
1069
|
|
|
1075
|
-
def lumped_port(self, stripline: StripLine, z_ground: float = None) -> GeoPolygon:
|
|
1070
|
+
def lumped_port(self, stripline: StripLine, z_ground: float | None = None) -> GeoPolygon:
|
|
1076
1071
|
"""Generate a lumped-port object to be created.
|
|
1077
1072
|
|
|
1078
1073
|
Args:
|
|
@@ -1152,7 +1147,7 @@ class PCB:
|
|
|
1152
1147
|
|
|
1153
1148
|
plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2)
|
|
1154
1149
|
plate = change_coordinate_system(plate, self.cs)
|
|
1155
|
-
return plate
|
|
1150
|
+
return plate # type: ignore
|
|
1156
1151
|
|
|
1157
1152
|
def generate_vias(self, merge=False) -> list[Cyllinder] | Cyllinder:
|
|
1158
1153
|
"""Generates the via objects.
|
|
@@ -1175,14 +1170,14 @@ class PCB:
|
|
|
1175
1170
|
vias.append(cyl)
|
|
1176
1171
|
if merge:
|
|
1177
1172
|
|
|
1178
|
-
return GeoVolume.merged(vias)
|
|
1173
|
+
return GeoVolume.merged(vias) # type: ignore
|
|
1179
1174
|
return vias
|
|
1180
1175
|
|
|
1181
1176
|
def add_poly(self,
|
|
1182
1177
|
xs: list[float],
|
|
1183
1178
|
ys: list[float],
|
|
1184
1179
|
z: float = 0,
|
|
1185
|
-
material: Material = COPPER):
|
|
1180
|
+
material: Material = COPPER) -> None:
|
|
1186
1181
|
"""Add a custom polygon to the PCB
|
|
1187
1182
|
|
|
1188
1183
|
Args:
|
|
@@ -1210,6 +1205,12 @@ class PCB:
|
|
|
1210
1205
|
poly = GeoPolygon([planetag,])
|
|
1211
1206
|
return poly
|
|
1212
1207
|
|
|
1208
|
+
@overload
|
|
1209
|
+
def compile_paths(self, merge: Literal[True]) -> GeoSurface: ...
|
|
1210
|
+
|
|
1211
|
+
@overload
|
|
1212
|
+
def compile_paths(self, merge: Literal[False] = ...) -> list[GeoSurface]: ...
|
|
1213
|
+
|
|
1213
1214
|
def compile_paths(self, merge: bool = False) -> list[GeoPolygon] | GeoSurface:
|
|
1214
1215
|
"""Compiles the striplines and returns a list of polygons or asingle one.
|
|
1215
1216
|
|
|
@@ -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
|
|
emerge/_emerge/geometry.py
CHANGED
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
|
-
import gmsh
|
|
19
|
+
import gmsh # type: ignore
|
|
20
20
|
from .material import Material, AIR
|
|
21
|
-
from .selection import FaceSelection, DomainSelection, EdgeSelection, PointSelection
|
|
21
|
+
from .selection import FaceSelection, DomainSelection, EdgeSelection, PointSelection, Selection
|
|
22
22
|
from loguru import logger
|
|
23
|
-
from typing import Literal, Any
|
|
23
|
+
from typing import Literal, Any, Iterable, TypeVar
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
26
|
+
|
|
27
|
+
|
|
26
28
|
def _map_tags(tags: list[int], mapping: dict[int, list[int]]):
|
|
27
29
|
new_tags = []
|
|
28
30
|
for tag in tags:
|
|
@@ -48,12 +50,12 @@ class _GeometryManager:
|
|
|
48
50
|
self.geometry_list: dict[str, list[GeoObject]] = dict()
|
|
49
51
|
self.active: str = ''
|
|
50
52
|
|
|
51
|
-
def all_geometries(self, model: str = None) -> list[GeoObject]:
|
|
53
|
+
def all_geometries(self, model: str | None = None) -> list[GeoObject]:
|
|
52
54
|
if model is None:
|
|
53
55
|
model = self.active
|
|
54
56
|
return [geo for geo in self.geometry_list[model] if geo._exists]
|
|
55
57
|
|
|
56
|
-
def submit_geometry(self, geo: GeoObject, model: str = None) -> None:
|
|
58
|
+
def submit_geometry(self, geo: GeoObject, model: str | None = None) -> None:
|
|
57
59
|
if model is None:
|
|
58
60
|
model = self.active
|
|
59
61
|
self.geometry_list[model].append(geo)
|
|
@@ -135,7 +137,7 @@ class _FacePointer:
|
|
|
135
137
|
def translate(self, dx, dy, dz):
|
|
136
138
|
self.o = self.o + np.array([dx, dy, dz])
|
|
137
139
|
|
|
138
|
-
def mirror(self, c0: np.ndarray, pln: np.ndarray):
|
|
140
|
+
def mirror(self, c0: np.ndarray, pln: np.ndarray) -> None:
|
|
139
141
|
"""
|
|
140
142
|
Reflect self.o and self.n across the plane passing through c0
|
|
141
143
|
with normal pln.
|
|
@@ -205,9 +207,11 @@ class GeoObject:
|
|
|
205
207
|
"""A generalization of any OpenCASCADE entity described by a dimension and a set of tags.
|
|
206
208
|
"""
|
|
207
209
|
dim: int = -1
|
|
208
|
-
def __init__(self):
|
|
210
|
+
def __init__(self, tags: list[int] | None = None):
|
|
211
|
+
if tags is None:
|
|
212
|
+
tags = []
|
|
209
213
|
self.old_tags: list[int] = []
|
|
210
|
-
self.tags: list[int] =
|
|
214
|
+
self.tags: list[int] = tags
|
|
211
215
|
self.material: Material = AIR
|
|
212
216
|
self.mesh_multiplier: float = 1.0
|
|
213
217
|
self.max_meshsize: float = 1e9
|
|
@@ -225,7 +229,7 @@ class GeoObject:
|
|
|
225
229
|
_GEOMANAGER.submit_geometry(self)
|
|
226
230
|
|
|
227
231
|
@property
|
|
228
|
-
def color_rgb(self) -> tuple[
|
|
232
|
+
def color_rgb(self) -> tuple[float, float, float]:
|
|
229
233
|
return self.material.color_rgb
|
|
230
234
|
|
|
231
235
|
@property
|
|
@@ -233,7 +237,7 @@ class GeoObject:
|
|
|
233
237
|
return self.material.opacity
|
|
234
238
|
|
|
235
239
|
@property
|
|
236
|
-
def select(self) ->
|
|
240
|
+
def select(self) -> Selection:
|
|
237
241
|
'''Returns a corresponding Face/Domain or Edge Selection object'''
|
|
238
242
|
if self.dim==1:
|
|
239
243
|
return EdgeSelection(self.tags)
|
|
@@ -241,11 +245,14 @@ class GeoObject:
|
|
|
241
245
|
return FaceSelection(self.tags)
|
|
242
246
|
elif self.dim==3:
|
|
243
247
|
return DomainSelection(self.tags)
|
|
248
|
+
else:
|
|
249
|
+
return Selection(self.tags)
|
|
244
250
|
|
|
245
251
|
@staticmethod
|
|
246
|
-
def merged(objects: list[GeoObject]) -> list[GeoObject]:
|
|
252
|
+
def merged(objects: list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject]) -> list[GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject] | GeoPoint | GeoEdge | GeoSurface | GeoVolume | GeoObject:
|
|
247
253
|
dim = objects[0].dim
|
|
248
254
|
tags = []
|
|
255
|
+
out: GeoObject | None = None
|
|
249
256
|
for obj in objects:
|
|
250
257
|
tags.extend(obj.tags)
|
|
251
258
|
if dim==2:
|
|
@@ -260,8 +267,8 @@ class GeoObject:
|
|
|
260
267
|
def __repr__(self) -> str:
|
|
261
268
|
return f'{self.__class__.__name__}({self.dim},{self.tags})'
|
|
262
269
|
|
|
263
|
-
def _data(self, *labels) -> tuple[Any]:
|
|
264
|
-
return tuple([self._aux_data
|
|
270
|
+
def _data(self, *labels) -> tuple[Any | None, ...]:
|
|
271
|
+
return tuple([self._aux_data[lab] for lab in labels])
|
|
265
272
|
|
|
266
273
|
def _add_face_pointer(self,
|
|
267
274
|
name: str,
|
|
@@ -325,7 +332,7 @@ class GeoObject:
|
|
|
325
332
|
self._tools.update(obj._tools)
|
|
326
333
|
return self
|
|
327
334
|
|
|
328
|
-
def _face_tags(self, name: FaceNames, tool: GeoObject = None) -> list[int]:
|
|
335
|
+
def _face_tags(self, name: FaceNames, tool: GeoObject | None = None) -> list[int]:
|
|
329
336
|
names = self._all_pointer_names
|
|
330
337
|
if name not in names:
|
|
331
338
|
raise ValueError(f'The face {name} does not exist in {self}')
|
|
@@ -380,7 +387,7 @@ class GeoObject:
|
|
|
380
387
|
self._priority -= 1
|
|
381
388
|
return self
|
|
382
389
|
|
|
383
|
-
def outside(self, *exclude: FaceNames, tags: list[int] = None) -> FaceSelection:
|
|
390
|
+
def outside(self, *exclude: FaceNames, tags: list[int] | None = None) -> FaceSelection:
|
|
384
391
|
"""Returns the complete set of outside faces.
|
|
385
392
|
|
|
386
393
|
If implemented, it is possible to exclude a set of faces based on their name
|
|
@@ -394,7 +401,7 @@ class GeoObject:
|
|
|
394
401
|
dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
|
|
395
402
|
return FaceSelection([t for d,t in dimtags if t not in tags])
|
|
396
403
|
|
|
397
|
-
def face(self, name: FaceNames, tool: GeoObject = None) -> FaceSelection:
|
|
404
|
+
def face(self, name: FaceNames, tool: GeoObject | None = None) -> FaceSelection:
|
|
398
405
|
"""Returns the FaceSelection for a given face name.
|
|
399
406
|
|
|
400
407
|
The face name must be defined for the type of geometry.
|
|
@@ -422,7 +429,7 @@ class GeoObject:
|
|
|
422
429
|
return FaceSelection([t[1] for t in tags])
|
|
423
430
|
if self.dim == 2:
|
|
424
431
|
return FaceSelection(self.tags)
|
|
425
|
-
|
|
432
|
+
else:
|
|
426
433
|
raise ValueError('Can only generate faces for objects of dimension 2 or higher.')
|
|
427
434
|
|
|
428
435
|
@staticmethod
|
|
@@ -443,12 +450,13 @@ class GeoVolume(GeoObject):
|
|
|
443
450
|
'''GeoVolume is an interface to the GMSH CAD kernel. It does not represent EMerge
|
|
444
451
|
specific geometry data.'''
|
|
445
452
|
dim = 3
|
|
446
|
-
def __init__(self, tag: int |
|
|
453
|
+
def __init__(self, tag: int | Iterable[int]):
|
|
447
454
|
super().__init__()
|
|
448
|
-
|
|
449
|
-
|
|
455
|
+
self.tags: list[int] = []
|
|
456
|
+
if isinstance(tag, Iterable):
|
|
457
|
+
self.tags = list(tag)
|
|
450
458
|
else:
|
|
451
|
-
self.tags
|
|
459
|
+
self.tags = [tag,]
|
|
452
460
|
|
|
453
461
|
@property
|
|
454
462
|
def select(self) -> DomainSelection:
|
|
@@ -463,10 +471,12 @@ class GeoPoint(GeoObject):
|
|
|
463
471
|
|
|
464
472
|
def __init__(self, tag: int | list[int]):
|
|
465
473
|
super().__init__()
|
|
466
|
-
|
|
467
|
-
|
|
474
|
+
|
|
475
|
+
self.tags: list[int] = []
|
|
476
|
+
if isinstance(tag, Iterable):
|
|
477
|
+
self.tags = list(tag)
|
|
468
478
|
else:
|
|
469
|
-
self.tags
|
|
479
|
+
self.tags = [tag,]
|
|
470
480
|
|
|
471
481
|
class GeoEdge(GeoObject):
|
|
472
482
|
dim = 1
|
|
@@ -477,10 +487,11 @@ class GeoEdge(GeoObject):
|
|
|
477
487
|
|
|
478
488
|
def __init__(self, tag: int | list[int]):
|
|
479
489
|
super().__init__()
|
|
480
|
-
|
|
481
|
-
|
|
490
|
+
self.tags: list[int] = []
|
|
491
|
+
if isinstance(tag, Iterable):
|
|
492
|
+
self.tags = list(tag)
|
|
482
493
|
else:
|
|
483
|
-
self.tags
|
|
494
|
+
self.tags = [tag,]
|
|
484
495
|
|
|
485
496
|
|
|
486
497
|
class GeoSurface(GeoObject):
|
|
@@ -494,17 +505,20 @@ class GeoSurface(GeoObject):
|
|
|
494
505
|
|
|
495
506
|
def __init__(self, tag: int | list[int]):
|
|
496
507
|
super().__init__()
|
|
497
|
-
|
|
498
|
-
|
|
508
|
+
self.tags: list[int] = []
|
|
509
|
+
if isinstance(tag, Iterable):
|
|
510
|
+
self.tags = list(tag)
|
|
499
511
|
else:
|
|
500
|
-
self.tags
|
|
512
|
+
self.tags = [tag,]
|
|
501
513
|
|
|
502
514
|
class GeoPolygon(GeoSurface):
|
|
503
515
|
|
|
504
516
|
def __init__(self,
|
|
505
517
|
tags: list[int]):
|
|
506
518
|
super().__init__(tags)
|
|
507
|
-
self.points: list[int] =
|
|
508
|
-
self.lines: list[int] =
|
|
519
|
+
self.points: list[int] = []
|
|
520
|
+
self.lines: list[int] = []
|
|
509
521
|
|
|
510
522
|
|
|
523
|
+
T = TypeVar('T', GeoVolume, GeoEdge, GeoPoint, GeoSurface)
|
|
524
|
+
|
emerge/_emerge/logsettings.py
CHANGED
|
@@ -51,8 +51,8 @@ class LogController:
|
|
|
51
51
|
logger.remove()
|
|
52
52
|
self.std_handlers: list[int] = []
|
|
53
53
|
self.file_handlers: list[int] = []
|
|
54
|
-
self.level:
|
|
55
|
-
self.file_level:
|
|
54
|
+
self.level: str = 'INFO'
|
|
55
|
+
self.file_level: str = 'INFO'
|
|
56
56
|
|
|
57
57
|
def set_default(self):
|
|
58
58
|
value = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
|
|
@@ -68,7 +68,7 @@ class LogController:
|
|
|
68
68
|
handler = {"sink": sys.stdout,
|
|
69
69
|
"level": loglevel,
|
|
70
70
|
"format": FORMAT_DICT.get(loglevel, INFO_FORMAT)}
|
|
71
|
-
logger.configure(handlers=[handler])
|
|
71
|
+
logger.configure(handlers=[handler]) # type: ignore
|
|
72
72
|
self.level = loglevel
|
|
73
73
|
os.environ["EMERGE_STD_LOGLEVEL"] = loglevel
|
|
74
74
|
|