emerge 1.0.7__py3-none-any.whl → 1.1.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 +15 -3
- emerge/_emerge/const.py +2 -1
- emerge/_emerge/elements/ned2_interp.py +122 -42
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/operations.py +20 -0
- emerge/_emerge/geo/pcb.py +162 -71
- emerge/_emerge/geo/shapes.py +12 -7
- emerge/_emerge/geo/step.py +177 -41
- emerge/_emerge/geometry.py +189 -27
- emerge/_emerge/logsettings.py +26 -2
- emerge/_emerge/material.py +2 -0
- emerge/_emerge/mesh3d.py +6 -8
- emerge/_emerge/mesher.py +67 -11
- emerge/_emerge/mth/common_functions.py +1 -1
- emerge/_emerge/mth/optimized.py +2 -2
- emerge/_emerge/physics/microwave/adaptive_mesh.py +549 -116
- emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
- emerge/_emerge/physics/microwave/microwave_3d.py +133 -83
- emerge/_emerge/physics/microwave/microwave_bc.py +158 -8
- emerge/_emerge/physics/microwave/microwave_data.py +94 -5
- emerge/_emerge/plot/pyvista/display.py +36 -23
- emerge/_emerge/selection.py +17 -2
- emerge/_emerge/settings.py +124 -6
- emerge/_emerge/simmodel.py +273 -150
- emerge/_emerge/simstate.py +106 -0
- emerge/_emerge/simulation_data.py +11 -23
- emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
- emerge/_emerge/solver.py +4 -4
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/METADATA +7 -3
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/RECORD +33 -32
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/WHEEL +0 -0
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/geo/pcb.py
CHANGED
|
@@ -25,7 +25,7 @@ from .polybased import XYPolygon
|
|
|
25
25
|
from .operations import change_coordinate_system, unite
|
|
26
26
|
from .pcb_tools.macro import parse_macro
|
|
27
27
|
from .pcb_tools.calculator import PCBCalculator
|
|
28
|
-
|
|
28
|
+
from ..logsettings import DEBUG_COLLECTOR
|
|
29
29
|
import numpy as np
|
|
30
30
|
from loguru import logger
|
|
31
31
|
from typing import Literal, Callable, overload
|
|
@@ -934,12 +934,14 @@ class StripPath:
|
|
|
934
934
|
return self.path[element_nr]
|
|
935
935
|
|
|
936
936
|
class PCBLayer:
|
|
937
|
-
|
|
937
|
+
_DEFNAME: str = 'PCBLayer'
|
|
938
938
|
def __init__(self,
|
|
939
939
|
thickness: float,
|
|
940
|
-
material: Material
|
|
940
|
+
material: Material,
|
|
941
|
+
name: str | None = None):
|
|
941
942
|
self.th: float = thickness
|
|
942
943
|
self.mat: Material = material
|
|
944
|
+
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
943
945
|
|
|
944
946
|
############################################################
|
|
945
947
|
# PCB DESIGN CLASS #
|
|
@@ -958,7 +960,8 @@ class PCB:
|
|
|
958
960
|
stack: list[PCBLayer] = None,
|
|
959
961
|
name: str | None = None,
|
|
960
962
|
trace_thickness: float | None = None,
|
|
961
|
-
zs: np.ndarray | None = None
|
|
963
|
+
zs: np.ndarray | None = None,
|
|
964
|
+
thick_traces: bool = False,
|
|
962
965
|
):
|
|
963
966
|
"""Creates a new PCB layout class instance
|
|
964
967
|
|
|
@@ -972,10 +975,13 @@ class PCB:
|
|
|
972
975
|
stack (list[PCBLayer], optional): Optional list of PCBLayer classes for multilayer PCB with different dielectrics. Defaults to None.
|
|
973
976
|
name (str | None, optional): The PCB object name. Defaults to None.
|
|
974
977
|
trace_thickness (float | None, optional): The conductor trace thickness if important. Defaults to None.
|
|
978
|
+
thick_traces: (bool, optional): If traces should be given a thickness and modeled in 3D. Defaults to False
|
|
975
979
|
"""
|
|
976
980
|
|
|
977
981
|
self.thickness: float = thickness
|
|
982
|
+
self._thick_traces: bool = thick_traces
|
|
978
983
|
self._stack: list[PCBLayer] = []
|
|
984
|
+
|
|
979
985
|
if zs is not None:
|
|
980
986
|
self._zs = zs
|
|
981
987
|
self.thickness = np.max(zs)-np.min(zs)
|
|
@@ -1013,9 +1019,10 @@ class PCB:
|
|
|
1013
1019
|
|
|
1014
1020
|
self.dielectric_priority: int = 11
|
|
1015
1021
|
self.via_priority: int = 12
|
|
1022
|
+
self.conductor_priority: int = 13
|
|
1016
1023
|
|
|
1017
|
-
self.traces: list[GeoPolygon] = []
|
|
1018
|
-
self.ports: list[GeoPolygon] = []
|
|
1024
|
+
self.traces: list[GeoPolygon | GeoVolume] = []
|
|
1025
|
+
self.ports: list[GeoPolygon | GeoVolume] = []
|
|
1019
1026
|
self.vias: list[Via] = []
|
|
1020
1027
|
|
|
1021
1028
|
self.xs: list[float] = []
|
|
@@ -1029,9 +1036,18 @@ class PCB:
|
|
|
1029
1036
|
self.calc: PCBCalculator = PCBCalculator(self.thickness, self._zs, self.material, self.unit)
|
|
1030
1037
|
|
|
1031
1038
|
self.name: str = _NAME_MANAGER(name, self._DEFNAME)
|
|
1032
|
-
|
|
1039
|
+
|
|
1040
|
+
if not self._thick_traces and self.trace_material is not PEC:
|
|
1041
|
+
DEBUG_COLLECTOR.add_report('Non PEC surface materials are used without thick traces. The SurfaceImpedance boundary condition will be used that is known to not be accurate.' +
|
|
1042
|
+
'Please set thick_traces=True in the PCB constructor to ensure accurate losses until this issue is fixed.')
|
|
1043
|
+
|
|
1044
|
+
############################################################
|
|
1045
|
+
# PROPERTIES #
|
|
1046
|
+
############################################################
|
|
1047
|
+
|
|
1033
1048
|
@property
|
|
1034
1049
|
def trace(self) -> GeoPolygon:
|
|
1050
|
+
""
|
|
1035
1051
|
tags = []
|
|
1036
1052
|
for trace in self.traces:
|
|
1037
1053
|
tags.extend(trace.tags)
|
|
@@ -1040,29 +1056,36 @@ class PCB:
|
|
|
1040
1056
|
|
|
1041
1057
|
@property
|
|
1042
1058
|
def all_objects(self) -> list[GeoPolygon]:
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
def z(self, layer: int) -> float:
|
|
1047
|
-
"""Returns the z-height of the given layer number counter from 1 (bottom) to N (top)
|
|
1048
|
-
|
|
1049
|
-
Args:
|
|
1050
|
-
layer (int): The layer number (1 to N)
|
|
1059
|
+
"""Returns all objects gnerated by the PCB layer.
|
|
1051
1060
|
|
|
1052
1061
|
Returns:
|
|
1053
|
-
|
|
1062
|
+
list[GeoPolygon]: _description_
|
|
1054
1063
|
"""
|
|
1055
|
-
|
|
1056
|
-
return self._zs[layer]
|
|
1057
|
-
return self._zs[layer-1]
|
|
1064
|
+
return self.traces + self.ports
|
|
1058
1065
|
|
|
1059
1066
|
@property
|
|
1060
1067
|
def top(self) -> float:
|
|
1068
|
+
""" The top conductor later height (z-value in meters)."""
|
|
1061
1069
|
return self._zs[-1]
|
|
1062
1070
|
|
|
1063
1071
|
@property
|
|
1064
1072
|
def bottom(self) -> float:
|
|
1073
|
+
"""The bottom conductor layer height (z-value in meters)
|
|
1074
|
+
|
|
1075
|
+
"""
|
|
1065
1076
|
return self._zs[0]
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
############################################################
|
|
1080
|
+
# PRIVATE FUNCTIONS #
|
|
1081
|
+
############################################################
|
|
1082
|
+
|
|
1083
|
+
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float, name: str | None = 'LumpedElement') -> None:
|
|
1084
|
+
geopoly = poly._finalize(self.cs, name=name)
|
|
1085
|
+
geopoly._aux_data['func'] = function
|
|
1086
|
+
geopoly._aux_data['width'] = width
|
|
1087
|
+
geopoly._aux_data['height'] = length
|
|
1088
|
+
self.lumped_elements.append(geopoly)
|
|
1066
1089
|
|
|
1067
1090
|
def _get_z(self, element: RouteElement) -> float :
|
|
1068
1091
|
"""Return the z-height of a given Route Element
|
|
@@ -1077,12 +1100,70 @@ class PCB:
|
|
|
1077
1100
|
if path._has(element):
|
|
1078
1101
|
return path.z
|
|
1079
1102
|
raise RouteException('Requesting z-height of route element that is not contained in a path.')
|
|
1103
|
+
|
|
1104
|
+
def __call__(self, path_nr: int) -> StripPath:
|
|
1105
|
+
if path_nr >= len(self.paths):
|
|
1106
|
+
self.paths.append(StripPath(self))
|
|
1107
|
+
return self.paths[path_nr]
|
|
1108
|
+
|
|
1109
|
+
def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon | GeoVolume:
|
|
1110
|
+
""" Generates a GeoPoly out of a list of (x,y) coordinate tuples.
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
Args:
|
|
1114
|
+
xys (list[tuple[float, float]]): A list of (x,y) coordinate tuples.
|
|
1115
|
+
z (float, optional): The z-height of the polygon. Defaults to the top layer.
|
|
1116
|
+
name (str, optional): The name of the polygon.
|
|
1117
|
+
"""
|
|
1118
|
+
ptags = []
|
|
1119
|
+
for x,y in xys:
|
|
1120
|
+
px, py, pz = self.cs.in_global_cs(x*self.unit, y*self.unit, z*self.unit)
|
|
1121
|
+
ptags.append(gmsh.model.occ.addPoint(px, py, pz))
|
|
1122
|
+
|
|
1123
|
+
ltags = []
|
|
1124
|
+
for t1, t2 in zip(ptags[:-1], ptags[1:]):
|
|
1125
|
+
ltags.append(gmsh.model.occ.addLine(t1, t2))
|
|
1126
|
+
ltags.append(gmsh.model.occ.addLine(ptags[-1], ptags[0]))
|
|
1127
|
+
|
|
1128
|
+
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1129
|
+
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1130
|
+
if self._thick_traces:
|
|
1131
|
+
if self.trace_thickness is None:
|
|
1132
|
+
raise ValueError('Trace thickness not defined, cannot generate polygons. Make sure to define a trace thickness in the PCB() constructor.')
|
|
1133
|
+
dx, dy, dz = self.cs.zax.np*self.trace_thickness
|
|
1134
|
+
dimtags = gmsh.model.occ.extrude([(2,planetag),], dx, dy, dz)
|
|
1135
|
+
voltags = [dt[1] for dt in dimtags if dt[0]==3]
|
|
1136
|
+
poly = GeoVolume(voltags, name=name).prio_set(self.conductor_priority)
|
|
1137
|
+
else:
|
|
1138
|
+
poly = GeoPolygon([planetag,], name=name)
|
|
1139
|
+
poly._store('thickness', self.trace_thickness)
|
|
1140
|
+
return poly
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
############################################################
|
|
1144
|
+
# USER FUNCTIONS #
|
|
1145
|
+
############################################################
|
|
1146
|
+
|
|
1147
|
+
def z(self, layer: int) -> float:
|
|
1148
|
+
"""Returns the z-height of the given layer number counter from 1 (bottom) to N (top)
|
|
1149
|
+
|
|
1150
|
+
Args:
|
|
1151
|
+
layer (int): The layer number (1 to N)
|
|
1152
|
+
|
|
1153
|
+
Returns:
|
|
1154
|
+
float: the z-height
|
|
1155
|
+
"""
|
|
1156
|
+
if layer <= 0:
|
|
1157
|
+
return self._zs[layer]
|
|
1158
|
+
return self._zs[layer-1]
|
|
1080
1159
|
|
|
1081
1160
|
def add_vias(self, *coordinates: tuple[float, float], radius: float,
|
|
1082
1161
|
z1: float | None = None,
|
|
1083
1162
|
z2: float | None = None,
|
|
1084
1163
|
segments: int = 6) -> None:
|
|
1085
|
-
"""Add a series of vias provided by a list of coordinates.
|
|
1164
|
+
"""Add a series of vias provided by a list of coordinates.
|
|
1165
|
+
|
|
1166
|
+
Vias will not be created yet. To generate the actual geometries use the function .generate_vias().
|
|
1086
1167
|
|
|
1087
1168
|
Make sure to define the radius explicitly, otherwise the radius gets interpreted as a coordinate:
|
|
1088
1169
|
|
|
@@ -1118,10 +1199,7 @@ class PCB:
|
|
|
1118
1199
|
return poly
|
|
1119
1200
|
raise ValueError(f'There is no stripline or coordinate under the name of {name}')
|
|
1120
1201
|
|
|
1121
|
-
|
|
1122
|
-
if path_nr >= len(self.paths):
|
|
1123
|
-
self.paths.append(StripPath(self))
|
|
1124
|
-
return self.paths[path_nr]
|
|
1202
|
+
|
|
1125
1203
|
|
|
1126
1204
|
def determine_bounds(self,
|
|
1127
1205
|
leftmargin: float = 0,
|
|
@@ -1173,7 +1251,7 @@ class PCB:
|
|
|
1173
1251
|
height: float | None = None,
|
|
1174
1252
|
origin: tuple[float, float] | None = None,
|
|
1175
1253
|
alignment: Alignment = Alignment.CORNER,
|
|
1176
|
-
name: str | None = None) -> GeoSurface:
|
|
1254
|
+
name: str | None = None) -> GeoSurface | GeoVolume:
|
|
1177
1255
|
"""Generates a generic rectangular plate in the XY grid.
|
|
1178
1256
|
If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
|
|
1179
1257
|
|
|
@@ -1201,25 +1279,55 @@ class PCB:
|
|
|
1201
1279
|
origin[1] - height*self.unit/2,
|
|
1202
1280
|
origin[2])
|
|
1203
1281
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1282
|
+
if self._thick_traces:
|
|
1283
|
+
plane = Box(width*self.unit, height*self.unit, self.trace_thickness, position=origin, name=name).set_material(self.trace_material)
|
|
1284
|
+
else:
|
|
1285
|
+
plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0), name=name) # type: ignore
|
|
1286
|
+
plane._store('thickness', self.thickness)
|
|
1287
|
+
plane = change_coordinate_system(plane, self.cs) # type: ignore
|
|
1288
|
+
plane.set_material(self.trace_material)
|
|
1208
1289
|
return plane # type: ignore
|
|
1209
1290
|
|
|
1291
|
+
def radial_stub(self, pos: tuple[float, float], length: float, angle: float, direction: tuple[float, float], Nsections: int = 8, w0: float = 0, z: float = 0, material: Material = None, name: str = None) -> None:
|
|
1292
|
+
x0, y0 = pos
|
|
1293
|
+
dx, dy = direction
|
|
1294
|
+
|
|
1295
|
+
rx, ry = dy, -dx
|
|
1296
|
+
|
|
1297
|
+
points = []
|
|
1298
|
+
if w0==0:
|
|
1299
|
+
points.append(pos)
|
|
1300
|
+
else:
|
|
1301
|
+
points.append((x0-rx*w0/2, y0-ry*w0/2))
|
|
1302
|
+
points.append((x0+rx*w0/2, y0+ry*w0/2))
|
|
1303
|
+
|
|
1304
|
+
angs = np.linspace(-angle/2, angle/2, Nsections)*np.pi/180
|
|
1305
|
+
c0 = x0 + 1j*y0
|
|
1306
|
+
vec = length*dx + 1j*length*dy
|
|
1307
|
+
for a in angs:
|
|
1308
|
+
p = c0 + vec*np.exp(1j*a)
|
|
1309
|
+
points.append((p.real, p.imag))
|
|
1310
|
+
|
|
1311
|
+
xs, ys = zip(*points)
|
|
1312
|
+
self.add_poly(xs, ys, z, material, name)
|
|
1313
|
+
|
|
1210
1314
|
def generate_pcb(self,
|
|
1211
1315
|
split_z: bool = True,
|
|
1212
|
-
merge: bool = True) -> GeoVolume:
|
|
1316
|
+
merge: bool = True) -> list[GeoVolume] | GeoVolume:
|
|
1213
1317
|
"""Generate the PCB Block object
|
|
1214
1318
|
|
|
1319
|
+
Args:
|
|
1320
|
+
split_z (bool, optional): If a PCB consisting of a thickness, material and n_layers should be split in sub domains. Defaults to True
|
|
1321
|
+
merge: (bool, optional): If an output list of multiple volumes should be merged into a single object.
|
|
1322
|
+
|
|
1215
1323
|
Returns:
|
|
1216
|
-
GeoVolume: The PCB Block
|
|
1324
|
+
GeoVolume | List[GeoVolume]: The PCB Block or blocks
|
|
1217
1325
|
"""
|
|
1218
1326
|
x0, y0, z0 = self.origin*self.unit
|
|
1219
1327
|
|
|
1220
|
-
|
|
1328
|
+
n_materials = len(set([layer.mat.name for layer in self._stack]))
|
|
1221
1329
|
|
|
1222
|
-
if split_z and self._zs.shape[0]>2 or
|
|
1330
|
+
if split_z and self._zs.shape[0]>2 or n_materials > 1:
|
|
1223
1331
|
|
|
1224
1332
|
boxes: list[GeoVolume] = []
|
|
1225
1333
|
for i, (z1, z2, layer) in enumerate(zip(self._zs[:-1],self._zs[1:],self._stack)):
|
|
@@ -1228,12 +1336,12 @@ class PCB:
|
|
|
1228
1336
|
self.length*self.unit,
|
|
1229
1337
|
h*self.unit,
|
|
1230
1338
|
position=(x0, y0, z0+z1*self.unit),
|
|
1231
|
-
name=
|
|
1339
|
+
name=layer.name)
|
|
1232
1340
|
box.material = layer.mat
|
|
1233
1341
|
box = change_coordinate_system(box, self.cs)
|
|
1234
1342
|
box.prio_set(self.dielectric_priority)
|
|
1235
1343
|
boxes.append(box)
|
|
1236
|
-
if merge and
|
|
1344
|
+
if merge and n_materials == 1:
|
|
1237
1345
|
return GeoVolume.merged(boxes).prio_set(self.dielectric_priority) # type: ignore
|
|
1238
1346
|
return boxes # type: ignore
|
|
1239
1347
|
|
|
@@ -1247,7 +1355,7 @@ class PCB:
|
|
|
1247
1355
|
box = change_coordinate_system(box, self.cs)
|
|
1248
1356
|
return box # type: ignore
|
|
1249
1357
|
|
|
1250
|
-
def generate_air(self, height: float, name: str = 'PCBAirbox') -> GeoVolume:
|
|
1358
|
+
def generate_air(self, height: float, name: str = 'PCBAirbox', bottom: bool = False) -> GeoVolume:
|
|
1251
1359
|
"""Generate the Air Block object
|
|
1252
1360
|
|
|
1253
1361
|
This requires that the width, depth and origin are deterimed. This
|
|
@@ -1256,11 +1364,16 @@ class PCB:
|
|
|
1256
1364
|
Returns:
|
|
1257
1365
|
GeoVolume: The PCB Block
|
|
1258
1366
|
"""
|
|
1367
|
+
dz = 0
|
|
1368
|
+
|
|
1259
1369
|
x0, y0, z0 = self.origin*self.unit
|
|
1370
|
+
if bottom:
|
|
1371
|
+
dz = z0-self.thickness*self.unit-height*self.unit
|
|
1372
|
+
|
|
1260
1373
|
box = Box(self.width*self.unit,
|
|
1261
1374
|
self.length*self.unit,
|
|
1262
1375
|
height*self.unit,
|
|
1263
|
-
position=(x0,y0,z0),
|
|
1376
|
+
position=(x0,y0,z0+dz),
|
|
1264
1377
|
name=name)
|
|
1265
1378
|
box = change_coordinate_system(box, self.cs)
|
|
1266
1379
|
return box # type: ignore
|
|
@@ -1334,16 +1447,9 @@ class PCB:
|
|
|
1334
1447
|
|
|
1335
1448
|
return poly
|
|
1336
1449
|
|
|
1337
|
-
def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float, name: str | None = 'LumpedElement') -> None:
|
|
1338
|
-
geopoly = poly._finalize(self.cs, name=name)
|
|
1339
|
-
geopoly._aux_data['func'] = function
|
|
1340
|
-
geopoly._aux_data['width'] = width
|
|
1341
|
-
geopoly._aux_data['height'] = length
|
|
1342
|
-
self.lumped_elements.append(geopoly)
|
|
1343
|
-
|
|
1344
1450
|
def modal_port(self,
|
|
1345
1451
|
point: StripLine,
|
|
1346
|
-
height: float,
|
|
1452
|
+
height: float | tuple[float, float],
|
|
1347
1453
|
width_multiplier: float = 5.0,
|
|
1348
1454
|
width: float | None = None,
|
|
1349
1455
|
name: str | None = 'ModalPort'
|
|
@@ -1362,8 +1468,12 @@ class PCB:
|
|
|
1362
1468
|
Returns:
|
|
1363
1469
|
GeoSurface: The GeoSurface object that can be used for the waveguide.
|
|
1364
1470
|
"""
|
|
1365
|
-
|
|
1366
|
-
|
|
1471
|
+
if isinstance(height, tuple):
|
|
1472
|
+
dz, height = height
|
|
1473
|
+
else:
|
|
1474
|
+
dz = 0
|
|
1475
|
+
|
|
1476
|
+
height = (self.thickness + height + dz)
|
|
1367
1477
|
|
|
1368
1478
|
if width is not None:
|
|
1369
1479
|
W = width
|
|
@@ -1373,7 +1483,7 @@ class PCB:
|
|
|
1373
1483
|
ds = point.dirright
|
|
1374
1484
|
x0 = point.x - ds[0]*W/2
|
|
1375
1485
|
y0 = point.y - ds[1]*W/2
|
|
1376
|
-
z0 = - self.thickness
|
|
1486
|
+
z0 = - self.thickness - dz
|
|
1377
1487
|
ax1 = np.array([ds[0], ds[1], 0])*self.unit*W
|
|
1378
1488
|
ax2 = np.array([0,0,1])*height*self.unit
|
|
1379
1489
|
|
|
@@ -1428,36 +1538,17 @@ class PCB:
|
|
|
1428
1538
|
"""
|
|
1429
1539
|
if material is None:
|
|
1430
1540
|
material = self.trace_material
|
|
1431
|
-
poly = PCBPoly(xs, ys, z, material,name=name)
|
|
1541
|
+
poly = PCBPoly(xs, ys, z, material, name=name)
|
|
1432
1542
|
|
|
1433
1543
|
self.polies.append(poly)
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon:
|
|
1437
|
-
""" Generates a GeoPoly out of a list of (x,y) coordinate tuples"""
|
|
1438
|
-
ptags = []
|
|
1439
|
-
for x,y in xys:
|
|
1440
|
-
px, py, pz = self.cs.in_global_cs(x*self.unit, y*self.unit, z*self.unit)
|
|
1441
|
-
ptags.append(gmsh.model.occ.addPoint(px, py, pz))
|
|
1442
|
-
|
|
1443
|
-
ltags = []
|
|
1444
|
-
for t1, t2 in zip(ptags[:-1], ptags[1:]):
|
|
1445
|
-
ltags.append(gmsh.model.occ.addLine(t1, t2))
|
|
1446
|
-
ltags.append(gmsh.model.occ.addLine(ptags[-1], ptags[0]))
|
|
1447
|
-
|
|
1448
|
-
tag_wire = gmsh.model.occ.addWire(ltags)
|
|
1449
|
-
planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
|
|
1450
|
-
poly = GeoPolygon([planetag,], name=name)
|
|
1451
|
-
poly._store('thickness', self.trace_thickness)
|
|
1452
|
-
return poly
|
|
1453
1544
|
|
|
1454
1545
|
@overload
|
|
1455
|
-
def compile_paths(self, merge: Literal[True]) -> GeoSurface: ...
|
|
1546
|
+
def compile_paths(self, merge: Literal[True]) -> GeoSurface | GeoVolume: ...
|
|
1456
1547
|
|
|
1457
1548
|
@overload
|
|
1458
|
-
def compile_paths(self, merge: Literal[False] = ...) -> list[GeoSurface]: ...
|
|
1549
|
+
def compile_paths(self, merge: Literal[False] = ...) -> list[GeoSurface] | list[GeoVolume]: ...
|
|
1459
1550
|
|
|
1460
|
-
def compile_paths(self, merge: bool = False) -> list[GeoPolygon] | GeoSurface:
|
|
1551
|
+
def compile_paths(self, merge: bool = False) -> list[GeoPolygon] | GeoSurface | GeoVolume:
|
|
1461
1552
|
"""Compiles the striplines and returns a list of polygons or asingle one.
|
|
1462
1553
|
|
|
1463
1554
|
The Z=0 argument determines the height of the striplines. Z=0 corresponds to the top of
|
emerge/_emerge/geo/shapes.py
CHANGED
|
@@ -160,11 +160,14 @@ class Sphere(GeoVolume):
|
|
|
160
160
|
x,y,z = position
|
|
161
161
|
self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
|
|
162
162
|
|
|
163
|
+
gmsh.model.occ.synchronize()
|
|
164
|
+
self._add_face_pointer('outside', tag=self.boundary().tags[0])
|
|
165
|
+
|
|
163
166
|
@property
|
|
164
167
|
def outside(self) -> FaceSelection:
|
|
165
168
|
"""The outside boundary of the sphere.
|
|
166
169
|
"""
|
|
167
|
-
return self.
|
|
170
|
+
return self.face('outside')
|
|
168
171
|
|
|
169
172
|
class XYPlate(GeoSurface):
|
|
170
173
|
"""Generates and XY-plane oriented plate
|
|
@@ -311,6 +314,7 @@ class Cylinder(GeoVolume):
|
|
|
311
314
|
height*ax[0], height*ax[1], height*ax[2],
|
|
312
315
|
radius)
|
|
313
316
|
super().__init__(cyl, name=name)
|
|
317
|
+
|
|
314
318
|
self._add_face_pointer('front', cs.origin, -cs.zax.np)
|
|
315
319
|
self._add_face_pointer('back', cs.origin+height*cs.zax.np, cs.zax.np)
|
|
316
320
|
self._add_face_pointer('bottom', cs.origin, -cs.zax.np)
|
|
@@ -471,15 +475,16 @@ class HalfSphere(GeoVolume):
|
|
|
471
475
|
self._add_face_pointer('bottom',np.array(position), np.array(direction))
|
|
472
476
|
self._add_face_pointer('face',np.array(position), np.array(direction))
|
|
473
477
|
self._add_face_pointer('disc',np.array(position), np.array(direction))
|
|
474
|
-
|
|
478
|
+
|
|
479
|
+
gmsh.model.occ.synchronize()
|
|
480
|
+
self._add_face_pointer('outside', tag=self.boundary(exclude='disc').tags[0])
|
|
481
|
+
|
|
475
482
|
@property
|
|
476
483
|
def outside(self) -> FaceSelection:
|
|
477
|
-
"""The outside of the sphere
|
|
478
|
-
|
|
479
|
-
Returns:
|
|
480
|
-
FaceSelection: _description_
|
|
484
|
+
"""The outside boundary of the half sphere.
|
|
481
485
|
"""
|
|
482
|
-
return self.
|
|
486
|
+
return self.face('outside')
|
|
487
|
+
|
|
483
488
|
|
|
484
489
|
@property
|
|
485
490
|
def disc(self) -> FaceSelection:
|