emerge 0.4.7__py3-none-any.whl → 0.4.9__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 (78) hide show
  1. emerge/__init__.py +14 -14
  2. emerge/_emerge/__init__.py +42 -0
  3. emerge/_emerge/bc.py +197 -0
  4. emerge/_emerge/coord.py +119 -0
  5. emerge/_emerge/cs.py +523 -0
  6. emerge/_emerge/dataset.py +36 -0
  7. emerge/_emerge/elements/__init__.py +19 -0
  8. emerge/_emerge/elements/femdata.py +212 -0
  9. emerge/_emerge/elements/index_interp.py +64 -0
  10. emerge/_emerge/elements/legrange2.py +172 -0
  11. emerge/_emerge/elements/ned2_interp.py +645 -0
  12. emerge/_emerge/elements/nedelec2.py +140 -0
  13. emerge/_emerge/elements/nedleg2.py +217 -0
  14. emerge/_emerge/geo/__init__.py +24 -0
  15. emerge/_emerge/geo/horn.py +107 -0
  16. emerge/_emerge/geo/modeler.py +449 -0
  17. emerge/_emerge/geo/operations.py +254 -0
  18. emerge/_emerge/geo/pcb.py +1244 -0
  19. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  20. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  21. emerge/_emerge/geo/pmlbox.py +204 -0
  22. emerge/_emerge/geo/polybased.py +529 -0
  23. emerge/_emerge/geo/shapes.py +427 -0
  24. emerge/_emerge/geo/step.py +77 -0
  25. emerge/_emerge/geo2d.py +86 -0
  26. emerge/_emerge/geometry.py +510 -0
  27. emerge/_emerge/howto.py +214 -0
  28. emerge/_emerge/logsettings.py +5 -0
  29. emerge/_emerge/material.py +118 -0
  30. emerge/_emerge/mesh3d.py +730 -0
  31. emerge/_emerge/mesher.py +339 -0
  32. emerge/_emerge/mth/common_functions.py +33 -0
  33. emerge/_emerge/mth/integrals.py +71 -0
  34. emerge/_emerge/mth/optimized.py +357 -0
  35. emerge/_emerge/periodic.py +263 -0
  36. emerge/_emerge/physics/__init__.py +0 -0
  37. emerge/_emerge/physics/microwave/__init__.py +1 -0
  38. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  39. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  40. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  41. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  42. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  43. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  44. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  45. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  46. emerge/_emerge/physics/microwave/periodic.py +82 -0
  47. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  48. emerge/_emerge/physics/microwave/sc.py +175 -0
  49. emerge/_emerge/physics/microwave/simjob.py +147 -0
  50. emerge/_emerge/physics/microwave/sparam.py +138 -0
  51. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  52. emerge/_emerge/plot/__init__.py +0 -0
  53. emerge/_emerge/plot/display.py +394 -0
  54. emerge/_emerge/plot/grapher.py +93 -0
  55. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  56. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  57. emerge/_emerge/plot/pyvista/display.py +931 -0
  58. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  59. emerge/_emerge/plot/simple_plots.py +551 -0
  60. emerge/_emerge/plot.py +225 -0
  61. emerge/_emerge/projects/__init__.py +0 -0
  62. emerge/_emerge/projects/_gen_base.txt +32 -0
  63. emerge/_emerge/projects/_load_base.txt +24 -0
  64. emerge/_emerge/projects/generate_project.py +40 -0
  65. emerge/_emerge/selection.py +596 -0
  66. emerge/_emerge/simmodel.py +444 -0
  67. emerge/_emerge/simulation_data.py +411 -0
  68. emerge/_emerge/solver.py +993 -0
  69. emerge/_emerge/system.py +54 -0
  70. emerge/cli.py +19 -0
  71. emerge/lib.py +1 -1
  72. emerge/plot.py +1 -1
  73. {emerge-0.4.7.dist-info → emerge-0.4.9.dist-info}/METADATA +7 -6
  74. emerge-0.4.9.dist-info/RECORD +78 -0
  75. emerge-0.4.9.dist-info/entry_points.txt +2 -0
  76. emerge-0.4.7.dist-info/RECORD +0 -9
  77. emerge-0.4.7.dist-info/entry_points.txt +0 -2
  78. {emerge-0.4.7.dist-info → emerge-0.4.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,427 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import gmsh
19
+ from ..geometry import GeoObject, GeoSurface, GeoVolume
20
+ from ..cs import CoordinateSystem, GCS
21
+ import numpy as np
22
+ from enum import Enum
23
+ from .operations import subtract
24
+ from ..selection import FaceSelection, Selector, SELECTOR_OBJ
25
+
26
+ from typing import Literal
27
+ from functools import reduce
28
+
29
+ class Alignment(Enum):
30
+ CENTER = 1
31
+ CORNER = 2
32
+
33
+ class Box(GeoVolume):
34
+ """ A class that represents a box shaped volume
35
+
36
+ """
37
+
38
+ def __init__(self,
39
+ width: float,
40
+ depth: float,
41
+ height: float,
42
+ position: tuple = (0,0,0),
43
+ alignment: Alignment = Alignment.CORNER,
44
+ cs: CoordinateSystem = GCS):
45
+ """Creates a box volume object.
46
+ Specify the alignment of the box with the provided position. The options are CORNER (default)
47
+ for the front-left-bottom node of the box or CENTER for the center of the box.
48
+
49
+ Args:
50
+ width (float): The x-size
51
+ depth (float): The y-size
52
+ height (float): The z-size
53
+ position (tuple, optional): The position of the box. Defaults to (0,0,0).
54
+ alignment (Alignment, optional): Which point of the box is placed at the position.
55
+ Defaults to Alignment.CORNER.
56
+ """
57
+ if alignment is Alignment.CENTER:
58
+ position = (position[0]-width/2, position[1]-depth/2, position[2]-height/2)
59
+
60
+ x,y,z = position
61
+
62
+ tag = gmsh.model.occ.addBox(x,y,z,width,depth,height)
63
+ super().__init__(tag)
64
+
65
+ self.center = (x+width/2, y+depth/2, z+height/2)
66
+ self.width = width
67
+ self.height = height
68
+ self.depth = depth
69
+
70
+ wax = cs.xax.np
71
+ dax = cs.yax.np
72
+ hax = cs.zax.np
73
+
74
+ p0 = self.center
75
+ pc = p0
76
+ self._add_face_pointer('front', pc - depth/2*dax, -dax)
77
+ self._add_face_pointer('back', pc + depth/2*dax, dax)
78
+ self._add_face_pointer('left', pc - width/2*wax, -wax)
79
+ self._add_face_pointer('right', pc + width/2*wax, wax)
80
+ self._add_face_pointer('top', pc + height/2*hax, hax)
81
+ self._add_face_pointer('bottom', pc - height/2*hax, -hax)
82
+
83
+
84
+ def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
85
+ """Select all outside faces except for the once specified by outside
86
+
87
+ Returns:
88
+ FaceSelection: The resultant face selection
89
+ """
90
+ tagslist = [self._face_tags(name) for name in ['bottom','top','right','left','front','back'] if name not in exclude]
91
+
92
+ tags = list(reduce(lambda a,b: a+b, tagslist))
93
+ return FaceSelection(tags)
94
+
95
+
96
+ class Sphere(GeoVolume):
97
+
98
+ def __init__(self,
99
+ radius: float,
100
+ position: tuple = (0,0,0)):
101
+ """Generates a sphere objected centered ont he position with the given radius
102
+
103
+ Args:
104
+ radius (float): The sphere radius
105
+ position (tuple, optional): The center position. Defaults to (0,0,0).
106
+ """
107
+ super().__init__([])
108
+ x,y,z = position
109
+ self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
110
+
111
+ class XYPlate(GeoSurface):
112
+ def __init__(self,
113
+ width: float,
114
+ depth: float,
115
+ position: tuple = (0,0,0),
116
+ alignment: Alignment = Alignment.CORNER):
117
+ """Generates and XY-plane oriented plate
118
+
119
+ Specify the alignment of the plate with the provided position. The options are CORNER (default)
120
+ for the front-left node of the plate or CENTER for the center of the plate.
121
+
122
+ Args:
123
+ width (float): The x-size of the plate
124
+ depth (float): The y-size of the plate
125
+ position (tuple, optional): The position of the alignment node. Defaults to (0,0,0).
126
+ alignment (Alignment, optional): Which node to align to. Defaults to Alignment.CORNER.
127
+ """
128
+ super().__init__([])
129
+ if alignment is Alignment.CENTER:
130
+ position = (position[0]-width/2, position[1]-depth/2, position[2])
131
+
132
+ x,y,z = position
133
+ self.tags: list[int] = [gmsh.model.occ.addRectangle(x,y,z,width,depth),]
134
+
135
+
136
+ class Plate(GeoSurface):
137
+
138
+ def __init__(self,
139
+ origin: tuple[float, float, float],
140
+ u: tuple[float, float, float],
141
+ v: tuple[float, float, float]):
142
+ """A generalized 2D rectangular plate in XYZ-space.
143
+
144
+ The plate is specified by an origin (o) in meters coordinate plus two vectors (u,v) in meters
145
+ that span two of the sides such that all points of the plate are defined by:
146
+ p1 = o
147
+ p2 = o+u
148
+ p3 = o+v
149
+ p4 = o+u+v
150
+ Args:
151
+ origin (tuple[float, float, float]): The origin of the plate in meters
152
+ u (tuple[float, float, float]): The u-axis of the plate
153
+ v (tuple[float, float, float]): The v-axis of the plate
154
+ """
155
+
156
+ origin = np.array(origin)
157
+ u = np.array(u)
158
+ v = np.array(v)
159
+
160
+ tagp1 = gmsh.model.occ.addPoint(*origin)
161
+ tagp2 = gmsh.model.occ.addPoint(*(origin+u))
162
+ tagp3 = gmsh.model.occ.addPoint(*(origin+v))
163
+ tagp4 = gmsh.model.occ.addPoint(*(origin+u+v))
164
+
165
+ tagl1 = gmsh.model.occ.addLine(tagp1, tagp2)
166
+ tagl2 = gmsh.model.occ.addLine(tagp2, tagp4)
167
+ tagl3 = gmsh.model.occ.addLine(tagp4, tagp3)
168
+ tagl4 = gmsh.model.occ.addLine(tagp3, tagp1)
169
+
170
+ tag_wire = gmsh.model.occ.addWire([tagl1,tagl2, tagl3, tagl4])
171
+
172
+ tags: list[int] = [gmsh.model.occ.addPlaneSurface([tag_wire,]),]
173
+ super().__init__(tags)
174
+
175
+ class Cyllinder(GeoVolume):
176
+
177
+ def __init__(self,
178
+ radius: float,
179
+ height: float,
180
+ cs: CoordinateSystem = None,
181
+ Nsections: int = None):
182
+ """Generates a Cyllinder 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
185
+ oriented along the Z-axis.
186
+
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.
189
+ As an alternative, the argument Nsections may be provided in which case the Cyllinder is replaced
190
+ by an extrusion of a regular N-sided polygon.
191
+
192
+ Args:
193
+ radius (float): The radius of the Cyllinder
194
+ height (float): The height of the Cyllinder
195
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
196
+ Nsections (int, optional): The number of sections. Defaults to None.
197
+ """
198
+ ax = cs.zax.np
199
+
200
+ if Nsections:
201
+ from .polybased import XYPolygon
202
+ cyl = XYPolygon.circle(radius, Nsections=Nsections).extrude(height, cs)
203
+ cyl._exists = False
204
+ self._face_pointers = cyl._face_pointers
205
+ super().__init__(cyl.tags)
206
+ else:
207
+ cyl = gmsh.model.occ.addCylinder(cs.origin[0], cs.origin[1], cs.origin[2],
208
+ height*ax[0], height*ax[1], height*ax[2],
209
+ radius)
210
+ super().__init__(cyl)
211
+ self._add_face_pointer('front', cs.origin, -cs.zax.np)
212
+ self._add_face_pointer('back', cs.origin+height*cs.zax.np, cs.zax.np)
213
+
214
+ self.cs: CoordinateSystem = cs
215
+ self.radius = radius
216
+ self.height = height
217
+
218
+ def face_points(self, nRadius: int = 10, Angle: int = 10, face_number: int = 1) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
219
+ """Returns the points of the cylinder."""
220
+ rs = np.linspace(0, self.radius, nRadius)
221
+ angles = np.linspace(0, 2 * np.pi, int(360 / Angle), endpoint=False)
222
+ R, A = np.meshgrid(rs, angles)
223
+ x = R * np.cos(A)
224
+ y = R * np.sin(A)
225
+ z = np.zeros_like(x)
226
+ if face_number == 2:
227
+ z = z + self.height
228
+
229
+ xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
230
+ return xo, yo, zo
231
+
232
+ class CoaxCyllinder(GeoVolume):
233
+ """A coaxial cylinder with an inner and outer radius."""
234
+
235
+ def __init__(self,
236
+ rout: float,
237
+ rin: float,
238
+ height: float,
239
+ cs: CoordinateSystem = None,
240
+ Nsections: int = None):
241
+ """Generates a Coaxial cyllinder object in 3D space.
242
+ The coaxial cyllinder will always be placed in the origin of the provided CoordinateSystem.
243
+ The bottom coax plane is always placed in the XY-plane. The lenth of the coax is
244
+ oriented along the Z-axis.
245
+
246
+ By default the coax uses the Open Cascade modeling for a cyllinder. In this representation
247
+ the surface of the cyllinder is approximated with a tolerance thay may be irregular.
248
+ As an alternative, the argument Nsections may be provided in which case the Cyllinder is replaced
249
+ by an extrusion of a regular N-sided polygon.
250
+
251
+ Args:
252
+ radius (float): The radius of the Cyllinder
253
+ height (float): The height of the Cyllinder
254
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
255
+ Nsections (int, optional): The number of sections. Defaults to None.
256
+ """
257
+ if cs is None:
258
+ cs = GCS
259
+ if rout <= rin:
260
+ raise ValueError("Outer radius must be greater than inner radius.")
261
+
262
+ self.rout = rout
263
+ self.rin = rin
264
+ self.height = height
265
+
266
+ self.cyl_out = Cyllinder(rout, height, cs, Nsections=Nsections)
267
+ self.cyl_in = Cyllinder(rin, height, cs, Nsections=Nsections)
268
+ self.cyl_in._exists = False
269
+ self.cyl_out._exists = False
270
+ cyltags, _ = gmsh.model.occ.cut(self.cyl_out.dimtags, self.cyl_in.dimtags)
271
+
272
+ super().__init__([dt[1] for dt in cyltags])
273
+
274
+ self._add_face_pointer('front', cs.origin, -cs.zax.np)
275
+ self._add_face_pointer('back', cs.origin+height*cs.zax.np, cs.zax.np)
276
+
277
+ self.cs = cs
278
+
279
+ def face_points(self, nRadius: int = 10, Angle: int = 10, face_number: int = 1) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
280
+ """Returns the points of the coaxial cylinder."""
281
+ rs = np.linspace(self.rin, self.rout, nRadius)
282
+ angles = np.linspace(0, 2 * np.pi, int(360 / Angle), endpoint=False)
283
+ R, A = np.meshgrid(rs, angles)
284
+ x = R * np.cos(A)
285
+ y = R * np.sin(A)
286
+ z = np.zeros_like(x)
287
+ if face_number == 2:
288
+ z = z + self.height
289
+
290
+ xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
291
+ return xo, yo, zo
292
+ return super().boundary()
293
+
294
+ class HalfSphere(GeoVolume):
295
+
296
+ def __init__(self,
297
+ radius: float,
298
+ position: tuple = (0,0,0),
299
+ direction: tuple = (1,0,0)):
300
+ super().__init__([])
301
+ sphere = Sphere(radius, position=position)
302
+ cx, cy, cz = position
303
+
304
+ box = Box(1.1*radius, 2.2*radius, 2.2*radius, position=(cx-radius*1.1,cy-radius*1.1, cz-radius*1.1))
305
+
306
+ self.tag = subtract(sphere, box)[0].tag
307
+
308
+
309
+
310
+ class OldBox(GeoVolume):
311
+ '''The sided box class creates a box just like the Box class but with selectable face tags.
312
+ This class is more convenient in use when defining radiation boundaries.'''
313
+ def __init__(self,
314
+ width: float,
315
+ depth: float,
316
+ height: float,
317
+ position: tuple = (0,0,0),
318
+ cs: CoordinateSystem = GCS,
319
+ alignment: Alignment = Alignment.CORNER):
320
+ """Creates a box volume object with selectable sides.
321
+
322
+ Specify the alignment of the box with the provided position. The options are CORNER (default)
323
+ for the front-left-bottom node of the box or CENTER for the center of the box.
324
+
325
+ Args:
326
+ width (float): The x-size
327
+ depth (float): The y-size
328
+ height (float): The z-size
329
+ position (tuple, optional): The position of the box. Defaults to (0,0,0).
330
+ alignment (Alignment, optional): Which point of the box is placed at the position.
331
+ Defaults to Alignment.CORNER.
332
+ """
333
+
334
+
335
+ if alignment is Alignment.CORNER:
336
+ position = (position[0]+width/2, position[1]+depth/2, position[2])
337
+ elif alignment is Alignment.CENTER:
338
+ position = (position[0], position[1], position[2] - height/2)
339
+ p0 = np.array(position)
340
+ p1 = p0 + cs.zax.np * height
341
+
342
+ wax = cs.xax.np
343
+ dax = cs.yax.np
344
+ hax = cs.zax.np
345
+
346
+ w = width
347
+ d = depth
348
+
349
+ p11 = p0 + wax * w/2 + dax * d/2 # right back
350
+ p12 = p0 + wax * w/2 - dax * d/2 # right front
351
+ p13 = p0 - wax * w/2 - dax * d/2 # Left front
352
+ p14 = p0 - wax * w/2 + dax * d/2 # left back
353
+
354
+ p21 = p1 + wax * w/2 + dax * d/2
355
+ p22 = p1 + wax * w/2 - dax * d/2
356
+ p23 = p1 - wax * w/2 - dax * d/2
357
+ p24 = p1 - wax * w/2 + dax * d/2
358
+
359
+ pt11 = gmsh.model.occ.addPoint(*p11) # right back
360
+ pt12 = gmsh.model.occ.addPoint(*p12) # Right front
361
+ pt13 = gmsh.model.occ.addPoint(*p13) # left front
362
+ pt14 = gmsh.model.occ.addPoint(*p14) # Left back
363
+ pt21 = gmsh.model.occ.addPoint(*p21)
364
+ pt22 = gmsh.model.occ.addPoint(*p22)
365
+ pt23 = gmsh.model.occ.addPoint(*p23)
366
+ pt24 = gmsh.model.occ.addPoint(*p24)
367
+
368
+ l1r = gmsh.model.occ.addLine(pt11, pt12) #Right
369
+ l1f = gmsh.model.occ.addLine(pt12, pt13) #Front
370
+ l1l = gmsh.model.occ.addLine(pt13, pt14) #Left
371
+ l1b = gmsh.model.occ.addLine(pt14, pt11) #Back
372
+
373
+ l2r = gmsh.model.occ.addLine(pt21, pt22)
374
+ l2f = gmsh.model.occ.addLine(pt22, pt23)
375
+ l2l = gmsh.model.occ.addLine(pt23, pt24)
376
+ l2b = gmsh.model.occ.addLine(pt24, pt21)
377
+
378
+ dbr = gmsh.model.occ.addLine(pt11, pt21)
379
+ dfr = gmsh.model.occ.addLine(pt12, pt22)
380
+ dfl = gmsh.model.occ.addLine(pt13, pt23)
381
+ dbl = gmsh.model.occ.addLine(pt14, pt24)
382
+
383
+ wbot = gmsh.model.occ.addWire([l1r, l1b, l1l, l1f])
384
+ wtop = gmsh.model.occ.addWire([l2r, l2b, l2l, l2f])
385
+ wright = gmsh.model.occ.addWire([l1r, dbr, l2r, dfr])
386
+ wleft = gmsh.model.occ.addWire([l1l, dbl, l2l, dfl])
387
+ wback = gmsh.model.occ.addWire([l1b, dbl, l2b, dbr])
388
+ wfront = gmsh.model.occ.addWire([l1f, dfr, l2f, dfl])
389
+
390
+ bottom_tag = gmsh.model.occ.addSurfaceFilling(wbot)
391
+ top_tag = gmsh.model.occ.addSurfaceFilling(wtop)
392
+ front_tag = gmsh.model.occ.addSurfaceFilling(wfront)
393
+ back_tag = gmsh.model.occ.addSurfaceFilling(wback)
394
+ left_tag = gmsh.model.occ.addSurfaceFilling(wleft)
395
+ right_tag = gmsh.model.occ.addSurfaceFilling(wright)
396
+
397
+ sv = gmsh.model.occ.addSurfaceLoop([bottom_tag,
398
+ top_tag,
399
+ right_tag,
400
+ left_tag,
401
+ front_tag,
402
+ back_tag])
403
+
404
+ volume_tag: int = gmsh.model.occ.addVolume([sv,])
405
+
406
+ super().__init__(volume_tag)
407
+ #self.tags: list[int] = [volume_tag,]
408
+
409
+ pc = p0 + height/2*hax
410
+ self._add_face_pointer('front', pc - depth/2*dax, -dax)
411
+ self._add_face_pointer('back', pc + depth/2*dax, dax)
412
+ self._add_face_pointer('left', pc - width/2*wax, -wax)
413
+ self._add_face_pointer('right', pc + width/2*wax, wax)
414
+ self._add_face_pointer('top', pc + height/2*hax, hax)
415
+ self._add_face_pointer('bottom', pc - height/2*hax, -hax)
416
+
417
+
418
+ def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
419
+ """Select all outside faces except for the once specified by outside
420
+
421
+ Returns:
422
+ FaceSelection: The resultant face selection
423
+ """
424
+ tagslist = [self._face_tags(name) for name in ['bottom','top','right','left','front','back'] if name not in exclude]
425
+
426
+ tags = list(reduce(lambda a,b: a+b, tagslist))
427
+ return FaceSelection(tags)
@@ -0,0 +1,77 @@
1
+ import gmsh
2
+ from ..geometry import GeoPoint, GeoEdge, GeoVolume, GeoSurface, GeoObject
3
+ from pathlib import Path
4
+ import numpy as np
5
+
6
+ class STEPItems:
7
+
8
+ def __init__(self, filename: str, unit: float = 1.0):
9
+ """Imports the provided STEP file.
10
+ Specify the unit in case of scaling issues where mm units are not taken into consideration.
11
+
12
+ Args:
13
+ filename (str): The filename
14
+ unit (float, optional): The STEP file size unit. Defaults to 1.0.
15
+
16
+ Raises:
17
+ FileNotFoundError: If a file does not exist
18
+ """
19
+ stl_path = Path(filename)
20
+ gmsh.option.setNumber("Geometry.OCCScaling", 1)
21
+ gmsh.option.setNumber("Geometry.OCCImportLabels", 2)
22
+
23
+ if not stl_path.exists:
24
+ raise FileNotFoundError(f'File with name {stl_path} does not exist.')
25
+
26
+ dimtags = gmsh.model.occ.import_shapes(filename, format='step')
27
+
28
+ gmsh.model.occ.affine_transform(dimtags, np.array([unit, 0, 0, 0,
29
+ 0, unit, 0, 0,
30
+ 0, 0, unit, 0,
31
+ 0, 0, 0, 1]))
32
+ #dimtags = gmsh.model.occ.heal_shapes(dimtags, tolerance=1e-6)
33
+
34
+ self.points: dict[str, GeoPoint] = dict()
35
+ self.edges: dict[str, GeoEdge] = dict()
36
+ self.surfaces: dict[str, GeoSurface] = dict()
37
+ self.volumes: dict[str, GeoVolume] = dict()
38
+
39
+ i = 0
40
+ for dim, tag in dimtags:
41
+ name = gmsh.model.getPhysicalName(dim, tag)
42
+ if name == '':
43
+ name = f'Obj{i}'
44
+ i+=1
45
+ if dim == 0:
46
+ self.points[name] = GeoPoint(tag)
47
+ elif dim == 1:
48
+ self.edges[name] = GeoEdge(tag)
49
+ elif dim == 2:
50
+ self.surfaces[name] = GeoSurface(tag)
51
+ elif dim == 3:
52
+ self.volumes[name] = GeoVolume(tag)
53
+
54
+ @property
55
+ def _dicts(self):
56
+ yield self.points
57
+ yield self.edges
58
+ yield self.surfaces
59
+ yield self.volumes
60
+
61
+ @property
62
+ def objects(self) -> tuple[GeoObject,...]:
63
+ objects = tuple()
64
+ for dct in self._dicts:
65
+ objects = objects + tuple(dct.values())
66
+ return objects
67
+
68
+ def __getitem__(self, name: str) -> GeoObject:
69
+ if name in self.points:
70
+ return self.points[name]
71
+ elif name in self.edges:
72
+ return self.edges[name]
73
+ elif name in self.surfaces:
74
+ return self.surfaces[name]
75
+ elif name in self.volumes:
76
+ return self.volumes[name]
77
+
@@ -0,0 +1,86 @@
1
+ # import shapely as shp
2
+ # import numpy as np
3
+
4
+
5
+ # def rectangle(p1: tuple, p2: tuple) -> shp.Polygon:
6
+ # x1, y1 = p1
7
+ # x2, y2 = p2
8
+ # x1, x2 = sorted([x1, x2])
9
+ # y1, y2 = sorted([y1, y2])
10
+ # return shp.Polygon(((x1, y1), (x2, y1), (x2, y2), (x1, y2)))
11
+
12
+ # def rect_center(center: tuple, width: float, height: float):
13
+ # x0, y0 = center
14
+ # w, h = width, height
15
+ # return shp.Polygon(((x0-w/2,y0-h/2),(x0+w/2,y0-h/2),(x0+w/2,y0+h/2),(x0-w/2,y0+h/2)))
16
+
17
+ # def polygon(points) -> shp.Polygon:
18
+ # return shp.Polygon(points)
19
+
20
+ # def circle(center: tuple, radius: float, segments: int, ang_range: tuple = (0,360)):
21
+ # x,y = center
22
+ # points = []
23
+ # path = []
24
+ # angs = np.linspace(ang_range[0],ang_range[1],segments+1)
25
+
26
+ # angs = angs*np.pi/180
27
+ # for i in range(segments+1):
28
+ # ang = angs[i]
29
+ # points.append((x+radius*np.cos(ang),y+radius*np.sin(ang)))
30
+ # path.append((x+radius*np.cos(ang),y+radius*np.sin(ang)))
31
+
32
+ # if ang_range[0] != ang_range[1]%360:
33
+ # points = [center,] + points
34
+ # else:
35
+ # points = points[:-1]
36
+ # path = path + [path[0],]
37
+
38
+
39
+ # return shp.Polygon(points), shp.LineString(path)
40
+
41
+ # def transform(polygon: shp.Polygon, transformation: callable, reverse=False) -> shp.Polygon:
42
+ # ''' Transforms the provided shapely polygon by applying the transformation f(x,y) to each coordinate'''
43
+ # ex, ey = polygon.exterior.xy
44
+ # interiors = [interior.xy for interior in polygon.interiors]
45
+ # new_poly = shp.Polygon(transformation(x,y) for x,y in zip(ex, ey))
46
+ # for interior in interiors:
47
+ # print(f'Type of interior = {type(interior)}, {interior}')
48
+ # ix, iy = interior
49
+ # new_poly = new_poly.difference(shp.Polygon(transformation(x,y) for x,y in zip(ix, iy)))
50
+ # if reverse:
51
+ # new_poly = new_poly.reverse()
52
+ # return new_poly
53
+
54
+
55
+ # def move(polygon: shp.Polygon, dx: float, dy: float) -> shp.Polygon:
56
+ # func = lambda x,y: (x+dx, y+dy)
57
+ # return transform(polygon, func)
58
+
59
+ # def array(polygon: shp.Polygon, direction: tuple, N: int, include_original: bool = True) -> list[shp.Polygon]:
60
+ # polys = []
61
+ # for i in range(N+1):
62
+ # polys.append(move(polygon, direction[0]*i, direction[1]*i))
63
+ # if not include_original:
64
+ # polys = polys[1:]
65
+ # return polys
66
+
67
+ # def mirror(polygon: shp.Polygon, origin: tuple, axis: tuple) -> shp.Polygon:
68
+ # ax, ay = axis[0]/np.sqrt(axis[0]**2 + axis[1]**2), axis[1]/np.sqrt(axis[0]**2 + axis[1]**2)
69
+ # def _mir_transform(x, y):
70
+ # dotprod = (x-origin[0])*ax + (y-origin[1])*ay
71
+ # x2 = x - dotprod*ax*2
72
+ # y2 = y - dotprod*ay*2
73
+ # return x2, y2
74
+ # return transform(polygon, _mir_transform, reverse=True)
75
+
76
+ # def rasterize(polygons: list[shp.Polygon], gridsize: float) -> list[shp.Polygon]:
77
+ # output_polygons = []
78
+ # grid = lambda x: np.round(np.array(x)/gridsize)*gridsize
79
+ # for poly in polygons:
80
+ # ex, ey = poly.exterior.xy
81
+ # interiors = [interior.xy for interior in poly.interiors]
82
+ # new_poly = shp.Polygon((x,y) for x,y in zip(grid(ex), grid(ey)))
83
+ # for ix, iy in interiors:
84
+ # new_poly.difference(shp.Polygon((x,y) for x,y in zip(grid(ix), grid(iy))))
85
+ # output_polygons.append(new_poly)
86
+ # return output_polygons