radia 1.3.2__py3-none-any.whl → 1.3.4__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.
- python/nastran_mesh_import.py +441 -0
- python/netgen_mesh_import.py +572 -0
- python/rad_ngsolve.pyd +0 -0
- python/radia_field_cached.py +274 -274
- python/radia_ngsolve.pyd +0 -0
- python/radia_ngsolve_utils.py +293 -0
- python/radia_vtk_export.py +106 -19
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/METADATA +16 -19
- radia-1.3.4.dist-info/RECORD +17 -0
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/licenses/LICENSE +93 -93
- python/nastran_reader.py +0 -295
- python/radia.pyd +0 -0
- radia-1.3.2.dist-info/RECORD +0 -15
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/WHEEL +0 -0
- {radia-1.3.2.dist-info → radia-1.3.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
netgen_mesh_import.py - Convert Netgen meshes to Radia geometry
|
|
4
|
+
|
|
5
|
+
This module provides functionality to import NGSolve/Netgen tetrahedral meshes
|
|
6
|
+
into Radia's magnetic field computation framework.
|
|
7
|
+
|
|
8
|
+
Author: Radia Development Team
|
|
9
|
+
Created: 2025-11-21
|
|
10
|
+
Version: 0.1.0
|
|
11
|
+
|
|
12
|
+
Functions
|
|
13
|
+
---------
|
|
14
|
+
netgen_mesh_to_radia : Convert NGSolve mesh to Radia geometry
|
|
15
|
+
extract_tetrahedra : Extract tetrahedral elements from mesh
|
|
16
|
+
create_radia_tetrahedron : Create single tetrahedron in Radia
|
|
17
|
+
|
|
18
|
+
Example
|
|
19
|
+
-------
|
|
20
|
+
>>> from ngsolve import Mesh
|
|
21
|
+
>>> from netgen.occ import Box, OCCGeometry
|
|
22
|
+
>>> import radia as rad
|
|
23
|
+
>>> from netgen_mesh_import import netgen_mesh_to_radia
|
|
24
|
+
>>>
|
|
25
|
+
>>> rad.FldUnits('m')
|
|
26
|
+
>>> geo = OCCGeometry(Box((0, 0, 0), (0.01, 0.01, 0.01)))
|
|
27
|
+
>>> mesh = Mesh(geo.GenerateMesh(maxh=0.003))
|
|
28
|
+
>>> mag_obj = netgen_mesh_to_radia(mesh,
|
|
29
|
+
... material={'magnetization': [0, 0, 1.2]},
|
|
30
|
+
... units='m')
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import sys
|
|
34
|
+
import radia as rad
|
|
35
|
+
from ngsolve import VOL, ET
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Standard tetrahedral face topology (1-indexed for Radia)
|
|
39
|
+
# Face winding REVERSED from Nastran CTETRA standard to ensure outward normals
|
|
40
|
+
# This correction was identified through test_face_winding.py (2025-11-22)
|
|
41
|
+
# Vertices: v0, v1, v2, v3 (0-indexed) -> 1, 2, 3, 4 (Radia 1-indexed)
|
|
42
|
+
TETRA_FACES = [
|
|
43
|
+
[1, 3, 2], # Face 0: v0-v2-v1
|
|
44
|
+
[1, 2, 4], # Face 1: v0-v1-v3
|
|
45
|
+
[2, 3, 4], # Face 2: v1-v2-v3
|
|
46
|
+
[3, 1, 4] # Face 3: v2-v0-v3
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Hexahedral face topology (1-indexed for Radia)
|
|
50
|
+
# Standard brick/hexahedron with 8 vertices
|
|
51
|
+
# Vertices numbered as: v0-v7 (0-indexed) -> 1-8 (Radia 1-indexed)
|
|
52
|
+
#
|
|
53
|
+
# WARNING: Hexahedral elements have known issues in Radia MMM
|
|
54
|
+
# ============================================================
|
|
55
|
+
# 1. Non-convex (concave) meshes: Hexahedral meshes from Netgen may produce
|
|
56
|
+
# non-convex (concave) elements, which cause errors in Radia MMM.
|
|
57
|
+
# Radia requires convex polyhedra for correct field computation.
|
|
58
|
+
#
|
|
59
|
+
# 2. Netgen 3D hex meshing limitations: Netgen's 3D hexahedral meshing
|
|
60
|
+
# functionality is very limited and often produces poor quality meshes.
|
|
61
|
+
#
|
|
62
|
+
# EXCEPTION: Regular cubic hexahedral meshes (structured grids) are safe
|
|
63
|
+
# and do not produce concave elements. These work correctly in Radia.
|
|
64
|
+
#
|
|
65
|
+
# Recommended: Use tetrahedral meshes for general geometries
|
|
66
|
+
HEX_FACES = [
|
|
67
|
+
[1, 4, 3, 2], # Bottom face (z=0)
|
|
68
|
+
[5, 6, 7, 8], # Top face (z=1)
|
|
69
|
+
[1, 2, 6, 5], # Front face (y=0)
|
|
70
|
+
[3, 4, 8, 7], # Back face (y=1)
|
|
71
|
+
[1, 5, 8, 4], # Left face (x=0)
|
|
72
|
+
[2, 3, 7, 6] # Right face (x=1)
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
# Wedge/Pentahedron face topology (1-indexed for Radia)
|
|
76
|
+
# Standard wedge/prism with 6 vertices (triangular prism)
|
|
77
|
+
# Vertices: v0-v5 (0-indexed) -> 1-6 (Radia 1-indexed)
|
|
78
|
+
# Bottom triangle: v0, v1, v2
|
|
79
|
+
# Top triangle: v3, v4, v5
|
|
80
|
+
WEDGE_FACES = [
|
|
81
|
+
[1, 3, 2], # Bottom triangle face (v0-v2-v1)
|
|
82
|
+
[4, 5, 6], # Top triangle face (v3-v4-v5)
|
|
83
|
+
[1, 2, 5, 4], # Quad face (v0-v1-v4-v3)
|
|
84
|
+
[2, 3, 6, 5], # Quad face (v1-v2-v5-v4)
|
|
85
|
+
[3, 1, 4, 6] # Quad face (v2-v0-v3-v5)
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# Pyramid face topology (1-indexed for Radia)
|
|
89
|
+
# Standard pyramid with 5 vertices
|
|
90
|
+
# Vertices: v0-v4 (0-indexed) -> 1-5 (Radia 1-indexed)
|
|
91
|
+
# Base quad: v0, v1, v2, v3
|
|
92
|
+
# Apex: v4
|
|
93
|
+
PYRAMID_FACES = [
|
|
94
|
+
[1, 4, 3, 2], # Base quad face (v0-v3-v2-v1)
|
|
95
|
+
[1, 2, 5], # Triangle face (v0-v1-v4)
|
|
96
|
+
[2, 3, 5], # Triangle face (v1-v2-v4)
|
|
97
|
+
[3, 4, 5], # Triangle face (v2-v3-v4)
|
|
98
|
+
[4, 1, 5] # Triangle face (v3-v0-v4)
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def create_radia_hexahedron(vertices, magnetization=None):
|
|
103
|
+
"""
|
|
104
|
+
Create a single hexahedral (brick) polyhedron in Radia.
|
|
105
|
+
|
|
106
|
+
WARNING: Hexahedral elements may cause numerical issues in Radia MMM.
|
|
107
|
+
Tetrahedral meshes are recommended for better stability.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
vertices : list of list
|
|
112
|
+
8 vertices: [[x1,y1,z1], ..., [x8,y8,z8]]
|
|
113
|
+
Vertices should follow standard hexahedron numbering
|
|
114
|
+
magnetization : list, optional
|
|
115
|
+
Magnetization vector [Mx, My, Mz] in Tesla
|
|
116
|
+
Default: [0, 0, 0]
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
int
|
|
121
|
+
Radia object ID
|
|
122
|
+
|
|
123
|
+
Raises
|
|
124
|
+
------
|
|
125
|
+
RuntimeError
|
|
126
|
+
If Radia polyhedron creation fails
|
|
127
|
+
|
|
128
|
+
Notes
|
|
129
|
+
-----
|
|
130
|
+
Face topology uses HEX_FACES constant (see module level definition)
|
|
131
|
+
"""
|
|
132
|
+
if magnetization is None:
|
|
133
|
+
magnetization = [0, 0, 0]
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
# Create polyhedron with hexahedral faces
|
|
137
|
+
poly_id = rad.ObjPolyhdr(vertices, HEX_FACES, magnetization)
|
|
138
|
+
return poly_id
|
|
139
|
+
except Exception as e:
|
|
140
|
+
raise RuntimeError(
|
|
141
|
+
f"Failed to create Radia hexahedron: {e}\n"
|
|
142
|
+
f"Vertices: {vertices}\n"
|
|
143
|
+
f"Faces: {HEX_FACES}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def create_radia_tetrahedron(vertices, magnetization=None):
|
|
148
|
+
"""
|
|
149
|
+
Create a single tetrahedral polyhedron in Radia.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
vertices : list of list
|
|
154
|
+
4 vertices: [[x1,y1,z1], [x2,y2,z2], [x3,y3,z3], [x4,y4,z4]]
|
|
155
|
+
Coordinates should be in units matching rad.FldUnits() setting.
|
|
156
|
+
magnetization : list, optional
|
|
157
|
+
Magnetization vector [Mx, My, Mz] in Tesla.
|
|
158
|
+
Default: [0, 0, 0] (no magnetization)
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
int
|
|
163
|
+
Radia object ID
|
|
164
|
+
|
|
165
|
+
Raises
|
|
166
|
+
------
|
|
167
|
+
RuntimeError
|
|
168
|
+
If Radia polyhedron creation fails
|
|
169
|
+
|
|
170
|
+
Notes
|
|
171
|
+
-----
|
|
172
|
+
Face topology (1-indexed):
|
|
173
|
+
[1, 3, 2], # Bottom face
|
|
174
|
+
[1, 2, 4], # Front face
|
|
175
|
+
[1, 4, 3], # Left face
|
|
176
|
+
[2, 3, 4] # Back face
|
|
177
|
+
|
|
178
|
+
Tetrahedra are always convex, making them ideal for Radia polyhedra.
|
|
179
|
+
"""
|
|
180
|
+
if magnetization is None:
|
|
181
|
+
magnetization = [0, 0, 0]
|
|
182
|
+
|
|
183
|
+
if len(vertices) != 4:
|
|
184
|
+
raise ValueError(f"Tetrahedron must have exactly 4 vertices, got {len(vertices)}")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
obj_id = rad.ObjPolyhdr(vertices, TETRA_FACES, magnetization)
|
|
188
|
+
return obj_id
|
|
189
|
+
except Exception as e:
|
|
190
|
+
raise RuntimeError(f"Failed to create Radia tetrahedron: {e}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def extract_elements(mesh, material_filter=None, allow_hex=False):
|
|
194
|
+
"""
|
|
195
|
+
Extract volume elements (tetrahedra and optionally hexahedra) from NGSolve mesh.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
mesh : ngsolve.Mesh
|
|
200
|
+
Input mesh from Netgen/NGSolve
|
|
201
|
+
material_filter : str, list of str, or None, optional
|
|
202
|
+
Filter elements by material name(s):
|
|
203
|
+
- str: Import only elements with this material name
|
|
204
|
+
- list of str: Import only elements with these material names
|
|
205
|
+
- None: Import all elements (default)
|
|
206
|
+
allow_hex : bool, optional
|
|
207
|
+
If True, allow hexahedral elements (ET.HEX)
|
|
208
|
+
If False, raise error on non-tetrahedral elements
|
|
209
|
+
Default: False
|
|
210
|
+
WARNING: Hexahedral elements may cause issues in Radia MMM
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
tuple of (list of dict, int)
|
|
215
|
+
(elements, skipped_count) where elements is list of dicts:
|
|
216
|
+
{
|
|
217
|
+
'vertices': [[x1,y1,z1], ...], # 4 for TET, 8 for HEX
|
|
218
|
+
'element_index': int,
|
|
219
|
+
'element_type': str, # 'TET' or 'HEX'
|
|
220
|
+
'material': str # Material name
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Raises
|
|
224
|
+
------
|
|
225
|
+
ValueError
|
|
226
|
+
If mesh contains unsupported element types
|
|
227
|
+
|
|
228
|
+
Notes
|
|
229
|
+
-----
|
|
230
|
+
- Vertex coordinates extracted from mesh.vertices
|
|
231
|
+
- Tetrahedral elements (ET.TET): 4 vertices
|
|
232
|
+
- Hexahedral elements (ET.HEX): 8 vertices (if allow_hex=True)
|
|
233
|
+
- Element indices are 0-based
|
|
234
|
+
- Material filtering reduces import time for multi-material meshes
|
|
235
|
+
"""
|
|
236
|
+
# Normalize material_filter to set
|
|
237
|
+
if material_filter is None:
|
|
238
|
+
allowed_materials = None
|
|
239
|
+
elif isinstance(material_filter, str):
|
|
240
|
+
allowed_materials = {material_filter}
|
|
241
|
+
elif isinstance(material_filter, (list, tuple)):
|
|
242
|
+
allowed_materials = set(material_filter)
|
|
243
|
+
else:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"material_filter must be str, list, or None, got {type(material_filter)}"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
elements = []
|
|
249
|
+
skipped_count = 0
|
|
250
|
+
hex_count = 0
|
|
251
|
+
tet_count = 0
|
|
252
|
+
|
|
253
|
+
for el_idx, el in enumerate(mesh.Elements(VOL)):
|
|
254
|
+
# Check material filter
|
|
255
|
+
if allowed_materials is not None:
|
|
256
|
+
if el.mat not in allowed_materials:
|
|
257
|
+
skipped_count += 1
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
# Check element type
|
|
261
|
+
if el.type == ET.TET:
|
|
262
|
+
element_type = 'TET'
|
|
263
|
+
expected_vertices = 4
|
|
264
|
+
tet_count += 1
|
|
265
|
+
elif el.type == ET.HEX and allow_hex:
|
|
266
|
+
element_type = 'HEX'
|
|
267
|
+
expected_vertices = 8
|
|
268
|
+
hex_count += 1
|
|
269
|
+
else:
|
|
270
|
+
if el.type == ET.HEX:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"Element {el_idx} is hexahedral (ET.HEX). "
|
|
273
|
+
f"Hexahedral elements are not allowed by default. "
|
|
274
|
+
f"Set allow_hex=True to enable (WARNING: may cause MMM issues)."
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
raise ValueError(
|
|
278
|
+
f"Element {el_idx} has unsupported type {el.type}. "
|
|
279
|
+
f"Only ET.TET (tetrahedra) and optionally ET.HEX (hexahedra) supported."
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Extract vertex NodeId objects (NGSolve format: V0, V1, etc.)
|
|
283
|
+
vert_node_ids = el.vertices
|
|
284
|
+
|
|
285
|
+
if len(vert_node_ids) != expected_vertices:
|
|
286
|
+
raise ValueError(
|
|
287
|
+
f"Element {el_idx}: Expected {expected_vertices} vertices for {element_type}, "
|
|
288
|
+
f"got {len(vert_node_ids)}"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Get vertex coordinates in original NGSolve order
|
|
292
|
+
vertices = []
|
|
293
|
+
for v_node in vert_node_ids:
|
|
294
|
+
v_idx = v_node.nr # Get integer index from NodeId
|
|
295
|
+
v = mesh.vertices[v_idx]
|
|
296
|
+
coord = v.point
|
|
297
|
+
vertices.append([coord[0], coord[1], coord[2]])
|
|
298
|
+
|
|
299
|
+
elements.append({
|
|
300
|
+
'vertices': vertices,
|
|
301
|
+
'element_index': el_idx,
|
|
302
|
+
'element_type': element_type,
|
|
303
|
+
'material': el.mat
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
if hex_count > 0:
|
|
307
|
+
print(f"[WARNING] Imported {hex_count} hexahedral elements. "
|
|
308
|
+
f"Hexahedra may cause numerical issues in Radia MMM.")
|
|
309
|
+
|
|
310
|
+
return elements, skipped_count
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def netgen_mesh_to_radia(mesh, material=None, units='m', combine=True, verbose=True,
|
|
314
|
+
material_filter=None, allow_hex=False):
|
|
315
|
+
"""
|
|
316
|
+
Convert NGSolve/Netgen mesh to Radia geometry.
|
|
317
|
+
|
|
318
|
+
IMPORTANT: Call rad.FldUnits() BEFORE using this function to ensure
|
|
319
|
+
unit consistency between Netgen (meters) and Radia.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
mesh : ngsolve.Mesh
|
|
324
|
+
Input mesh from Netgen/NGSolve
|
|
325
|
+
material : dict or callable, optional
|
|
326
|
+
Material specification:
|
|
327
|
+
|
|
328
|
+
- dict: {'magnetization': [Mx, My, Mz]}
|
|
329
|
+
Applies uniform magnetization to all elements
|
|
330
|
+
|
|
331
|
+
- callable: function(element_index) -> {'magnetization': [Mx, My, Mz]}
|
|
332
|
+
Per-element material specification
|
|
333
|
+
|
|
334
|
+
- None: Use default [0, 0, 0] (no magnetization)
|
|
335
|
+
|
|
336
|
+
units : str, default='m'
|
|
337
|
+
Unit system: 'm' (meters) or 'mm' (millimeters).
|
|
338
|
+
Must match rad.FldUnits() setting.
|
|
339
|
+
|
|
340
|
+
Note: As of v1.3.4, coordinate scaling is handled automatically by
|
|
341
|
+
rad.FldUnits(). This parameter is kept for API compatibility but
|
|
342
|
+
the actual scaling is performed by Radia's unit conversion system.
|
|
343
|
+
|
|
344
|
+
combine : bool, default=True
|
|
345
|
+
If True, return rad.ObjCnt() container of all elements.
|
|
346
|
+
If False, return list of individual polyhedra object IDs.
|
|
347
|
+
|
|
348
|
+
verbose : bool, default=True
|
|
349
|
+
If True, print progress information during conversion.
|
|
350
|
+
|
|
351
|
+
material_filter : str, list of str, or None, optional
|
|
352
|
+
Filter elements by mesh material name(s):
|
|
353
|
+
- str: Import only elements with this material name
|
|
354
|
+
- list of str: Import only elements with these material names
|
|
355
|
+
- None: Import all elements (default)
|
|
356
|
+
|
|
357
|
+
Example: material_filter='magnetic' imports only 'magnetic' material elements
|
|
358
|
+
|
|
359
|
+
allow_hex : bool, optional
|
|
360
|
+
If True, allow hexahedral elements (ET.HEX) in addition to tetrahedra
|
|
361
|
+
If False, raise error if mesh contains hexahedral elements
|
|
362
|
+
Default: False
|
|
363
|
+
|
|
364
|
+
WARNING: Hexahedral meshes may produce concave elements causing MMM errors.
|
|
365
|
+
Exception: Regular cubic grids (structured hex meshes) are safe.
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
int or list
|
|
370
|
+
- If combine=True: Radia container object ID (int)
|
|
371
|
+
- If combine=False: List of individual polyhedron object IDs (list of int)
|
|
372
|
+
|
|
373
|
+
Raises
|
|
374
|
+
------
|
|
375
|
+
ValueError
|
|
376
|
+
If mesh contains hexahedral elements and allow_hex=False
|
|
377
|
+
If mesh contains unsupported element types
|
|
378
|
+
If material specification is invalid
|
|
379
|
+
If units parameter is not 'm' or 'mm'
|
|
380
|
+
RuntimeError
|
|
381
|
+
If Radia polyhedron creation fails
|
|
382
|
+
|
|
383
|
+
Examples
|
|
384
|
+
--------
|
|
385
|
+
Basic usage with uniform magnetization:
|
|
386
|
+
|
|
387
|
+
>>> from ngsolve import Mesh
|
|
388
|
+
>>> from netgen.occ import Box, OCCGeometry
|
|
389
|
+
>>> import radia as rad
|
|
390
|
+
>>> from netgen_mesh_import import netgen_mesh_to_radia
|
|
391
|
+
>>>
|
|
392
|
+
>>> # IMPORTANT: Set Radia units first!
|
|
393
|
+
>>> rad.FldUnits('m')
|
|
394
|
+
>>>
|
|
395
|
+
>>> # Create Netgen mesh
|
|
396
|
+
>>> geo = OCCGeometry(Box((0, 0, 0), (0.01, 0.01, 0.01)))
|
|
397
|
+
>>> mesh = Mesh(geo.GenerateMesh(maxh=0.003))
|
|
398
|
+
>>>
|
|
399
|
+
>>> # Convert to Radia with uniform magnetization
|
|
400
|
+
>>> mag_obj = netgen_mesh_to_radia(mesh,
|
|
401
|
+
... material={'magnetization': [0, 0, 1.2]},
|
|
402
|
+
... units='m')
|
|
403
|
+
>>> print(f"Created Radia object: {mag_obj}")
|
|
404
|
+
|
|
405
|
+
Per-element material specification:
|
|
406
|
+
|
|
407
|
+
>>> def material_func(el_idx):
|
|
408
|
+
... # Left half: magnetized, right half: air
|
|
409
|
+
... if el_idx < 100:
|
|
410
|
+
... return {'magnetization': [0, 0, 1.2]}
|
|
411
|
+
... else:
|
|
412
|
+
... return {'magnetization': [0, 0, 0]}
|
|
413
|
+
>>>
|
|
414
|
+
>>> mag_obj = netgen_mesh_to_radia(mesh, material=material_func)
|
|
415
|
+
|
|
416
|
+
Using millimeters (Radia default):
|
|
417
|
+
|
|
418
|
+
>>> rad.FldUnits('mm') # Set Radia to mm
|
|
419
|
+
>>> mag_obj = netgen_mesh_to_radia(mesh, units='mm') # Auto-scales coordinates
|
|
420
|
+
|
|
421
|
+
Notes
|
|
422
|
+
-----
|
|
423
|
+
- Supports tetrahedral (ET.TET) and optionally hexahedral (ET.HEX) elements
|
|
424
|
+
- Vertex coordinates are extracted in Netgen's native units (meters)
|
|
425
|
+
- Scaling applied automatically if units='mm'
|
|
426
|
+
- All tetrahedra are convex, suitable for rad.ObjPolyhdr()
|
|
427
|
+
- Hexahedra may be concave (avoid except for structured cubic grids)
|
|
428
|
+
- Progress printed every 100 elements if verbose=True
|
|
429
|
+
"""
|
|
430
|
+
# Validate units parameter
|
|
431
|
+
if units not in ['m', 'mm']:
|
|
432
|
+
raise ValueError(f"units must be 'm' or 'mm', got '{units}'")
|
|
433
|
+
|
|
434
|
+
# Extract elements (tetrahedra and optionally hexahedra)
|
|
435
|
+
if verbose:
|
|
436
|
+
print(f"[Netgen Import] Extracting elements from mesh...")
|
|
437
|
+
print(f" Mesh: {mesh.ne} elements")
|
|
438
|
+
if material_filter is not None:
|
|
439
|
+
filter_str = material_filter if isinstance(material_filter, str) else ', '.join(material_filter)
|
|
440
|
+
print(f" Material filter: {filter_str}")
|
|
441
|
+
if allow_hex:
|
|
442
|
+
print(f" [WARNING] Hexahedral elements enabled (may cause MMM issues)")
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
elements, skipped_count = extract_elements(mesh, material_filter=material_filter, allow_hex=allow_hex)
|
|
446
|
+
except ValueError as e:
|
|
447
|
+
print(f"[ERROR] {e}")
|
|
448
|
+
raise
|
|
449
|
+
|
|
450
|
+
num_elements = len(elements)
|
|
451
|
+
if verbose:
|
|
452
|
+
# Count element types
|
|
453
|
+
tet_count = sum(1 for el in elements if el['element_type'] == 'TET')
|
|
454
|
+
hex_count = sum(1 for el in elements if el['element_type'] == 'HEX')
|
|
455
|
+
|
|
456
|
+
if hex_count > 0:
|
|
457
|
+
print(f" Extracted: {num_elements} elements ({tet_count} TET, {hex_count} HEX)")
|
|
458
|
+
else:
|
|
459
|
+
print(f" Extracted: {num_elements} tetrahedra")
|
|
460
|
+
|
|
461
|
+
if skipped_count > 0:
|
|
462
|
+
print(f" Skipped: {skipped_count} elements (filtered by material)")
|
|
463
|
+
|
|
464
|
+
# Coordinate scaling is now handled by rad.FldUnits()
|
|
465
|
+
# No manual scaling needed - Radia automatically converts units
|
|
466
|
+
coord_scale = 1.0
|
|
467
|
+
|
|
468
|
+
if verbose:
|
|
469
|
+
print(f" Units: {units} (scaling handled by rad.FldUnits())")
|
|
470
|
+
|
|
471
|
+
# Process material specification
|
|
472
|
+
if material is None:
|
|
473
|
+
# Default: no magnetization
|
|
474
|
+
def get_material(el_idx):
|
|
475
|
+
return {'magnetization': [0, 0, 0]}
|
|
476
|
+
elif isinstance(material, dict):
|
|
477
|
+
# Uniform material
|
|
478
|
+
def get_material(el_idx):
|
|
479
|
+
return material
|
|
480
|
+
elif callable(material):
|
|
481
|
+
# Per-element material function
|
|
482
|
+
get_material = material
|
|
483
|
+
else:
|
|
484
|
+
raise ValueError(
|
|
485
|
+
f"material must be dict, callable, or None. Got {type(material)}"
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Create Radia polyhedra
|
|
489
|
+
polyhedra = []
|
|
490
|
+
|
|
491
|
+
if verbose:
|
|
492
|
+
print(f"[Netgen Import] Creating Radia polyhedra...")
|
|
493
|
+
|
|
494
|
+
for i, element in enumerate(elements):
|
|
495
|
+
# Scale coordinates if needed
|
|
496
|
+
vertices = element['vertices']
|
|
497
|
+
if coord_scale != 1.0:
|
|
498
|
+
vertices = [[x*coord_scale, y*coord_scale, z*coord_scale]
|
|
499
|
+
for x, y, z in vertices]
|
|
500
|
+
|
|
501
|
+
# Get material for this element
|
|
502
|
+
el_idx = element['element_index']
|
|
503
|
+
try:
|
|
504
|
+
mat = get_material(el_idx)
|
|
505
|
+
except Exception as e:
|
|
506
|
+
raise RuntimeError(
|
|
507
|
+
f"Material function failed for element {el_idx}: {e}"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Validate material specification
|
|
511
|
+
if not isinstance(mat, dict) or 'magnetization' not in mat:
|
|
512
|
+
raise ValueError(
|
|
513
|
+
f"Material function for element {el_idx} must return "
|
|
514
|
+
f"dict with 'magnetization' key. Got: {mat}"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
magnetization = mat['magnetization']
|
|
518
|
+
|
|
519
|
+
# Create polyhedron based on element type
|
|
520
|
+
element_type = element['element_type']
|
|
521
|
+
try:
|
|
522
|
+
if element_type == 'TET':
|
|
523
|
+
obj_id = create_radia_tetrahedron(vertices, magnetization)
|
|
524
|
+
elif element_type == 'HEX':
|
|
525
|
+
obj_id = create_radia_hexahedron(vertices, magnetization)
|
|
526
|
+
else:
|
|
527
|
+
raise ValueError(f"Unknown element type: {element_type}")
|
|
528
|
+
|
|
529
|
+
polyhedra.append(obj_id)
|
|
530
|
+
except RuntimeError as e:
|
|
531
|
+
raise RuntimeError(
|
|
532
|
+
f"Failed to create {element_type} polyhedron for element {el_idx}: {e}"
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
# Progress reporting
|
|
536
|
+
if verbose and (i + 1) % 100 == 0:
|
|
537
|
+
print(f" Progress: {i+1}/{num_elements}", end='\r')
|
|
538
|
+
|
|
539
|
+
if verbose:
|
|
540
|
+
print(f" Progress: {num_elements}/{num_elements}")
|
|
541
|
+
print(f" [OK] Created {len(polyhedra)} polyhedra")
|
|
542
|
+
|
|
543
|
+
# Return result
|
|
544
|
+
if combine:
|
|
545
|
+
if verbose:
|
|
546
|
+
print(f"[Netgen Import] Combining into container...")
|
|
547
|
+
|
|
548
|
+
container = rad.ObjCnt(polyhedra)
|
|
549
|
+
|
|
550
|
+
if verbose:
|
|
551
|
+
print(f" [OK] Container object ID: {container}")
|
|
552
|
+
|
|
553
|
+
return container
|
|
554
|
+
else:
|
|
555
|
+
if verbose:
|
|
556
|
+
print(f"[Netgen Import] Returning list of {len(polyhedra)} object IDs")
|
|
557
|
+
|
|
558
|
+
return polyhedra
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# Module-level constants for external use
|
|
562
|
+
__version__ = '0.2.0'
|
|
563
|
+
__all__ = [
|
|
564
|
+
'netgen_mesh_to_radia',
|
|
565
|
+
'extract_elements',
|
|
566
|
+
'create_radia_tetrahedron',
|
|
567
|
+
'create_radia_hexahedron',
|
|
568
|
+
'TETRA_FACES',
|
|
569
|
+
'HEX_FACES',
|
|
570
|
+
'WEDGE_FACES',
|
|
571
|
+
'PYRAMID_FACES'
|
|
572
|
+
]
|
python/rad_ngsolve.pyd
CHANGED
|
Binary file
|