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/step.py
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
1
1
|
import gmsh
|
|
2
2
|
from ..geometry import GeoPoint, GeoEdge, GeoVolume, GeoSurface, GeoObject
|
|
3
|
+
from ..selection import FaceSelection
|
|
4
|
+
from .shapes import Box
|
|
5
|
+
from .operations import unite
|
|
6
|
+
|
|
3
7
|
from pathlib import Path
|
|
4
8
|
import numpy as np
|
|
9
|
+
from typing import Callable
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
def _select_num(num1: float | None, num2: float | None) -> float:
|
|
12
|
+
if num1 is None and num2 is None:
|
|
13
|
+
return 0.0
|
|
14
|
+
if isinstance(num1, float) and num2 is None:
|
|
15
|
+
return num1
|
|
16
|
+
if num1 is None and isinstance(num2, float):
|
|
17
|
+
return num2
|
|
18
|
+
return max(num1, num2)
|
|
19
|
+
|
|
20
|
+
class _FaceSliceSelector:
|
|
21
|
+
|
|
22
|
+
def __init__(self, face_numbers: list[int], selector: Callable):
|
|
23
|
+
self.numbers: list[str] = face_numbers
|
|
24
|
+
self.selector: Callable = selector
|
|
25
|
+
|
|
26
|
+
def __getitem__(self, slice) -> FaceSelection:
|
|
27
|
+
nums = self.numbers.__getitem__(slice)
|
|
28
|
+
if isinstance(nums, int):
|
|
29
|
+
nums = [nums,]
|
|
30
|
+
return self.selector(*nums)
|
|
31
|
+
|
|
32
|
+
class StepVolume(GeoVolume):
|
|
33
|
+
"""The StepVoume class extens the EMerge GeoVolume class to add easier
|
|
34
|
+
face selection functionalities based on numbers as face names are not
|
|
35
|
+
imported currently
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
GeoVolume (_type_): _description_
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
_type_: _description_
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def _face_numbers(self) -> list[int]:
|
|
47
|
+
return sorted([int(name[4:]) for name in self._face_pointers.keys()])
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def face_slice(self) -> _FaceSliceSelector:
|
|
51
|
+
return _FaceSliceSelector(self._face_numbers, self.faces)
|
|
52
|
+
|
|
53
|
+
def faces(self, *numbers: int) -> FaceSelection:
|
|
54
|
+
"""Select a set of faces by number
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
FaceSelection: _description_
|
|
58
|
+
"""
|
|
59
|
+
names = [f'Face{num}' for num in numbers]
|
|
60
|
+
return super().faces(*names)
|
|
61
|
+
|
|
7
62
|
|
|
8
|
-
|
|
63
|
+
class STEPItems:
|
|
64
|
+
"""STEPItems imports geometries form a STEP file and exposes them to the user.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
def __init__(self, name: str, filename: str, unit: float = 1.0):
|
|
9
68
|
"""Imports the provided STEP file.
|
|
10
69
|
Specify the unit in case of scaling issues where mm units are not taken into consideration.
|
|
11
70
|
|
|
@@ -16,62 +75,139 @@ class STEPItems:
|
|
|
16
75
|
Raises:
|
|
17
76
|
FileNotFoundError: If a file does not exist
|
|
18
77
|
"""
|
|
78
|
+
self.name: str = name
|
|
79
|
+
|
|
19
80
|
stl_path = Path(filename)
|
|
20
|
-
gmsh.option.setNumber("Geometry.OCCScaling",
|
|
21
|
-
gmsh.option.setNumber("Geometry.OCCImportLabels",
|
|
81
|
+
gmsh.option.setNumber("Geometry.OCCScaling", unit)
|
|
82
|
+
gmsh.option.setNumber("Geometry.OCCImportLabels", 1)
|
|
22
83
|
|
|
23
84
|
if not stl_path.exists:
|
|
24
85
|
raise FileNotFoundError(f'File with name {stl_path} does not exist.')
|
|
25
86
|
|
|
26
87
|
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
88
|
|
|
34
|
-
self.points:
|
|
35
|
-
self.edges:
|
|
36
|
-
self.surfaces:
|
|
37
|
-
self.volumes:
|
|
38
|
-
|
|
89
|
+
self.points: list[GeoPoint] = []
|
|
90
|
+
self.edges: list[GeoEdge] = []
|
|
91
|
+
self.surfaces: list[GeoSurface] = []
|
|
92
|
+
self.volumes: list[GeoVolume] = []
|
|
93
|
+
|
|
39
94
|
i = 0
|
|
40
95
|
for dim, tag in dimtags:
|
|
41
|
-
name = gmsh.model.getPhysicalName(dim, tag)
|
|
96
|
+
name = gmsh.model.getPhysicalName(dim, tag) #for now, this doesn't actually ever work.
|
|
42
97
|
if name == '':
|
|
43
98
|
name = f'Obj{i}'
|
|
44
99
|
i+=1
|
|
45
100
|
if dim == 0:
|
|
46
|
-
self.points
|
|
101
|
+
self.points.append(GeoPoint(tag, name=f'{self.name}_{name}'))
|
|
47
102
|
elif dim == 1:
|
|
48
|
-
self.edges
|
|
103
|
+
self.edges.append(GeoEdge(tag, name=f'{self.name}_{name}'))
|
|
49
104
|
elif dim == 2:
|
|
50
|
-
self.surfaces
|
|
105
|
+
self.surfaces.append(GeoSurface(tag, name=f'{self.name}_{name}'))
|
|
51
106
|
elif dim == 3:
|
|
52
|
-
self.volumes
|
|
53
|
-
|
|
107
|
+
self.volumes.append(StepVolume(tag, name=f'{self.name}_{name}'))
|
|
108
|
+
|
|
109
|
+
gmsh.model.occ.synchronize()
|
|
110
|
+
|
|
54
111
|
@property
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
yield self.surfaces
|
|
59
|
-
yield self.volumes
|
|
60
|
-
|
|
112
|
+
def dictionary(self) -> dict[str, GeoObject]:
|
|
113
|
+
return {obj.name: obj for obj in self.objects}
|
|
114
|
+
|
|
61
115
|
@property
|
|
62
116
|
def objects(self) -> tuple[GeoObject,...]:
|
|
63
|
-
objects
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
117
|
+
"""Returns a list of all objects in the STEP file
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
tuple[GeoObject,...]: _description_
|
|
121
|
+
"""
|
|
122
|
+
return tuple(self.points+self.edges+self.surfaces+self.volumes)
|
|
123
|
+
|
|
124
|
+
def __getitem__(self, name: str) -> GeoObject | None:
|
|
125
|
+
return self.dictionary.get(name, None)
|
|
126
|
+
|
|
127
|
+
def as_volume(self) -> StepVolume:
|
|
128
|
+
"""Returns the 3D volumetric part of the STEP file as a single geometry
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
StepVolume: The resultant StepVolume(GeoVolume) object.
|
|
132
|
+
"""
|
|
133
|
+
if len(self.volumes)==1:
|
|
134
|
+
return self.volumes[0]
|
|
135
|
+
return unite(*self.volumes)._auto_face_tag()
|
|
136
|
+
|
|
137
|
+
def as_surface(self) -> GeoSurface:
|
|
138
|
+
"""Returns the 2D surface part of the STEP file as a single geometry
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
GeoSurface: The resultant GeoSurface object
|
|
143
|
+
"""
|
|
144
|
+
if len(self.surfaces)==1:
|
|
145
|
+
return self.surfaces[0]
|
|
146
|
+
return unite(*self.surfaces)
|
|
147
|
+
|
|
148
|
+
def as_edge(self) -> GeoEdge:
|
|
149
|
+
"""Returns the 1D Edge part of the STEP file as a single geometry
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
GeoEdge: The resultant GeoEdge object
|
|
153
|
+
"""
|
|
154
|
+
if len(self.edges)==1:
|
|
155
|
+
return self.edges[1]
|
|
156
|
+
return unite(*self.edges)
|
|
67
157
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
158
|
+
def as_point(self) -> GeoPoint:
|
|
159
|
+
"""Returns the 0D Point part of the STEP file as a single geometry
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
GeoPoint: The resultant GeoPoint object.
|
|
163
|
+
"""
|
|
164
|
+
if len(self.points)==1:
|
|
165
|
+
return self.points[0]
|
|
166
|
+
return unite(*self.points)
|
|
167
|
+
|
|
168
|
+
def enclose(self,
|
|
169
|
+
margin: float = None,
|
|
170
|
+
x_margins: tuple[float, float] = (None, None),
|
|
171
|
+
y_margins: tuple[float, float] = (None, None),
|
|
172
|
+
z_margins: tuple[float, float] = (None, None)) -> Box:
|
|
173
|
+
"""Create an enclosing bounding box for the step model.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
margin (float, optional): _description_. Defaults to 0.
|
|
177
|
+
x_margins (tuple[float, float], optional): _description_. Defaults to (0., 0.).
|
|
178
|
+
y_margins (tuple[float, float], optional): _description_. Defaults to (0., 0.).
|
|
179
|
+
z_margins (tuple[float, float], optional): _description_. Defaults to (0., 0.).
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Box: _description_
|
|
183
|
+
"""
|
|
184
|
+
xminm = _select_num(margin, x_margins[0])
|
|
185
|
+
xmaxm = _select_num(margin, x_margins[1])
|
|
186
|
+
yminm = _select_num(margin, y_margins[0])
|
|
187
|
+
ymaxm = _select_num(margin, y_margins[1])
|
|
188
|
+
zminm = _select_num(margin, z_margins[0])
|
|
189
|
+
zmaxm = _select_num(margin, z_margins[1])
|
|
190
|
+
|
|
191
|
+
xmin = 1000000
|
|
192
|
+
xmax = -1000000
|
|
193
|
+
ymin = 1000000
|
|
194
|
+
ymax = -1000000
|
|
195
|
+
zmin = 1000000
|
|
196
|
+
zmax = -1000000
|
|
197
|
+
|
|
198
|
+
for obj in self.objects:
|
|
199
|
+
for dim, tag in obj.dimtags:
|
|
200
|
+
x1, y1, z1, x2, y2, z2 = gmsh.model.occ.getBoundingBox(dim, tag)
|
|
201
|
+
xmin = min(xmin, x1)
|
|
202
|
+
xmax = max(xmax, x2)
|
|
203
|
+
ymin = min(ymin, y1)
|
|
204
|
+
ymax = max(ymax, y2)
|
|
205
|
+
zmin = min(zmin, z1)
|
|
206
|
+
zmax = max(zmax, z2)
|
|
207
|
+
|
|
208
|
+
width = xmax-xmin + xminm + xmaxm
|
|
209
|
+
depth = ymax-ymin + yminm + ymaxm
|
|
210
|
+
height = zmax -zmin + zminm + zmaxm
|
|
211
|
+
|
|
212
|
+
return Box(width, depth, height, (xmin-xminm, ymin-yminm, zmin-zminm)).background()
|
|
213
|
+
|
emerge/_emerge/geometry.py
CHANGED
|
@@ -18,13 +18,12 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
import gmsh # type: ignore
|
|
20
20
|
from .material import Material, AIR
|
|
21
|
-
from .selection import FaceSelection, DomainSelection, EdgeSelection, PointSelection, Selection
|
|
21
|
+
from .selection import FaceSelection, DomainSelection, EdgeSelection, PointSelection, Selection, SelectionError
|
|
22
22
|
from loguru import logger
|
|
23
|
-
from typing import Literal, Any, Iterable,
|
|
23
|
+
from typing import Literal, Any, Iterable, Callable
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
def _map_tags(tags: list[int], mapping: dict[int, list[int]]):
|
|
29
28
|
new_tags = []
|
|
30
29
|
for tag in tags:
|
|
@@ -34,6 +33,7 @@ def _map_tags(tags: list[int], mapping: dict[int, list[int]]):
|
|
|
34
33
|
def _bbcenter(x1, y1, z1, x2, y2, z2):
|
|
35
34
|
return np.array([(x1+x2)/2, (y1+y2)/2, (z1+z2)/2])
|
|
36
35
|
|
|
36
|
+
|
|
37
37
|
FaceNames = Literal['back','front','left','right','top','bottom']
|
|
38
38
|
|
|
39
39
|
class _KEY_GENERATOR:
|
|
@@ -88,15 +88,12 @@ class _GeometryManager:
|
|
|
88
88
|
def sign_in(self, modelname: str) -> None:
|
|
89
89
|
# if modelname not in self.geometry_list:
|
|
90
90
|
# self.geometry_list[modelname] = []
|
|
91
|
-
if modelname is self.geometry_list:
|
|
92
|
-
logger.warning(f'{modelname} already exist, Geometries will be reset.')
|
|
93
91
|
self.geometry_list[modelname] = dict()
|
|
94
92
|
self.geometry_names[modelname] = set()
|
|
95
93
|
self.active = modelname
|
|
96
94
|
|
|
97
95
|
def reset(self, modelname: str) -> None:
|
|
98
|
-
self.
|
|
99
|
-
self.geometry_names[modelname] = set()
|
|
96
|
+
self.sign_in(modelname)
|
|
100
97
|
|
|
101
98
|
def lowest_priority(self) -> int:
|
|
102
99
|
return min([geo._priority for geo in self.all_geometries()])
|
|
@@ -123,9 +120,9 @@ class _FacePointer:
|
|
|
123
120
|
normals: list[np.ndarray]) -> list[int]:
|
|
124
121
|
tags = []
|
|
125
122
|
for (d,t), o, n in zip(dimtags, origins, normals):
|
|
126
|
-
normdist = np.abs((o-self.o)@self.n)
|
|
123
|
+
normdist = np.abs((o-self.o) @ self.n)
|
|
127
124
|
dotnorm = np.abs(n@self.n)
|
|
128
|
-
if normdist < 1e-
|
|
125
|
+
if normdist < 1e-5 and dotnorm > 0.999:
|
|
129
126
|
tags.append(t)
|
|
130
127
|
return tags
|
|
131
128
|
|
|
@@ -235,6 +232,8 @@ class _FacePointer:
|
|
|
235
232
|
def copy(self) -> _FacePointer:
|
|
236
233
|
return _FacePointer(self.o, self.n)
|
|
237
234
|
|
|
235
|
+
def __eq__(self, other: _FacePointer) -> bool:
|
|
236
|
+
return (np.linalg.norm(self.o - other.o) + np.linalg.norm(self.n - other.n)) < 1e-6
|
|
238
237
|
|
|
239
238
|
_GENERATOR = _KEY_GENERATOR()
|
|
240
239
|
_GEOMANAGER = _GeometryManager()
|
|
@@ -244,6 +243,7 @@ class GeoObject:
|
|
|
244
243
|
"""
|
|
245
244
|
dim: int = -1
|
|
246
245
|
_default_name: str = 'GeoObject'
|
|
246
|
+
|
|
247
247
|
def __init__(self, tags: list[int] | None = None, name: str | None = None):
|
|
248
248
|
if tags is None:
|
|
249
249
|
tags = []
|
|
@@ -257,7 +257,7 @@ class GeoObject:
|
|
|
257
257
|
self._embeddings: list[GeoObject] = []
|
|
258
258
|
self._face_pointers: dict[str, _FacePointer] = dict()
|
|
259
259
|
self._tools: dict[int, dict[str, _FacePointer]] = dict()
|
|
260
|
-
|
|
260
|
+
self._hidden: bool = False
|
|
261
261
|
self._key = _GENERATOR.new()
|
|
262
262
|
self._aux_data: dict[str, Any] = dict()
|
|
263
263
|
self._priority: int = 10
|
|
@@ -266,7 +266,24 @@ class GeoObject:
|
|
|
266
266
|
|
|
267
267
|
self.give_name(name)
|
|
268
268
|
_GEOMANAGER.submit_geometry(self)
|
|
269
|
-
|
|
269
|
+
self._fill_face_pointers()
|
|
270
|
+
|
|
271
|
+
def _fill_face_pointers(self) -> None:
|
|
272
|
+
""" Fills the list of all face pointers of this object
|
|
273
|
+
"""
|
|
274
|
+
current = list(self._face_pointers.values())
|
|
275
|
+
ctr = 0
|
|
276
|
+
gmsh.model.occ.synchronize()
|
|
277
|
+
for dim, tag in gmsh.model.get_boundary(self.dimtags, True, False):
|
|
278
|
+
if dim != 2:
|
|
279
|
+
continue
|
|
280
|
+
o = gmsh.model.occ.get_center_of_mass(2, tag)
|
|
281
|
+
n = gmsh.model.get_normal(tag, (0,0))
|
|
282
|
+
fp = _FacePointer(o, n)
|
|
283
|
+
if fp not in current:
|
|
284
|
+
self._face_pointers[f'Face{ctr}'] = fp
|
|
285
|
+
ctr += 1
|
|
286
|
+
|
|
270
287
|
def _store(self, name: str, data: Any) -> None:
|
|
271
288
|
"""Store a property as auxilliary data under a given name
|
|
272
289
|
|
|
@@ -364,6 +381,19 @@ class GeoObject:
|
|
|
364
381
|
def _data(self, *labels) -> tuple[Any | None, ...]:
|
|
365
382
|
return tuple([self._aux_data[lab] for lab in labels])
|
|
366
383
|
|
|
384
|
+
def _replace_pointer(self, name: str, face_pointer: _FacePointer) -> None:
|
|
385
|
+
"""Will be used to replace face pointers so only one unique one exists.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
name (str): _description_
|
|
389
|
+
face_pointer (_FacePointer): _description_
|
|
390
|
+
"""
|
|
391
|
+
for key, fp in self._face_pointers.items():
|
|
392
|
+
if fp == face_pointer:
|
|
393
|
+
self._face_pointers.pop(key)
|
|
394
|
+
break
|
|
395
|
+
self._face_pointers[name] = face_pointer
|
|
396
|
+
|
|
367
397
|
def _add_face_pointer(self,
|
|
368
398
|
name: str,
|
|
369
399
|
origin: np.ndarray | None = None,
|
|
@@ -381,15 +411,16 @@ class GeoObject:
|
|
|
381
411
|
ValueError: _description_
|
|
382
412
|
"""
|
|
383
413
|
if tag is not None:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
414
|
+
origin = gmsh.model.occ.get_center_of_mass(2, tag)
|
|
415
|
+
normal = gmsh.model.get_normal(tag, (0,0))
|
|
416
|
+
|
|
417
|
+
if origin is None or normal is None:
|
|
418
|
+
raise ValueError('Eitehr a tag or an origin + normal must be provided!')
|
|
419
|
+
fp = _FacePointer(origin, normal)
|
|
420
|
+
self._face_pointers[name] = fp
|
|
421
|
+
#self._replace_pointer(name, fp) <-- Will be added in later versions
|
|
422
|
+
|
|
423
|
+
|
|
393
424
|
def make_copy(self) -> GeoObject:
|
|
394
425
|
""" Copies this object and returns a new object (also in GMSH)"""
|
|
395
426
|
new_dimtags = gmsh.model.occ.copy(self.dimtags)
|
|
@@ -455,7 +486,7 @@ class GeoObject:
|
|
|
455
486
|
def _face_tags(self, name: FaceNames, tool: GeoObject | None = None) -> list[int]:
|
|
456
487
|
names = self._all_pointer_names
|
|
457
488
|
if name not in names:
|
|
458
|
-
raise ValueError(f'The face {name} does not exist in {self}')
|
|
489
|
+
raise ValueError(f'The face {name} does not exist in {self}. Only {list(self._face_pointers.keys())}')
|
|
459
490
|
|
|
460
491
|
gmsh.model.occ.synchronize()
|
|
461
492
|
dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
|
|
@@ -470,6 +501,7 @@ class GeoObject:
|
|
|
470
501
|
logger.info(f'Selected face {tags}.')
|
|
471
502
|
return tags
|
|
472
503
|
|
|
504
|
+
|
|
473
505
|
def set_material(self, material: Material) -> GeoObject:
|
|
474
506
|
self.material = material
|
|
475
507
|
return self
|
|
@@ -566,6 +598,9 @@ class GeoObject:
|
|
|
566
598
|
Returns:
|
|
567
599
|
FaceSelection: The selected faces
|
|
568
600
|
"""
|
|
601
|
+
# if not self._exists:
|
|
602
|
+
# raise SelectionError('Cannot select faces from an object that no longer exists.')
|
|
603
|
+
|
|
569
604
|
if isinstance(exclude, str):
|
|
570
605
|
exclude = (exclude,)
|
|
571
606
|
|
|
@@ -575,11 +610,13 @@ class GeoObject:
|
|
|
575
610
|
if tags is None:
|
|
576
611
|
tags = []
|
|
577
612
|
|
|
578
|
-
|
|
579
613
|
for name in exclude:
|
|
580
614
|
tags.extend(self.face(name, tool=tool).tags)
|
|
581
615
|
dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
|
|
582
|
-
|
|
616
|
+
selname = 'Boundary'
|
|
617
|
+
if exclude:
|
|
618
|
+
selname = selname + 'Except[' + ','.join(exclude) + ']'
|
|
619
|
+
return FaceSelection([t for d,t in dimtags if t not in tags])._named(selname)
|
|
583
620
|
|
|
584
621
|
def face(self, name: FaceNames = None, tool: GeoObject | None = None, no: FaceNames = None) -> FaceSelection:
|
|
585
622
|
"""Returns the FaceSelection for a given face name.
|
|
@@ -599,8 +636,16 @@ class GeoObject:
|
|
|
599
636
|
if no is not None:
|
|
600
637
|
return self.boundary(exclude=no)
|
|
601
638
|
|
|
602
|
-
return FaceSelection(self._face_tags(name, tool))
|
|
639
|
+
return FaceSelection(self._face_tags(name, tool))._named(name)
|
|
603
640
|
|
|
641
|
+
def all_faces(self) -> list[FaceSelection]:
|
|
642
|
+
"""Returns a list of all face selections of this object
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
list[FaceSelection]: A list of all face selections
|
|
646
|
+
"""
|
|
647
|
+
return [self.face(name) for name in self._face_pointers]
|
|
648
|
+
|
|
604
649
|
def faces(self, *names: FaceNames, tool: GeoObject | None = None) -> FaceSelection:
|
|
605
650
|
"""Returns the FaceSelection for a given face names.
|
|
606
651
|
|
|
@@ -616,8 +661,26 @@ class GeoObject:
|
|
|
616
661
|
tags = []
|
|
617
662
|
for name in names:
|
|
618
663
|
tags.extend(self._face_tags(name, tool))
|
|
619
|
-
return FaceSelection(tags)
|
|
620
|
-
|
|
664
|
+
return FaceSelection(tags)._named('Faces[' + ','.join(names) + ']')
|
|
665
|
+
|
|
666
|
+
def hide(self) -> GeoObject:
|
|
667
|
+
"""Hides the object from views
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
GeoObject: _description_
|
|
671
|
+
"""
|
|
672
|
+
self._hidden = True
|
|
673
|
+
return self
|
|
674
|
+
|
|
675
|
+
def unhide(self) -> GeoObject:
|
|
676
|
+
"""Unhides the object from views
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
GeoObject: _description_
|
|
680
|
+
"""
|
|
681
|
+
self._hidden = False
|
|
682
|
+
return self
|
|
683
|
+
|
|
621
684
|
@property
|
|
622
685
|
def dimtags(self) -> list[tuple[int, int]]:
|
|
623
686
|
return [(self.dim, tag) for tag in self.tags]
|
|
@@ -643,7 +706,22 @@ class GeoObject:
|
|
|
643
706
|
def remove(self) -> None:
|
|
644
707
|
self._exists = False
|
|
645
708
|
gmsh.model.occ.remove(self.dimtags, True)
|
|
646
|
-
|
|
709
|
+
|
|
710
|
+
def extract(self, tags: int | list[int]) -> GeoObject:
|
|
711
|
+
"""Returns a new GeoObject of the same dimensional type that isolates a set of given tags
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
tags (list[int]): A list of GMSH tags
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
GeoObject: _description_
|
|
718
|
+
"""
|
|
719
|
+
if isinstance(tags, int):
|
|
720
|
+
tags = [tags,]
|
|
721
|
+
self.tags = [t for t in self.tags if t not in tags]
|
|
722
|
+
dts = [(self.dim, t) for t in tags]
|
|
723
|
+
return GeoObject.from_dimtags(dts)
|
|
724
|
+
|
|
647
725
|
class GeoVolume(GeoObject):
|
|
648
726
|
'''GeoVolume is an interface to the GMSH CAD kernel. It does not represent EMerge
|
|
649
727
|
specific geometry data.'''
|
|
@@ -660,10 +738,94 @@ class GeoVolume(GeoObject):
|
|
|
660
738
|
else:
|
|
661
739
|
self.tags = [tag,]
|
|
662
740
|
|
|
741
|
+
self._fill_face_pointers()
|
|
742
|
+
self._autoname()
|
|
743
|
+
|
|
663
744
|
@property
|
|
664
745
|
def selection(self) -> DomainSelection:
|
|
665
746
|
return DomainSelection(self.tags)
|
|
666
747
|
|
|
748
|
+
def _auto_face_tag(self) -> GeoVolume:
|
|
749
|
+
self._face_pointers = dict()
|
|
750
|
+
self._tools = dict()
|
|
751
|
+
logger.trace('Automatically assigning face pointers.')
|
|
752
|
+
ctr = 0
|
|
753
|
+
for tag in self.tags:
|
|
754
|
+
loops = gmsh.model.occ.getSurfaceLoops(tag)
|
|
755
|
+
for loopcluster in loops[1]:
|
|
756
|
+
for st in loopcluster:
|
|
757
|
+
self._add_face_pointer(f'Face{ctr}', tag=st)
|
|
758
|
+
ctr += 1
|
|
759
|
+
return self
|
|
760
|
+
|
|
761
|
+
def exterior_faces(self, base_object: GeoObject) -> FaceSelection:
|
|
762
|
+
"""Select the exterior faces of an object based on the face
|
|
763
|
+
pointers of an original base object. For example
|
|
764
|
+
|
|
765
|
+
Example:
|
|
766
|
+
>>> cheese = em.geo.Box(...)
|
|
767
|
+
>>> hole = em.geo.Box(...)
|
|
768
|
+
>>> holed_cheese = em.geo.subtract(cheese, hole)
|
|
769
|
+
>>> holed_cheese.exterior_faces(cheese)
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
base_object (GeoObject): The object to base the selection on.
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
FaceSelection: The resultant face selection
|
|
776
|
+
"""
|
|
777
|
+
dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
|
|
778
|
+
|
|
779
|
+
normals = [gmsh.model.get_normal(t, [0,0]) for d,t, in dimtags]
|
|
780
|
+
origins = [gmsh.model.occ.get_center_of_mass(d, t) for d,t in dimtags]
|
|
781
|
+
|
|
782
|
+
tool_tags = []
|
|
783
|
+
for key, tool in self._tools.items():
|
|
784
|
+
if key == base_object._key:
|
|
785
|
+
continue
|
|
786
|
+
for name in tool.keys():
|
|
787
|
+
tags = tool[name].find(dimtags, origins, normals)
|
|
788
|
+
tool_tags.extend(tags)
|
|
789
|
+
tags = [dt[1] for dt in dimtags if dt[1] not in tool_tags]
|
|
790
|
+
return FaceSelection(tags)
|
|
791
|
+
|
|
792
|
+
def _find_fp(self, condition: Callable):
|
|
793
|
+
for key, fp in self._face_pointers.items():
|
|
794
|
+
if condition(fp):
|
|
795
|
+
return key, fp
|
|
796
|
+
return None, None
|
|
797
|
+
|
|
798
|
+
def _rename_fp(self, new_name: str, condition: Callable):
|
|
799
|
+
name, fp = self._find_fp(condition)
|
|
800
|
+
if fp is not None:
|
|
801
|
+
self._face_pointers.pop(name)
|
|
802
|
+
self._face_pointers[new_name] = fp
|
|
803
|
+
|
|
804
|
+
def _autoname(self) -> None:
|
|
805
|
+
if len(self._face_pointers)==0:
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
xs = []
|
|
809
|
+
ys = []
|
|
810
|
+
zs = []
|
|
811
|
+
for fp in self._face_pointers.values():
|
|
812
|
+
xs.append(fp.o[0])
|
|
813
|
+
ys.append(fp.o[1])
|
|
814
|
+
zs.append(fp.o[2])
|
|
815
|
+
minx = min(xs)
|
|
816
|
+
maxx = max(xs)
|
|
817
|
+
miny = min(ys)
|
|
818
|
+
maxy = max(ys)
|
|
819
|
+
minz = min(zs)
|
|
820
|
+
maxz = max(zs)
|
|
821
|
+
|
|
822
|
+
self._rename_fp('-x', lambda fp: (fp.o[0]==minx) and np.abs(np.dot(fp.n, np.array([1,0,0])))>0.999)
|
|
823
|
+
self._rename_fp('+x', lambda fp: (fp.o[0]==maxx) and np.abs(np.dot(fp.n, np.array([1,0,0])))>0.999)
|
|
824
|
+
self._rename_fp('-y', lambda fp: (fp.o[1]==miny) and np.abs(np.dot(fp.n, np.array([0,1,0])))>0.999)
|
|
825
|
+
self._rename_fp('+y', lambda fp: (fp.o[1]==maxy) and np.abs(np.dot(fp.n, np.array([0,1,0])))>0.999)
|
|
826
|
+
self._rename_fp('-z', lambda fp: (fp.o[2]==minz) and np.abs(np.dot(fp.n, np.array([0,0,1])))>0.999)
|
|
827
|
+
self._rename_fp('+z', lambda fp: (fp.o[2]==maxz) and np.abs(np.dot(fp.n, np.array([0,0,1])))>0.999)
|
|
828
|
+
|
|
667
829
|
class GeoPoint(GeoObject):
|
|
668
830
|
dim = 0
|
|
669
831
|
_default_name: str = 'GeoPoint'
|
emerge/_emerge/logsettings.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
from loguru import logger
|
|
19
19
|
import sys
|
|
20
|
-
from typing import Literal
|
|
20
|
+
from typing import Literal, Generator
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
import os
|
|
23
23
|
from collections import deque
|
|
@@ -132,4 +132,28 @@ class LogController:
|
|
|
132
132
|
self.file_level = loglevel
|
|
133
133
|
os.environ["EMERGE_FILE_LOGLEVEL"] = loglevel
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
class DebugCollector:
|
|
136
|
+
"""The DebugController is used by EMerge to collect heuristic
|
|
137
|
+
warnings for detections of things that might be causing problems but aren't
|
|
138
|
+
guaranteed to cause them. These logs will be printed at the end of a simulation
|
|
139
|
+
to ensure that users are aware of them if they abort simulations.
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
def __init__(self):
|
|
143
|
+
self.reports: list[str] = []
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def any_warnings(self) -> bool:
|
|
147
|
+
return len(self.reports)>0
|
|
148
|
+
|
|
149
|
+
def add_report(self, message: str):
|
|
150
|
+
self.reports.append(message)
|
|
151
|
+
|
|
152
|
+
def all_reports(self) -> Generator[tuple[int, str], None, None]:
|
|
153
|
+
|
|
154
|
+
for i, message in enumerate(self.reports):
|
|
155
|
+
yield i+1, message
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
LOG_CONTROLLER = LogController()
|
|
159
|
+
DEBUG_COLLECTOR = DebugCollector()
|
emerge/_emerge/material.py
CHANGED
|
@@ -244,6 +244,7 @@ class FreqCoordDependent(MatProperty):
|
|
|
244
244
|
if scalar is not None:
|
|
245
245
|
def _func(f, x, y, z) -> np.ndarray:
|
|
246
246
|
return np.eye(3)[:, :, None] * scalar(f,x,y,z)[None, None, :]
|
|
247
|
+
|
|
247
248
|
if vector is not None:
|
|
248
249
|
def _func(f,x, y, z) -> np.ndarray:
|
|
249
250
|
N = x.shape[0]
|
|
@@ -251,6 +252,7 @@ class FreqCoordDependent(MatProperty):
|
|
|
251
252
|
idx = np.arange(3)
|
|
252
253
|
out[idx, idx, :] = vector(f,x,y,z)
|
|
253
254
|
return out
|
|
255
|
+
|
|
254
256
|
if matrix is not None:
|
|
255
257
|
_func = matrix
|
|
256
258
|
|