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,449 @@
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
+ from __future__ import annotations
19
+ import numpy as np
20
+ from typing import Callable, Iterable, Any
21
+ from .shapes import Box, Sphere, Cyllinder, CoaxCyllinder, Alignment
22
+ from ..geometry import GeoVolume, GeoObject, GeoSurface
23
+ from .operations import rotate, mirror, translate, add, subtract, embed
24
+ from numbers import Number
25
+ from functools import reduce
26
+ from operator import mul
27
+ from ..cs import CoordinateSystem, GCS
28
+ from .polybased import XYPolygon
29
+
30
+ def get_flat_index(indices, shape):
31
+ flat_index = 0
32
+ for i, idx in enumerate(indices):
33
+ stride = reduce(mul, shape[i+1:], 1)
34
+ flat_index += idx * stride
35
+ return flat_index
36
+
37
+
38
+ class Series:
39
+
40
+ def __init__(self, values: Iterable[Number]):
41
+ self.values: list[Number] = list(values)
42
+ self.N: int = len(self.values)
43
+
44
+ def __len__(self) -> int:
45
+ return self.N
46
+
47
+ def __getitem__(self, index: int):
48
+ return self.values[index]
49
+
50
+ def get_count(args) -> int:
51
+ N = 1
52
+ for arg in args:
53
+ if isinstance(arg,Series):
54
+ N = max(N, arg.N)
55
+ return N
56
+ elif isinstance(arg, Iterable):
57
+ return get_count(arg)
58
+ return 1
59
+
60
+ def get_item(value, n: int):
61
+ if isinstance(value, Number):
62
+ return value
63
+ elif isinstance(value, Series):
64
+ return value[n]
65
+ elif isinstance(value, Iterable):
66
+ typ = type(value)
67
+ return typ([get_item(v,n) for v in value])
68
+ return None
69
+
70
+ def unpack(*args):
71
+ output = []
72
+ N = get_count(args)
73
+ return tuple(zip(*[[get_item(a,n) for a in args] for n in range(N)]))
74
+
75
+ ObjectTransformer = Callable[[GeoObject,], GeoObject]
76
+
77
+ class PartialFunction:
78
+
79
+ def __init__(self,
80
+ function: Callable,
81
+ identity: bool = False,
82
+ permute: bool = False,
83
+ **kwargs):
84
+ self.f: Callable = function
85
+ self.kwargs: dict[str, float | Series] = kwargs
86
+ self.identity: bool = identity
87
+ self.permute: bool = permute
88
+ self.N: int = 1
89
+ self.kwargset: list[dict] = None
90
+
91
+ for key, value in self.kwargs.items():
92
+ if not isinstance(value, Series):
93
+ continue
94
+ self.N = max(self.N, value.N)
95
+
96
+ # Include the identity operation
97
+ if self.identity:
98
+ self.N = self.N + 1
99
+
100
+ def _compile(self) -> None:
101
+ kwargset = []
102
+ N = self.N
103
+
104
+ if self.identity:
105
+ kwargset.append(None)
106
+ N = N - 1
107
+
108
+ for n in range(N):
109
+ kwargs = dict()
110
+ for key,value in self.kwargs.items():
111
+ if not isinstance(value, Series):
112
+ kwargs[key] = value
113
+ else:
114
+ kwargs[key] = value[n]
115
+ kwargset.append(kwargs)
116
+ self.kwargset = kwargset
117
+
118
+ def __call__(self, objects: list[GeoObject], index: int) -> list[GeoObject]:
119
+
120
+ kwargs = self.kwargset[index]
121
+ if kwargs is None:
122
+ return objects
123
+
124
+ objects_out = []
125
+ for obj in objects:
126
+ objects_out.append(self.f(obj, **kwargs))
127
+ return objects_out
128
+
129
+ class NDimContainer:
130
+
131
+ def __init__(self):
132
+ self.items: list= []
133
+ self.dimensions: list[tuple[int,int]] = []
134
+ self.expanded_dims: list[tuple[int,int,int]] = []
135
+ self.map: np.ndarray = []
136
+ self.Ncopies: int = 1
137
+
138
+ @property
139
+ def Ntot(self) -> int:
140
+ """The Total number of different objects this NDimContainer acts on.
141
+ """
142
+ n = self.Ncopies
143
+ for i,N in self.dimensions:
144
+ n = n*N
145
+
146
+ return n
147
+
148
+ @property
149
+ def Nop(self) -> int:
150
+ """The number of operations corresponding to this NDimContainer
151
+
152
+ Returns:
153
+ int: The number of operations this container can support.
154
+ """
155
+ n = 1
156
+ for i,N in self.dimensions:
157
+ n = n*N
158
+
159
+ return n
160
+
161
+ @property
162
+ def dimtup(self) -> tuple[int]:
163
+ """A dimension tuple containing the dimensions of the NDimContainer
164
+
165
+ Returns:
166
+ tuple[int]: A tuple of dimension sizes to be used in np.zeros(shape)
167
+ """
168
+ dimtup = tuple([dim[1] for dim in self.dimensions])
169
+ if self.Ncopies:
170
+ dimtup = dimtup + (self.Ncopies,)
171
+ return dimtup
172
+
173
+ def _init(self) -> None:
174
+ """Initialization function to set the index map and expanded dimension list.
175
+ """
176
+ i = 0
177
+ for i,(ncopies,N) in enumerate(self.dimensions):
178
+ for n in range(ncopies):
179
+ self.expanded_dims.append((i,n,N))
180
+
181
+ if self.Ncopies > 1:
182
+ self.expanded_dims.append((i+1, (1, self.Ncopies)))
183
+
184
+ self.map = np.arange(self.Ntot).reshape(self.dimtup)
185
+
186
+ def add_dim(self, N: int, same_axis: bool = False) -> None:
187
+ """Aads a new dimension to iterate over.
188
+
189
+ Args:
190
+ N (int): The size of the dimension (number of differnt items contained)
191
+ same_axis (bool, optional): Defines that the next iteration dimension is actually parallel
192
+ to the last existing dimension. In this case N must be equal to the size of the last defined dimension. Defaults to False.
193
+
194
+ Raises:
195
+ ValueError: An error if the provided dimension is not correct.
196
+ """
197
+ if same_axis:
198
+ if N != self.dimensions[-1][1]:
199
+ raise ValueError('Trying to add a dimension with the same size as previous but the sizes are different.' +
200
+ f'The provided size is {N} but the last dimensions is {self.dimensions[-1][1]}')
201
+ self.dimensions[-1] = (self.dimensions[-1][0]+1, N)
202
+ else:
203
+ self.dimensions.append((1,N))
204
+
205
+ def set_copies(self, N: int) -> None:
206
+ """Define how many original copies of the new object will be created.
207
+
208
+ Args:
209
+ N (int): The number of copies.
210
+ """
211
+ self.Ncopies = N
212
+
213
+ def get(self,
214
+ dim_ex_dim: int,
215
+ number: int) -> list:
216
+ dimindex, niter, Nvariations = self.expanded_dims[dim_ex_dim]
217
+ slclist = [slice(None) for _ in self.dimensions]
218
+ slclist[dimindex] = number
219
+ return list(self.map[tuple(slclist)].flatten())
220
+
221
+
222
+ class Modeler:
223
+
224
+ def __init__(self):
225
+ ## Function importing
226
+ self.rotate = rotate
227
+ self.add = add
228
+ self.translate = translate
229
+ self.remove = subtract
230
+ self.subtract = subtract
231
+ self.embed = embed
232
+ self.mirror = mirror
233
+
234
+ self.pre_transforms: list[PartialFunction] = []
235
+ self.post_transforms: list[PartialFunction] = []
236
+ self.last_object: GeoObject = None
237
+ self.ndimcont: NDimContainer = NDimContainer()
238
+ self._and: bool = False
239
+
240
+ @property
241
+ def AND(self) -> Modeler:
242
+ """This method chain property ensures that the following transformation will be merged
243
+ with the previous one. For example, a rotation plus a translation with AND in between
244
+ both containing three operations will merge their operations so that each single value
245
+ is paired up with the next.
246
+
247
+ For example, if you wish to create a three boxes that are rotated in increments of 90
248
+ degrees and traslated by 1, 2 and 3 meters respectively (90deg, 1m) (180deg, 2m) and (270deg, 3m)
249
+ you would do:
250
+
251
+ >>> m.rotated(...,..., m.Series(90,180,270)).AND.translated(m.Series(1,2,3),...)
252
+
253
+ If you would not use AND, the operations are permuted resulting in 9 boxes.
254
+ """
255
+ self._and = True
256
+ return self
257
+
258
+ def series(self, *values: Number) -> Series:
259
+ """Returns a Series object to represent a series of values instead of a single value.
260
+
261
+ A series of values can be used at any argument that accepts Series objects. If such a
262
+ variable is provided as a series, the modeler will execute multiple iterations of the
263
+ object or transformation for each value in the series.
264
+
265
+ Returns:
266
+ Series: The generated Series object.
267
+
268
+ Example:
269
+ >>> modeler.box(modeler.Series(1,2,3), 2, 3)
270
+
271
+ """
272
+ return Series(values)
273
+
274
+ def nvars(self) -> int:
275
+ return self.ndimcont.Nop
276
+
277
+ def _merge_object(self, objs: list[GeoObject]) -> GeoObject:
278
+ """Merges a list of GeoObjects into a single GeoObject.
279
+
280
+ Args:
281
+ objs (list[GeoObject]): A list of GMSH objects
282
+
283
+ Returns:
284
+ GeoObject: A single GeoObject
285
+ """
286
+ tags = []
287
+ for obj in objs:
288
+ tags.extend(obj.tags)
289
+ gmshobj = GeoObject.from_dimtags(objs[0].dim, tags)
290
+ for obj in objs:
291
+ gmshobj._take_tools(obj)
292
+ return gmshobj
293
+
294
+ def _clean(self) -> None:
295
+ self.pre_transforms = []
296
+ self._and = False
297
+ self._combine = False
298
+
299
+ def _add_dim(self, N: int) -> None:
300
+ self.ndimcont.add_dim(N, self._and)
301
+ self._and = False
302
+
303
+ def _add_function(self, function: PartialFunction):
304
+ self._add_dim(function.N)
305
+ self.pre_transforms.append(function)
306
+
307
+ def _apply_presteps(self, objects: list[GeoObject]) -> list[GeoObject]:
308
+ self.ndimcont._init()
309
+ for func in self.pre_transforms:
310
+ func._compile()
311
+ if not self.pre_transforms:
312
+ return objects
313
+
314
+ for i, function in enumerate(self.pre_transforms):
315
+ for n in range(function.N):
316
+ ids = self.ndimcont.get(i,n)
317
+ sel = [objects[i] for i in ids]
318
+ objects_out = function(sel, n)
319
+ for j, obj in zip(ids, objects_out):
320
+ objects[j] = obj
321
+
322
+ self._clean()
323
+ return objects
324
+
325
+ def rotated(self,
326
+ c0: tuple[float, float, float],
327
+ ax: tuple[float, float, float],
328
+ angle: float | Series,
329
+ ) -> Modeler:
330
+ """Adds a rotation task to the geometry builder.
331
+
332
+ Args:
333
+ c0 (tuple[float, float, float]): The origin at which the rotation axis is placed.
334
+ ax (tuple[float, float, float]): The Rotation axis to be used
335
+ angle (float | Series): The angles or series of angles provided in degrees.
336
+
337
+ Returns:
338
+ Modeler: The same modeler instance
339
+ """
340
+ function = PartialFunction(rotate, c0=c0, ax=ax, angle=angle)
341
+ self._add_function(function)
342
+ return self
343
+
344
+ def translated(self,
345
+ dx: float | Series,
346
+ dy: float | Series,
347
+ dz: float | Series) -> Modeler:
348
+ """Adds a translation task to the geometry builder pipeline.
349
+ The dx, dy, dz may either be a single displacement coordinate or a series of them in the
350
+ shape of a series object.
351
+
352
+ Args:
353
+ dx (float | Series): The x-displacement
354
+ dy (float | Series): The y-displacement
355
+ dz (float | Series): The z-displacement
356
+
357
+ Returns:
358
+ Modeler: The Modeler object
359
+ """
360
+ function = PartialFunction(translate, dx=dx, dy=dy, dz=dz)
361
+ self._add_function(function)
362
+ return self
363
+
364
+ def mirrored(self,
365
+ origin: tuple[float, float, float],
366
+ direction: tuple[float, float, float],
367
+ keep_original: bool = True) -> Modeler:
368
+ """Adds a mirror transformation to the geometry builder pipeline.
369
+
370
+ Args:
371
+ origin (tuple[float, float, float]): The origin of the mirror axis plane
372
+ direction (tuple[float, float, float]): The mirror direction which is normal to the plane of reflection.
373
+ keep_original (bool, optional): Whether to keep the original object. Defaults to True.
374
+
375
+ Returns:
376
+ Modeler: The Modeler object
377
+ """
378
+ function = PartialFunction(mirror,
379
+ identity=keep_original,
380
+ origin=origin,
381
+ direction=direction)
382
+ self._add_function(function)
383
+ return self
384
+
385
+ def cyllinder(self,
386
+ radius: float | Series,
387
+ height: float | Series,
388
+ position: tuple[float | Series, float | Series, float | Series],
389
+ cs: CoordinateSystem = GCS,
390
+ merge: bool = False,
391
+ NPoly: int = False):
392
+
393
+ N_objects = self.nvars()
394
+ Rs, Hs, Ps = unpack(radius, height, position)
395
+ N = len(Rs)
396
+ cyls = []
397
+
398
+ for _ in range(N_objects):
399
+ for r,h,p in zip(Rs, Hs, Ps):
400
+ cs2 = cs.displace(p[0], p[1], p[2])
401
+ if NPoly:
402
+ cyl = XYPolygon.circle(r, Nsections=NPoly).extrude(h, cs2)
403
+ else:
404
+ cyl = Cyllinder(r,h, cs2)
405
+ cyls.append(cyl)
406
+
407
+ self.ndimcont.set_copies(N)
408
+ self._apply_presteps(cyls)
409
+
410
+ if merge:
411
+ cyls = self._merge_object(cyls)
412
+
413
+ return cyls
414
+
415
+ def box(self, width: float | Series,
416
+ depth: float | Series,
417
+ height: float | Series,
418
+ position: tuple[float | Series, float | Series, float | Series],
419
+ alignment: Alignment = Alignment.CORNER,
420
+ merge: bool = False) -> list[GeoVolume] | GeoVolume:
421
+ """Create a box object which will be transformed by the transformation pipeline.
422
+
423
+ Args:
424
+ width (float): The box's width (X direction)
425
+ depth (float): The box's depth (Y direction)
426
+ height (float): The box's height (Z direction)
427
+ position (tuple[float, float, float]): The position of the box object.
428
+ alignment (Alignment, optional): Where to alight the box. Defaults to Alignment.CORNER.
429
+ merge (bool): Whether to merge the final result into a single GeoVolume object.
430
+
431
+ Returns:
432
+ list[GeoVolume]: A list of GeoVolume objects for each box.
433
+ """
434
+ N_objects = self.nvars()
435
+ Ws, Ds, Hs, Ps = unpack(width, depth, height, position)
436
+ N = len(Ws)
437
+ boxes = []
438
+
439
+ for _ in range(N_objects):
440
+ for w, d, h, p in zip(Ws, Ds, Hs, Ps):
441
+ box = Box(w,d,h, position=p, alignment=alignment)
442
+ boxes.append(box)
443
+ self.ndimcont.set_copies(N)
444
+ self._apply_presteps(boxes)
445
+
446
+ if merge:
447
+ boxes = self._merge_object(boxes)
448
+
449
+ return boxes
@@ -0,0 +1,254 @@
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
+ from typing import TypeVar
19
+ from ..geometry import GeoSurface, GeoVolume
20
+ from ..cs import CoordinateSystem, GCS
21
+ import gmsh
22
+ import numpy as np
23
+
24
+ T = TypeVar('T', GeoSurface, GeoVolume)
25
+
26
+ def _gen_mapping(obj_in, obj_out) -> dict:
27
+ tag_mapping: dict[int, dict] = {0: dict(),
28
+ 1: dict(),
29
+ 2: dict(),
30
+ 3: dict()}
31
+ for domain, mapping in zip(obj_in, obj_out):
32
+ tag_mapping[domain[0]][domain[1]] = [o[1] for o in mapping]
33
+ return tag_mapping
34
+
35
+ def add(main: T, tool: T,
36
+ remove_object: bool = True,
37
+ remove_tool: bool = True) -> T:
38
+ ''' Adds two GMSH objects together, returning a new object that is the union of the two.
39
+
40
+ Parameters
41
+ ----------
42
+ main : GeoSurface | GeoVolume
43
+ tool : GeoSurface | GeoVolume
44
+ remove_object : bool, optional
45
+ If True, the main object will be removed from the model after the operation. Default is True.
46
+ remove_tool : bool, optional
47
+ If True, the tool object will be removed from the model after the operation. Default is True.
48
+
49
+ Returns
50
+ -------
51
+ GeoSurface | GeoVolume
52
+ A new object that is the union of the main and tool objects.
53
+ '''
54
+ out_dim_tags, out_dim_tags_map = gmsh.model.occ.fuse(main.dimtags, tool.dimtags, removeObject=remove_object, removeTool=remove_tool)
55
+ if out_dim_tags[0][0] == 3:
56
+ output = GeoVolume([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
57
+ elif out_dim_tags[0][0] == 2:
58
+ output = GeoSurface([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
59
+ if remove_object:
60
+ main._exists = False
61
+ if remove_tool:
62
+ tool._exists = False
63
+ return output
64
+
65
+ def remove(main: T, tool: T,
66
+ remove_object: bool = True,
67
+ remove_tool: bool = True) -> T:
68
+ ''' Subtractes a tool object GMSH from the main object, returning a new object that is the difference of the two.
69
+
70
+ Parameters
71
+ ----------
72
+ main : GeoSurface | GeoVolume
73
+ tool : GeoSurface | GeoVolume
74
+ remove_object : bool, optional
75
+ If True, the main object will be removed from the model after the operation. Default is True.
76
+ remove_tool : bool, optional
77
+ If True, the tool object will be removed from the model after the operation. Default is True.
78
+
79
+ Returns
80
+ -------
81
+ GeoSurface | GeoVolume
82
+ A new object that is the difference of the main and tool objects.
83
+ '''
84
+ out_dim_tags, out_dim_tags_map = gmsh.model.occ.cut(main.dimtags, tool.dimtags, removeObject=remove_object, removeTool=remove_tool)
85
+ if out_dim_tags[0][0] == 3:
86
+ output = GeoVolume([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
87
+ elif out_dim_tags[0][0] == 2:
88
+ output = GeoSurface([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
89
+ if remove_object:
90
+ main._exists = False
91
+ if remove_tool:
92
+ tool._exists = False
93
+ return output
94
+
95
+ subtract = remove
96
+
97
+ def intersect(main: T, tool: T,
98
+ remove_object: bool = True,
99
+ remove_tool: bool = True) -> T:
100
+ ''' Intersection of a tool object GMSH with the main object, returning a new object that is the intersection of the two.
101
+
102
+ Parameters
103
+ ----------
104
+ main : GeoSurface | GeoVolume
105
+ tool : GeoSurface | GeoVolume
106
+ remove_object : bool, optional
107
+ If True, the main object will be removed from the model after the operation. Default is True.
108
+ remove_tool : bool, optional
109
+ If True, the tool object will be removed from the model after the operation. Default is True.
110
+
111
+ Returns
112
+ -------
113
+ GeoSurface | GeoVolume
114
+ A new object that is the difference of the main and tool objects.
115
+ '''
116
+ out_dim_tags, out_dim_tags_map = gmsh.model.occ.intersect(main.dimtags, tool.dimtags, removeObject=remove_object, removeTool=remove_tool)
117
+ if out_dim_tags[0][0] == 3:
118
+ output = GeoVolume([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
119
+ elif out_dim_tags[0][0] == 2:
120
+ output = GeoSurface([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
121
+ if remove_object:
122
+ main._exists = False
123
+ if remove_tool:
124
+ tool._exists = False
125
+ return output
126
+
127
+ def embed(main: GeoVolume, other: GeoSurface) -> None:
128
+ ''' Embeds a surface into a volume in the GMSH model.
129
+ Parameters
130
+ ----------
131
+ main : GeoVolume
132
+ The volume into which the surface will be embedded.
133
+ other : GeoSurface
134
+ The surface to be embedded into the volume.
135
+
136
+ Returns
137
+ -------
138
+ None
139
+ '''
140
+ gmsh.model.geo.synchronize()
141
+ gmsh.model.mesh.embed(other.dim, [other.tag,], main.dim, main.tags)
142
+
143
+ def rotate(main: GeoVolume,
144
+ c0: tuple[float, float, float],
145
+ ax: tuple[float, float, float],
146
+ angle: float,
147
+ degree=True) -> GeoVolume:
148
+ """Rotates a GeoVolume object around an axist defined at a coordinate.
149
+
150
+ Args:
151
+ main (GeoVolume): The object to rotate
152
+ c0 (tuple[float, float, float]): The point of origin for the rotation axis
153
+ ax (tuple[float, float, float]): A vector defining the rotation axis
154
+ angle (float): The angle in degrees (if degree is True)
155
+ degree (bool, optional): Whether to interpret the angle in degrees. Defaults to True.
156
+
157
+ Returns:
158
+ GeoVolume: The rotated GeoVolume object.
159
+ """
160
+ if degree:
161
+ angle = angle * np.pi/180
162
+ gmsh.model.occ.rotate(main.dimtags, *c0, *ax, -angle)
163
+
164
+ # Rotate the facepointers
165
+ for fp in main._all_pointers:
166
+ fp.rotate(c0, ax, angle)
167
+ return main
168
+
169
+ def translate(main: GeoVolume,
170
+ dx: float = 0,
171
+ dy: float = 0,
172
+ dz: float = 0) -> GeoVolume:
173
+ """Translates the GeoVolume object along a given displacement
174
+
175
+ Args:
176
+ main (GeoVolume): The object to translate
177
+ dx (float, optional): The X-displacement in meters. Defaults to 0.
178
+ dy (float, optional): The Y-displacement in meters. Defaults to 0.
179
+ dz (float, optional): The Z-displacement in meters. Defaults to 0.
180
+
181
+ Returns:
182
+ GeoVolume: The translated object
183
+ """
184
+ gmsh.model.occ.translate(main.dimtags, dx, dy, dz)
185
+
186
+ # Rotate the facepointers
187
+ for fp in main._all_pointers:
188
+ fp.translate(dx, dy, dz)
189
+
190
+ return main
191
+
192
+ def mirror(main: GeoVolume,
193
+ origin: tuple[float, float, float] = (0.0, 0.0, 0.0),
194
+ direction: tuple[float, float, float] = (1.0, 0.0, 0.0),
195
+ make_copy: bool = True) -> GeoVolume:
196
+ """Mirrors a GeoVolume object along a miror plane defined by a direction originating at a point
197
+
198
+ Args:
199
+ main (GeoVolume): The object to mirror
200
+ origin (tuple[float, float, float], optional): The point of origin in meters. Defaults to (0.0, 0.0, 0.0).
201
+ direction (tuple[float, float, float], optional): The normal axis defining the plane of reflection. Defaults to (1.0, 0.0, 0.0).
202
+
203
+ Returns:
204
+ GeoVolume: The mirrored GeoVolume object
205
+ """
206
+ a, b, c = direction
207
+ x0, y0, z0 = origin
208
+ d = -(a*x0 + b*y0 + c*z0)
209
+ if (a==0) and (b==0) and (c==0):
210
+ return main
211
+ mirror_obj = main
212
+ if make_copy:
213
+ new_obj = main.make_copy()
214
+ gmsh.model.occ.mirror(new_obj.dimtags, a,b,c,d)
215
+ mirror_obj = new_obj
216
+ else:
217
+ gmsh.model.occ.mirror(main.dimtags, a,b,c,d)
218
+
219
+ for fp in mirror_obj._all_pointers:
220
+ fp.mirror(origin, direction)
221
+ return mirror_obj
222
+
223
+ def change_coordinate_system(main: GeoVolume,
224
+ new_cs: CoordinateSystem = GCS,
225
+ old_cs: CoordinateSystem = GCS):
226
+ """Moves the GeoVolume object from a current coordinate system to a new one.
227
+
228
+ The old and new coordinate system by default are the global coordinate system.
229
+ Thus only one needs to be provided to transform to and from these coordinate systems.
230
+
231
+ Args:
232
+ main (GeoVolume): The object to transform
233
+ new_cs (CoordinateSystem): The new coordinate system. Defaults to GCS
234
+ old_cs (CoordinateSystem, optional): The old coordinate system. Defaults to GCS.
235
+
236
+ Returns:
237
+ _type_: _description_
238
+ """
239
+ if new_cs._is_global and old_cs._is_global:
240
+ return main
241
+
242
+ M1 = old_cs.affine_to_global()
243
+ M2 = new_cs.affine_from_global()
244
+ # Transform to the global coordinate system.
245
+ if not old_cs._is_global:
246
+ gmsh.model.occ.affine_transform(main.dimtags, M1.flatten()[:12])
247
+ # Transform to a new coordinate system.
248
+ if not new_cs._is_global:
249
+ gmsh.model.occ.affineTransform(main.dimtags, M2.flatten()[:12])
250
+
251
+ for fp in main._all_pointers:
252
+ fp.affine_transform(M1)
253
+ fp.affine_transform(M2)
254
+ return main