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,441 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
nastran_mesh_import.py - Import Nastran mesh files to Radia
|
|
4
|
+
|
|
5
|
+
Supports fixed-width and long-format Nastran (.bdf, .nas, .dat)
|
|
6
|
+
for hexahedral (CHEXA) and tetrahedral (CTETRA) elements.
|
|
7
|
+
|
|
8
|
+
Handles continuation lines marked with '+' for multi-line cards.
|
|
9
|
+
|
|
10
|
+
Author: Radia Development Team
|
|
11
|
+
Created: 2025-11-22
|
|
12
|
+
Version: 0.2.0
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_nastran_fixed_width(line, card_type):
|
|
17
|
+
"""
|
|
18
|
+
Parse Nastran fixed-width format card.
|
|
19
|
+
|
|
20
|
+
Fixed-width format:
|
|
21
|
+
- Field width: 8 characters
|
|
22
|
+
- Fields: 0-7, 8-15, 16-23, 24-31, ...
|
|
23
|
+
"""
|
|
24
|
+
fields = []
|
|
25
|
+
for i in range(0, len(line), 8):
|
|
26
|
+
field = line[i:i+8].strip()
|
|
27
|
+
if field and field != '+': # Ignore continuation markers
|
|
28
|
+
fields.append(field)
|
|
29
|
+
return fields
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def import_nastran_mesh(nas_file, units='m', verbose=True):
|
|
33
|
+
"""
|
|
34
|
+
Import Nastran mesh file to Radia.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
nas_file : str
|
|
39
|
+
Path to Nastran file (.bdf, .nas, .dat)
|
|
40
|
+
units : str, default='m'
|
|
41
|
+
Unit system: 'm' (meters) or 'mm' (millimeters)
|
|
42
|
+
Must match rad.FldUnits() setting
|
|
43
|
+
verbose : bool, default=True
|
|
44
|
+
Print progress information
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
dict
|
|
49
|
+
{
|
|
50
|
+
'vertices': list of [x, y, z],
|
|
51
|
+
'hex_elements': list of element vertex indices,
|
|
52
|
+
'tet_elements': list of element vertex indices,
|
|
53
|
+
'wedge_elements': list of element vertex indices,
|
|
54
|
+
'pyramid_elements': list of element vertex indices
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Notes
|
|
58
|
+
-----
|
|
59
|
+
Supports fixed-width and long-format Nastran:
|
|
60
|
+
- GRID/GRID* cards: Node definitions (with continuation)
|
|
61
|
+
- CHEXA cards: 8-node hexahedral elements (with continuation)
|
|
62
|
+
- CTETRA cards: 4-node tetrahedral elements (with continuation)
|
|
63
|
+
- CPENTA cards: 6-node wedge/pentahedron elements (with continuation)
|
|
64
|
+
- CPYRAM cards: 5-node pyramid elements (with continuation)
|
|
65
|
+
"""
|
|
66
|
+
if verbose:
|
|
67
|
+
print(f"[Nastran Import] Reading file: {nas_file}")
|
|
68
|
+
|
|
69
|
+
vertices = []
|
|
70
|
+
hex_elements = []
|
|
71
|
+
tet_elements = []
|
|
72
|
+
wedge_elements = []
|
|
73
|
+
pyramid_elements = []
|
|
74
|
+
tria_groups = {} # material_id -> {'faces': [[n1,n2,n3], ...], 'node_ids': set()}
|
|
75
|
+
vertex_map = {} # node_id -> vertex_index
|
|
76
|
+
|
|
77
|
+
with open(nas_file, 'r') as f:
|
|
78
|
+
lines = f.readlines()
|
|
79
|
+
|
|
80
|
+
i = 0
|
|
81
|
+
while i < len(lines):
|
|
82
|
+
line = lines[i]
|
|
83
|
+
|
|
84
|
+
# Skip comments and empty lines
|
|
85
|
+
if line.startswith('$') or not line.strip():
|
|
86
|
+
i += 1
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# End of bulk data
|
|
90
|
+
if line.strip().startswith('ENDDATA'):
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
# Parse card type (first 8 characters)
|
|
94
|
+
card_type = line[:8].strip()
|
|
95
|
+
|
|
96
|
+
# GRID/GRID* card: Node definition with continuation support
|
|
97
|
+
if card_type == 'GRID' or card_type.startswith('GRID'):
|
|
98
|
+
# Check for long format (GRID*)
|
|
99
|
+
is_long = '*' in card_type
|
|
100
|
+
|
|
101
|
+
if is_long:
|
|
102
|
+
# GRID* long format: 16-character fields
|
|
103
|
+
# First line: GRID* (0-7), node_id (8-23), coord_sys (24-39), X (40-55), Y (56-71)
|
|
104
|
+
# Second line: * (0-7), Z (8-23)
|
|
105
|
+
try:
|
|
106
|
+
node_id = int(line[8:24].strip())
|
|
107
|
+
coord_sys = int(line[24:40].strip()) if len(line) > 24 else 0
|
|
108
|
+
x = float(line[40:56].strip())
|
|
109
|
+
y = float(line[56:72].strip()) if len(line) > 56 else 0.0
|
|
110
|
+
|
|
111
|
+
# Get continuation line for Z coordinate
|
|
112
|
+
z = 0.0
|
|
113
|
+
if i + 1 < len(lines) and lines[i + 1].strip().startswith('*'):
|
|
114
|
+
i += 1
|
|
115
|
+
cont_line = lines[i]
|
|
116
|
+
z = float(cont_line[8:24].strip())
|
|
117
|
+
|
|
118
|
+
vertex_idx = len(vertices)
|
|
119
|
+
vertices.append([x, y, z])
|
|
120
|
+
vertex_map[node_id] = vertex_idx
|
|
121
|
+
except (ValueError, IndexError) as e:
|
|
122
|
+
if verbose:
|
|
123
|
+
print(f" Warning: Failed to parse GRID* at line {i+1}: {e}")
|
|
124
|
+
else:
|
|
125
|
+
# Standard GRID format: 8-character fields
|
|
126
|
+
# GRID (0-7), node_id (8-15), coord_sys (16-23), X (24-31), Y (32-39), Z (40-47)
|
|
127
|
+
try:
|
|
128
|
+
node_id = int(line[8:16].strip())
|
|
129
|
+
coord_sys = int(line[16:24].strip()) if len(line) > 16 and line[16:24].strip() else 0
|
|
130
|
+
x = float(line[24:32].strip())
|
|
131
|
+
y = float(line[32:40].strip()) if len(line) > 32 else 0.0
|
|
132
|
+
z = float(line[40:48].strip()) if len(line) > 40 else 0.0
|
|
133
|
+
|
|
134
|
+
vertex_idx = len(vertices)
|
|
135
|
+
vertices.append([x, y, z])
|
|
136
|
+
vertex_map[node_id] = vertex_idx
|
|
137
|
+
except (ValueError, IndexError) as e:
|
|
138
|
+
if verbose:
|
|
139
|
+
print(f" Warning: Failed to parse GRID at line {i+1}: {e}")
|
|
140
|
+
|
|
141
|
+
# CHEXA card: 8-node hexahedral element with continuation support
|
|
142
|
+
elif card_type == 'CHEXA' or card_type.startswith('CHEXA'):
|
|
143
|
+
# Collect continuation lines (marked with + at end)
|
|
144
|
+
full_line = line
|
|
145
|
+
while i + 1 < len(lines) and full_line.rstrip().endswith('+'):
|
|
146
|
+
i += 1
|
|
147
|
+
full_line = full_line.rstrip()[:-1] # Remove trailing '+'
|
|
148
|
+
cont_line = lines[i]
|
|
149
|
+
if cont_line.startswith('+'):
|
|
150
|
+
cont_line = cont_line[1:] # Remove leading '+'
|
|
151
|
+
full_line += cont_line
|
|
152
|
+
|
|
153
|
+
fields = parse_nastran_fixed_width(full_line, card_type)
|
|
154
|
+
if len(fields) >= 10:
|
|
155
|
+
try:
|
|
156
|
+
# Element ID (field 1), Property ID (field 2), then 8 nodes
|
|
157
|
+
node_ids = [int(fields[j]) for j in range(3, 11)]
|
|
158
|
+
element_verts = [vertex_map[nid] for nid in node_ids]
|
|
159
|
+
hex_elements.append(element_verts)
|
|
160
|
+
except (ValueError, KeyError, IndexError) as e:
|
|
161
|
+
if verbose:
|
|
162
|
+
print(f" Warning: Failed to parse CHEXA at line {i+1}: {e}")
|
|
163
|
+
|
|
164
|
+
# CTETRA card: 4-node tetrahedral element with continuation support
|
|
165
|
+
elif card_type == 'CTETRA' or card_type.startswith('CTETRA'):
|
|
166
|
+
# Collect continuation lines
|
|
167
|
+
full_line = line
|
|
168
|
+
while i + 1 < len(lines) and full_line.rstrip().endswith('+'):
|
|
169
|
+
i += 1
|
|
170
|
+
full_line = full_line.rstrip()[:-1] # Remove trailing '+'
|
|
171
|
+
cont_line = lines[i]
|
|
172
|
+
if cont_line.startswith('+'):
|
|
173
|
+
cont_line = cont_line[1:] # Remove leading '+'
|
|
174
|
+
full_line += cont_line
|
|
175
|
+
|
|
176
|
+
fields = parse_nastran_fixed_width(full_line, card_type)
|
|
177
|
+
if len(fields) >= 6:
|
|
178
|
+
try:
|
|
179
|
+
node_ids = [int(fields[j]) for j in range(3, 7)]
|
|
180
|
+
element_verts = [vertex_map[nid] for nid in node_ids]
|
|
181
|
+
tet_elements.append(element_verts)
|
|
182
|
+
except (ValueError, KeyError, IndexError) as e:
|
|
183
|
+
if verbose:
|
|
184
|
+
print(f" Warning: Failed to parse CTETRA at line {i+1}: {e}")
|
|
185
|
+
|
|
186
|
+
# CPENTA card: 6-node wedge/pentahedron element with continuation support
|
|
187
|
+
elif card_type == 'CPENTA' or card_type.startswith('CPENTA'):
|
|
188
|
+
# Collect continuation lines
|
|
189
|
+
full_line = line
|
|
190
|
+
while i + 1 < len(lines) and full_line.rstrip().endswith('+'):
|
|
191
|
+
i += 1
|
|
192
|
+
full_line = full_line.rstrip()[:-1] # Remove trailing '+'
|
|
193
|
+
cont_line = lines[i]
|
|
194
|
+
if cont_line.startswith('+'):
|
|
195
|
+
cont_line = cont_line[1:] # Remove leading '+'
|
|
196
|
+
full_line += cont_line
|
|
197
|
+
|
|
198
|
+
fields = parse_nastran_fixed_width(full_line, card_type)
|
|
199
|
+
if len(fields) >= 8:
|
|
200
|
+
try:
|
|
201
|
+
# Element ID (field 1), Property ID (field 2), then 6 nodes
|
|
202
|
+
node_ids = [int(fields[j]) for j in range(3, 9)]
|
|
203
|
+
element_verts = [vertex_map[nid] for nid in node_ids]
|
|
204
|
+
wedge_elements.append(element_verts)
|
|
205
|
+
except (ValueError, KeyError, IndexError) as e:
|
|
206
|
+
if verbose:
|
|
207
|
+
print(f" Warning: Failed to parse CPENTA at line {i+1}: {e}")
|
|
208
|
+
|
|
209
|
+
# CPYRAM card: 5-node pyramid element with continuation support
|
|
210
|
+
elif card_type == 'CPYRAM' or card_type.startswith('CPYRAM'):
|
|
211
|
+
# Collect continuation lines
|
|
212
|
+
full_line = line
|
|
213
|
+
while i + 1 < len(lines) and full_line.rstrip().endswith('+'):
|
|
214
|
+
i += 1
|
|
215
|
+
full_line = full_line.rstrip()[:-1] # Remove trailing '+'
|
|
216
|
+
cont_line = lines[i]
|
|
217
|
+
if cont_line.startswith('+'):
|
|
218
|
+
cont_line = cont_line[1:] # Remove leading '+'
|
|
219
|
+
full_line += cont_line
|
|
220
|
+
|
|
221
|
+
fields = parse_nastran_fixed_width(full_line, card_type)
|
|
222
|
+
if len(fields) >= 7:
|
|
223
|
+
try:
|
|
224
|
+
# Element ID (field 1), Property ID (field 2), then 5 nodes
|
|
225
|
+
node_ids = [int(fields[j]) for j in range(3, 8)]
|
|
226
|
+
element_verts = [vertex_map[nid] for nid in node_ids]
|
|
227
|
+
pyramid_elements.append(element_verts)
|
|
228
|
+
except (ValueError, KeyError, IndexError) as e:
|
|
229
|
+
if verbose:
|
|
230
|
+
print(f" Warning: Failed to parse CPYRAM at line {i+1}: {e}")
|
|
231
|
+
|
|
232
|
+
# CTRIA3 card: 3-node triangle surface element
|
|
233
|
+
elif card_type == 'CTRIA3' or card_type.startswith('CTRIA3'):
|
|
234
|
+
# CTRIA3 format: CTRIA3, elem_id, prop_id, n1, n2, n3
|
|
235
|
+
# Fixed format: 8-character fields
|
|
236
|
+
try:
|
|
237
|
+
elem_id = int(line[8:16].strip())
|
|
238
|
+
prop_id = int(line[16:24].strip()) # Material/Property ID
|
|
239
|
+
n1 = int(line[24:32].strip())
|
|
240
|
+
n2 = int(line[32:40].strip())
|
|
241
|
+
n3 = int(line[40:48].strip())
|
|
242
|
+
|
|
243
|
+
# Group by material ID (prop_id)
|
|
244
|
+
if prop_id not in tria_groups:
|
|
245
|
+
tria_groups[prop_id] = {'faces': [], 'node_ids': set()}
|
|
246
|
+
|
|
247
|
+
# Store face (using original node IDs, will convert later)
|
|
248
|
+
tria_groups[prop_id]['faces'].append([n1, n2, n3])
|
|
249
|
+
tria_groups[prop_id]['node_ids'].update([n1, n2, n3])
|
|
250
|
+
|
|
251
|
+
except (ValueError, IndexError) as e:
|
|
252
|
+
if verbose:
|
|
253
|
+
print(f" Warning: Failed to parse CTRIA3 at line {i+1}: {e}")
|
|
254
|
+
|
|
255
|
+
i += 1
|
|
256
|
+
|
|
257
|
+
# Calculate total triangle faces across all material groups
|
|
258
|
+
total_tria_faces = sum(len(group['faces']) for group in tria_groups.values())
|
|
259
|
+
|
|
260
|
+
if verbose:
|
|
261
|
+
print(f"[Nastran Import] Parsing complete")
|
|
262
|
+
print(f" Vertices: {len(vertices)}")
|
|
263
|
+
print(f" Hexahedral elements: {len(hex_elements)}")
|
|
264
|
+
print(f" Wedge elements: {len(wedge_elements)}")
|
|
265
|
+
print(f" Pyramid elements: {len(pyramid_elements)}")
|
|
266
|
+
print(f" Tetrahedral elements: {len(tet_elements)}")
|
|
267
|
+
print(f" Triangle surface groups: {len(tria_groups)} (total {total_tria_faces} faces)")
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
'vertices': vertices,
|
|
271
|
+
'hex_elements': hex_elements,
|
|
272
|
+
'wedge_elements': wedge_elements,
|
|
273
|
+
'pyramid_elements': pyramid_elements,
|
|
274
|
+
'tet_elements': tet_elements,
|
|
275
|
+
'tria_groups': tria_groups,
|
|
276
|
+
'vertex_map': vertex_map
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def create_radia_from_nastran(nas_file, material=None, units='m',
|
|
281
|
+
combine=True, verbose=True):
|
|
282
|
+
"""
|
|
283
|
+
Create Radia geometry from Nastran mesh file.
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
nas_file : str
|
|
288
|
+
Path to Nastran file
|
|
289
|
+
material : dict or None
|
|
290
|
+
Material specification: {'magnetization': [Mx, My, Mz]}
|
|
291
|
+
units : str, default='m'
|
|
292
|
+
'm' (meters) or 'mm' (millimeters)
|
|
293
|
+
combine : bool, default=True
|
|
294
|
+
If True, return rad.ObjCnt() container
|
|
295
|
+
If False, return list of object IDs
|
|
296
|
+
verbose : bool, default=True
|
|
297
|
+
Print progress
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
int or list
|
|
302
|
+
Radia object ID (if combine=True) or list of IDs (if combine=False)
|
|
303
|
+
"""
|
|
304
|
+
import radia as rad
|
|
305
|
+
|
|
306
|
+
# Import mesh data
|
|
307
|
+
mesh_data = import_nastran_mesh(nas_file, units=units, verbose=verbose)
|
|
308
|
+
|
|
309
|
+
vertices = mesh_data['vertices']
|
|
310
|
+
hex_elements = mesh_data['hex_elements']
|
|
311
|
+
wedge_elements = mesh_data['wedge_elements']
|
|
312
|
+
pyramid_elements = mesh_data['pyramid_elements']
|
|
313
|
+
tet_elements = mesh_data['tet_elements']
|
|
314
|
+
tria_groups = mesh_data['tria_groups']
|
|
315
|
+
vertex_map = mesh_data['vertex_map']
|
|
316
|
+
|
|
317
|
+
total_elements = len(hex_elements) + len(wedge_elements) + len(pyramid_elements) + len(tet_elements)
|
|
318
|
+
total_tria_groups = len(tria_groups)
|
|
319
|
+
if total_elements == 0 and total_tria_groups == 0:
|
|
320
|
+
raise ValueError("No valid elements found in Nastran file")
|
|
321
|
+
|
|
322
|
+
# Material specification
|
|
323
|
+
if material is None:
|
|
324
|
+
magnetization = [0, 0, 0]
|
|
325
|
+
elif isinstance(material, dict) and 'magnetization' in material:
|
|
326
|
+
magnetization = material['magnetization']
|
|
327
|
+
else:
|
|
328
|
+
raise ValueError("material must be dict with 'magnetization' key")
|
|
329
|
+
|
|
330
|
+
# Import face topologies
|
|
331
|
+
from netgen_mesh_import import HEX_FACES, WEDGE_FACES, PYRAMID_FACES, TETRA_FACES
|
|
332
|
+
|
|
333
|
+
# Create Radia objects
|
|
334
|
+
radia_objects = []
|
|
335
|
+
|
|
336
|
+
# Create hexahedral elements
|
|
337
|
+
if len(hex_elements) > 0 and verbose:
|
|
338
|
+
print(f"[Nastran Import] Creating {len(hex_elements)} hexahedral elements...")
|
|
339
|
+
|
|
340
|
+
for i, elem_verts in enumerate(hex_elements):
|
|
341
|
+
elem_coords = [vertices[vi] for vi in elem_verts]
|
|
342
|
+
obj_id = rad.ObjPolyhdr(elem_coords, HEX_FACES, magnetization)
|
|
343
|
+
radia_objects.append(obj_id)
|
|
344
|
+
|
|
345
|
+
if verbose and (i + 1) % 25 == 0:
|
|
346
|
+
print(f" Progress: {i+1}/{len(hex_elements)}", end='\r')
|
|
347
|
+
|
|
348
|
+
if len(hex_elements) > 0 and verbose:
|
|
349
|
+
print(f" Progress: {len(hex_elements)}/{len(hex_elements)}")
|
|
350
|
+
|
|
351
|
+
# Create wedge elements
|
|
352
|
+
if len(wedge_elements) > 0 and verbose:
|
|
353
|
+
print(f"[Nastran Import] Creating {len(wedge_elements)} wedge elements...")
|
|
354
|
+
|
|
355
|
+
for i, elem_verts in enumerate(wedge_elements):
|
|
356
|
+
elem_coords = [vertices[vi] for vi in elem_verts]
|
|
357
|
+
obj_id = rad.ObjPolyhdr(elem_coords, WEDGE_FACES, magnetization)
|
|
358
|
+
radia_objects.append(obj_id)
|
|
359
|
+
|
|
360
|
+
if verbose and (i + 1) % 50 == 0:
|
|
361
|
+
print(f" Progress: {i+1}/{len(wedge_elements)}", end='\r')
|
|
362
|
+
|
|
363
|
+
if len(wedge_elements) > 0 and verbose:
|
|
364
|
+
print(f" Progress: {len(wedge_elements)}/{len(wedge_elements)}")
|
|
365
|
+
|
|
366
|
+
# Create pyramid elements
|
|
367
|
+
if len(pyramid_elements) > 0 and verbose:
|
|
368
|
+
print(f"[Nastran Import] Creating {len(pyramid_elements)} pyramid elements...")
|
|
369
|
+
|
|
370
|
+
for i, elem_verts in enumerate(pyramid_elements):
|
|
371
|
+
elem_coords = [vertices[vi] for vi in elem_verts]
|
|
372
|
+
obj_id = rad.ObjPolyhdr(elem_coords, PYRAMID_FACES, magnetization)
|
|
373
|
+
radia_objects.append(obj_id)
|
|
374
|
+
|
|
375
|
+
if verbose and (i + 1) % 50 == 0:
|
|
376
|
+
print(f" Progress: {i+1}/{len(pyramid_elements)}", end='\r')
|
|
377
|
+
|
|
378
|
+
if len(pyramid_elements) > 0 and verbose:
|
|
379
|
+
print(f" Progress: {len(pyramid_elements)}/{len(pyramid_elements)}")
|
|
380
|
+
|
|
381
|
+
# Create tetrahedral elements
|
|
382
|
+
if len(tet_elements) > 0 and verbose:
|
|
383
|
+
print(f"[Nastran Import] Creating {len(tet_elements)} tetrahedral elements...")
|
|
384
|
+
|
|
385
|
+
for i, elem_verts in enumerate(tet_elements):
|
|
386
|
+
elem_coords = [vertices[vi] for vi in elem_verts]
|
|
387
|
+
obj_id = rad.ObjPolyhdr(elem_coords, TETRA_FACES, magnetization)
|
|
388
|
+
radia_objects.append(obj_id)
|
|
389
|
+
|
|
390
|
+
if verbose and (i + 1) % 100 == 0:
|
|
391
|
+
print(f" Progress: {i+1}/{len(tet_elements)}", end='\r')
|
|
392
|
+
|
|
393
|
+
if len(tet_elements) > 0 and verbose:
|
|
394
|
+
print(f" Progress: {len(tet_elements)}/{len(tet_elements)}")
|
|
395
|
+
|
|
396
|
+
# Create surface polyhedra from triangle groups
|
|
397
|
+
if len(tria_groups) > 0 and verbose:
|
|
398
|
+
print(f"[Nastran Import] Creating {len(tria_groups)} surface polyhedra from triangle groups...")
|
|
399
|
+
|
|
400
|
+
for mat_id, group in tria_groups.items():
|
|
401
|
+
faces_global = group['faces'] # Faces with global node IDs
|
|
402
|
+
node_ids = sorted(group['node_ids']) # All unique node IDs for this material
|
|
403
|
+
|
|
404
|
+
# Create local node ID mapping (global node ID -> local index 1-based for Radia)
|
|
405
|
+
node_id_to_local = {nid: idx + 1 for idx, nid in enumerate(node_ids)}
|
|
406
|
+
|
|
407
|
+
# Get vertex coordinates for this group
|
|
408
|
+
group_vertices = [vertices[vertex_map[nid]] for nid in node_ids]
|
|
409
|
+
|
|
410
|
+
# Convert faces from global node IDs to local 1-based indices
|
|
411
|
+
faces_local = []
|
|
412
|
+
for face in faces_global:
|
|
413
|
+
local_face = [node_id_to_local[nid] for nid in face]
|
|
414
|
+
faces_local.append(local_face)
|
|
415
|
+
|
|
416
|
+
# Create polyhedron from surface mesh
|
|
417
|
+
obj_id = rad.ObjPolyhdr(group_vertices, faces_local, magnetization)
|
|
418
|
+
radia_objects.append(obj_id)
|
|
419
|
+
|
|
420
|
+
if verbose:
|
|
421
|
+
print(f" Material {mat_id}: {len(faces_local)} faces, {len(group_vertices)} vertices")
|
|
422
|
+
|
|
423
|
+
# Return result
|
|
424
|
+
if combine:
|
|
425
|
+
if verbose:
|
|
426
|
+
print(f"[Nastran Import] Combining into container...")
|
|
427
|
+
|
|
428
|
+
container = rad.ObjCnt(radia_objects)
|
|
429
|
+
|
|
430
|
+
if verbose:
|
|
431
|
+
print(f" [OK] Container object ID: {container}")
|
|
432
|
+
|
|
433
|
+
return container
|
|
434
|
+
else:
|
|
435
|
+
return radia_objects
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
__all__ = [
|
|
439
|
+
'import_nastran_mesh',
|
|
440
|
+
'create_radia_from_nastran',
|
|
441
|
+
]
|