emerge 0.6.5__tar.gz → 0.6.7__tar.gz

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 (110) hide show
  1. {emerge-0.6.5 → emerge-0.6.7}/.bumpversion.toml +1 -1
  2. {emerge-0.6.5 → emerge-0.6.7}/PKG-INFO +1 -1
  3. {emerge-0.6.5 → emerge-0.6.7}/emerge/__init__.py +2 -1
  4. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/__init__.py +1 -1
  5. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/operations.py +97 -23
  6. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/pcb.py +54 -14
  7. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/shapes.py +101 -30
  8. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geometry.py +45 -14
  9. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/howto.py +1 -1
  10. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/microwave_bc.py +2 -4
  11. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/microwave_data.py +13 -0
  12. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/pyvista/display.py +113 -15
  13. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/selection.py +1 -0
  14. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/simmodel.py +10 -8
  15. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/solver.py +4 -4
  16. {emerge-0.6.5 → emerge-0.6.7}/examples/demo10_sgh.py +2 -2
  17. {emerge-0.6.5 → emerge-0.6.7}/examples/demo11_lumped_element_filter.py +2 -2
  18. {emerge-0.6.5 → emerge-0.6.7}/examples/demo12_mode_alignment.py +1 -1
  19. {emerge-0.6.5 → emerge-0.6.7}/examples/demo13_helix_antenna.py +3 -3
  20. {emerge-0.6.5 → emerge-0.6.7}/examples/demo14_boundary_selection.py +2 -2
  21. {emerge-0.6.5 → emerge-0.6.7}/examples/demo1_stepped_imp_filter.py +2 -2
  22. {emerge-0.6.5 → emerge-0.6.7}/examples/demo2_combline_filter.py +1 -1
  23. {emerge-0.6.5 → emerge-0.6.7}/examples/demo3_coupled_line_filter.py +1 -1
  24. {emerge-0.6.5 → emerge-0.6.7}/examples/demo4_patch_antenna.py +3 -3
  25. {emerge-0.6.5 → emerge-0.6.7}/examples/demo5_revolve.py +1 -1
  26. {emerge-0.6.5 → emerge-0.6.7}/examples/demo6_striplines_with_vias.py +4 -4
  27. {emerge-0.6.5 → emerge-0.6.7}/examples/demo7_periodic_cells.py +1 -1
  28. {emerge-0.6.5 → emerge-0.6.7}/examples/demo8_waveguide_bpf_synthesis.py +1 -1
  29. {emerge-0.6.5 → emerge-0.6.7}/examples/demo9_dielectric_resonator.py +1 -1
  30. {emerge-0.6.5 → emerge-0.6.7}/pyproject.toml +1 -1
  31. {emerge-0.6.5 → emerge-0.6.7}/uv.lock +1 -1
  32. {emerge-0.6.5 → emerge-0.6.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  33. {emerge-0.6.5 → emerge-0.6.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  34. {emerge-0.6.5 → emerge-0.6.7}/.gitignore +0 -0
  35. {emerge-0.6.5 → emerge-0.6.7}/.opt +0 -0
  36. {emerge-0.6.5 → emerge-0.6.7}/.python-version +0 -0
  37. {emerge-0.6.5 → emerge-0.6.7}/LICENSE +0 -0
  38. {emerge-0.6.5 → emerge-0.6.7}/README.md +0 -0
  39. {emerge-0.6.5 → emerge-0.6.7}/THIRD_PARTY_LICENSES.md +0 -0
  40. {emerge-0.6.5 → emerge-0.6.7}/UMFPACK_Install_windows.md +0 -0
  41. {emerge-0.6.5 → emerge-0.6.7}/UMFPACK_installer_windows.py +0 -0
  42. {emerge-0.6.5 → emerge-0.6.7}/emerge/__main__.py +0 -0
  43. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/__init__.py +0 -0
  44. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/_cache_check.py +0 -0
  45. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/bc.py +0 -0
  46. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/const.py +0 -0
  47. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/coord.py +0 -0
  48. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/cs.py +0 -0
  49. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/dataset.py +0 -0
  50. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/elements/__init__.py +0 -0
  51. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/elements/femdata.py +0 -0
  52. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/elements/index_interp.py +0 -0
  53. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/elements/ned2_interp.py +0 -0
  54. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/elements/nedelec2.py +0 -0
  55. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/elements/nedleg2.py +0 -0
  56. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/horn.py +0 -0
  57. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/modeler.py +0 -0
  58. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  59. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  60. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/pmlbox.py +0 -0
  61. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/polybased.py +0 -0
  62. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo/step.py +0 -0
  63. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/geo2d.py +0 -0
  64. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/logsettings.py +0 -0
  65. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/material.py +0 -0
  66. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/mesh3d.py +0 -0
  67. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/mesher.py +0 -0
  68. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/mth/common_functions.py +0 -0
  69. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/mth/integrals.py +0 -0
  70. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/mth/optimized.py +0 -0
  71. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/mth/pairing.py +0 -0
  72. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/periodic.py +0 -0
  73. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/__init__.py +0 -0
  74. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  75. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  76. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/assembly/assembler.py +0 -0
  77. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
  78. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  79. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  80. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  81. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  82. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/microwave_3d.py +0 -0
  83. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  84. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  85. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/sc.py +0 -0
  86. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  87. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  88. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  89. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/__init__.py +0 -0
  90. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/display.py +0 -0
  91. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  92. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  93. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  94. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot/simple_plots.py +0 -0
  95. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/plot.py +0 -0
  96. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/projects/__init__.py +0 -0
  97. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/projects/_gen_base.txt +0 -0
  98. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/projects/_load_base.txt +0 -0
  99. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/projects/generate_project.py +0 -0
  100. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/simulation_data.py +0 -0
  101. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
  102. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  103. {emerge-0.6.5 → emerge-0.6.7}/emerge/_emerge/system.py +0 -0
  104. {emerge-0.6.5 → emerge-0.6.7}/emerge/cli.py +0 -0
  105. {emerge-0.6.5 → emerge-0.6.7}/emerge/ext.py +0 -0
  106. {emerge-0.6.5 → emerge-0.6.7}/emerge/lib.py +0 -0
  107. {emerge-0.6.5 → emerge-0.6.7}/emerge/plot.py +0 -0
  108. {emerge-0.6.5 → emerge-0.6.7}/emerge/pyvista.py +0 -0
  109. {emerge-0.6.5 → emerge-0.6.7}/src/__init__.py +0 -0
  110. {emerge-0.6.5 → emerge-0.6.7}/src/_img/logo.jpeg +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.6.5"
2
+ current_version = "0.6.7"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 0.6.5
3
+ Version: 0.6.7
4
4
  Summary: An open source EM FEM simulator in Python
5
5
  Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
6
  Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "0.6.5"
21
+ __version__ = "0.6.7"
22
22
 
23
23
  ############################################################
24
24
  # HANDLE ENVIRONMENT VARIABLES #
@@ -52,6 +52,7 @@ from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, X
52
52
  from ._emerge.coord import Line
53
53
  from ._emerge import geo
54
54
  from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
55
+ from ._emerge.geometry import select
55
56
  from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
56
57
  from ._emerge.physics.microwave.sc import stratton_chu
57
58
  from ._emerge.periodic import RectCell, HexCell
@@ -19,6 +19,6 @@ from .pcb import PCB
19
19
  from .pmlbox import pmlbox
20
20
  from .horn import Horn
21
21
  from .shapes import Cylinder, CoaxCylinder, Box, XYPlate, HalfSphere, Sphere, Plate, OldBox, Alignment, Cone
22
- from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect
22
+ from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect, unite, expand_surface
23
23
  from .polybased import XYPolygon, GeoPrism, Disc, Curve
24
24
  from .step import STEPItems
@@ -15,8 +15,8 @@
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
- from typing import TypeVar
19
- from ..geometry import GeoSurface, GeoVolume, GeoObject, GeoPoint, GeoEdge
18
+ from typing import TypeVar, overload
19
+ from ..geometry import GeoSurface, GeoVolume, GeoObject, GeoPoint, GeoEdge, GeoPolygon
20
20
  from ..cs import CoordinateSystem, GCS
21
21
  import gmsh
22
22
  import numpy as np
@@ -60,7 +60,7 @@ def add(main: T, tool: T,
60
60
  main._exists = False
61
61
  if remove_tool:
62
62
  tool._exists = False
63
- return output # type: ignore
63
+ return output.set_material(main.material) # type: ignore
64
64
 
65
65
  def remove(main: T, tool: T,
66
66
  remove_object: bool = True,
@@ -90,7 +90,7 @@ def remove(main: T, tool: T,
90
90
  main._exists = False
91
91
  if remove_tool:
92
92
  tool._exists = False
93
- return output # type: ignore
93
+ return output.set_material(main.material) # type: ignore
94
94
 
95
95
  subtract = remove
96
96
 
@@ -122,7 +122,7 @@ def intersect(main: T, tool: T,
122
122
  main._exists = False
123
123
  if remove_tool:
124
124
  tool._exists = False
125
- return output #type:ignore
125
+ return output.set_material(main.material) #type:ignore
126
126
 
127
127
  def embed(main: GeoVolume, other: GeoSurface) -> None:
128
128
  ''' Embeds a surface into a volume in the GMSH model.
@@ -144,7 +144,8 @@ def rotate(main: GeoVolume,
144
144
  c0: tuple[float, float, float],
145
145
  ax: tuple[float, float, float],
146
146
  angle: float,
147
- degree=True) -> GeoVolume:
147
+ make_copy: bool = False,
148
+ degree=True) -> GeoObject:
148
149
  """Rotates a GeoVolume object around an axist defined at a coordinate.
149
150
 
150
151
  Args:
@@ -159,17 +160,23 @@ def rotate(main: GeoVolume,
159
160
  """
160
161
  if degree:
161
162
  angle = angle * np.pi/180
162
- gmsh.model.occ.rotate(main.dimtags, *c0, *ax, -angle)
163
-
163
+
164
+ if make_copy:
165
+ rotate_obj = main.make_copy()
166
+ else:
167
+ rotate_obj = main
168
+
169
+ gmsh.model.occ.rotate(rotate_obj.dimtags, *c0, *ax, -angle)
164
170
  # Rotate the facepointers
165
- for fp in main._all_pointers:
171
+ for fp in rotate_obj._all_pointers:
166
172
  fp.rotate(c0, ax, angle)
167
- return main
173
+ return rotate_obj
168
174
 
169
175
  def translate(main: GeoVolume,
170
176
  dx: float = 0,
171
177
  dy: float = 0,
172
- dz: float = 0) -> GeoVolume:
178
+ dz: float = 0,
179
+ make_copy: bool = False) -> GeoObject:
173
180
  """Translates the GeoVolume object along a given displacement
174
181
 
175
182
  Args:
@@ -177,17 +184,23 @@ def translate(main: GeoVolume,
177
184
  dx (float, optional): The X-displacement in meters. Defaults to 0.
178
185
  dy (float, optional): The Y-displacement in meters. Defaults to 0.
179
186
  dz (float, optional): The Z-displacement in meters. Defaults to 0.
187
+ make_copy (bool, optional): Whether to make a copy first before translating.
180
188
 
181
189
  Returns:
182
- GeoVolume: The translated object
190
+ GeoObject: The translated object
183
191
  """
184
- gmsh.model.occ.translate(main.dimtags, dx, dy, dz)
192
+
193
+ if make_copy:
194
+ trans_obj = main.make_copy()
195
+ else:
196
+ trans_obj = main
197
+ gmsh.model.occ.translate(trans_obj.dimtags, dx, dy, dz)
185
198
 
186
199
  # Rotate the facepointers
187
- for fp in main._all_pointers:
200
+ for fp in trans_obj._all_pointers:
188
201
  fp.translate(dx, dy, dz)
189
202
 
190
- return main
203
+ return trans_obj
191
204
 
192
205
  def mirror(main: GeoObject,
193
206
  origin: tuple[float, float, float] = (0.0, 0.0, 0.0),
@@ -199,6 +212,7 @@ def mirror(main: GeoObject,
199
212
  main (GeoVolume): The object to mirror
200
213
  origin (tuple[float, float, float], optional): The point of origin in meters. Defaults to (0.0, 0.0, 0.0).
201
214
  direction (tuple[float, float, float], optional): The normal axis defining the plane of reflection. Defaults to (1.0, 0.0, 0.0).
215
+ make_copy (bool, optional): Whether to make a copy first before mirroring.
202
216
 
203
217
  Returns:
204
218
  GeoVolume: The mirrored GeoVolume object
@@ -211,14 +225,12 @@ def mirror(main: GeoObject,
211
225
  d = -(a*x0 + b*y0 + c*z0)
212
226
  if (a==0) and (b==0) and (c==0):
213
227
  return main
228
+
214
229
  mirror_obj = main
215
230
  if make_copy:
216
- new_obj = main.make_copy()
217
- gmsh.model.occ.mirror(new_obj.dimtags, a,b,c,d)
218
- mirror_obj = new_obj
219
- else:
220
- gmsh.model.occ.mirror(main.dimtags, a,b,c,d)
221
-
231
+ mirror_obj = main.make_copy()
232
+ gmsh.model.occ.mirror(mirror_obj.dimtags, a,b,c,d)
233
+
222
234
  for fp in mirror_obj._all_pointers:
223
235
  fp.mirror(origin, direction)
224
236
  return mirror_obj
@@ -237,7 +249,7 @@ def change_coordinate_system(main: GeoObject,
237
249
  old_cs (CoordinateSystem, optional): The old coordinate system. Defaults to GCS.
238
250
 
239
251
  Returns:
240
- _type_: _description_
252
+ GeoObject: The output object
241
253
  """
242
254
  if new_cs._is_global and old_cs._is_global:
243
255
  return main
@@ -254,4 +266,66 @@ def change_coordinate_system(main: GeoObject,
254
266
  for fp in main._all_pointers:
255
267
  fp.affine_transform(M1)
256
268
  fp.affine_transform(M2)
257
- return main
269
+ return main
270
+
271
+ @overload
272
+ def unite(*objects: GeoVolume) -> GeoVolume: ...
273
+
274
+ @overload
275
+ def unite(*objects: GeoSurface) -> GeoSurface: ...
276
+
277
+ @overload
278
+ def unite(*objects: GeoEdge) -> GeoEdge: ...
279
+
280
+ @overload
281
+ def unite(*objects: GeoPolygon) -> GeoSurface: ...
282
+
283
+ def unite(*objects: GeoObject) -> GeoObject:
284
+ """Applies a fusion consisting of all geometries in the argument.
285
+
286
+ Returns:
287
+ GeoObject: The resultant object
288
+ """
289
+ main, *rest = objects
290
+ if not rest:
291
+ return main
292
+ main._exists = False
293
+ dts = []
294
+ for other in rest:
295
+ dts.extend(other.dimtags)
296
+ other._exists = False
297
+
298
+ new_dimtags, mapping = gmsh.model.occ.fuse(main.dimtags, dts)
299
+
300
+ new_obj = GeoObject.from_dimtags(new_dimtags)._take_tools(*objects)
301
+ new_obj.set_material(main.material)
302
+ new_obj.prio_set(main._priority)
303
+
304
+ return new_obj
305
+
306
+ def expand_surface(surface: GeoSurface, distance: float) -> GeoSurface:
307
+ """EXPERIMENTAL: Expands an input surface. The surface must exist on a 2D plane.
308
+
309
+ The output surface does not inherit material properties.
310
+
311
+ If any problems occur, reach out through email.
312
+
313
+ Args:
314
+ surface (GeoSurface): The input surface to expand
315
+ distance (float): The exapansion distance
316
+
317
+ Returns:
318
+ GeoSurface: The output surface
319
+ """
320
+ surfs = []
321
+ for tag in surface.tags:
322
+ looptags, _ = gmsh.model.occ.get_curve_loops(tag)
323
+ new_curves = []
324
+ for looptag in looptags:
325
+ curve_tags = gmsh.model.occ.offset_curve(looptag, distance)
326
+ loop_tag = gmsh.model.occ.addCurveLoop([t for d,t in curve_tags])
327
+ new_curves.append(loop_tag)
328
+ surftag = gmsh.model.occ.addPlaneSurface(new_curves)
329
+ surfs.append(surftag)
330
+ surf = GeoSurface(surfs)
331
+ return surf
@@ -22,7 +22,7 @@ from ..geometry import GeoPolygon, GeoVolume, GeoSurface
22
22
  from ..material import Material, AIR, COPPER
23
23
  from .shapes import Box, Plate, Cylinder
24
24
  from .polybased import XYPolygon
25
- from .operations import change_coordinate_system
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
 
@@ -310,7 +310,6 @@ class StripCurve(StripTurn):
310
310
  points: list[tuple[float, float]] = []
311
311
  Npts = int(np.ceil(abs(self.angle/self.dang)))
312
312
  R = self.radius-np.sign(self.angle)*self.width/2
313
- print(R, self.circ_origin, self._xhat, self._yhat)
314
313
  for i in range(Npts):
315
314
  ang = abs((i+1)/Npts * self.angle * np.pi/180)
316
315
  pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
@@ -324,7 +323,6 @@ class StripCurve(StripTurn):
324
323
 
325
324
  Npts = int(np.ceil(abs(self.angle/self.dang)))
326
325
  R = self.radius+np.sign(self.angle)*self.width/2
327
- print(R, self.circ_origin, self._xhat, self._yhat)
328
326
  for i in range(Npts):
329
327
  ang = abs((i+1)/Npts * self.angle * np.pi/180)
330
328
  pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
@@ -965,8 +963,18 @@ class PCB:
965
963
  Returns:
966
964
  float: the z-height
967
965
  """
966
+ if layer <= 0:
967
+ return self._zs[layer]
968
968
  return self._zs[layer-1]
969
969
 
970
+ @property
971
+ def top(self) -> float:
972
+ return self._zs[-1]
973
+
974
+ @property
975
+ def bottom(self) -> float:
976
+ return self._zs[0]
977
+
970
978
  def _get_z(self, element: RouteElement) -> float :
971
979
  """Return the z-height of a given Route Element
972
980
 
@@ -981,6 +989,31 @@ class PCB:
981
989
  return path.z
982
990
  raise RouteException('Requesting z-height of route element that is not contained in a path.')
983
991
 
992
+ def add_vias(self, *coordinates: tuple[float, float], radius: float,
993
+ z1: float | None = None,
994
+ z2: float | None = None,
995
+ segments: int = 6) -> None:
996
+ """Add a series of vias provided by a list of coordinates.
997
+
998
+ Make sure to define the radius explicitly, otherwise the radius gets interpreted as a coordinate:
999
+
1000
+ >>> pcb.add_vias((x1,y1), (x1,y2), radius=1)
1001
+
1002
+ Args:
1003
+ *coordinates (tuple(float, float)): A series of coordinates
1004
+ radius (float): The radius
1005
+ z1 (float | None, optional): The bottom z-coordinate. Defaults to None.
1006
+ z2 (float | None, optional): The top z-coordinate. Defaults to None.
1007
+ segments (int, optional): The number of segmets for the via. Defaults to 6.
1008
+ """
1009
+ if z1 is None:
1010
+ z1 = self.z(0)
1011
+ if z2 is None:
1012
+ z2 = self.z(-1)
1013
+
1014
+ for x,y in coordinates:
1015
+ self.vias.append(Via(x,y,z1,z2,radius,segments))
1016
+
984
1017
  def load(self, name: str) -> StripLine:
985
1018
  """Acquire the x,y, coordinate associated with the label name.
986
1019
 
@@ -1062,7 +1095,7 @@ class PCB:
1062
1095
  GeoSurface: _description_
1063
1096
  """
1064
1097
  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:
1098
+ if self.width is None or self.length is None or self.origin is None:
1066
1099
  raise RouteException('Cannot define a plane with no possible definition of its size.')
1067
1100
  width = self.width
1068
1101
  height = self.length
@@ -1077,6 +1110,7 @@ class PCB:
1077
1110
 
1078
1111
  plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
1079
1112
  plane = change_coordinate_system(plane, self.cs) # type: ignore
1113
+ plane.set_material(COPPER)
1080
1114
  return plane # type: ignore
1081
1115
 
1082
1116
  def generate_pcb(self,
@@ -1123,7 +1157,7 @@ class PCB:
1123
1157
  """Generate the Air Block object
1124
1158
 
1125
1159
  This requires that the width, depth and origin are deterimed. This
1126
- can either be done manually or via the .determine_boudns() method.
1160
+ can either be done manually or via the .determine_bounds() method.
1127
1161
 
1128
1162
  Returns:
1129
1163
  GeoVolume: The PCB Block
@@ -1247,7 +1281,13 @@ class PCB:
1247
1281
  plate = change_coordinate_system(plate, self.cs)
1248
1282
  return plate # type: ignore
1249
1283
 
1250
- def generate_vias(self, merge=False) -> list[Cylinder] | Cylinder:
1284
+ @overload
1285
+ def generate_vias(self, merge=Literal[True]) -> GeoVolume: ...
1286
+
1287
+ @overload
1288
+ def generate_vias(self, merge=Literal[False]) -> list[Cylinder]: ...
1289
+
1290
+ def generate_vias(self, merge=False) -> list[Cylinder] | GeoVolume:
1251
1291
  """Generates the via objects.
1252
1292
 
1253
1293
  Args:
@@ -1321,7 +1361,7 @@ class PCB:
1321
1361
  Returns:
1322
1362
  list[Polygon] | GeoSurface: The output stripline polygons possibly merged if merge = True.
1323
1363
  """
1324
- polys = []
1364
+ polys: list[GeoSurface] = []
1325
1365
  allx = []
1326
1366
  ally = []
1327
1367
 
@@ -1353,19 +1393,19 @@ class PCB:
1353
1393
  poly = self._gen_poly(pcbpoly.xys, pcbpoly.z)
1354
1394
  poly.material = pcbpoly.material
1355
1395
  polys.append(poly)
1396
+ xs, ys = zip(*pcbpoly.xys)
1397
+ allx.extend(xs)
1398
+ ally.extend(ys)
1399
+
1356
1400
 
1357
1401
  self.xs = allx
1358
1402
  self.ys = ally
1359
1403
 
1360
1404
  self.traces = polys
1405
+
1361
1406
  if merge:
1362
- tags = []
1363
- for p in polys:
1364
- tags.extend(p.tags)
1365
- if p.material != COPPER:
1366
- logger.warning(f'Merging a polygon with material {p.material} into a single polygon that will be COPPER.')
1367
- polys = GeoSurface(tags)
1368
- polys.material = COPPER
1407
+ polys = unite(*polys)
1408
+
1369
1409
  return polys
1370
1410
 
1371
1411
  ############################################################
@@ -30,10 +30,20 @@ class Alignment(Enum):
30
30
  CENTER = 1
31
31
  CORNER = 2
32
32
 
33
+
33
34
  class Box(GeoVolume):
34
- """ A class that represents a box shaped volume
35
+ """Creates a box volume object.
36
+ Specify the alignment of the box with the provided position. The options are CORNER (default)
37
+ for the front-left-bottom node of the box or CENTER for the center of the box.
35
38
 
36
- """
39
+ Args:
40
+ width (float): The x-size
41
+ depth (float): The y-size
42
+ height (float): The z-size
43
+ position (tuple, optional): The position of the box. Defaults to (0,0,0).
44
+ alignment (Alignment, optional): Which point of the box is placed at the position.
45
+ Defaults to Alignment.CORNER.
46
+ """
37
47
 
38
48
  def __init__(self,
39
49
  width: float,
@@ -91,10 +101,15 @@ class Box(GeoVolume):
91
101
 
92
102
  tags = list(reduce(lambda a,b: a+b, tagslist))
93
103
  return FaceSelection(tags)
94
-
104
+
95
105
 
96
106
  class Sphere(GeoVolume):
107
+ """Generates a sphere objected centered ont he position with the given radius
97
108
 
109
+ Args:
110
+ radius (float): The sphere radius
111
+ position (tuple, optional): The center position. Defaults to (0,0,0).
112
+ """
98
113
  def __init__(self,
99
114
  radius: float,
100
115
  position: tuple = (0,0,0)):
@@ -108,7 +123,19 @@ class Sphere(GeoVolume):
108
123
  x,y,z = position
109
124
  self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
110
125
 
126
+
111
127
  class XYPlate(GeoSurface):
128
+ """Generates and XY-plane oriented plate
129
+
130
+ Specify the alignment of the plate with the provided position. The options are CORNER (default)
131
+ for the front-left node of the plate or CENTER for the center of the plate.
132
+
133
+ Args:
134
+ width (float): The x-size of the plate
135
+ depth (float): The y-size of the plate
136
+ position (tuple, optional): The position of the alignment node. Defaults to (0,0,0).
137
+ alignment (Alignment, optional): Which node to align to. Defaults to Alignment.CORNER.
138
+ """
112
139
  def __init__(self,
113
140
  width: float,
114
141
  depth: float,
@@ -134,7 +161,19 @@ class XYPlate(GeoSurface):
134
161
 
135
162
 
136
163
  class Plate(GeoSurface):
137
-
164
+ """A generalized 2D rectangular plate in XYZ-space.
165
+
166
+ The plate is specified by an origin (o) in meters coordinate plus two vectors (u,v) in meters
167
+ that span two of the sides such that all points of the plate are defined by:
168
+ p1 = o
169
+ p2 = o+u
170
+ p3 = o+v
171
+ p4 = o+u+v
172
+ Args:
173
+ origin (tuple[float, float, float]): The origin of the plate in meters
174
+ u (tuple[float, float, float]): The u-axis of the plate
175
+ v (tuple[float, float, float]): The v-axis of the plate
176
+ """
138
177
  def __init__(self,
139
178
  origin: tuple[float, float, float],
140
179
  u: tuple[float, float, float],
@@ -172,27 +211,43 @@ class Plate(GeoSurface):
172
211
  tags: list[int] = [gmsh.model.occ.addPlaneSurface([tag_wire,]),]
173
212
  super().__init__(tags)
174
213
 
214
+
175
215
  class Cylinder(GeoVolume):
216
+ """Generates a Cylinder object in 3D space.
217
+ The cylinder will always be placed in the origin of the provided CoordinateSystem.
218
+ The bottom cylinder plane is always placed in the XY-plane. The length of the cylinder is
219
+ oriented along the Z-axis.
176
220
 
221
+ By default the cylinder uses the Open Cascade modeling for a cylinder. In this representation
222
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
223
+ As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
224
+ by an extrusion of a regular N-sided polygon.
225
+
226
+ Args:
227
+ radius (float): The radius of the Cylinder
228
+ height (float): The height of the Cylinder
229
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
230
+ Nsections (int, optional): The number of sections. Defaults to None.
231
+ """
177
232
  def __init__(self,
178
233
  radius: float,
179
234
  height: float,
180
- cs: CoordinateSystem = None,
181
- Nsections: int = None):
235
+ cs: CoordinateSystem = GCS,
236
+ Nsections: int | None = None):
182
237
  """Generates a Cylinder object in 3D space.
183
- The cyllinder will always be placed in the origin of the provided CoordinateSystem.
184
- The bottom cyllinder plane is always placed in the XY-plane. The lenth of the cyllinder is
238
+ The cylinder will always be placed in the origin of the provided CoordinateSystem.
239
+ The bottom cylinder plane is always placed in the XY-plane. The length of the cylinder is
185
240
  oriented along the Z-axis.
186
241
 
187
- By default the cyllinder uses the Open Cascade modeling for a cyllinder. In this representation
188
- the surface of the cyllinder is approximated with a tolerance thay may be irregular.
242
+ By default the cylinder uses the Open Cascade modeling for a cylinder. In this representation
243
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
189
244
  As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
190
245
  by an extrusion of a regular N-sided polygon.
191
246
 
192
247
  Args:
193
248
  radius (float): The radius of the Cylinder
194
249
  height (float): The height of the Cylinder
195
- cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
250
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
196
251
  Nsections (int, optional): The number of sections. Defaults to None.
197
252
  """
198
253
  ax = cs.zax.np
@@ -233,33 +288,46 @@ class Cylinder(GeoVolume):
233
288
  xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
234
289
  return xo, yo, zo
235
290
 
291
+
236
292
  class CoaxCylinder(GeoVolume):
237
- """A coaxial cylinder with an inner and outer radius."""
238
-
293
+ """Generates a Coaxial cylinder object in 3D space.
294
+ The coaxial cylinder will always be placed in the origin of the provided CoordinateSystem.
295
+ The bottom coax plane is always placed in the XY-plane. The lenth of the coax is
296
+ oriented along the Z-axis.
297
+
298
+ By default the coax uses the Open Cascade modeling for a cylinder. In this representation
299
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
300
+ As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
301
+ by an extrusion of a regular N-sided polygon.
302
+
303
+ Args:
304
+ radius (float): The radius of the Cylinder
305
+ height (float): The height of the Cylinder
306
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
307
+ Nsections (int, optional): The number of sections. Defaults to None.
308
+ """
239
309
  def __init__(self,
240
310
  rout: float,
241
311
  rin: float,
242
312
  height: float,
243
- cs: CoordinateSystem = None,
244
- Nsections: int = None):
245
- """Generates a Coaxial cyllinder object in 3D space.
246
- The coaxial cyllinder will always be placed in the origin of the provided CoordinateSystem.
313
+ cs: CoordinateSystem = GCS,
314
+ Nsections: int | None = None):
315
+ """Generates a Coaxial cylinder object in 3D space.
316
+ The coaxial cylinder will always be placed in the origin of the provided CoordinateSystem.
247
317
  The bottom coax plane is always placed in the XY-plane. The lenth of the coax is
248
318
  oriented along the Z-axis.
249
319
 
250
- By default the coax uses the Open Cascade modeling for a cyllinder. In this representation
251
- the surface of the cyllinder is approximated with a tolerance thay may be irregular.
320
+ By default the coax uses the Open Cascade modeling for a cylinder. In this representation
321
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
252
322
  As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
253
323
  by an extrusion of a regular N-sided polygon.
254
324
 
255
325
  Args:
256
326
  radius (float): The radius of the Cylinder
257
327
  height (float): The height of the Cylinder
258
- cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
328
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
259
329
  Nsections (int, optional): The number of sections. Defaults to None.
260
330
  """
261
- if cs is None:
262
- cs = GCS
263
331
  if rout <= rin:
264
332
  raise ValueError("Outer radius must be greater than inner radius.")
265
333
 
@@ -297,10 +365,9 @@ class CoaxCylinder(GeoVolume):
297
365
 
298
366
  xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
299
367
  return xo, yo, zo
300
- return super().boundary()
301
368
 
302
369
  class HalfSphere(GeoVolume):
303
-
370
+ """A half sphere volume."""
304
371
  def __init__(self,
305
372
  radius: float,
306
373
  position: tuple = (0,0,0),
@@ -326,8 +393,6 @@ class HalfSphere(GeoVolume):
326
393
  self._add_face_pointer('back',np.array(position), np.array(direction))
327
394
  self._add_face_pointer('bottom',np.array(position), np.array(direction))
328
395
  self._add_face_pointer('face',np.array(position), np.array(direction))
329
-
330
-
331
396
 
332
397
 
333
398
  class OldBox(GeoVolume):
@@ -353,8 +418,6 @@ class OldBox(GeoVolume):
353
418
  alignment (Alignment, optional): Which point of the box is placed at the position.
354
419
  Defaults to Alignment.CORNER.
355
420
  """
356
-
357
-
358
421
  if alignment is Alignment.CORNER:
359
422
  position = (position[0]+width/2, position[1]+depth/2, position[2])
360
423
  elif alignment is Alignment.CENTER:
@@ -436,7 +499,6 @@ class OldBox(GeoVolume):
436
499
  self._add_face_pointer('right', pc + width/2*wax, wax)
437
500
  self._add_face_pointer('top', pc + height/2*hax, hax)
438
501
  self._add_face_pointer('bottom', pc - height/2*hax, -hax)
439
-
440
502
 
441
503
  def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
442
504
  """Select all outside faces except for the once specified by outside
@@ -449,8 +511,17 @@ class OldBox(GeoVolume):
449
511
  tags = list(reduce(lambda a,b: a+b, tagslist))
450
512
  return FaceSelection(tags)
451
513
 
514
+
452
515
  class Cone(GeoVolume):
453
-
516
+ """Constructis a cone that starts at position p0 and is aimed in the given direction.
517
+ r1 is the start radius and r2 the end radius. The magnitude of direction determines its length.
518
+
519
+ Args:
520
+ p0 (tuple[float, float, float]): _description_
521
+ direction (tuple[float, float, float]): _description_
522
+ r1 (float): _description_
523
+ r2 (float): _description_
524
+ """
454
525
  def __init__(self, p0: tuple[float, float, float],
455
526
  direction: tuple[float, float, float],
456
527
  r1: float,