Coreform-Cubit-Mesh-Export 1.11.0__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.
- coreform_cubit_mesh_export-1.11.0.dist-info/METADATA +292 -0
- coreform_cubit_mesh_export-1.11.0.dist-info/RECORD +9 -0
- coreform_cubit_mesh_export-1.11.0.dist-info/WHEEL +5 -0
- coreform_cubit_mesh_export-1.11.0.dist-info/licenses/LICENSE +504 -0
- coreform_cubit_mesh_export-1.11.0.dist-info/top_level.txt +2 -0
- cubit_mesh_export.py +2738 -0
- other_formats/cubit_mesh_export_2d_geo_lukas_format.py +77 -0
- other_formats/cubit_mesh_export_3d_cdb.py +142 -0
- other_formats/cubit_mesh_export_3d_freefem_mesh.py +59 -0
cubit_mesh_export.py
ADDED
|
@@ -0,0 +1,2738 @@
|
|
|
1
|
+
from typing import Any, List, Optional, Tuple
|
|
2
|
+
|
|
3
|
+
########################################################################
|
|
4
|
+
### Common utility functions
|
|
5
|
+
########################################################################
|
|
6
|
+
|
|
7
|
+
def _block_contains_geometry(cubit: Any, block_id: int) -> bool:
|
|
8
|
+
"""Check if a block contains geometry (volume, surface, curve, vertex) instead of mesh elements."""
|
|
9
|
+
try:
|
|
10
|
+
if len(cubit.get_block_volumes(block_id)) > 0:
|
|
11
|
+
return True
|
|
12
|
+
except:
|
|
13
|
+
pass
|
|
14
|
+
try:
|
|
15
|
+
if len(cubit.get_block_surfaces(block_id)) > 0:
|
|
16
|
+
return True
|
|
17
|
+
except:
|
|
18
|
+
pass
|
|
19
|
+
try:
|
|
20
|
+
if len(cubit.get_block_curves(block_id)) > 0:
|
|
21
|
+
return True
|
|
22
|
+
except:
|
|
23
|
+
pass
|
|
24
|
+
try:
|
|
25
|
+
if len(cubit.get_block_vertices(block_id)) > 0:
|
|
26
|
+
return True
|
|
27
|
+
except:
|
|
28
|
+
pass
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_block_elements(cubit: Any, block_id: int, elem_type: str) -> Tuple[int, ...]:
|
|
33
|
+
"""Get mesh elements from a block, supporting both geometry and mesh element blocks.
|
|
34
|
+
|
|
35
|
+
This function mimics Cubit's 'draw <elem_type> in block X' behavior.
|
|
36
|
+
When a block contains geometry (volume, surface, curve, vertex),
|
|
37
|
+
it uses parse_cubit_list to get the associated mesh elements.
|
|
38
|
+
When a block contains direct mesh elements, it uses the standard get_block_* functions.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
cubit: Cubit Python interface object
|
|
42
|
+
block_id: Block ID
|
|
43
|
+
elem_type: Element type ('tet', 'hex', 'wedge', 'pyramid', 'tri', 'face',
|
|
44
|
+
'quad', 'edge', 'node')
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Tuple of element IDs
|
|
48
|
+
"""
|
|
49
|
+
# Check if block contains geometry (volume, surface, curve, vertex)
|
|
50
|
+
if _block_contains_geometry(cubit, block_id):
|
|
51
|
+
# Use parse_cubit_list for geometry-based blocks
|
|
52
|
+
# This mimics Cubit's 'draw <elem_type> in block X' behavior
|
|
53
|
+
try:
|
|
54
|
+
elements = cubit.parse_cubit_list(elem_type, f"in block {block_id}")
|
|
55
|
+
return tuple(elements)
|
|
56
|
+
except:
|
|
57
|
+
return ()
|
|
58
|
+
|
|
59
|
+
# For mesh element blocks, use the standard get_block_* functions
|
|
60
|
+
try:
|
|
61
|
+
if elem_type == "hex":
|
|
62
|
+
return cubit.get_block_hexes(block_id)
|
|
63
|
+
elif elem_type == "tet":
|
|
64
|
+
return cubit.get_block_tets(block_id)
|
|
65
|
+
elif elem_type == "wedge":
|
|
66
|
+
return cubit.get_block_wedges(block_id)
|
|
67
|
+
elif elem_type == "pyramid":
|
|
68
|
+
return cubit.get_block_pyramids(block_id)
|
|
69
|
+
elif elem_type == "tri":
|
|
70
|
+
return cubit.get_block_tris(block_id)
|
|
71
|
+
elif elem_type == "face":
|
|
72
|
+
return cubit.get_block_faces(block_id)
|
|
73
|
+
elif elem_type == "quad":
|
|
74
|
+
return cubit.get_block_quads(block_id)
|
|
75
|
+
elif elem_type == "edge":
|
|
76
|
+
return cubit.get_block_edges(block_id)
|
|
77
|
+
elif elem_type == "node":
|
|
78
|
+
return cubit.get_block_nodes(block_id)
|
|
79
|
+
else:
|
|
80
|
+
return ()
|
|
81
|
+
except:
|
|
82
|
+
return ()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _warn_mixed_element_types_in_blocks(cubit: Any) -> None:
|
|
88
|
+
"""Warn if any block contains multiple 3D element types.
|
|
89
|
+
|
|
90
|
+
When a block contains multiple element types (e.g., tet + hex + pyramid),
|
|
91
|
+
Cubit's 'block X element type' command only reports the last set type via
|
|
92
|
+
get_block_element_type(). While Cubit does convert all elements to 2nd order
|
|
93
|
+
when you issue commands like 'block 1 element type tetra10', this warning
|
|
94
|
+
helps users understand they need to issue multiple commands for mixed blocks.
|
|
95
|
+
|
|
96
|
+
This function prints a warning for each block with mixed 3D element types.
|
|
97
|
+
"""
|
|
98
|
+
# 3D element types to check
|
|
99
|
+
volume_elem_types = ["hex", "tet", "wedge", "pyramid"]
|
|
100
|
+
|
|
101
|
+
for block_id in cubit.get_block_id_list():
|
|
102
|
+
found_types = []
|
|
103
|
+
for elem_type in volume_elem_types:
|
|
104
|
+
elements = _get_block_elements(cubit, block_id, elem_type)
|
|
105
|
+
if len(elements) > 0:
|
|
106
|
+
found_types.append(elem_type)
|
|
107
|
+
|
|
108
|
+
if len(found_types) > 1:
|
|
109
|
+
block_name = cubit.get_exodus_entity_name("block", block_id)
|
|
110
|
+
type_str = ", ".join(found_types)
|
|
111
|
+
print(f"WARNING: Block {block_id} ('{block_name}') contains multiple 3D element types: {type_str}")
|
|
112
|
+
print(f" To convert all elements to 2nd order, you need to issue separate commands:")
|
|
113
|
+
for elem_type in found_types:
|
|
114
|
+
if elem_type == "tet":
|
|
115
|
+
print(f" block {block_id} element type tetra10")
|
|
116
|
+
elif elem_type == "hex":
|
|
117
|
+
print(f" block {block_id} element type hex20")
|
|
118
|
+
elif elem_type == "wedge":
|
|
119
|
+
print(f" block {block_id} element type wedge15")
|
|
120
|
+
elif elem_type == "pyramid":
|
|
121
|
+
print(f" block {block_id} element type pyramid13")
|
|
122
|
+
|
|
123
|
+
########################################################################
|
|
124
|
+
### Gmsh format version 2.2
|
|
125
|
+
### - Flat structure: $Nodes and $Elements as simple lists
|
|
126
|
+
########################################################################
|
|
127
|
+
|
|
128
|
+
def export_Gmsh_ver2(cubit: Any, FileName: str) -> Any:
|
|
129
|
+
"""Export mesh to Gmsh format version 2.2.
|
|
130
|
+
|
|
131
|
+
Exports 1D, 2D, and 3D mesh elements from Cubit to Gmsh v2.2 format.
|
|
132
|
+
Supports both first-order and second-order elements.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
cubit: Cubit Python interface object
|
|
136
|
+
FileName: Output file path for the .msh file
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
cubit: The cubit object (for method chaining)
|
|
140
|
+
|
|
141
|
+
Supported elements:
|
|
142
|
+
- 1st order: Point, Line, Triangle, Quad, Tetrahedron, Hexahedron, Wedge, Pyramid
|
|
143
|
+
- 2nd order: Line3, Triangle6, Quad8/9, Tetrahedron10/11, Hexahedron20, Wedge15, Pyramid13
|
|
144
|
+
"""
|
|
145
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
146
|
+
|
|
147
|
+
with open(FileName, 'w') as fid:
|
|
148
|
+
|
|
149
|
+
fid.write("$MeshFormat\n")
|
|
150
|
+
fid.write("2.2 0 8\n")
|
|
151
|
+
fid.write("$EndMeshFormat\n")
|
|
152
|
+
|
|
153
|
+
fid.write("$PhysicalNames\n")
|
|
154
|
+
fid.write(f'{cubit.get_block_count()}\n')
|
|
155
|
+
for block_id in cubit.get_block_id_list():
|
|
156
|
+
name = cubit.get_exodus_entity_name("block", block_id)
|
|
157
|
+
if len(_get_block_elements(cubit, block_id, "node")) > 0:
|
|
158
|
+
fid.write(f'0 {block_id} "{name}"\n')
|
|
159
|
+
elif len(_get_block_elements(cubit, block_id, "edge")) > 0:
|
|
160
|
+
fid.write(f'1 {block_id} "{name}"\n')
|
|
161
|
+
elif len(_get_block_elements(cubit, block_id, "tri")) + len(_get_block_elements(cubit, block_id, "face")) > 0:
|
|
162
|
+
fid.write(f'2 {block_id} "{name}"\n')
|
|
163
|
+
else:
|
|
164
|
+
fid.write(f'3 {block_id} "{name}"\n')
|
|
165
|
+
fid.write('$EndPhysicalNames\n')
|
|
166
|
+
|
|
167
|
+
fid.write("$Nodes\n")
|
|
168
|
+
node_list = set()
|
|
169
|
+
for block_id in cubit.get_block_id_list():
|
|
170
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge", "node"]
|
|
171
|
+
for elem_type in elem_types:
|
|
172
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
173
|
+
node_ids = cubit.get_expanded_connectivity(elem_type, element_id)
|
|
174
|
+
node_list.update(node_ids)
|
|
175
|
+
|
|
176
|
+
fid.write(f'{len(node_list)}\n')
|
|
177
|
+
for node_id in node_list:
|
|
178
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
179
|
+
fid.write(f'{node_id} {coord[0]} {coord[1]} {coord[2]}\n')
|
|
180
|
+
fid.write('$EndNodes\n')
|
|
181
|
+
|
|
182
|
+
hex_list = set()
|
|
183
|
+
tet_list = set()
|
|
184
|
+
wedge_list = set()
|
|
185
|
+
pyramid_list = set()
|
|
186
|
+
tri_list = set()
|
|
187
|
+
quad_list = set()
|
|
188
|
+
edge_list = set()
|
|
189
|
+
node_list = set()
|
|
190
|
+
|
|
191
|
+
for block_id in cubit.get_block_id_list():
|
|
192
|
+
tet_list.update(_get_block_elements(cubit, block_id, "tet"))
|
|
193
|
+
hex_list.update(_get_block_elements(cubit, block_id, "hex"))
|
|
194
|
+
wedge_list.update(_get_block_elements(cubit, block_id, "wedge"))
|
|
195
|
+
pyramid_list.update(_get_block_elements(cubit, block_id, "pyramid"))
|
|
196
|
+
tri_list.update(_get_block_elements(cubit, block_id, "tri"))
|
|
197
|
+
quad_list.update(_get_block_elements(cubit, block_id, "face"))
|
|
198
|
+
edge_list.update(_get_block_elements(cubit, block_id, "edge"))
|
|
199
|
+
node_list.update(_get_block_elements(cubit, block_id, "node"))
|
|
200
|
+
|
|
201
|
+
element_id = 0
|
|
202
|
+
fid.write('$Elements\n')
|
|
203
|
+
fid.write(f'{len(hex_list) + len(tet_list) + len(wedge_list) + len(pyramid_list) + len(tri_list) + len(quad_list) + len(edge_list) + len(node_list)}\n')
|
|
204
|
+
|
|
205
|
+
for block_id in cubit.get_block_id_list():
|
|
206
|
+
|
|
207
|
+
tet_list = _get_block_elements(cubit, block_id, "tet")
|
|
208
|
+
for tet_id in tet_list:
|
|
209
|
+
element_id += 1
|
|
210
|
+
node_list = cubit.get_expanded_connectivity("tet", tet_id)
|
|
211
|
+
if len(node_list)==4:
|
|
212
|
+
fid.write(f'{element_id} { 4} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]}\n')
|
|
213
|
+
elif len(node_list)==10:
|
|
214
|
+
fid.write(f'{element_id} {11} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]} {node_list[9]} {node_list[8]}\n')
|
|
215
|
+
elif len(node_list)==11:
|
|
216
|
+
fid.write(f'{element_id} {35} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]} {node_list[9]} {node_list[8]} {node_list[10]}\n')
|
|
217
|
+
|
|
218
|
+
hex_list = _get_block_elements(cubit, block_id, "hex")
|
|
219
|
+
for hex_id in hex_list:
|
|
220
|
+
element_id += 1
|
|
221
|
+
node_list = cubit.get_expanded_connectivity("hex", hex_id)
|
|
222
|
+
if len(node_list)==8:
|
|
223
|
+
fid.write(f'{element_id} { 5} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]}\n')
|
|
224
|
+
elif len(node_list)==20:
|
|
225
|
+
fid.write(f'{element_id} {17} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]} {node_list[8]} {node_list[11]} {node_list[12]} {node_list[9]} {node_list[13]} {node_list[10]} {node_list[14]} {node_list[15]} {node_list[16]} {node_list[19]} {node_list[17]} {node_list[18]}\n')
|
|
226
|
+
|
|
227
|
+
wedge_list = _get_block_elements(cubit, block_id, "wedge")
|
|
228
|
+
for wedge_id in wedge_list:
|
|
229
|
+
element_id += 1
|
|
230
|
+
node_list = cubit.get_expanded_connectivity("wedge", wedge_id)
|
|
231
|
+
if len(node_list)==6:
|
|
232
|
+
fid.write(f'{element_id} { 6} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]}\n')
|
|
233
|
+
elif len(node_list)==15:
|
|
234
|
+
fid.write(f'{element_id} {18} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[8]} {node_list[9]} {node_list[7]} {node_list[10]} {node_list[11]} {node_list[12]} {node_list[14]} {node_list[13]}\n')
|
|
235
|
+
|
|
236
|
+
pyramid_list = _get_block_elements(cubit, block_id, "pyramid")
|
|
237
|
+
for pyramid_id in pyramid_list:
|
|
238
|
+
element_id += 1
|
|
239
|
+
node_list = cubit.get_expanded_connectivity("pyramid", pyramid_id)
|
|
240
|
+
if len(node_list)==5:
|
|
241
|
+
fid.write(f'{element_id} { 7} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]}\n')
|
|
242
|
+
elif len(node_list)==13:
|
|
243
|
+
fid.write(f'{element_id} {19} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[8]} {node_list[9]} {node_list[6]} {node_list[10]} {node_list[7]} {node_list[11]} {node_list[12]} \n')
|
|
244
|
+
|
|
245
|
+
tri_list = _get_block_elements(cubit, block_id, "tri")
|
|
246
|
+
for tri_id in tri_list:
|
|
247
|
+
element_id += 1
|
|
248
|
+
node_list = cubit.get_expanded_connectivity("tri", tri_id)
|
|
249
|
+
if len(node_list)==3:
|
|
250
|
+
fid.write(f'{element_id} { 2} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]}\n')
|
|
251
|
+
elif len(node_list)==6:
|
|
252
|
+
fid.write(f'{element_id} { 9} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]}\n')
|
|
253
|
+
elif len(node_list)==7:
|
|
254
|
+
fid.write(f'{element_id} {42} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]}\n')
|
|
255
|
+
|
|
256
|
+
quad_list = _get_block_elements(cubit, block_id, "face")
|
|
257
|
+
for quad_id in quad_list:
|
|
258
|
+
element_id += 1
|
|
259
|
+
node_list = cubit.get_expanded_connectivity("quad", quad_id)
|
|
260
|
+
if len(node_list)==4:
|
|
261
|
+
fid.write(f'{element_id} { 3} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]}\n')
|
|
262
|
+
elif len(node_list)==8:
|
|
263
|
+
fid.write(f'{element_id} {16} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]}\n')
|
|
264
|
+
elif len(node_list)==9:
|
|
265
|
+
fid.write(f'{element_id} {10} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]} {node_list[8]}\n')
|
|
266
|
+
|
|
267
|
+
edge_list = _get_block_elements(cubit, block_id, "edge")
|
|
268
|
+
for edge_id in edge_list:
|
|
269
|
+
element_id += 1
|
|
270
|
+
node_list = cubit.get_expanded_connectivity("edge", edge_id)
|
|
271
|
+
if len(node_list)==2:
|
|
272
|
+
fid.write(f'{element_id} {1} {2} {block_id} {block_id} {node_list[0]} {node_list[1]}\n')
|
|
273
|
+
elif len(node_list)==3:
|
|
274
|
+
fid.write(f'{element_id} {8} {2} {block_id} {block_id} {node_list[0]} {node_list[1]} {node_list[2]}\n')
|
|
275
|
+
|
|
276
|
+
node_list = _get_block_elements(cubit, block_id, "node")
|
|
277
|
+
for node_id in node_list:
|
|
278
|
+
element_id += 1
|
|
279
|
+
fid.write(f'{element_id} {15} {2} {block_id} {block_id} {node_id}\n')
|
|
280
|
+
|
|
281
|
+
fid.write('$EndElements\n')
|
|
282
|
+
return cubit
|
|
283
|
+
|
|
284
|
+
########################################################################
|
|
285
|
+
### Gmsh format version 4.1
|
|
286
|
+
### - Hierarchical structure: $Entities defines geometry topology
|
|
287
|
+
### - Entity-based $Nodes and $Elements grouping
|
|
288
|
+
########################################################################
|
|
289
|
+
|
|
290
|
+
def export_Gmsh_ver4(cubit: Any, FileName: str, DIM: str = "auto") -> Any:
|
|
291
|
+
"""Export mesh to Gmsh format version 4.1.
|
|
292
|
+
|
|
293
|
+
Exports 1D, 2D, and 3D mesh elements from Cubit to Gmsh v4.1 format.
|
|
294
|
+
Supports both first-order and second-order elements.
|
|
295
|
+
Includes full $Entities section with geometry topology.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
cubit: Cubit Python interface object
|
|
299
|
+
FileName: Output file path for the .msh file
|
|
300
|
+
DIM: Dimension mode
|
|
301
|
+
- "auto": Auto-detect (3D if volume elements exist, else 2D)
|
|
302
|
+
- "2D": 2D mode (orient surface normals to +z direction, z-coordinates set to 0)
|
|
303
|
+
- "3D": 3D mode (no normal orientation)
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
cubit: The cubit object (for method chaining)
|
|
307
|
+
|
|
308
|
+
Supported elements:
|
|
309
|
+
- 1st order: Point, Line, Triangle, Quad, Tetrahedron, Hexahedron, Wedge, Pyramid
|
|
310
|
+
- 2nd order: Line3, Triangle6, Quad8/9, Tetrahedron10/11, Hexahedron20, Wedge15, Pyramid13
|
|
311
|
+
"""
|
|
312
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ============================================================
|
|
316
|
+
# Gmsh element type codes (same as v2)
|
|
317
|
+
# ============================================================
|
|
318
|
+
GMSH_POINT = 15
|
|
319
|
+
GMSH_LINE2 = 1
|
|
320
|
+
GMSH_LINE3 = 8
|
|
321
|
+
GMSH_TRI3 = 2
|
|
322
|
+
GMSH_TRI6 = 9
|
|
323
|
+
GMSH_TRI7 = 42
|
|
324
|
+
GMSH_QUAD4 = 3
|
|
325
|
+
GMSH_QUAD8 = 16
|
|
326
|
+
GMSH_QUAD9 = 10
|
|
327
|
+
GMSH_TET4 = 4
|
|
328
|
+
GMSH_TET10 = 11
|
|
329
|
+
GMSH_TET11 = 35
|
|
330
|
+
GMSH_HEX8 = 5
|
|
331
|
+
GMSH_HEX20 = 17
|
|
332
|
+
GMSH_WEDGE6 = 6
|
|
333
|
+
GMSH_WEDGE15 = 18
|
|
334
|
+
GMSH_PYRAMID5 = 7
|
|
335
|
+
GMSH_PYRAMID13 = 19
|
|
336
|
+
|
|
337
|
+
# ============================================================
|
|
338
|
+
# Collect block information and determine dimension
|
|
339
|
+
# ============================================================
|
|
340
|
+
|
|
341
|
+
# Collect elements by type from all blocks
|
|
342
|
+
all_tets = set()
|
|
343
|
+
all_hexes = set()
|
|
344
|
+
all_wedges = set()
|
|
345
|
+
all_pyramids = set()
|
|
346
|
+
all_tris = set()
|
|
347
|
+
all_quads = set()
|
|
348
|
+
all_edges = set()
|
|
349
|
+
all_points = set()
|
|
350
|
+
|
|
351
|
+
# Map element to block_id
|
|
352
|
+
elem_to_block = {}
|
|
353
|
+
|
|
354
|
+
for block_id in cubit.get_block_id_list():
|
|
355
|
+
for tet_id in _get_block_elements(cubit, block_id, "tet"):
|
|
356
|
+
all_tets.add(tet_id)
|
|
357
|
+
elem_to_block[('tet', tet_id)] = block_id
|
|
358
|
+
for hex_id in _get_block_elements(cubit, block_id, "hex"):
|
|
359
|
+
all_hexes.add(hex_id)
|
|
360
|
+
elem_to_block[('hex', hex_id)] = block_id
|
|
361
|
+
for wedge_id in _get_block_elements(cubit, block_id, "wedge"):
|
|
362
|
+
all_wedges.add(wedge_id)
|
|
363
|
+
elem_to_block[('wedge', wedge_id)] = block_id
|
|
364
|
+
for pyramid_id in _get_block_elements(cubit, block_id, "pyramid"):
|
|
365
|
+
all_pyramids.add(pyramid_id)
|
|
366
|
+
elem_to_block[('pyramid', pyramid_id)] = block_id
|
|
367
|
+
for tri_id in _get_block_elements(cubit, block_id, "tri"):
|
|
368
|
+
all_tris.add(tri_id)
|
|
369
|
+
elem_to_block[('tri', tri_id)] = block_id
|
|
370
|
+
for quad_id in _get_block_elements(cubit, block_id, "face"):
|
|
371
|
+
all_quads.add(quad_id)
|
|
372
|
+
elem_to_block[('quad', quad_id)] = block_id
|
|
373
|
+
for edge_id in _get_block_elements(cubit, block_id, "edge"):
|
|
374
|
+
all_edges.add(edge_id)
|
|
375
|
+
elem_to_block[('edge', edge_id)] = block_id
|
|
376
|
+
for node_id in _get_block_elements(cubit, block_id, "node"):
|
|
377
|
+
all_points.add(node_id)
|
|
378
|
+
elem_to_block[('node', node_id)] = block_id
|
|
379
|
+
|
|
380
|
+
has_3d_elements = len(all_tets) + len(all_hexes) + len(all_wedges) + len(all_pyramids) > 0
|
|
381
|
+
has_2d_elements = len(all_tris) + len(all_quads) > 0
|
|
382
|
+
|
|
383
|
+
# Determine dimension mode
|
|
384
|
+
if DIM == "auto":
|
|
385
|
+
actual_dim = "3D" if has_3d_elements else "2D"
|
|
386
|
+
else:
|
|
387
|
+
actual_dim = DIM
|
|
388
|
+
|
|
389
|
+
# ============================================================
|
|
390
|
+
# Collect nodes
|
|
391
|
+
# ============================================================
|
|
392
|
+
|
|
393
|
+
node_set = set()
|
|
394
|
+
for block_id in cubit.get_block_id_list():
|
|
395
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge", "node"]
|
|
396
|
+
for elem_type in elem_types:
|
|
397
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
398
|
+
node_ids = cubit.get_expanded_connectivity(elem_type, element_id)
|
|
399
|
+
node_set.update(node_ids)
|
|
400
|
+
|
|
401
|
+
sorted_nodes = sorted(node_set)
|
|
402
|
+
num_nodes = len(sorted_nodes)
|
|
403
|
+
min_node_tag = min(sorted_nodes) if sorted_nodes else 0
|
|
404
|
+
max_node_tag = max(sorted_nodes) if sorted_nodes else 0
|
|
405
|
+
|
|
406
|
+
# ============================================================
|
|
407
|
+
# Collect geometry entities for $Entities section
|
|
408
|
+
# ============================================================
|
|
409
|
+
|
|
410
|
+
# Get volumes and surfaces associated with blocks
|
|
411
|
+
volume_set = set()
|
|
412
|
+
surface_set = set()
|
|
413
|
+
curve_set = set()
|
|
414
|
+
vertex_set = set()
|
|
415
|
+
|
|
416
|
+
# Physical tags: block_id -> set of entity IDs
|
|
417
|
+
volume_physical = {} # volume_id -> block_id
|
|
418
|
+
surface_physical = {} # surface_id -> block_id
|
|
419
|
+
|
|
420
|
+
for block_id in cubit.get_block_id_list():
|
|
421
|
+
# 3D blocks -> volumes
|
|
422
|
+
for vol_id in cubit.get_block_volumes(block_id):
|
|
423
|
+
volume_set.add(vol_id)
|
|
424
|
+
volume_physical[vol_id] = block_id
|
|
425
|
+
# Get surfaces of this volume
|
|
426
|
+
try:
|
|
427
|
+
vol = cubit.volume(vol_id)
|
|
428
|
+
for surf in vol.surfaces():
|
|
429
|
+
surface_set.add(surf.id())
|
|
430
|
+
# Get curves of this surface
|
|
431
|
+
for curve in surf.curves():
|
|
432
|
+
curve_set.add(curve.id())
|
|
433
|
+
# Get vertices of this curve
|
|
434
|
+
for vert in curve.vertices():
|
|
435
|
+
vertex_set.add(vert.id())
|
|
436
|
+
except:
|
|
437
|
+
# Fallback: use parse_cubit_list
|
|
438
|
+
surf_ids = cubit.parse_cubit_list("surface", f"in volume {vol_id}")
|
|
439
|
+
surface_set.update(surf_ids)
|
|
440
|
+
|
|
441
|
+
# 2D blocks -> surfaces
|
|
442
|
+
tri_list = _get_block_elements(cubit, block_id, "tri")
|
|
443
|
+
quad_list = _get_block_elements(cubit, block_id, "face")
|
|
444
|
+
if len(tri_list) + len(quad_list) > 0:
|
|
445
|
+
# Find surfaces containing these elements
|
|
446
|
+
for tri_id in tri_list:
|
|
447
|
+
try:
|
|
448
|
+
owner = cubit.get_geometry_owner("tri", tri_id)
|
|
449
|
+
if owner and "surface" in owner.lower():
|
|
450
|
+
surf_id = int(owner.split()[1])
|
|
451
|
+
surface_set.add(surf_id)
|
|
452
|
+
surface_physical[surf_id] = block_id
|
|
453
|
+
except:
|
|
454
|
+
pass
|
|
455
|
+
for quad_id in quad_list:
|
|
456
|
+
try:
|
|
457
|
+
owner = cubit.get_geometry_owner("quad", quad_id)
|
|
458
|
+
if owner and "surface" in owner.lower():
|
|
459
|
+
surf_id = int(owner.split()[1])
|
|
460
|
+
surface_set.add(surf_id)
|
|
461
|
+
surface_physical[surf_id] = block_id
|
|
462
|
+
except:
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
# If no geometry found, create minimal entities based on blocks
|
|
466
|
+
if not volume_set and not surface_set:
|
|
467
|
+
# Use block IDs as entity tags
|
|
468
|
+
for block_id in cubit.get_block_id_list():
|
|
469
|
+
if len(_get_block_elements(cubit, block_id, "tet")) + len(_get_block_elements(cubit, block_id, "hex")) + \
|
|
470
|
+
len(_get_block_elements(cubit, block_id, "wedge")) + len(_get_block_elements(cubit, block_id, "pyramid")) > 0:
|
|
471
|
+
volume_set.add(block_id)
|
|
472
|
+
volume_physical[block_id] = block_id
|
|
473
|
+
elif len(_get_block_elements(cubit, block_id, "tri")) + len(_get_block_elements(cubit, block_id, "face")) > 0:
|
|
474
|
+
surface_set.add(block_id)
|
|
475
|
+
surface_physical[block_id] = block_id
|
|
476
|
+
|
|
477
|
+
# ============================================================
|
|
478
|
+
# Write Gmsh v4 file
|
|
479
|
+
# ============================================================
|
|
480
|
+
|
|
481
|
+
with open(FileName, 'w') as fid:
|
|
482
|
+
|
|
483
|
+
# ------------------------------------------------------------
|
|
484
|
+
# $MeshFormat
|
|
485
|
+
# ------------------------------------------------------------
|
|
486
|
+
fid.write('$MeshFormat\n')
|
|
487
|
+
fid.write('4.1 0 8\n')
|
|
488
|
+
fid.write('$EndMeshFormat\n')
|
|
489
|
+
|
|
490
|
+
# ------------------------------------------------------------
|
|
491
|
+
# $PhysicalNames
|
|
492
|
+
# ------------------------------------------------------------
|
|
493
|
+
fid.write('$PhysicalNames\n')
|
|
494
|
+
fid.write(f'{cubit.get_block_count()}\n')
|
|
495
|
+
for block_id in cubit.get_block_id_list():
|
|
496
|
+
name = cubit.get_exodus_entity_name("block", block_id)
|
|
497
|
+
if len(_get_block_elements(cubit, block_id, "node")) > 0:
|
|
498
|
+
dim = 0
|
|
499
|
+
elif len(_get_block_elements(cubit, block_id, "edge")) > 0:
|
|
500
|
+
dim = 1
|
|
501
|
+
elif len(_get_block_elements(cubit, block_id, "tri")) + len(_get_block_elements(cubit, block_id, "face")) > 0:
|
|
502
|
+
dim = 2
|
|
503
|
+
else:
|
|
504
|
+
dim = 3
|
|
505
|
+
fid.write(f'{dim} {block_id} "{name}"\n')
|
|
506
|
+
fid.write('$EndPhysicalNames\n')
|
|
507
|
+
|
|
508
|
+
# ------------------------------------------------------------
|
|
509
|
+
# $Entities
|
|
510
|
+
# ------------------------------------------------------------
|
|
511
|
+
num_points_ent = len(vertex_set)
|
|
512
|
+
num_curves_ent = len(curve_set)
|
|
513
|
+
num_surfaces_ent = len(surface_set) if surface_set else len([b for b in cubit.get_block_id_list() if len(_get_block_elements(cubit, b, "tri")) + len(_get_block_elements(cubit, b, "face")) > 0])
|
|
514
|
+
num_volumes_ent = len(volume_set) if volume_set else len([b for b in cubit.get_block_id_list() if len(_get_block_elements(cubit, b, "tet")) + len(_get_block_elements(cubit, b, "hex")) + len(_get_block_elements(cubit, b, "wedge")) + len(_get_block_elements(cubit, b, "pyramid")) > 0])
|
|
515
|
+
|
|
516
|
+
fid.write('$Entities\n')
|
|
517
|
+
fid.write(f'{num_points_ent} {num_curves_ent} {num_surfaces_ent} {num_volumes_ent}\n')
|
|
518
|
+
|
|
519
|
+
# Points (vertices)
|
|
520
|
+
for vert_id in sorted(vertex_set):
|
|
521
|
+
try:
|
|
522
|
+
coord = cubit.vertex(vert_id).coordinates()
|
|
523
|
+
fid.write(f'{vert_id} {coord[0]} {coord[1]} {coord[2]} 0\n')
|
|
524
|
+
except:
|
|
525
|
+
fid.write(f'{vert_id} 0 0 0 0\n')
|
|
526
|
+
|
|
527
|
+
# Curves
|
|
528
|
+
for curve_id in sorted(curve_set):
|
|
529
|
+
try:
|
|
530
|
+
bbox = cubit.get_bounding_box("curve", curve_id)
|
|
531
|
+
minx, maxx = bbox[0], bbox[1]
|
|
532
|
+
miny, maxy = bbox[3], bbox[4]
|
|
533
|
+
minz, maxz = bbox[6], bbox[7]
|
|
534
|
+
# Get bounding vertices
|
|
535
|
+
curve = cubit.curve(curve_id)
|
|
536
|
+
vert_ids = [v.id() for v in curve.vertices()]
|
|
537
|
+
num_bnd = len(vert_ids)
|
|
538
|
+
vert_str = ' '.join(str(v) for v in vert_ids)
|
|
539
|
+
fid.write(f'{curve_id} {minx} {miny} {minz} {maxx} {maxy} {maxz} 0 {num_bnd} {vert_str}\n')
|
|
540
|
+
except:
|
|
541
|
+
fid.write(f'{curve_id} 0 0 0 0 0 0 0 0\n')
|
|
542
|
+
|
|
543
|
+
# Surfaces
|
|
544
|
+
if surface_set:
|
|
545
|
+
for surf_id in sorted(surface_set):
|
|
546
|
+
try:
|
|
547
|
+
bbox = cubit.get_bounding_box("surface", surf_id)
|
|
548
|
+
minx, maxx = bbox[0], bbox[1]
|
|
549
|
+
miny, maxy = bbox[3], bbox[4]
|
|
550
|
+
minz, maxz = bbox[6], bbox[7]
|
|
551
|
+
# Physical tag
|
|
552
|
+
phys_tag = surface_physical.get(surf_id, 0)
|
|
553
|
+
num_phys = 1 if phys_tag else 0
|
|
554
|
+
phys_str = f' {phys_tag}' if phys_tag else ''
|
|
555
|
+
# Get bounding curves
|
|
556
|
+
try:
|
|
557
|
+
surf = cubit.surface(surf_id)
|
|
558
|
+
curve_ids = [c.id() for c in surf.curves()]
|
|
559
|
+
num_bnd = len(curve_ids)
|
|
560
|
+
curve_str = ' '.join(str(c) for c in curve_ids)
|
|
561
|
+
except:
|
|
562
|
+
num_bnd = 0
|
|
563
|
+
curve_str = ''
|
|
564
|
+
fid.write(f'{surf_id} {minx} {miny} {minz} {maxx} {maxy} {maxz} {num_phys}{phys_str} {num_bnd} {curve_str}\n')
|
|
565
|
+
except:
|
|
566
|
+
fid.write(f'{surf_id} 0 0 0 0 0 0 0 0\n')
|
|
567
|
+
else:
|
|
568
|
+
# Fallback: use blocks as surfaces
|
|
569
|
+
for block_id in cubit.get_block_id_list():
|
|
570
|
+
if len(_get_block_elements(cubit, block_id, "tri")) + len(_get_block_elements(cubit, block_id, "face")) > 0:
|
|
571
|
+
fid.write(f'{block_id} 0 0 0 0 0 0 1 {block_id} 0\n')
|
|
572
|
+
|
|
573
|
+
# Volumes
|
|
574
|
+
if volume_set:
|
|
575
|
+
for vol_id in sorted(volume_set):
|
|
576
|
+
try:
|
|
577
|
+
bbox = cubit.get_bounding_box("volume", vol_id)
|
|
578
|
+
minx, maxx = bbox[0], bbox[1]
|
|
579
|
+
miny, maxy = bbox[3], bbox[4]
|
|
580
|
+
minz, maxz = bbox[6], bbox[7]
|
|
581
|
+
# Physical tag
|
|
582
|
+
phys_tag = volume_physical.get(vol_id, 0)
|
|
583
|
+
num_phys = 1 if phys_tag else 0
|
|
584
|
+
phys_str = f' {phys_tag}' if phys_tag else ''
|
|
585
|
+
# Get bounding surfaces
|
|
586
|
+
try:
|
|
587
|
+
vol = cubit.volume(vol_id)
|
|
588
|
+
surf_ids = [s.id() for s in vol.surfaces()]
|
|
589
|
+
num_bnd = len(surf_ids)
|
|
590
|
+
surf_str = ' '.join(str(s) for s in surf_ids)
|
|
591
|
+
except:
|
|
592
|
+
num_bnd = 0
|
|
593
|
+
surf_str = ''
|
|
594
|
+
fid.write(f'{vol_id} {minx} {miny} {minz} {maxx} {maxy} {maxz} {num_phys}{phys_str} {num_bnd} {surf_str}\n')
|
|
595
|
+
except:
|
|
596
|
+
fid.write(f'{vol_id} 0 0 0 0 0 0 0 0\n')
|
|
597
|
+
else:
|
|
598
|
+
# Fallback: use blocks as volumes
|
|
599
|
+
for block_id in cubit.get_block_id_list():
|
|
600
|
+
if len(_get_block_elements(cubit, block_id, "tet")) + len(_get_block_elements(cubit, block_id, "hex")) + \
|
|
601
|
+
len(_get_block_elements(cubit, block_id, "wedge")) + len(_get_block_elements(cubit, block_id, "pyramid")) > 0:
|
|
602
|
+
fid.write(f'{block_id} 0 0 0 0 0 0 1 {block_id} 0\n')
|
|
603
|
+
|
|
604
|
+
fid.write('$EndEntities\n')
|
|
605
|
+
|
|
606
|
+
# ------------------------------------------------------------
|
|
607
|
+
# $Nodes (v4 format: entity blocks)
|
|
608
|
+
# ------------------------------------------------------------
|
|
609
|
+
# For simplicity, put all nodes in one entity block
|
|
610
|
+
fid.write('$Nodes\n')
|
|
611
|
+
# numEntityBlocks numNodes minNodeTag maxNodeTag
|
|
612
|
+
fid.write(f'1 {num_nodes} {min_node_tag} {max_node_tag}\n')
|
|
613
|
+
# entityDim entityTag parametric numNodesInBlock
|
|
614
|
+
entity_dim = 3 if has_3d_elements else 2
|
|
615
|
+
entity_tag = 1
|
|
616
|
+
fid.write(f'{entity_dim} {entity_tag} 0 {num_nodes}\n')
|
|
617
|
+
# Node tags
|
|
618
|
+
for node_id in sorted_nodes:
|
|
619
|
+
fid.write(f'{node_id}\n')
|
|
620
|
+
# Node coordinates
|
|
621
|
+
for node_id in sorted_nodes:
|
|
622
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
623
|
+
if actual_dim == "2D":
|
|
624
|
+
fid.write(f'{coord[0]} {coord[1]} 0\n')
|
|
625
|
+
else:
|
|
626
|
+
fid.write(f'{coord[0]} {coord[1]} {coord[2]}\n')
|
|
627
|
+
fid.write('$EndNodes\n')
|
|
628
|
+
|
|
629
|
+
# ------------------------------------------------------------
|
|
630
|
+
# $Elements (v4 format: entity blocks)
|
|
631
|
+
# ------------------------------------------------------------
|
|
632
|
+
|
|
633
|
+
# Helper function: get element nodes with optional normal flip for 2D
|
|
634
|
+
def get_element_nodes(elem_type, elem_id, flip_normal=False):
|
|
635
|
+
nodes = list(cubit.get_expanded_connectivity(elem_type, elem_id))
|
|
636
|
+
if flip_normal:
|
|
637
|
+
if elem_type == "tri":
|
|
638
|
+
if len(nodes) == 3:
|
|
639
|
+
nodes = [nodes[0], nodes[2], nodes[1]]
|
|
640
|
+
elif len(nodes) == 6:
|
|
641
|
+
nodes = [nodes[0], nodes[2], nodes[1], nodes[5], nodes[4], nodes[3]]
|
|
642
|
+
elif elem_type in ["quad", "face"]:
|
|
643
|
+
if len(nodes) == 4:
|
|
644
|
+
nodes = [nodes[0], nodes[3], nodes[2], nodes[1]]
|
|
645
|
+
elif len(nodes) == 8:
|
|
646
|
+
nodes = [nodes[0], nodes[3], nodes[2], nodes[1], nodes[7], nodes[6], nodes[5], nodes[4]]
|
|
647
|
+
return nodes
|
|
648
|
+
|
|
649
|
+
# Check if normal flip is needed for 2D elements
|
|
650
|
+
def needs_normal_flip(elem_type, elem_id):
|
|
651
|
+
if actual_dim != "2D":
|
|
652
|
+
return False
|
|
653
|
+
try:
|
|
654
|
+
owner = cubit.get_geometry_owner(elem_type, elem_id)
|
|
655
|
+
if owner and "surface" in owner.lower():
|
|
656
|
+
surf_id = int(owner.split()[1])
|
|
657
|
+
normal = cubit.get_surface_normal(surf_id)
|
|
658
|
+
return normal[2] < 0
|
|
659
|
+
except:
|
|
660
|
+
pass
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
# Count elements per block for entity blocks
|
|
664
|
+
block_elements = {} # block_id -> {'tet': [...], 'hex': [...], ...}
|
|
665
|
+
for block_id in cubit.get_block_id_list():
|
|
666
|
+
block_elements[block_id] = {
|
|
667
|
+
'tet': list(_get_block_elements(cubit, block_id, "tet")),
|
|
668
|
+
'hex': list(_get_block_elements(cubit, block_id, "hex")),
|
|
669
|
+
'wedge': list(_get_block_elements(cubit, block_id, "wedge")),
|
|
670
|
+
'pyramid': list(_get_block_elements(cubit, block_id, "pyramid")),
|
|
671
|
+
'tri': list(_get_block_elements(cubit, block_id, "tri")),
|
|
672
|
+
'quad': list(_get_block_elements(cubit, block_id, "face")),
|
|
673
|
+
'edge': list(_get_block_elements(cubit, block_id, "edge")),
|
|
674
|
+
'node': list(_get_block_elements(cubit, block_id, "node")),
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
# Count total elements and entity blocks
|
|
678
|
+
total_elements = len(all_tets) + len(all_hexes) + len(all_wedges) + len(all_pyramids) + \
|
|
679
|
+
len(all_tris) + len(all_quads) + len(all_edges) + len(all_points)
|
|
680
|
+
|
|
681
|
+
# Count entity blocks (one per element type per block)
|
|
682
|
+
num_entity_blocks = 0
|
|
683
|
+
for block_id, elems in block_elements.items():
|
|
684
|
+
for elem_type, elem_list in elems.items():
|
|
685
|
+
if len(elem_list) > 0:
|
|
686
|
+
num_entity_blocks += 1
|
|
687
|
+
|
|
688
|
+
if total_elements == 0:
|
|
689
|
+
min_elem_tag = 0
|
|
690
|
+
max_elem_tag = 0
|
|
691
|
+
else:
|
|
692
|
+
min_elem_tag = 1
|
|
693
|
+
max_elem_tag = total_elements
|
|
694
|
+
|
|
695
|
+
fid.write('$Elements\n')
|
|
696
|
+
fid.write(f'{num_entity_blocks} {total_elements} {min_elem_tag} {max_elem_tag}\n')
|
|
697
|
+
|
|
698
|
+
element_tag = 0
|
|
699
|
+
|
|
700
|
+
for block_id in cubit.get_block_id_list():
|
|
701
|
+
elems = block_elements[block_id]
|
|
702
|
+
|
|
703
|
+
# Tetrahedra
|
|
704
|
+
if len(elems['tet']) > 0:
|
|
705
|
+
tet_list = elems['tet']
|
|
706
|
+
# Determine element type from first element
|
|
707
|
+
sample_nodes = cubit.get_expanded_connectivity("tet", tet_list[0])
|
|
708
|
+
if len(sample_nodes) == 4:
|
|
709
|
+
gmsh_type = GMSH_TET4
|
|
710
|
+
elif len(sample_nodes) == 10:
|
|
711
|
+
gmsh_type = GMSH_TET10
|
|
712
|
+
else:
|
|
713
|
+
gmsh_type = GMSH_TET11
|
|
714
|
+
|
|
715
|
+
fid.write(f'3 {block_id} {gmsh_type} {len(tet_list)}\n')
|
|
716
|
+
for tet_id in tet_list:
|
|
717
|
+
element_tag += 1
|
|
718
|
+
nodes = cubit.get_expanded_connectivity("tet", tet_id)
|
|
719
|
+
if len(nodes) == 4:
|
|
720
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
721
|
+
elif len(nodes) == 10:
|
|
722
|
+
# Cubit -> Gmsh node order for TET10
|
|
723
|
+
reordered = [nodes[i] for i in [0, 1, 2, 3, 4, 5, 6, 7, 9, 8]]
|
|
724
|
+
node_str = ' '.join(str(n) for n in reordered)
|
|
725
|
+
else: # TET11
|
|
726
|
+
reordered = [nodes[i] for i in [0, 1, 2, 3, 4, 5, 6, 7, 9, 8, 10]]
|
|
727
|
+
node_str = ' '.join(str(n) for n in reordered)
|
|
728
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
729
|
+
|
|
730
|
+
# Hexahedra
|
|
731
|
+
if len(elems['hex']) > 0:
|
|
732
|
+
hex_list = elems['hex']
|
|
733
|
+
sample_nodes = cubit.get_expanded_connectivity("hex", hex_list[0])
|
|
734
|
+
gmsh_type = GMSH_HEX20 if len(sample_nodes) == 20 else GMSH_HEX8
|
|
735
|
+
|
|
736
|
+
fid.write(f'3 {block_id} {gmsh_type} {len(hex_list)}\n')
|
|
737
|
+
for hex_id in hex_list:
|
|
738
|
+
element_tag += 1
|
|
739
|
+
nodes = cubit.get_expanded_connectivity("hex", hex_id)
|
|
740
|
+
if len(nodes) == 8:
|
|
741
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
742
|
+
else: # HEX20
|
|
743
|
+
# Cubit -> Gmsh node order for HEX20
|
|
744
|
+
reordered = [nodes[i] for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 9, 13, 10, 14, 15, 16, 19, 17, 18]]
|
|
745
|
+
node_str = ' '.join(str(n) for n in reordered)
|
|
746
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
747
|
+
|
|
748
|
+
# Wedges
|
|
749
|
+
if len(elems['wedge']) > 0:
|
|
750
|
+
wedge_list = elems['wedge']
|
|
751
|
+
sample_nodes = cubit.get_expanded_connectivity("wedge", wedge_list[0])
|
|
752
|
+
gmsh_type = GMSH_WEDGE15 if len(sample_nodes) == 15 else GMSH_WEDGE6
|
|
753
|
+
|
|
754
|
+
fid.write(f'3 {block_id} {gmsh_type} {len(wedge_list)}\n')
|
|
755
|
+
for wedge_id in wedge_list:
|
|
756
|
+
element_tag += 1
|
|
757
|
+
nodes = cubit.get_expanded_connectivity("wedge", wedge_id)
|
|
758
|
+
if len(nodes) == 6:
|
|
759
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
760
|
+
else: # WEDGE15
|
|
761
|
+
reordered = [nodes[i] for i in [0, 1, 2, 3, 4, 5, 6, 8, 9, 7, 10, 11, 12, 14, 13]]
|
|
762
|
+
node_str = ' '.join(str(n) for n in reordered)
|
|
763
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
764
|
+
|
|
765
|
+
# Pyramids
|
|
766
|
+
if len(elems['pyramid']) > 0:
|
|
767
|
+
pyramid_list = elems['pyramid']
|
|
768
|
+
sample_nodes = cubit.get_expanded_connectivity("pyramid", pyramid_list[0])
|
|
769
|
+
gmsh_type = GMSH_PYRAMID13 if len(sample_nodes) == 13 else GMSH_PYRAMID5
|
|
770
|
+
|
|
771
|
+
fid.write(f'3 {block_id} {gmsh_type} {len(pyramid_list)}\n')
|
|
772
|
+
for pyramid_id in pyramid_list:
|
|
773
|
+
element_tag += 1
|
|
774
|
+
nodes = cubit.get_expanded_connectivity("pyramid", pyramid_id)
|
|
775
|
+
if len(nodes) == 5:
|
|
776
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
777
|
+
else: # PYRAMID13
|
|
778
|
+
reordered = [nodes[i] for i in [0, 1, 2, 3, 4, 5, 8, 9, 6, 10, 7, 11, 12]]
|
|
779
|
+
node_str = ' '.join(str(n) for n in reordered)
|
|
780
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
781
|
+
|
|
782
|
+
# Triangles
|
|
783
|
+
if len(elems['tri']) > 0:
|
|
784
|
+
tri_list = elems['tri']
|
|
785
|
+
sample_nodes = cubit.get_expanded_connectivity("tri", tri_list[0])
|
|
786
|
+
if len(sample_nodes) == 3:
|
|
787
|
+
gmsh_type = GMSH_TRI3
|
|
788
|
+
elif len(sample_nodes) == 6:
|
|
789
|
+
gmsh_type = GMSH_TRI6
|
|
790
|
+
else:
|
|
791
|
+
gmsh_type = GMSH_TRI7
|
|
792
|
+
|
|
793
|
+
fid.write(f'2 {block_id} {gmsh_type} {len(tri_list)}\n')
|
|
794
|
+
for tri_id in tri_list:
|
|
795
|
+
element_tag += 1
|
|
796
|
+
flip = needs_normal_flip("tri", tri_id)
|
|
797
|
+
nodes = get_element_nodes("tri", tri_id, flip)
|
|
798
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
799
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
800
|
+
|
|
801
|
+
# Quads
|
|
802
|
+
if len(elems['quad']) > 0:
|
|
803
|
+
quad_list = elems['quad']
|
|
804
|
+
sample_nodes = cubit.get_expanded_connectivity("quad", quad_list[0])
|
|
805
|
+
if len(sample_nodes) == 4:
|
|
806
|
+
gmsh_type = GMSH_QUAD4
|
|
807
|
+
elif len(sample_nodes) == 8:
|
|
808
|
+
gmsh_type = GMSH_QUAD8
|
|
809
|
+
else:
|
|
810
|
+
gmsh_type = GMSH_QUAD9
|
|
811
|
+
|
|
812
|
+
fid.write(f'2 {block_id} {gmsh_type} {len(quad_list)}\n')
|
|
813
|
+
for quad_id in quad_list:
|
|
814
|
+
element_tag += 1
|
|
815
|
+
flip = needs_normal_flip("quad", quad_id)
|
|
816
|
+
nodes = get_element_nodes("quad", quad_id, flip)
|
|
817
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
818
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
819
|
+
|
|
820
|
+
# Edges
|
|
821
|
+
if len(elems['edge']) > 0:
|
|
822
|
+
edge_list = elems['edge']
|
|
823
|
+
sample_nodes = cubit.get_expanded_connectivity("edge", edge_list[0])
|
|
824
|
+
gmsh_type = GMSH_LINE3 if len(sample_nodes) == 3 else GMSH_LINE2
|
|
825
|
+
|
|
826
|
+
fid.write(f'1 {block_id} {gmsh_type} {len(edge_list)}\n')
|
|
827
|
+
for edge_id in edge_list:
|
|
828
|
+
element_tag += 1
|
|
829
|
+
nodes = cubit.get_expanded_connectivity("edge", edge_id)
|
|
830
|
+
node_str = ' '.join(str(n) for n in nodes)
|
|
831
|
+
fid.write(f'{element_tag} {node_str}\n')
|
|
832
|
+
|
|
833
|
+
# Points (nodes as elements)
|
|
834
|
+
if len(elems['node']) > 0:
|
|
835
|
+
node_list = elems['node']
|
|
836
|
+
fid.write(f'0 {block_id} {GMSH_POINT} {len(node_list)}\n')
|
|
837
|
+
for node_id in node_list:
|
|
838
|
+
element_tag += 1
|
|
839
|
+
fid.write(f'{element_tag} {node_id}\n')
|
|
840
|
+
|
|
841
|
+
fid.write('$EndElements\n')
|
|
842
|
+
|
|
843
|
+
return cubit
|
|
844
|
+
|
|
845
|
+
########################################################################
|
|
846
|
+
### Nastran file
|
|
847
|
+
########################################################################
|
|
848
|
+
|
|
849
|
+
def export_Nastran(cubit: Any, FileName: str, DIM: str = "3D", PYRAM: bool = True) -> Any:
|
|
850
|
+
"""Export mesh to Nastran format.
|
|
851
|
+
|
|
852
|
+
Exports mesh elements from Cubit to NX Nastran bulk data format.
|
|
853
|
+
Supports 2D and 3D meshes with first-order elements only.
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
cubit: Cubit Python interface object
|
|
857
|
+
FileName: Output file path for the .nas or .bdf file
|
|
858
|
+
DIM: Dimension mode - "2D" for 2D meshes, "3D" for 3D meshes (default: "3D")
|
|
859
|
+
PYRAM: If True, export pyramids as CPYRAM; if False, convert to degenerate hex (default: True)
|
|
860
|
+
|
|
861
|
+
Returns:
|
|
862
|
+
cubit: The cubit object (for method chaining)
|
|
863
|
+
|
|
864
|
+
Supported elements:
|
|
865
|
+
- 3D: CTETRA (tet4), CHEXA (hex8), CPENTA (wedge6), CPYRAM (pyramid5)
|
|
866
|
+
- 2D: CTRIA3 (tri3), CQUAD4 (quad4)
|
|
867
|
+
- 1D: CROD (edge/bar)
|
|
868
|
+
- 0D: CMASS (point mass)
|
|
869
|
+
|
|
870
|
+
Note:
|
|
871
|
+
Only first-order elements are supported. Second-order elements are not exported.
|
|
872
|
+
"""
|
|
873
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
874
|
+
|
|
875
|
+
import datetime
|
|
876
|
+
formatted_date_time = datetime.datetime.now().strftime("%d-%b-%y at %H:%M:%S")
|
|
877
|
+
with open(FileName,'w',encoding='UTF-8') as fid:
|
|
878
|
+
fid.write("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n")
|
|
879
|
+
fid.write("$\n")
|
|
880
|
+
fid.write("$ CUBIT NX Nastran Translator\n")
|
|
881
|
+
fid.write("$\n")
|
|
882
|
+
fid.write(f"$ File: {FileName}\n")
|
|
883
|
+
fid.write(f"$ Time Stamp: {formatted_date_time}\n")
|
|
884
|
+
fid.write("$\n")
|
|
885
|
+
fid.write("$\n")
|
|
886
|
+
fid.write("$ PLEASE CHECK YOUR MODEL FOR UNITS CONSISTENCY.\n")
|
|
887
|
+
fid.write("$\n")
|
|
888
|
+
fid.write("$ It should be noted that load ID's from CUBIT may NOT correspond to Nastran SID's\n")
|
|
889
|
+
fid.write("$ The SID's for the load and restraint sets start at one and increment by one:i.e.,1,2,3,4...\n")
|
|
890
|
+
fid.write("$\n")
|
|
891
|
+
fid.write("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n")
|
|
892
|
+
fid.write("$\n")
|
|
893
|
+
fid.write("$\n")
|
|
894
|
+
fid.write("$ -------------------------\n")
|
|
895
|
+
fid.write("$ Executive Control Section\n")
|
|
896
|
+
fid.write("$ -------------------------\n")
|
|
897
|
+
fid.write("$\n")
|
|
898
|
+
fid.write("SOL 101\n")
|
|
899
|
+
fid.write("CEND\n")
|
|
900
|
+
fid.write("$\n")
|
|
901
|
+
fid.write("$\n")
|
|
902
|
+
fid.write("$ --------------------\n")
|
|
903
|
+
fid.write("$ Case Control Section\n")
|
|
904
|
+
fid.write("$ --------------------\n")
|
|
905
|
+
fid.write("$\n")
|
|
906
|
+
fid.write("ECHO = SORT\n")
|
|
907
|
+
fid.write("$\n")
|
|
908
|
+
fid.write("$\n")
|
|
909
|
+
fid.write("$ Name: Initial\n")
|
|
910
|
+
fid.write("$\n")
|
|
911
|
+
fid.write("$\n")
|
|
912
|
+
fid.write("$ Name: Default Set\n")
|
|
913
|
+
fid.write("$\n")
|
|
914
|
+
fid.write("SUBCASE = 1\n")
|
|
915
|
+
fid.write("$\n")
|
|
916
|
+
fid.write("LABEL = Default Set\n")
|
|
917
|
+
fid.write("$\n")
|
|
918
|
+
fid.write("$ -----------------\n")
|
|
919
|
+
fid.write("$ Bulk Data Section\n")
|
|
920
|
+
fid.write("$ -----------------\n")
|
|
921
|
+
fid.write("$\n")
|
|
922
|
+
fid.write("BEGIN BULK\n")
|
|
923
|
+
fid.write("$\n")
|
|
924
|
+
fid.write("$ Params\n")
|
|
925
|
+
fid.write("$\n")
|
|
926
|
+
fid.write("$\n")
|
|
927
|
+
fid.write("$ Node cards\n")
|
|
928
|
+
fid.write("$\n")
|
|
929
|
+
|
|
930
|
+
node_list = set()
|
|
931
|
+
for block_id in cubit.get_block_id_list():
|
|
932
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge", "node"]
|
|
933
|
+
for elem_type in elem_types:
|
|
934
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
935
|
+
node_ids = cubit.get_connectivity(elem_type, element_id)
|
|
936
|
+
node_list.update(node_ids)
|
|
937
|
+
for node_id in node_list:
|
|
938
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
939
|
+
if DIM == "3D":
|
|
940
|
+
fid.write(f"GRID* {node_id:>16}{0:>16}{coord[0]:>16.5f}{coord[1]:>16.5f}\n* {coord[2]:>16.5f}\n")
|
|
941
|
+
else:
|
|
942
|
+
fid.write(f"GRID* {node_id:>16}{0:>16}{coord[0]:>16.5f}{coord[1]:>16.5f}\n* {0}\n")
|
|
943
|
+
|
|
944
|
+
element_id = 0
|
|
945
|
+
fid.write("$\n")
|
|
946
|
+
fid.write("$ Element cards\n")
|
|
947
|
+
fid.write("$\n")
|
|
948
|
+
for block_id in cubit.get_block_id_list():
|
|
949
|
+
name = cubit.get_exodus_entity_name("block",block_id)
|
|
950
|
+
fid.write("$\n")
|
|
951
|
+
fid.write(f"$ Name: {name}\n")
|
|
952
|
+
fid.write("$\n")
|
|
953
|
+
tet_list = _get_block_elements(cubit, block_id, "tet")
|
|
954
|
+
|
|
955
|
+
if DIM=="3D":
|
|
956
|
+
for tet_id in tet_list:
|
|
957
|
+
node_list = cubit.get_connectivity('tet',tet_id)
|
|
958
|
+
element_id += 1
|
|
959
|
+
fid.write(f"CTETRA {element_id:>8}{block_id:>8}{node_list[0]:>8}{node_list[1]:>8}{node_list[2]:>8}{node_list[3]:>8}\n")
|
|
960
|
+
hex_list = _get_block_elements(cubit, block_id, "hex")
|
|
961
|
+
for hex_id in hex_list:
|
|
962
|
+
node_list = cubit.get_connectivity('hex',hex_id)
|
|
963
|
+
element_id += 1
|
|
964
|
+
fid.write(f"CHEXA {element_id:>8}{block_id:>8}{node_list[0]:>8}{node_list[1]:>8}{node_list[2]:>8}{node_list[3]:>8}{node_list[4]:>8}{node_list[5]:>8}+\n+ {node_list[6]:>8}{node_list[7]:>8}\n")
|
|
965
|
+
wedge_list = _get_block_elements(cubit, block_id, "wedge")
|
|
966
|
+
for wedge_id in wedge_list:
|
|
967
|
+
node_list = cubit.get_connectivity('wedge',wedge_id)
|
|
968
|
+
element_id += 1
|
|
969
|
+
fid.write(f"CPENTA {element_id:>8}{block_id:>8}{node_list[0]:>8}{node_list[1]:>8}{node_list[2]:>8}{node_list[3]:>8}{node_list[4]:>8}{node_list[5]:>8}\n")
|
|
970
|
+
pyramid_list = _get_block_elements(cubit, block_id, "pyramid")
|
|
971
|
+
for pyramid_id in pyramid_list:
|
|
972
|
+
node_list = cubit.get_connectivity('pyramid',pyramid_id)
|
|
973
|
+
if PYRAM:
|
|
974
|
+
element_id += 1
|
|
975
|
+
fid.write(f"CPYRAM {element_id:>8}{block_id:>8}{node_list[0]:>8}{node_list[1]:>8}{node_list[2]:>8}{node_list[3]:>8}{node_list[4]:>8}\n")
|
|
976
|
+
else:
|
|
977
|
+
element_id += 1
|
|
978
|
+
fid.write(f"CHEXA {element_id:>8}{block_id:>8}{node_list[0]:>8}{node_list[1]:>8}{node_list[2]:>8}{node_list[3]:>8}{node_list[4]:>8}{node_list[4]:>8}+\n+ {node_list[4]:>8}{node_list[4]:>8}\n")
|
|
979
|
+
|
|
980
|
+
tri_list = _get_block_elements(cubit, block_id, "tri")
|
|
981
|
+
for tri_id in tri_list:
|
|
982
|
+
node_list = cubit.get_connectivity('tri',tri_id)
|
|
983
|
+
element_id += 1
|
|
984
|
+
if DIM=="3D":
|
|
985
|
+
fid.write(f"CTRIA3 {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[1]:<8}{node_list[2]:<8}\n")
|
|
986
|
+
else:
|
|
987
|
+
surface_id = int(cubit.get_geometry_owner("tri", tri_id).split()[1])
|
|
988
|
+
normal = cubit.get_surface_normal(surface_id)
|
|
989
|
+
if normal[2] > 0:
|
|
990
|
+
fid.write(f"CTRIA3 {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[1]:<8}{node_list[2]:<8}\n")
|
|
991
|
+
else:
|
|
992
|
+
fid.write(f"CTRIA3 {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[2]:<8}{node_list[1]:<8}\n")
|
|
993
|
+
quad_list = _get_block_elements(cubit, block_id, "face")
|
|
994
|
+
for quad_id in quad_list:
|
|
995
|
+
node_list = cubit.get_connectivity('quad',quad_id)
|
|
996
|
+
element_id += 1
|
|
997
|
+
if DIM=="3D":
|
|
998
|
+
fid.write(f"CQUAD4 {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[1]:<8}{node_list[2]:<8}{node_list[3]:<8}\n")
|
|
999
|
+
else:
|
|
1000
|
+
surface_id = int(cubit.get_geometry_owner("quad", quad_id).split()[1])
|
|
1001
|
+
normal = cubit.get_surface_normal(surface_id)
|
|
1002
|
+
node_list = cubit.get_connectivity('quad',quad_id)
|
|
1003
|
+
if normal[2] > 0:
|
|
1004
|
+
fid.write(f"CQUAD4 {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[1]:<8}{node_list[2]:<8}{node_list[3]:<8}\n")
|
|
1005
|
+
else:
|
|
1006
|
+
fid.write(f"CQUAD4 {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[3]:<8}{node_list[2]:<8}{node_list[1]:<8}\n")
|
|
1007
|
+
edge_list = _get_block_elements(cubit, block_id, "edge")
|
|
1008
|
+
for edge_id in edge_list:
|
|
1009
|
+
element_id += 1
|
|
1010
|
+
node_list = cubit.get_connectivity('edge', edge_id)
|
|
1011
|
+
fid.write(f"CROD {element_id:<8}{block_id:<8}{node_list[0]:<8}{node_list[1]:<8}\n")
|
|
1012
|
+
node_list = _get_block_elements(cubit, block_id, "node")
|
|
1013
|
+
for node_id in node_list:
|
|
1014
|
+
element_id += 1
|
|
1015
|
+
fid.write(f"CMASS {element_id:<8}{block_id:<8}{node_id:<8}\n")
|
|
1016
|
+
fid.write("$\n")
|
|
1017
|
+
fid.write("$ Property cards\n")
|
|
1018
|
+
fid.write("$\n")
|
|
1019
|
+
|
|
1020
|
+
for block_id in cubit.get_block_id_list():
|
|
1021
|
+
name = cubit.get_exodus_entity_name("block",block_id)
|
|
1022
|
+
fid.write("$\n")
|
|
1023
|
+
fid.write(f"$ Name: {name}\n")
|
|
1024
|
+
if len(_get_block_elements(cubit, block_id, "node")) > 0:
|
|
1025
|
+
fid.write(f"PMASS {block_id:< 8}{block_id:< 8}\n")
|
|
1026
|
+
elif len(_get_block_elements(cubit, block_id, "edge")) > 0:
|
|
1027
|
+
fid.write(f"PROD {block_id:< 8}{block_id:< 8}\n")
|
|
1028
|
+
elif len(_get_block_elements(cubit, block_id, "tri")) + len(_get_block_elements(cubit, block_id, "face")) > 0:
|
|
1029
|
+
fid.write(f"PSHELL {block_id:< 8}{block_id:< 8}\n")
|
|
1030
|
+
else:
|
|
1031
|
+
fid.write(f"PSOLID {block_id:< 8}{block_id:< 8}\n")
|
|
1032
|
+
fid.write("$\n")
|
|
1033
|
+
|
|
1034
|
+
fid.write("ENDDATA\n")
|
|
1035
|
+
return cubit
|
|
1036
|
+
|
|
1037
|
+
########################################################################
|
|
1038
|
+
### ELF meg file
|
|
1039
|
+
########################################################################
|
|
1040
|
+
|
|
1041
|
+
def export_meg(cubit: Any, FileName: str, DIM: str = 'T', MGR2: Optional[List[List[float]]] = None) -> Any:
|
|
1042
|
+
"""Export mesh to ELF/MESH MEG format.
|
|
1043
|
+
|
|
1044
|
+
Exports mesh elements from Cubit to ELF/MAGIC MEG format for finite element analysis.
|
|
1045
|
+
|
|
1046
|
+
Args:
|
|
1047
|
+
cubit: Cubit Python interface object
|
|
1048
|
+
FileName: Output file path for the .meg file
|
|
1049
|
+
DIM: Dimension mode - 'T' for 3D, 'R' for axisymmetric, 'K' for 2D (default: 'T')
|
|
1050
|
+
MGR2: Optional list of spatial nodes [[x1,y1,z1], [x2,y2,z2], ...] (default: None)
|
|
1051
|
+
|
|
1052
|
+
Returns:
|
|
1053
|
+
cubit: The cubit object (for method chaining)
|
|
1054
|
+
|
|
1055
|
+
Supported elements:
|
|
1056
|
+
- 3D: Tetrahedron, Hexahedron, Wedge, Pyramid
|
|
1057
|
+
- 2D: Triangle, Quadrilateral
|
|
1058
|
+
- 1D: Edge
|
|
1059
|
+
- 0D: Node
|
|
1060
|
+
"""
|
|
1061
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
1062
|
+
|
|
1063
|
+
if MGR2 is None:
|
|
1064
|
+
MGR2 = []
|
|
1065
|
+
|
|
1066
|
+
with open(FileName,'w',encoding='UTF-8') as fid:
|
|
1067
|
+
fid.write("BOOK MEP 3.50\n")
|
|
1068
|
+
fid.write("* ELF/MESH VERSION 7.3.0\n")
|
|
1069
|
+
fid.write("* SOLVER = ELF/MAGIC\n")
|
|
1070
|
+
fid.write("MGSC 0.001\n")
|
|
1071
|
+
fid.write("* NODE\n")
|
|
1072
|
+
|
|
1073
|
+
node_list = set()
|
|
1074
|
+
for block_id in cubit.get_block_id_list():
|
|
1075
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge"]
|
|
1076
|
+
for elem_type in elem_types:
|
|
1077
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
1078
|
+
node_ids = cubit.get_connectivity(elem_type, element_id)
|
|
1079
|
+
node_list.update(node_ids)
|
|
1080
|
+
for node_id in node_list:
|
|
1081
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
1082
|
+
if DIM=='T':
|
|
1083
|
+
fid.write(f"MGR1 {node_id} 0 {coord[0]} {coord[1]} {coord[2]}\n")
|
|
1084
|
+
if DIM=='K':
|
|
1085
|
+
fid.write(f"MGR1 {node_id} 0 {coord[0]} {coord[1]} {0}\n")
|
|
1086
|
+
if DIM=='R':
|
|
1087
|
+
fid.write(f"MGR1 {node_id} 0 {coord[0]} {0} {coord[2]}\n")
|
|
1088
|
+
|
|
1089
|
+
element_id = 0
|
|
1090
|
+
fid.write("* ELEMENT K\n")
|
|
1091
|
+
for block_id in cubit.get_block_id_list():
|
|
1092
|
+
name = cubit.get_exodus_entity_name("block",block_id)
|
|
1093
|
+
|
|
1094
|
+
if DIM=='T':
|
|
1095
|
+
tet_list = _get_block_elements(cubit, block_id, "tet")
|
|
1096
|
+
for tet_id in tet_list:
|
|
1097
|
+
node_list = cubit.get_connectivity('tet',tet_id)
|
|
1098
|
+
element_id += 1
|
|
1099
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]}\n")
|
|
1100
|
+
|
|
1101
|
+
hex_list = _get_block_elements(cubit, block_id, "hex")
|
|
1102
|
+
for hex_id in hex_list:
|
|
1103
|
+
node_list = cubit.get_connectivity('hex',hex_id)
|
|
1104
|
+
element_id += 1
|
|
1105
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]} {node_list[6]} {node_list[7]}\n")
|
|
1106
|
+
|
|
1107
|
+
wedge_list = _get_block_elements(cubit, block_id, "wedge")
|
|
1108
|
+
for wedge_id in wedge_list:
|
|
1109
|
+
node_list = cubit.get_connectivity('wedge',wedge_id)
|
|
1110
|
+
element_id += 1
|
|
1111
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[5]}\n")
|
|
1112
|
+
|
|
1113
|
+
pyramid_list = _get_block_elements(cubit, block_id, "pyramid")
|
|
1114
|
+
for pyramid_id in pyramid_list:
|
|
1115
|
+
node_list = cubit.get_connectivity('pyramid',pyramid_id)
|
|
1116
|
+
element_id += 1
|
|
1117
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]} {node_list[4]} {node_list[4]} {node_list[4]} {node_list[4]}\n")
|
|
1118
|
+
|
|
1119
|
+
tri_list = _get_block_elements(cubit, block_id, "tri")
|
|
1120
|
+
for tri_id in tri_list:
|
|
1121
|
+
node_list = cubit.get_connectivity('tri',tri_id)
|
|
1122
|
+
element_id += 1
|
|
1123
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]} {node_list[2]}\n")
|
|
1124
|
+
|
|
1125
|
+
quad_list = _get_block_elements(cubit, block_id, "face")
|
|
1126
|
+
for quad_id in quad_list:
|
|
1127
|
+
node_list = cubit.get_connectivity('quad',quad_id)
|
|
1128
|
+
element_id += 1
|
|
1129
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]} {node_list[2]} {node_list[3]}\n")
|
|
1130
|
+
|
|
1131
|
+
edge_list = _get_block_elements(cubit, block_id, "edge")
|
|
1132
|
+
for edge_id in edge_list:
|
|
1133
|
+
node_list = cubit.get_connectivity('edge',edge_id)
|
|
1134
|
+
element_id += 1
|
|
1135
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_list[0]} {node_list[1]}\n")
|
|
1136
|
+
|
|
1137
|
+
node_list = _get_block_elements(cubit, block_id, "node")
|
|
1138
|
+
for node_id in node_list:
|
|
1139
|
+
element_id += 1
|
|
1140
|
+
fid.write(f"{name[0:4]}{DIM} {element_id} 0 {block_id} {node_id}\n")
|
|
1141
|
+
|
|
1142
|
+
fid.write("* NODE\n")
|
|
1143
|
+
for node_id in range(len(MGR2)):
|
|
1144
|
+
fid.write(f"MGR2 {node_id+1} 0 {MGR2[node_id][0]} {MGR2[node_id][1]} {MGR2[node_id][2]}\n")
|
|
1145
|
+
fid.write("BOOK END\n")
|
|
1146
|
+
return cubit
|
|
1147
|
+
|
|
1148
|
+
########################################################################
|
|
1149
|
+
### vtk format
|
|
1150
|
+
########################################################################
|
|
1151
|
+
|
|
1152
|
+
def export_vtk(cubit: Any, FileName: str) -> Any:
|
|
1153
|
+
"""Export mesh to Legacy VTK format.
|
|
1154
|
+
|
|
1155
|
+
Exports mesh elements from Cubit to VTK (Visualization Toolkit) Legacy format.
|
|
1156
|
+
Automatically detects element order (1st or 2nd) based on node count.
|
|
1157
|
+
Supports mixed-order meshes (1st and 2nd order elements in same file).
|
|
1158
|
+
|
|
1159
|
+
Args:
|
|
1160
|
+
cubit: Cubit Python interface object
|
|
1161
|
+
FileName: Output file path for the .vtk file
|
|
1162
|
+
|
|
1163
|
+
Returns:
|
|
1164
|
+
cubit: The cubit object (for method chaining)
|
|
1165
|
+
|
|
1166
|
+
Supported elements:
|
|
1167
|
+
- 1st order: Tet4, Hex8, Wedge6, Pyramid5, Triangle3, Quad4, Line2, Point
|
|
1168
|
+
- 2nd order: Tet10, Hex20, Wedge15, Pyramid13, Triangle6, Quad8, Line3
|
|
1169
|
+
|
|
1170
|
+
Note:
|
|
1171
|
+
VTK cell data includes scalar values to distinguish element types.
|
|
1172
|
+
Element order is automatically detected from node count per element.
|
|
1173
|
+
"""
|
|
1174
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
1175
|
+
|
|
1176
|
+
with open(FileName,'w') as fid:
|
|
1177
|
+
fid.write('# vtk DataFile Version 3.0\n')
|
|
1178
|
+
fid.write(f'Unstructured Grid {FileName}\n')
|
|
1179
|
+
fid.write('ASCII\n')
|
|
1180
|
+
fid.write('DATASET UNSTRUCTURED_GRID\n')
|
|
1181
|
+
# First, collect all unique node IDs from all elements
|
|
1182
|
+
node_list = set()
|
|
1183
|
+
hex_list = set()
|
|
1184
|
+
tet_list = set()
|
|
1185
|
+
wedge_list = set()
|
|
1186
|
+
pyramid_list = set()
|
|
1187
|
+
tri_list = set()
|
|
1188
|
+
quad_list = set()
|
|
1189
|
+
edge_list = set()
|
|
1190
|
+
nodes_list = set()
|
|
1191
|
+
|
|
1192
|
+
for block_id in cubit.get_block_id_list():
|
|
1193
|
+
tet_list.update(_get_block_elements(cubit, block_id, "tet"))
|
|
1194
|
+
hex_list.update(_get_block_elements(cubit, block_id, "hex"))
|
|
1195
|
+
wedge_list.update(_get_block_elements(cubit, block_id, "wedge"))
|
|
1196
|
+
pyramid_list.update(_get_block_elements(cubit, block_id, "pyramid"))
|
|
1197
|
+
tri_list.update(_get_block_elements(cubit, block_id, "tri"))
|
|
1198
|
+
quad_list.update(_get_block_elements(cubit, block_id, "face"))
|
|
1199
|
+
edge_list.update(_get_block_elements(cubit, block_id, "edge"))
|
|
1200
|
+
nodes_list.update(_get_block_elements(cubit, block_id, "node"))
|
|
1201
|
+
|
|
1202
|
+
# Collect all node IDs from all element types
|
|
1203
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge", "node"]
|
|
1204
|
+
for block_id in cubit.get_block_id_list():
|
|
1205
|
+
for elem_type in elem_types:
|
|
1206
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
1207
|
+
node_ids = cubit.get_expanded_connectivity(elem_type, element_id)
|
|
1208
|
+
node_list.update(node_ids)
|
|
1209
|
+
|
|
1210
|
+
# Write POINTS header
|
|
1211
|
+
fid.write(f'POINTS {len(node_list)} float\n')
|
|
1212
|
+
|
|
1213
|
+
# Create mapping from Cubit node ID to VTK index (0-indexed)
|
|
1214
|
+
# Write coordinates in sorted order for consistency
|
|
1215
|
+
node_id_to_vtk_index = {}
|
|
1216
|
+
vtk_index = 0
|
|
1217
|
+
for node_id in sorted(node_list):
|
|
1218
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
1219
|
+
fid.write(f'{coord[0]} {coord[1]} {coord[2]}\n')
|
|
1220
|
+
node_id_to_vtk_index[node_id] = vtk_index
|
|
1221
|
+
vtk_index += 1
|
|
1222
|
+
|
|
1223
|
+
# Pre-scan all elements to get node counts for each element
|
|
1224
|
+
# This enables auto-detection of element order and mixed-order support
|
|
1225
|
+
tet_node_counts = {tet_id: len(cubit.get_expanded_connectivity("tet", tet_id)) for tet_id in tet_list}
|
|
1226
|
+
hex_node_counts = {hex_id: len(cubit.get_expanded_connectivity("hex", hex_id)) for hex_id in hex_list}
|
|
1227
|
+
wedge_node_counts = {wedge_id: len(cubit.get_expanded_connectivity("wedge", wedge_id)) for wedge_id in wedge_list}
|
|
1228
|
+
pyramid_node_counts = {pyramid_id: len(cubit.get_expanded_connectivity("pyramid", pyramid_id)) for pyramid_id in pyramid_list}
|
|
1229
|
+
tri_node_counts = {tri_id: len(cubit.get_expanded_connectivity("tri", tri_id)) for tri_id in tri_list}
|
|
1230
|
+
quad_node_counts = {quad_id: len(cubit.get_expanded_connectivity("quad", quad_id)) for quad_id in quad_list}
|
|
1231
|
+
edge_node_counts = {edge_id: len(cubit.get_expanded_connectivity("edge", edge_id)) for edge_id in edge_list}
|
|
1232
|
+
|
|
1233
|
+
# Calculate total CELLS size: sum of (1 + node_count) for each element
|
|
1234
|
+
num_cells = len(tet_list) + len(hex_list) + len(wedge_list) + len(pyramid_list) + len(tri_list) + len(quad_list) + len(edge_list) + len(nodes_list)
|
|
1235
|
+
cells_size = (
|
|
1236
|
+
sum(1 + n for n in tet_node_counts.values()) +
|
|
1237
|
+
sum(1 + n for n in hex_node_counts.values()) +
|
|
1238
|
+
sum(1 + n for n in wedge_node_counts.values()) +
|
|
1239
|
+
sum(1 + n for n in pyramid_node_counts.values()) +
|
|
1240
|
+
sum(1 + n for n in tri_node_counts.values()) +
|
|
1241
|
+
sum(1 + n for n in quad_node_counts.values()) +
|
|
1242
|
+
sum(1 + n for n in edge_node_counts.values()) +
|
|
1243
|
+
2 * len(nodes_list) # Each node: 1 (count) + 1 (node_id)
|
|
1244
|
+
)
|
|
1245
|
+
fid.write(f'CELLS {num_cells} {cells_size}\n')
|
|
1246
|
+
|
|
1247
|
+
for tet_id in tet_list:
|
|
1248
|
+
node_list = cubit.get_expanded_connectivity("tet", tet_id)
|
|
1249
|
+
if len(node_list)==4:
|
|
1250
|
+
fid.write(f'4 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]}\n')
|
|
1251
|
+
elif len(node_list)==10:
|
|
1252
|
+
fid.write(f'10 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} {node_id_to_vtk_index[node_list[6]]} {node_id_to_vtk_index[node_list[7]]} {node_id_to_vtk_index[node_list[8]]} {node_id_to_vtk_index[node_list[9]]}\n')
|
|
1253
|
+
for hex_id in hex_list:
|
|
1254
|
+
node_list = cubit.get_expanded_connectivity("hex", hex_id)
|
|
1255
|
+
if len(node_list)==8:
|
|
1256
|
+
fid.write(f'8 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} {node_id_to_vtk_index[node_list[6]]} {node_id_to_vtk_index[node_list[7]]}\n')
|
|
1257
|
+
elif len(node_list)==20:
|
|
1258
|
+
fid.write(f'20 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} {node_id_to_vtk_index[node_list[6]]} {node_id_to_vtk_index[node_list[7]]} {node_id_to_vtk_index[node_list[8]]} {node_id_to_vtk_index[node_list[9]]} {node_id_to_vtk_index[node_list[10]]} {node_id_to_vtk_index[node_list[11]]} {node_id_to_vtk_index[node_list[16]]} {node_id_to_vtk_index[node_list[17]]} {node_id_to_vtk_index[node_list[18]]} {node_id_to_vtk_index[node_list[19]]} {node_id_to_vtk_index[node_list[12]]} {node_id_to_vtk_index[node_list[13]]} {node_id_to_vtk_index[node_list[14]]} {node_id_to_vtk_index[node_list[15]]}\n')
|
|
1259
|
+
for wedge_id in wedge_list:
|
|
1260
|
+
node_list = cubit.get_expanded_connectivity("wedge", wedge_id)
|
|
1261
|
+
if len(node_list)==6:
|
|
1262
|
+
fid.write(f'6 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} \n')
|
|
1263
|
+
elif len(node_list)==15:
|
|
1264
|
+
fid.write(f'15 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} {node_id_to_vtk_index[node_list[6]]} {node_id_to_vtk_index[node_list[7]]} {node_id_to_vtk_index[node_list[8]]} {node_id_to_vtk_index[node_list[12]]} {node_id_to_vtk_index[node_list[13]]} {node_id_to_vtk_index[node_list[14]]} {node_id_to_vtk_index[node_list[9]]} {node_id_to_vtk_index[node_list[10]]} {node_id_to_vtk_index[node_list[11]]} \n')
|
|
1265
|
+
|
|
1266
|
+
for pyramid_id in pyramid_list:
|
|
1267
|
+
node_list = cubit.get_expanded_connectivity("pyramid", pyramid_id)
|
|
1268
|
+
if len(node_list)==5:
|
|
1269
|
+
fid.write(f'5 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} \n')
|
|
1270
|
+
elif len(node_list)==13:
|
|
1271
|
+
fid.write(f'13 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} {node_id_to_vtk_index[node_list[6]]} {node_id_to_vtk_index[node_list[7]]} {node_id_to_vtk_index[node_list[8]]} {node_id_to_vtk_index[node_list[9]]} {node_id_to_vtk_index[node_list[10]]} {node_id_to_vtk_index[node_list[11]]} {node_id_to_vtk_index[node_list[12]]} \n')
|
|
1272
|
+
for tri_id in tri_list:
|
|
1273
|
+
node_list = cubit.get_expanded_connectivity("tri", tri_id)
|
|
1274
|
+
if len(node_list)==3:
|
|
1275
|
+
fid.write(f'3 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} \n')
|
|
1276
|
+
elif len(node_list)==6:
|
|
1277
|
+
fid.write(f'6 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} \n')
|
|
1278
|
+
for quad_id in quad_list:
|
|
1279
|
+
node_list = cubit.get_expanded_connectivity("quad", quad_id)
|
|
1280
|
+
if len(node_list)==4:
|
|
1281
|
+
fid.write(f'4 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} \n')
|
|
1282
|
+
elif len(node_list)==8:
|
|
1283
|
+
fid.write(f'8 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} {node_id_to_vtk_index[node_list[3]]} {node_id_to_vtk_index[node_list[4]]} {node_id_to_vtk_index[node_list[5]]} {node_id_to_vtk_index[node_list[6]]} {node_id_to_vtk_index[node_list[7]]}\n')
|
|
1284
|
+
for edge_id in edge_list:
|
|
1285
|
+
node_list = cubit.get_expanded_connectivity("edge", edge_id)
|
|
1286
|
+
if len(node_list)==2:
|
|
1287
|
+
fid.write(f'2 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} \n')
|
|
1288
|
+
elif len(node_list)==3:
|
|
1289
|
+
fid.write(f'3 {node_id_to_vtk_index[node_list[0]]} {node_id_to_vtk_index[node_list[1]]} {node_id_to_vtk_index[node_list[2]]} \n')
|
|
1290
|
+
for node_id in nodes_list:
|
|
1291
|
+
fid.write(f'1 {node_id_to_vtk_index[node_id]} \n')
|
|
1292
|
+
|
|
1293
|
+
fid.write(f'CELL_TYPES {num_cells}\n')
|
|
1294
|
+
# VTK cell types based on node count (auto-detection)
|
|
1295
|
+
# 1st order: TET=10, HEX=12, WEDGE=13, PYRAMID=14, TRI=5, QUAD=9, LINE=3, POINT=1
|
|
1296
|
+
# 2nd order: TET=24, HEX=25, WEDGE=26, PYRAMID=27, TRI=22, QUAD=23, LINE=21
|
|
1297
|
+
for tet_id in tet_list:
|
|
1298
|
+
fid.write('24\n' if tet_node_counts[tet_id] == 10 else '10\n')
|
|
1299
|
+
for hex_id in hex_list:
|
|
1300
|
+
fid.write('25\n' if hex_node_counts[hex_id] == 20 else '12\n')
|
|
1301
|
+
for wedge_id in wedge_list:
|
|
1302
|
+
fid.write('26\n' if wedge_node_counts[wedge_id] == 15 else '13\n')
|
|
1303
|
+
for pyramid_id in pyramid_list:
|
|
1304
|
+
fid.write('27\n' if pyramid_node_counts[pyramid_id] == 13 else '14\n')
|
|
1305
|
+
for tri_id in tri_list:
|
|
1306
|
+
fid.write('22\n' if tri_node_counts[tri_id] == 6 else '5\n')
|
|
1307
|
+
for quad_id in quad_list:
|
|
1308
|
+
fid.write('23\n' if quad_node_counts[quad_id] == 8 else '9\n')
|
|
1309
|
+
for edge_id in edge_list:
|
|
1310
|
+
fid.write('21\n' if edge_node_counts[edge_id] == 3 else '3\n')
|
|
1311
|
+
for node_id in nodes_list:
|
|
1312
|
+
fid.write('1\n')
|
|
1313
|
+
fid.write(f'CELL_DATA {num_cells}\n')
|
|
1314
|
+
fid.write('SCALARS scalars float\n')
|
|
1315
|
+
fid.write('LOOKUP_TABLE default\n')
|
|
1316
|
+
for tet_id in tet_list:
|
|
1317
|
+
fid.write('1\n')
|
|
1318
|
+
for hex_id in hex_list:
|
|
1319
|
+
fid.write('2\n')
|
|
1320
|
+
for wedge_id in wedge_list:
|
|
1321
|
+
fid.write('3\n')
|
|
1322
|
+
for pyramid_id in pyramid_list:
|
|
1323
|
+
fid.write('4\n')
|
|
1324
|
+
for tri_id in tri_list:
|
|
1325
|
+
fid.write('5\n')
|
|
1326
|
+
for quad_id in quad_list:
|
|
1327
|
+
fid.write('6\n')
|
|
1328
|
+
for edge_id in edge_list:
|
|
1329
|
+
fid.write('0\n')
|
|
1330
|
+
for node_id in nodes_list:
|
|
1331
|
+
fid.write('-1\n')
|
|
1332
|
+
return cubit
|
|
1333
|
+
|
|
1334
|
+
########################################################################
|
|
1335
|
+
### NGSolve/Netgen format
|
|
1336
|
+
########################################################################
|
|
1337
|
+
|
|
1338
|
+
def export_NetgenMesh(cubit: Any, geometry_file: Optional[str] = None, geometry: Any = None) -> Any:
|
|
1339
|
+
"""Export Cubit mesh to Netgen mesh format.
|
|
1340
|
+
|
|
1341
|
+
Creates a netgen.meshing.Mesh object directly from Cubit mesh data.
|
|
1342
|
+
When geometry is provided (file or OCC object), the mesh can be curved
|
|
1343
|
+
using ngsolve.Mesh.Curve(order) for high-order geometry approximation.
|
|
1344
|
+
|
|
1345
|
+
Note: netgen.meshing.Mesh is the core mesh data structure.
|
|
1346
|
+
ngsolve.Mesh is a wrapper/view for FEM analysis.
|
|
1347
|
+
|
|
1348
|
+
Args:
|
|
1349
|
+
cubit: Cubit Python interface object
|
|
1350
|
+
geometry_file: Path to geometry file (.step, .stp, .brep, .iges) for
|
|
1351
|
+
mesh.Curve() support. If None, mesh is created without
|
|
1352
|
+
geometry reference (Curve() will not work).
|
|
1353
|
+
geometry: An netgen.occ.OCCGeometry object. If provided, this takes
|
|
1354
|
+
precedence over geometry_file. Useful for avoiding seam
|
|
1355
|
+
issues with cylindrical surfaces (see note below).
|
|
1356
|
+
|
|
1357
|
+
Returns:
|
|
1358
|
+
netgen.meshing.Mesh: Netgen mesh object ready for use with NGSolve
|
|
1359
|
+
|
|
1360
|
+
Supported elements:
|
|
1361
|
+
- 3D: Tetrahedron (4-node), Hexahedron (8-node), Wedge (6-node), Pyramid (5-node)
|
|
1362
|
+
- 2D boundary: Triangle (3-node), Quadrilateral (4-node)
|
|
1363
|
+
- 1D boundary: Edge (2-node)
|
|
1364
|
+
|
|
1365
|
+
Usage:
|
|
1366
|
+
# Basic usage with STEP file
|
|
1367
|
+
ngmesh = export_NetgenMesh(cubit, geometry_file="geometry.step")
|
|
1368
|
+
mesh = ngsolve.Mesh(ngmesh)
|
|
1369
|
+
mesh.Curve(3)
|
|
1370
|
+
|
|
1371
|
+
# Using OCC geometry directly (recommended for cylinders)
|
|
1372
|
+
from netgen import occ
|
|
1373
|
+
from netgen.occ import OCCGeometry
|
|
1374
|
+
cyl = occ.Cylinder(occ.Pnt(0,0,-1), occ.Vec(0,0,1), radius=0.5, height=2)
|
|
1375
|
+
geo = OCCGeometry(cyl)
|
|
1376
|
+
ngmesh = export_NetgenMesh(cubit, geometry=geo)
|
|
1377
|
+
mesh = ngsolve.Mesh(ngmesh)
|
|
1378
|
+
mesh.Curve(3)
|
|
1379
|
+
|
|
1380
|
+
# Without geometry (no Curve support)
|
|
1381
|
+
ngmesh = export_NetgenMesh(cubit)
|
|
1382
|
+
mesh = ngsolve.Mesh(ngmesh)
|
|
1383
|
+
|
|
1384
|
+
Note:
|
|
1385
|
+
- Only 1st order elements are transferred; use mesh.Curve(order) for
|
|
1386
|
+
high-order geometry approximation
|
|
1387
|
+
- The geometry should match the geometry used to create the mesh
|
|
1388
|
+
- Block names are used as Materials/Boundaries in NGSolve
|
|
1389
|
+
|
|
1390
|
+
Cylindrical Surface Note:
|
|
1391
|
+
When using STEP files exported from Cubit with cylindrical surfaces,
|
|
1392
|
+
OCC may split the surface at the seam line (y=0), creating 2 faces
|
|
1393
|
+
where Cubit has 1 surface. Mesh elements crossing this seam may not
|
|
1394
|
+
curve correctly. To avoid this:
|
|
1395
|
+
1. Use OCC primitives (occ.Cylinder) via the geometry parameter, or
|
|
1396
|
+
2. Ensure mesh elements don't cross y=0 on cylindrical surfaces
|
|
1397
|
+
"""
|
|
1398
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
1399
|
+
|
|
1400
|
+
from netgen.meshing import Mesh, MeshPoint, Element3D, Element2D, Element1D, FaceDescriptor
|
|
1401
|
+
from netgen.csg import Pnt
|
|
1402
|
+
|
|
1403
|
+
# ============================================================
|
|
1404
|
+
# Node ordering conversion tables: Cubit -> Netgen
|
|
1405
|
+
# ============================================================
|
|
1406
|
+
|
|
1407
|
+
# Tetrahedron: Cubit [0,1,2,3] -> Netgen [0,1,2,3]
|
|
1408
|
+
TET_ORDERING = [0, 1, 2, 3]
|
|
1409
|
+
|
|
1410
|
+
# Hexahedron: Cubit [0,1,2,3,4,5,6,7] -> Netgen [0,1,5,4,3,2,6,7]
|
|
1411
|
+
HEX_ORDERING = [0, 1, 5, 4, 3, 2, 6, 7]
|
|
1412
|
+
|
|
1413
|
+
# Wedge/Prism: Cubit [0,1,2,3,4,5] -> Netgen [0,2,1,3,5,4]
|
|
1414
|
+
WEDGE_ORDERING = [0, 2, 1, 3, 5, 4]
|
|
1415
|
+
|
|
1416
|
+
# Pyramid: Cubit [0,1,2,3,4] -> Netgen [3,2,1,0,4]
|
|
1417
|
+
PYRAMID_ORDERING = [3, 2, 1, 0, 4]
|
|
1418
|
+
|
|
1419
|
+
# Triangle: Cubit [0,1,2] -> Netgen [0,1,2]
|
|
1420
|
+
TRI_ORDERING = [0, 1, 2]
|
|
1421
|
+
|
|
1422
|
+
# Quadrilateral: Cubit [0,1,2,3] -> Netgen [0,1,2,3]
|
|
1423
|
+
QUAD_ORDERING = [0, 1, 2, 3]
|
|
1424
|
+
|
|
1425
|
+
# ============================================================
|
|
1426
|
+
# Create netgen mesh
|
|
1427
|
+
# ============================================================
|
|
1428
|
+
|
|
1429
|
+
ngmesh = Mesh(dim=3)
|
|
1430
|
+
|
|
1431
|
+
# Load and attach geometry if provided
|
|
1432
|
+
# Priority: geometry parameter > geometry_file parameter
|
|
1433
|
+
geo = None
|
|
1434
|
+
if geometry is not None:
|
|
1435
|
+
# Use provided OCC geometry object directly
|
|
1436
|
+
geo = geometry
|
|
1437
|
+
ngmesh.SetGeometry(geo)
|
|
1438
|
+
elif geometry_file is not None:
|
|
1439
|
+
from netgen.occ import OCCGeometry
|
|
1440
|
+
geo = OCCGeometry(geometry_file)
|
|
1441
|
+
ngmesh.SetGeometry(geo)
|
|
1442
|
+
|
|
1443
|
+
# ============================================================
|
|
1444
|
+
# Collect all nodes from blocks
|
|
1445
|
+
# ============================================================
|
|
1446
|
+
|
|
1447
|
+
node_map = {} # Cubit node ID -> netgen point index
|
|
1448
|
+
all_nodes = set()
|
|
1449
|
+
|
|
1450
|
+
for block_id in cubit.get_block_id_list():
|
|
1451
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge", "node"]
|
|
1452
|
+
for elem_type in elem_types:
|
|
1453
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
1454
|
+
# Use get_connectivity for 1st order nodes only
|
|
1455
|
+
node_ids = cubit.get_connectivity(elem_type, element_id)
|
|
1456
|
+
all_nodes.update(node_ids)
|
|
1457
|
+
|
|
1458
|
+
# Add nodes to netgen mesh
|
|
1459
|
+
for node_id in sorted(all_nodes):
|
|
1460
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
1461
|
+
pnt_idx = ngmesh.Add(MeshPoint(Pnt(coord[0], coord[1], coord[2])))
|
|
1462
|
+
node_map[node_id] = pnt_idx
|
|
1463
|
+
|
|
1464
|
+
# ============================================================
|
|
1465
|
+
# Add 3D volume elements
|
|
1466
|
+
# ============================================================
|
|
1467
|
+
|
|
1468
|
+
material_index = 0
|
|
1469
|
+
|
|
1470
|
+
for block_id in cubit.get_block_id_list():
|
|
1471
|
+
block_name = cubit.get_exodus_entity_name("block", block_id)
|
|
1472
|
+
|
|
1473
|
+
tet_list = _get_block_elements(cubit, block_id, "tet")
|
|
1474
|
+
hex_list = _get_block_elements(cubit, block_id, "hex")
|
|
1475
|
+
wedge_list = _get_block_elements(cubit, block_id, "wedge")
|
|
1476
|
+
pyramid_list = _get_block_elements(cubit, block_id, "pyramid")
|
|
1477
|
+
|
|
1478
|
+
# Only process blocks with 3D elements
|
|
1479
|
+
if len(tet_list) + len(hex_list) + len(wedge_list) + len(pyramid_list) > 0:
|
|
1480
|
+
material_index += 1
|
|
1481
|
+
ngmesh.SetMaterial(material_index, block_name)
|
|
1482
|
+
|
|
1483
|
+
# Add tetrahedra
|
|
1484
|
+
for tet_id in tet_list:
|
|
1485
|
+
nodes = cubit.get_connectivity("tet", tet_id)
|
|
1486
|
+
ng_nodes = [node_map[nodes[i]] for i in TET_ORDERING]
|
|
1487
|
+
ngmesh.Add(Element3D(material_index, ng_nodes))
|
|
1488
|
+
|
|
1489
|
+
# Add hexahedra
|
|
1490
|
+
for hex_id in hex_list:
|
|
1491
|
+
nodes = cubit.get_connectivity("hex", hex_id)
|
|
1492
|
+
ng_nodes = [node_map[nodes[i]] for i in HEX_ORDERING]
|
|
1493
|
+
ngmesh.Add(Element3D(material_index, ng_nodes))
|
|
1494
|
+
|
|
1495
|
+
# Add wedges/prisms
|
|
1496
|
+
for wedge_id in wedge_list:
|
|
1497
|
+
nodes = cubit.get_connectivity("wedge", wedge_id)
|
|
1498
|
+
ng_nodes = [node_map[nodes[i]] for i in WEDGE_ORDERING]
|
|
1499
|
+
ngmesh.Add(Element3D(material_index, ng_nodes))
|
|
1500
|
+
|
|
1501
|
+
# Add pyramids
|
|
1502
|
+
for pyramid_id in pyramid_list:
|
|
1503
|
+
nodes = cubit.get_connectivity("pyramid", pyramid_id)
|
|
1504
|
+
ng_nodes = [node_map[nodes[i]] for i in PYRAMID_ORDERING]
|
|
1505
|
+
ngmesh.Add(Element3D(material_index, ng_nodes))
|
|
1506
|
+
|
|
1507
|
+
# ============================================================
|
|
1508
|
+
# Add 2D surface elements (boundary faces)
|
|
1509
|
+
# ============================================================
|
|
1510
|
+
|
|
1511
|
+
fd_index = 0
|
|
1512
|
+
|
|
1513
|
+
# Build OCC face mapping when geometry is provided (for Curve() support)
|
|
1514
|
+
occ_face_info = None
|
|
1515
|
+
elem_to_occ_face = {} # (elem_type, elem_id) -> OCC face index
|
|
1516
|
+
|
|
1517
|
+
if geo is not None:
|
|
1518
|
+
# Analyze OCC faces to build mapping
|
|
1519
|
+
occ_faces = list(geo.shape.faces)
|
|
1520
|
+
occ_face_info = []
|
|
1521
|
+
for i, face in enumerate(occ_faces):
|
|
1522
|
+
center = face.center
|
|
1523
|
+
bb = face.bounding_box
|
|
1524
|
+
# bb is ((xmin, ymin, zmin), (xmax, ymax, zmax))
|
|
1525
|
+
z_range = bb[1][2] - bb[0][2]
|
|
1526
|
+
y_min, y_max = bb[0][1], bb[1][1]
|
|
1527
|
+
occ_face_info.append({
|
|
1528
|
+
'index': i,
|
|
1529
|
+
'center': (center.x, center.y, center.z),
|
|
1530
|
+
'bbox': bb,
|
|
1531
|
+
'z_range': z_range,
|
|
1532
|
+
'y_min': y_min,
|
|
1533
|
+
'y_max': y_max,
|
|
1534
|
+
'is_planar': z_range < 0.01,
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
# Build tri_id -> Cubit surface_id mapping
|
|
1538
|
+
tri_to_surface = {}
|
|
1539
|
+
surface_ids = cubit.get_entities("surface")
|
|
1540
|
+
for sid in surface_ids:
|
|
1541
|
+
for tri_id in cubit.get_surface_tris(sid):
|
|
1542
|
+
tri_to_surface[tri_id] = sid
|
|
1543
|
+
|
|
1544
|
+
# Build quad_id -> Cubit surface_id mapping
|
|
1545
|
+
quad_to_surface = {}
|
|
1546
|
+
for sid in surface_ids:
|
|
1547
|
+
for quad_id in cubit.get_surface_quads(sid):
|
|
1548
|
+
quad_to_surface[quad_id] = sid
|
|
1549
|
+
|
|
1550
|
+
# Helper function to get element centroid
|
|
1551
|
+
def get_elem_centroid(elem_type, elem_id):
|
|
1552
|
+
nodes = cubit.get_connectivity(elem_type, elem_id)
|
|
1553
|
+
coords = [cubit.get_nodal_coordinates(n) for n in nodes]
|
|
1554
|
+
return [sum(c[i] for c in coords) / len(coords) for i in range(3)]
|
|
1555
|
+
|
|
1556
|
+
# Helper function to check if element crosses y=0 seam
|
|
1557
|
+
def crosses_seam(elem_type, elem_id, tolerance=0.001):
|
|
1558
|
+
nodes = cubit.get_connectivity(elem_type, elem_id)
|
|
1559
|
+
coords = [cubit.get_nodal_coordinates(n) for n in nodes]
|
|
1560
|
+
y_vals = [c[1] for c in coords]
|
|
1561
|
+
y_min, y_max = min(y_vals), max(y_vals)
|
|
1562
|
+
# Element crosses seam if vertices are on both sides of y=0
|
|
1563
|
+
return y_min < -tolerance and y_max > tolerance
|
|
1564
|
+
|
|
1565
|
+
# Map Cubit surfaces to OCC faces based on centroid
|
|
1566
|
+
surface_to_occ = {}
|
|
1567
|
+
for sid in surface_ids:
|
|
1568
|
+
surf_centroid = cubit.get_surface_centroid(sid)
|
|
1569
|
+
surf_type = cubit.get_surface_type(sid).lower()
|
|
1570
|
+
|
|
1571
|
+
# For planar surfaces, match by z-coordinate
|
|
1572
|
+
if "plane" in surf_type:
|
|
1573
|
+
for info in occ_face_info:
|
|
1574
|
+
if info['is_planar']:
|
|
1575
|
+
if abs(info['center'][2] - surf_centroid[2]) < 0.1:
|
|
1576
|
+
surface_to_occ[sid] = [info['index']]
|
|
1577
|
+
break
|
|
1578
|
+
else:
|
|
1579
|
+
# For curved surfaces (cone, cylinder, etc.), find all matching OCC faces
|
|
1580
|
+
# OCC may split curved surfaces into multiple faces
|
|
1581
|
+
matching = []
|
|
1582
|
+
for info in occ_face_info:
|
|
1583
|
+
if not info['is_planar']:
|
|
1584
|
+
# Check if centroid z-range overlaps
|
|
1585
|
+
if (info['bbox'][0][2] <= surf_centroid[2] <= info['bbox'][1][2]):
|
|
1586
|
+
matching.append(info['index'])
|
|
1587
|
+
if matching:
|
|
1588
|
+
surface_to_occ[sid] = matching
|
|
1589
|
+
|
|
1590
|
+
# Map each triangle to its OCC face
|
|
1591
|
+
seam_crossing_count = 0
|
|
1592
|
+
for tri_id, sid in tri_to_surface.items():
|
|
1593
|
+
if sid not in surface_to_occ:
|
|
1594
|
+
continue
|
|
1595
|
+
occ_faces_for_surface = surface_to_occ[sid]
|
|
1596
|
+
if len(occ_faces_for_surface) == 1:
|
|
1597
|
+
elem_to_occ_face[('tri', tri_id)] = occ_faces_for_surface[0]
|
|
1598
|
+
else:
|
|
1599
|
+
# Multiple OCC faces for this surface (e.g., cylinder split)
|
|
1600
|
+
# Check if element crosses the seam (y=0)
|
|
1601
|
+
if crosses_seam("tri", tri_id):
|
|
1602
|
+
# Element crosses seam - mark as None to exclude from Curve()
|
|
1603
|
+
elem_to_occ_face[('tri', tri_id)] = None
|
|
1604
|
+
seam_crossing_count += 1
|
|
1605
|
+
else:
|
|
1606
|
+
# Determine by y-coordinate of triangle centroid
|
|
1607
|
+
centroid = get_elem_centroid("tri", tri_id)
|
|
1608
|
+
for occ_idx in occ_faces_for_surface:
|
|
1609
|
+
info = occ_face_info[occ_idx]
|
|
1610
|
+
# Check if centroid y is within OCC face bbox
|
|
1611
|
+
if info['y_min'] - 0.01 <= centroid[1] <= info['y_max'] + 0.01:
|
|
1612
|
+
elem_to_occ_face[('tri', tri_id)] = occ_idx
|
|
1613
|
+
break
|
|
1614
|
+
|
|
1615
|
+
# Map each quad to its OCC face
|
|
1616
|
+
for quad_id, sid in quad_to_surface.items():
|
|
1617
|
+
if sid not in surface_to_occ:
|
|
1618
|
+
continue
|
|
1619
|
+
occ_faces_for_surface = surface_to_occ[sid]
|
|
1620
|
+
if len(occ_faces_for_surface) == 1:
|
|
1621
|
+
elem_to_occ_face[('quad', quad_id)] = occ_faces_for_surface[0]
|
|
1622
|
+
else:
|
|
1623
|
+
# Check if element crosses the seam (y=0)
|
|
1624
|
+
if crosses_seam("face", quad_id):
|
|
1625
|
+
# Element crosses seam - mark as None to exclude from Curve()
|
|
1626
|
+
elem_to_occ_face[('quad', quad_id)] = None
|
|
1627
|
+
seam_crossing_count += 1
|
|
1628
|
+
else:
|
|
1629
|
+
centroid = get_elem_centroid("face", quad_id)
|
|
1630
|
+
for occ_idx in occ_faces_for_surface:
|
|
1631
|
+
info = occ_face_info[occ_idx]
|
|
1632
|
+
if info['y_min'] - 0.01 <= centroid[1] <= info['y_max'] + 0.01:
|
|
1633
|
+
elem_to_occ_face[('quad', quad_id)] = occ_idx
|
|
1634
|
+
break
|
|
1635
|
+
|
|
1636
|
+
# Print warning if seam-crossing elements found
|
|
1637
|
+
if seam_crossing_count > 0:
|
|
1638
|
+
print(f" Note: {seam_crossing_count} boundary element(s) cross y=0 seam - excluded from Curve()")
|
|
1639
|
+
print(f" For better results, use OCC geometry primitives (geometry= parameter)")
|
|
1640
|
+
|
|
1641
|
+
# Create FaceDescriptors and add boundary elements
|
|
1642
|
+
if occ_face_info is not None:
|
|
1643
|
+
# Create one FaceDescriptor per OCC face
|
|
1644
|
+
# surfnr is 1-indexed in Netgen (OCC Face i -> surfnr=i+1)
|
|
1645
|
+
occ_to_fd_index = {}
|
|
1646
|
+
for info in occ_face_info:
|
|
1647
|
+
fd_index += 1
|
|
1648
|
+
fd = FaceDescriptor(bc=fd_index, surfnr=info['index'] + 1)
|
|
1649
|
+
fd.bcname = f"face_{info['index']}"
|
|
1650
|
+
ngmesh.Add(fd)
|
|
1651
|
+
ngmesh.SetBCName(fd_index - 1, f"face_{info['index']}")
|
|
1652
|
+
occ_to_fd_index[info['index']] = fd_index
|
|
1653
|
+
|
|
1654
|
+
# Create a special FaceDescriptor for seam-crossing elements (surfnr=0)
|
|
1655
|
+
fd_index += 1
|
|
1656
|
+
fd_seam = FaceDescriptor(bc=fd_index, surfnr=0) # surfnr=0 -> no geometry curving
|
|
1657
|
+
fd_seam.bcname = "seam_crossing"
|
|
1658
|
+
ngmesh.Add(fd_seam)
|
|
1659
|
+
ngmesh.SetBCName(fd_index - 1, "seam_crossing")
|
|
1660
|
+
fd_seam_index = fd_index
|
|
1661
|
+
|
|
1662
|
+
# Add boundary elements with correct FaceDescriptor
|
|
1663
|
+
for block_id in cubit.get_block_id_list():
|
|
1664
|
+
tri_list = _get_block_elements(cubit, block_id, "tri")
|
|
1665
|
+
quad_list = _get_block_elements(cubit, block_id, "face")
|
|
1666
|
+
|
|
1667
|
+
for tri_id in tri_list:
|
|
1668
|
+
nodes = cubit.get_connectivity("tri", tri_id)
|
|
1669
|
+
if all(n in node_map for n in nodes):
|
|
1670
|
+
ng_nodes = [node_map[nodes[i]] for i in TRI_ORDERING]
|
|
1671
|
+
key = ('tri', tri_id)
|
|
1672
|
+
if key in elem_to_occ_face:
|
|
1673
|
+
occ_idx = elem_to_occ_face[key]
|
|
1674
|
+
if occ_idx is None:
|
|
1675
|
+
bc_idx = fd_seam_index # Seam-crossing element
|
|
1676
|
+
else:
|
|
1677
|
+
bc_idx = occ_to_fd_index[occ_idx]
|
|
1678
|
+
else:
|
|
1679
|
+
bc_idx = 1 # Fallback
|
|
1680
|
+
ngmesh.Add(Element2D(bc_idx, ng_nodes))
|
|
1681
|
+
|
|
1682
|
+
for quad_id in quad_list:
|
|
1683
|
+
nodes = cubit.get_connectivity("quad", quad_id)
|
|
1684
|
+
if all(n in node_map for n in nodes):
|
|
1685
|
+
ng_nodes = [node_map[nodes[i]] for i in QUAD_ORDERING]
|
|
1686
|
+
key = ('quad', quad_id)
|
|
1687
|
+
if key in elem_to_occ_face:
|
|
1688
|
+
occ_idx = elem_to_occ_face[key]
|
|
1689
|
+
if occ_idx is None:
|
|
1690
|
+
bc_idx = fd_seam_index # Seam-crossing element
|
|
1691
|
+
else:
|
|
1692
|
+
bc_idx = occ_to_fd_index[occ_idx]
|
|
1693
|
+
else:
|
|
1694
|
+
bc_idx = 1 # Fallback
|
|
1695
|
+
ngmesh.Add(Element2D(bc_idx, ng_nodes))
|
|
1696
|
+
else:
|
|
1697
|
+
# No geometry file - use simple block-based FaceDescriptors
|
|
1698
|
+
for block_id in cubit.get_block_id_list():
|
|
1699
|
+
block_name = cubit.get_exodus_entity_name("block", block_id)
|
|
1700
|
+
|
|
1701
|
+
tri_list = _get_block_elements(cubit, block_id, "tri")
|
|
1702
|
+
quad_list = _get_block_elements(cubit, block_id, "face")
|
|
1703
|
+
|
|
1704
|
+
# Only process blocks with 2D elements
|
|
1705
|
+
if len(tri_list) + len(quad_list) > 0:
|
|
1706
|
+
fd_index += 1
|
|
1707
|
+
fd = FaceDescriptor(bc=fd_index, surfnr=fd_index)
|
|
1708
|
+
fd.bcname = block_name
|
|
1709
|
+
ngmesh.Add(fd)
|
|
1710
|
+
ngmesh.SetBCName(fd_index - 1, block_name)
|
|
1711
|
+
|
|
1712
|
+
# Add triangles
|
|
1713
|
+
for tri_id in tri_list:
|
|
1714
|
+
nodes = cubit.get_connectivity("tri", tri_id)
|
|
1715
|
+
if all(n in node_map for n in nodes):
|
|
1716
|
+
ng_nodes = [node_map[nodes[i]] for i in TRI_ORDERING]
|
|
1717
|
+
ngmesh.Add(Element2D(fd_index, ng_nodes))
|
|
1718
|
+
|
|
1719
|
+
# Add quadrilaterals
|
|
1720
|
+
for quad_id in quad_list:
|
|
1721
|
+
nodes = cubit.get_connectivity("quad", quad_id)
|
|
1722
|
+
if all(n in node_map for n in nodes):
|
|
1723
|
+
ng_nodes = [node_map[nodes[i]] for i in QUAD_ORDERING]
|
|
1724
|
+
ngmesh.Add(Element2D(fd_index, ng_nodes))
|
|
1725
|
+
|
|
1726
|
+
# ============================================================
|
|
1727
|
+
# Add 1D edge elements (if any)
|
|
1728
|
+
# ============================================================
|
|
1729
|
+
|
|
1730
|
+
for block_id in cubit.get_block_id_list():
|
|
1731
|
+
block_name = cubit.get_exodus_entity_name("block", block_id)
|
|
1732
|
+
edge_list = _get_block_elements(cubit, block_id, "edge")
|
|
1733
|
+
|
|
1734
|
+
if len(edge_list) > 0:
|
|
1735
|
+
fd_index += 1
|
|
1736
|
+
fd = FaceDescriptor(bc=fd_index, surfnr=fd_index)
|
|
1737
|
+
fd.bcname = block_name
|
|
1738
|
+
ngmesh.Add(fd)
|
|
1739
|
+
|
|
1740
|
+
for edge_id in edge_list:
|
|
1741
|
+
nodes = cubit.get_connectivity("edge", edge_id)
|
|
1742
|
+
if all(n in node_map for n in nodes):
|
|
1743
|
+
ng_nodes = [node_map[n] for n in nodes]
|
|
1744
|
+
ngmesh.Add(Element1D(ng_nodes, index=fd_index))
|
|
1745
|
+
|
|
1746
|
+
return ngmesh
|
|
1747
|
+
|
|
1748
|
+
########################################################################
|
|
1749
|
+
### VTK XML format (VTU)
|
|
1750
|
+
########################################################################
|
|
1751
|
+
|
|
1752
|
+
def export_vtu(cubit: Any, FileName: str, binary: bool = False) -> Any:
|
|
1753
|
+
"""Export mesh to VTK XML format (VTU - Unstructured Grid).
|
|
1754
|
+
|
|
1755
|
+
Exports mesh elements from Cubit to VTK XML format (.vtu).
|
|
1756
|
+
This is the modern VTK format recommended for ParaView and other tools.
|
|
1757
|
+
Automatically detects element order (1st or 2nd) based on node count.
|
|
1758
|
+
|
|
1759
|
+
Args:
|
|
1760
|
+
cubit: Cubit Python interface object
|
|
1761
|
+
FileName: Output file path for the .vtu file
|
|
1762
|
+
binary: If True, write binary data (appended format). Default: False (ASCII)
|
|
1763
|
+
|
|
1764
|
+
Returns:
|
|
1765
|
+
cubit: The cubit object (for method chaining)
|
|
1766
|
+
|
|
1767
|
+
Supported elements:
|
|
1768
|
+
- 1st order: Tet4, Hex8, Wedge6, Pyramid5, Triangle3, Quad4, Line2, Point
|
|
1769
|
+
- 2nd order: Tet10, Hex20, Wedge15, Pyramid13, Triangle6, Quad8, Line3
|
|
1770
|
+
|
|
1771
|
+
VTK XML vs Legacy:
|
|
1772
|
+
- XML format is more efficient and extensible
|
|
1773
|
+
- Supports compression (when binary=True)
|
|
1774
|
+
- Better metadata support
|
|
1775
|
+
- Recommended format for modern workflows
|
|
1776
|
+
|
|
1777
|
+
Example:
|
|
1778
|
+
cubit_mesh_export.export_vtu(cubit, "mesh.vtu")
|
|
1779
|
+
cubit_mesh_export.export_vtu(cubit, "mesh.vtu", binary=True) # Binary mode
|
|
1780
|
+
"""
|
|
1781
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
1782
|
+
|
|
1783
|
+
import base64
|
|
1784
|
+
import struct
|
|
1785
|
+
|
|
1786
|
+
# VTK cell type codes
|
|
1787
|
+
VTK_VERTEX = 1
|
|
1788
|
+
VTK_LINE = 3
|
|
1789
|
+
VTK_QUADRATIC_EDGE = 21
|
|
1790
|
+
VTK_TRIANGLE = 5
|
|
1791
|
+
VTK_QUADRATIC_TRIANGLE = 22
|
|
1792
|
+
VTK_QUAD = 9
|
|
1793
|
+
VTK_QUADRATIC_QUAD = 23
|
|
1794
|
+
VTK_TETRA = 10
|
|
1795
|
+
VTK_QUADRATIC_TETRA = 24
|
|
1796
|
+
VTK_HEXAHEDRON = 12
|
|
1797
|
+
VTK_QUADRATIC_HEXAHEDRON = 25
|
|
1798
|
+
VTK_WEDGE = 13
|
|
1799
|
+
VTK_QUADRATIC_WEDGE = 26
|
|
1800
|
+
VTK_PYRAMID = 14
|
|
1801
|
+
VTK_QUADRATIC_PYRAMID = 27
|
|
1802
|
+
|
|
1803
|
+
# Collect all elements from blocks
|
|
1804
|
+
hex_list = set()
|
|
1805
|
+
tet_list = set()
|
|
1806
|
+
wedge_list = set()
|
|
1807
|
+
pyramid_list = set()
|
|
1808
|
+
tri_list = set()
|
|
1809
|
+
quad_list = set()
|
|
1810
|
+
edge_list = set()
|
|
1811
|
+
nodes_list = set()
|
|
1812
|
+
node_set = set()
|
|
1813
|
+
|
|
1814
|
+
for block_id in cubit.get_block_id_list():
|
|
1815
|
+
tet_list.update(_get_block_elements(cubit, block_id, "tet"))
|
|
1816
|
+
hex_list.update(_get_block_elements(cubit, block_id, "hex"))
|
|
1817
|
+
wedge_list.update(_get_block_elements(cubit, block_id, "wedge"))
|
|
1818
|
+
pyramid_list.update(_get_block_elements(cubit, block_id, "pyramid"))
|
|
1819
|
+
tri_list.update(_get_block_elements(cubit, block_id, "tri"))
|
|
1820
|
+
quad_list.update(_get_block_elements(cubit, block_id, "face"))
|
|
1821
|
+
edge_list.update(_get_block_elements(cubit, block_id, "edge"))
|
|
1822
|
+
nodes_list.update(_get_block_elements(cubit, block_id, "node"))
|
|
1823
|
+
|
|
1824
|
+
# Collect all node IDs
|
|
1825
|
+
elem_types = ["hex", "tet", "wedge", "pyramid", "tri", "face", "edge", "node"]
|
|
1826
|
+
for block_id in cubit.get_block_id_list():
|
|
1827
|
+
for elem_type in elem_types:
|
|
1828
|
+
for element_id in _get_block_elements(cubit, block_id, elem_type):
|
|
1829
|
+
node_ids = cubit.get_expanded_connectivity(elem_type, element_id)
|
|
1830
|
+
node_set.update(node_ids)
|
|
1831
|
+
|
|
1832
|
+
# Create node ID to VTK index mapping
|
|
1833
|
+
node_id_to_vtk_index = {}
|
|
1834
|
+
vtk_index = 0
|
|
1835
|
+
sorted_nodes = sorted(node_set)
|
|
1836
|
+
for node_id in sorted_nodes:
|
|
1837
|
+
node_id_to_vtk_index[node_id] = vtk_index
|
|
1838
|
+
vtk_index += 1
|
|
1839
|
+
|
|
1840
|
+
# Pre-scan elements for node counts (for auto order detection)
|
|
1841
|
+
tet_node_counts = {tet_id: len(cubit.get_expanded_connectivity("tet", tet_id)) for tet_id in tet_list}
|
|
1842
|
+
hex_node_counts = {hex_id: len(cubit.get_expanded_connectivity("hex", hex_id)) for hex_id in hex_list}
|
|
1843
|
+
wedge_node_counts = {wedge_id: len(cubit.get_expanded_connectivity("wedge", wedge_id)) for wedge_id in wedge_list}
|
|
1844
|
+
pyramid_node_counts = {pyramid_id: len(cubit.get_expanded_connectivity("pyramid", pyramid_id)) for pyramid_id in pyramid_list}
|
|
1845
|
+
tri_node_counts = {tri_id: len(cubit.get_expanded_connectivity("tri", tri_id)) for tri_id in tri_list}
|
|
1846
|
+
quad_node_counts = {quad_id: len(cubit.get_expanded_connectivity("quad", quad_id)) for quad_id in quad_list}
|
|
1847
|
+
edge_node_counts = {edge_id: len(cubit.get_expanded_connectivity("edge", edge_id)) for edge_id in edge_list}
|
|
1848
|
+
|
|
1849
|
+
num_points = len(sorted_nodes)
|
|
1850
|
+
num_cells = len(tet_list) + len(hex_list) + len(wedge_list) + len(pyramid_list) + len(tri_list) + len(quad_list) + len(edge_list) + len(nodes_list)
|
|
1851
|
+
|
|
1852
|
+
# Build connectivity and offsets arrays
|
|
1853
|
+
connectivity = []
|
|
1854
|
+
offsets = []
|
|
1855
|
+
cell_types = []
|
|
1856
|
+
block_ids = []
|
|
1857
|
+
current_offset = 0
|
|
1858
|
+
|
|
1859
|
+
# Helper to get VTK type based on element type and node count
|
|
1860
|
+
def get_vtk_type(elem_type, node_count):
|
|
1861
|
+
if elem_type == "tet":
|
|
1862
|
+
return VTK_QUADRATIC_TETRA if node_count == 10 else VTK_TETRA
|
|
1863
|
+
elif elem_type == "hex":
|
|
1864
|
+
return VTK_QUADRATIC_HEXAHEDRON if node_count == 20 else VTK_HEXAHEDRON
|
|
1865
|
+
elif elem_type == "wedge":
|
|
1866
|
+
return VTK_QUADRATIC_WEDGE if node_count == 15 else VTK_WEDGE
|
|
1867
|
+
elif elem_type == "pyramid":
|
|
1868
|
+
return VTK_QUADRATIC_PYRAMID if node_count == 13 else VTK_PYRAMID
|
|
1869
|
+
elif elem_type == "tri":
|
|
1870
|
+
return VTK_QUADRATIC_TRIANGLE if node_count == 6 else VTK_TRIANGLE
|
|
1871
|
+
elif elem_type == "quad":
|
|
1872
|
+
return VTK_QUADRATIC_QUAD if node_count == 8 else VTK_QUAD
|
|
1873
|
+
elif elem_type == "edge":
|
|
1874
|
+
return VTK_QUADRATIC_EDGE if node_count == 3 else VTK_LINE
|
|
1875
|
+
else:
|
|
1876
|
+
return VTK_VERTEX
|
|
1877
|
+
|
|
1878
|
+
# Process tetrahedra
|
|
1879
|
+
for tet_id in tet_list:
|
|
1880
|
+
nodes = cubit.get_expanded_connectivity("tet", tet_id)
|
|
1881
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1882
|
+
connectivity.extend(vtk_nodes)
|
|
1883
|
+
current_offset += len(nodes)
|
|
1884
|
+
offsets.append(current_offset)
|
|
1885
|
+
cell_types.append(get_vtk_type("tet", len(nodes)))
|
|
1886
|
+
# Find block ID for this element
|
|
1887
|
+
for block_id in cubit.get_block_id_list():
|
|
1888
|
+
if tet_id in _get_block_elements(cubit, block_id, "tet"):
|
|
1889
|
+
block_ids.append(block_id)
|
|
1890
|
+
break
|
|
1891
|
+
|
|
1892
|
+
# Process hexahedra
|
|
1893
|
+
for hex_id in hex_list:
|
|
1894
|
+
nodes = cubit.get_expanded_connectivity("hex", hex_id)
|
|
1895
|
+
# HEX20 node reordering: Cubit -> VTK
|
|
1896
|
+
if len(nodes) == 20:
|
|
1897
|
+
vtk_nodes = [node_id_to_vtk_index[nodes[i]] for i in [0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19,12,13,14,15]]
|
|
1898
|
+
else:
|
|
1899
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1900
|
+
connectivity.extend(vtk_nodes)
|
|
1901
|
+
current_offset += len(nodes)
|
|
1902
|
+
offsets.append(current_offset)
|
|
1903
|
+
cell_types.append(get_vtk_type("hex", len(nodes)))
|
|
1904
|
+
for block_id in cubit.get_block_id_list():
|
|
1905
|
+
if hex_id in _get_block_elements(cubit, block_id, "hex"):
|
|
1906
|
+
block_ids.append(block_id)
|
|
1907
|
+
break
|
|
1908
|
+
|
|
1909
|
+
# Process wedges
|
|
1910
|
+
for wedge_id in wedge_list:
|
|
1911
|
+
nodes = cubit.get_expanded_connectivity("wedge", wedge_id)
|
|
1912
|
+
# WEDGE15 node reordering: Cubit -> VTK
|
|
1913
|
+
if len(nodes) == 15:
|
|
1914
|
+
vtk_nodes = [node_id_to_vtk_index[nodes[i]] for i in [0,1,2,3,4,5,6,7,8,12,13,14,9,10,11]]
|
|
1915
|
+
else:
|
|
1916
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1917
|
+
connectivity.extend(vtk_nodes)
|
|
1918
|
+
current_offset += len(nodes)
|
|
1919
|
+
offsets.append(current_offset)
|
|
1920
|
+
cell_types.append(get_vtk_type("wedge", len(nodes)))
|
|
1921
|
+
for block_id in cubit.get_block_id_list():
|
|
1922
|
+
if wedge_id in _get_block_elements(cubit, block_id, "wedge"):
|
|
1923
|
+
block_ids.append(block_id)
|
|
1924
|
+
break
|
|
1925
|
+
|
|
1926
|
+
# Process pyramids
|
|
1927
|
+
for pyramid_id in pyramid_list:
|
|
1928
|
+
nodes = cubit.get_expanded_connectivity("pyramid", pyramid_id)
|
|
1929
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1930
|
+
connectivity.extend(vtk_nodes)
|
|
1931
|
+
current_offset += len(nodes)
|
|
1932
|
+
offsets.append(current_offset)
|
|
1933
|
+
cell_types.append(get_vtk_type("pyramid", len(nodes)))
|
|
1934
|
+
for block_id in cubit.get_block_id_list():
|
|
1935
|
+
if pyramid_id in _get_block_elements(cubit, block_id, "pyramid"):
|
|
1936
|
+
block_ids.append(block_id)
|
|
1937
|
+
break
|
|
1938
|
+
|
|
1939
|
+
# Process triangles
|
|
1940
|
+
for tri_id in tri_list:
|
|
1941
|
+
nodes = cubit.get_expanded_connectivity("tri", tri_id)
|
|
1942
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1943
|
+
connectivity.extend(vtk_nodes)
|
|
1944
|
+
current_offset += len(nodes)
|
|
1945
|
+
offsets.append(current_offset)
|
|
1946
|
+
cell_types.append(get_vtk_type("tri", len(nodes)))
|
|
1947
|
+
for block_id in cubit.get_block_id_list():
|
|
1948
|
+
if tri_id in _get_block_elements(cubit, block_id, "tri"):
|
|
1949
|
+
block_ids.append(block_id)
|
|
1950
|
+
break
|
|
1951
|
+
|
|
1952
|
+
# Process quads
|
|
1953
|
+
for quad_id in quad_list:
|
|
1954
|
+
nodes = cubit.get_expanded_connectivity("quad", quad_id)
|
|
1955
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1956
|
+
connectivity.extend(vtk_nodes)
|
|
1957
|
+
current_offset += len(nodes)
|
|
1958
|
+
offsets.append(current_offset)
|
|
1959
|
+
cell_types.append(get_vtk_type("quad", len(nodes)))
|
|
1960
|
+
for block_id in cubit.get_block_id_list():
|
|
1961
|
+
if quad_id in _get_block_elements(cubit, block_id, "face"):
|
|
1962
|
+
block_ids.append(block_id)
|
|
1963
|
+
break
|
|
1964
|
+
|
|
1965
|
+
# Process edges
|
|
1966
|
+
for edge_id in edge_list:
|
|
1967
|
+
nodes = cubit.get_expanded_connectivity("edge", edge_id)
|
|
1968
|
+
vtk_nodes = [node_id_to_vtk_index[n] for n in nodes]
|
|
1969
|
+
connectivity.extend(vtk_nodes)
|
|
1970
|
+
current_offset += len(nodes)
|
|
1971
|
+
offsets.append(current_offset)
|
|
1972
|
+
cell_types.append(get_vtk_type("edge", len(nodes)))
|
|
1973
|
+
for block_id in cubit.get_block_id_list():
|
|
1974
|
+
if edge_id in _get_block_elements(cubit, block_id, "edge"):
|
|
1975
|
+
block_ids.append(block_id)
|
|
1976
|
+
break
|
|
1977
|
+
|
|
1978
|
+
# Process point elements
|
|
1979
|
+
for node_id in nodes_list:
|
|
1980
|
+
connectivity.append(node_id_to_vtk_index[node_id])
|
|
1981
|
+
current_offset += 1
|
|
1982
|
+
offsets.append(current_offset)
|
|
1983
|
+
cell_types.append(VTK_VERTEX)
|
|
1984
|
+
for block_id in cubit.get_block_id_list():
|
|
1985
|
+
if node_id in _get_block_elements(cubit, block_id, "node"):
|
|
1986
|
+
block_ids.append(block_id)
|
|
1987
|
+
break
|
|
1988
|
+
|
|
1989
|
+
# Write VTU file
|
|
1990
|
+
with open(FileName, 'w', encoding='UTF-8') as fid:
|
|
1991
|
+
fid.write('<?xml version="1.0"?>\n')
|
|
1992
|
+
fid.write('<VTKFile type="UnstructuredGrid" version="1.0" byte_order="LittleEndian">\n')
|
|
1993
|
+
fid.write(' <UnstructuredGrid>\n')
|
|
1994
|
+
fid.write(f' <Piece NumberOfPoints="{num_points}" NumberOfCells="{num_cells}">\n')
|
|
1995
|
+
|
|
1996
|
+
# Points
|
|
1997
|
+
fid.write(' <Points>\n')
|
|
1998
|
+
fid.write(' <DataArray type="Float64" NumberOfComponents="3" format="ascii">\n')
|
|
1999
|
+
for node_id in sorted_nodes:
|
|
2000
|
+
coord = cubit.get_nodal_coordinates(node_id)
|
|
2001
|
+
fid.write(f' {coord[0]} {coord[1]} {coord[2]}\n')
|
|
2002
|
+
fid.write(' </DataArray>\n')
|
|
2003
|
+
fid.write(' </Points>\n')
|
|
2004
|
+
|
|
2005
|
+
# Cells
|
|
2006
|
+
fid.write(' <Cells>\n')
|
|
2007
|
+
|
|
2008
|
+
# Connectivity
|
|
2009
|
+
fid.write(' <DataArray type="Int64" Name="connectivity" format="ascii">\n')
|
|
2010
|
+
fid.write(' ')
|
|
2011
|
+
for i, c in enumerate(connectivity):
|
|
2012
|
+
fid.write(f'{c} ')
|
|
2013
|
+
if (i + 1) % 20 == 0:
|
|
2014
|
+
fid.write('\n ')
|
|
2015
|
+
fid.write('\n')
|
|
2016
|
+
fid.write(' </DataArray>\n')
|
|
2017
|
+
|
|
2018
|
+
# Offsets
|
|
2019
|
+
fid.write(' <DataArray type="Int64" Name="offsets" format="ascii">\n')
|
|
2020
|
+
fid.write(' ')
|
|
2021
|
+
for i, o in enumerate(offsets):
|
|
2022
|
+
fid.write(f'{o} ')
|
|
2023
|
+
if (i + 1) % 20 == 0:
|
|
2024
|
+
fid.write('\n ')
|
|
2025
|
+
fid.write('\n')
|
|
2026
|
+
fid.write(' </DataArray>\n')
|
|
2027
|
+
|
|
2028
|
+
# Cell types
|
|
2029
|
+
fid.write(' <DataArray type="UInt8" Name="types" format="ascii">\n')
|
|
2030
|
+
fid.write(' ')
|
|
2031
|
+
for i, t in enumerate(cell_types):
|
|
2032
|
+
fid.write(f'{t} ')
|
|
2033
|
+
if (i + 1) % 20 == 0:
|
|
2034
|
+
fid.write('\n ')
|
|
2035
|
+
fid.write('\n')
|
|
2036
|
+
fid.write(' </DataArray>\n')
|
|
2037
|
+
|
|
2038
|
+
fid.write(' </Cells>\n')
|
|
2039
|
+
|
|
2040
|
+
# Cell Data (Block IDs)
|
|
2041
|
+
fid.write(' <CellData Scalars="BlockID">\n')
|
|
2042
|
+
fid.write(' <DataArray type="Int32" Name="BlockID" format="ascii">\n')
|
|
2043
|
+
fid.write(' ')
|
|
2044
|
+
for i, b in enumerate(block_ids):
|
|
2045
|
+
fid.write(f'{b} ')
|
|
2046
|
+
if (i + 1) % 20 == 0:
|
|
2047
|
+
fid.write('\n ')
|
|
2048
|
+
fid.write('\n')
|
|
2049
|
+
fid.write(' </DataArray>\n')
|
|
2050
|
+
fid.write(' </CellData>\n')
|
|
2051
|
+
|
|
2052
|
+
# Point Data (Node IDs)
|
|
2053
|
+
fid.write(' <PointData Scalars="NodeID">\n')
|
|
2054
|
+
fid.write(' <DataArray type="Int32" Name="NodeID" format="ascii">\n')
|
|
2055
|
+
fid.write(' ')
|
|
2056
|
+
for i, node_id in enumerate(sorted_nodes):
|
|
2057
|
+
fid.write(f'{node_id} ')
|
|
2058
|
+
if (i + 1) % 20 == 0:
|
|
2059
|
+
fid.write('\n ')
|
|
2060
|
+
fid.write('\n')
|
|
2061
|
+
fid.write(' </DataArray>\n')
|
|
2062
|
+
fid.write(' </PointData>\n')
|
|
2063
|
+
|
|
2064
|
+
fid.write(' </Piece>\n')
|
|
2065
|
+
fid.write(' </UnstructuredGrid>\n')
|
|
2066
|
+
fid.write('</VTKFile>\n')
|
|
2067
|
+
|
|
2068
|
+
return cubit
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
########################################################################
|
|
2072
|
+
### Exodus II format (Cubit's native format)
|
|
2073
|
+
########################################################################
|
|
2074
|
+
|
|
2075
|
+
def export_exodus(
|
|
2076
|
+
cubit: Any,
|
|
2077
|
+
filename: str,
|
|
2078
|
+
overwrite: bool = True,
|
|
2079
|
+
large_model: bool = False
|
|
2080
|
+
) -> Any:
|
|
2081
|
+
"""Export mesh to Exodus II format.
|
|
2082
|
+
|
|
2083
|
+
Exports the current Cubit mesh to Exodus II format (.exo, .e, .g).
|
|
2084
|
+
This is Cubit's native format and supports all element types and orders.
|
|
2085
|
+
|
|
2086
|
+
Args:
|
|
2087
|
+
cubit: Cubit Python interface object
|
|
2088
|
+
filename: Output file path (typically .exo, .e, or .g extension)
|
|
2089
|
+
overwrite: Whether to overwrite existing file (default: True)
|
|
2090
|
+
large_model: Use 64-bit integers for large models (default: False)
|
|
2091
|
+
|
|
2092
|
+
Returns:
|
|
2093
|
+
cubit: The cubit object (for method chaining)
|
|
2094
|
+
|
|
2095
|
+
Supported elements:
|
|
2096
|
+
- 0D: NODE
|
|
2097
|
+
- 1D: BAR, BAR2, BAR3
|
|
2098
|
+
- 2D: TRI, TRI6, TRI7, QUAD, QUAD8, QUAD9
|
|
2099
|
+
- 3D: TET, TET10, TET11, HEX, HEX20, HEX27, WEDGE, WEDGE15, PYRAMID, PYRAMID13
|
|
2100
|
+
|
|
2101
|
+
Example:
|
|
2102
|
+
>>> cubit.cmd("create brick x 1 y 1 z 1")
|
|
2103
|
+
>>> cubit.cmd("volume 1 scheme tetmesh")
|
|
2104
|
+
>>> cubit.cmd("mesh volume 1")
|
|
2105
|
+
>>> cubit.cmd("block 1 add tet all")
|
|
2106
|
+
>>> cubit.cmd("block 1 name 'solid'")
|
|
2107
|
+
>>> export_exodus(cubit, "cube.exo")
|
|
2108
|
+
|
|
2109
|
+
Note:
|
|
2110
|
+
Exodus format natively supports nodesets and sidesets.
|
|
2111
|
+
For element order control (e.g., converting to TET10),
|
|
2112
|
+
blocks must contain mesh elements rather than geometry.
|
|
2113
|
+
"""
|
|
2114
|
+
_warn_mixed_element_types_in_blocks(cubit)
|
|
2115
|
+
|
|
2116
|
+
|
|
2117
|
+
# Build export command
|
|
2118
|
+
cmd_parts = ['export mesh']
|
|
2119
|
+
cmd_parts.append(f'"{filename}"')
|
|
2120
|
+
|
|
2121
|
+
# Add options
|
|
2122
|
+
if overwrite:
|
|
2123
|
+
cmd_parts.append('overwrite')
|
|
2124
|
+
|
|
2125
|
+
if large_model:
|
|
2126
|
+
cmd_parts.append('large')
|
|
2127
|
+
|
|
2128
|
+
# Execute export
|
|
2129
|
+
cmd = ' '.join(cmd_parts)
|
|
2130
|
+
cubit.cmd(cmd)
|
|
2131
|
+
|
|
2132
|
+
# Print summary
|
|
2133
|
+
_print_exodus_summary(cubit, filename)
|
|
2134
|
+
|
|
2135
|
+
return cubit
|
|
2136
|
+
|
|
2137
|
+
|
|
2138
|
+
def _print_exodus_summary(cubit: Any, filename: str) -> None:
|
|
2139
|
+
"""Print summary of exported Exodus mesh."""
|
|
2140
|
+
print(f"\nExodus export: {filename}")
|
|
2141
|
+
print("-" * 50)
|
|
2142
|
+
|
|
2143
|
+
# Count elements by type
|
|
2144
|
+
element_counts = {}
|
|
2145
|
+
|
|
2146
|
+
# 3D elements
|
|
2147
|
+
try:
|
|
2148
|
+
tet_count = cubit.get_tet_count()
|
|
2149
|
+
if tet_count > 0:
|
|
2150
|
+
element_counts['Tetrahedra'] = tet_count
|
|
2151
|
+
except:
|
|
2152
|
+
pass
|
|
2153
|
+
|
|
2154
|
+
try:
|
|
2155
|
+
hex_count = cubit.get_hex_count()
|
|
2156
|
+
if hex_count > 0:
|
|
2157
|
+
element_counts['Hexahedra'] = hex_count
|
|
2158
|
+
except:
|
|
2159
|
+
pass
|
|
2160
|
+
|
|
2161
|
+
try:
|
|
2162
|
+
wedge_count = cubit.get_wedge_count()
|
|
2163
|
+
if wedge_count > 0:
|
|
2164
|
+
element_counts['Wedges'] = wedge_count
|
|
2165
|
+
except:
|
|
2166
|
+
pass
|
|
2167
|
+
|
|
2168
|
+
try:
|
|
2169
|
+
pyramid_count = cubit.get_pyramid_count()
|
|
2170
|
+
if pyramid_count > 0:
|
|
2171
|
+
element_counts['Pyramids'] = pyramid_count
|
|
2172
|
+
except:
|
|
2173
|
+
pass
|
|
2174
|
+
|
|
2175
|
+
# 2D elements
|
|
2176
|
+
try:
|
|
2177
|
+
tri_count = cubit.get_tri_count()
|
|
2178
|
+
if tri_count > 0:
|
|
2179
|
+
element_counts['Triangles'] = tri_count
|
|
2180
|
+
except:
|
|
2181
|
+
pass
|
|
2182
|
+
|
|
2183
|
+
try:
|
|
2184
|
+
quad_count = cubit.get_quad_count()
|
|
2185
|
+
if quad_count > 0:
|
|
2186
|
+
element_counts['Quads'] = quad_count
|
|
2187
|
+
except:
|
|
2188
|
+
pass
|
|
2189
|
+
|
|
2190
|
+
# Print counts
|
|
2191
|
+
print(f"Nodes: {cubit.get_node_count()}")
|
|
2192
|
+
for elem_type, count in element_counts.items():
|
|
2193
|
+
print(f"{elem_type}: {count}")
|
|
2194
|
+
|
|
2195
|
+
# Blocks
|
|
2196
|
+
block_ids = cubit.get_block_id_list()
|
|
2197
|
+
print(f"\nBlocks: {len(block_ids)}")
|
|
2198
|
+
for block_id in block_ids:
|
|
2199
|
+
name = cubit.get_exodus_entity_name("block", block_id)
|
|
2200
|
+
if name:
|
|
2201
|
+
print(f" Block {block_id}: \"{name}\"")
|
|
2202
|
+
else:
|
|
2203
|
+
print(f" Block {block_id}")
|
|
2204
|
+
|
|
2205
|
+
# Nodesets
|
|
2206
|
+
nodeset_ids = cubit.get_nodeset_id_list()
|
|
2207
|
+
if len(nodeset_ids) > 0:
|
|
2208
|
+
print(f"\nNodesets: {len(nodeset_ids)}")
|
|
2209
|
+
for ns_id in nodeset_ids:
|
|
2210
|
+
name = cubit.get_exodus_entity_name("nodeset", ns_id)
|
|
2211
|
+
if name:
|
|
2212
|
+
print(f" Nodeset {ns_id}: \"{name}\"")
|
|
2213
|
+
else:
|
|
2214
|
+
print(f" Nodeset {ns_id}")
|
|
2215
|
+
|
|
2216
|
+
# Sidesets
|
|
2217
|
+
sideset_ids = cubit.get_sideset_id_list()
|
|
2218
|
+
if len(sideset_ids) > 0:
|
|
2219
|
+
print(f"\nSidesets: {len(sideset_ids)}")
|
|
2220
|
+
for ss_id in sideset_ids:
|
|
2221
|
+
name = cubit.get_exodus_entity_name("sideset", ss_id)
|
|
2222
|
+
if name:
|
|
2223
|
+
print(f" Sideset {ss_id}: \"{name}\"")
|
|
2224
|
+
else:
|
|
2225
|
+
print(f" Sideset {ss_id}")
|
|
2226
|
+
|
|
2227
|
+
print("-" * 50)
|
|
2228
|
+
|
|
2229
|
+
|
|
2230
|
+
########################################################################
|
|
2231
|
+
### Function Aliases (snake_case naming convention)
|
|
2232
|
+
### For backward compatibility, original names are preserved.
|
|
2233
|
+
### New code should prefer snake_case names.
|
|
2234
|
+
########################################################################
|
|
2235
|
+
|
|
2236
|
+
# Gmsh format aliases
|
|
2237
|
+
export_gmsh_v2 = export_Gmsh_ver2
|
|
2238
|
+
export_gmsh_v4 = export_Gmsh_ver4
|
|
2239
|
+
|
|
2240
|
+
# Nastran format alias
|
|
2241
|
+
export_nastran = export_Nastran
|
|
2242
|
+
|
|
2243
|
+
# Netgen format alias
|
|
2244
|
+
export_netgen = export_NetgenMesh
|
|
2245
|
+
|
|
2246
|
+
|
|
2247
|
+
########################################################################
|
|
2248
|
+
### SetDeformation functions for high-order curving in NGSolve
|
|
2249
|
+
### These functions provide an alternative to mesh.Curve() when
|
|
2250
|
+
### geominfo (UV parameters) are not available from imported meshes.
|
|
2251
|
+
########################################################################
|
|
2252
|
+
|
|
2253
|
+
def apply_cylinder_deformation(mesh: Any, radius: float, boundary_names: Any,
|
|
2254
|
+
order: int = 2, axis: str = 'z',
|
|
2255
|
+
center: tuple = (0, 0, 0), scale: float = 2.0) -> Any:
|
|
2256
|
+
"""Apply SetDeformation to project boundary elements onto a cylinder surface.
|
|
2257
|
+
|
|
2258
|
+
This function provides high-order curving for cylindrical surfaces when
|
|
2259
|
+
mesh.Curve() fails due to missing geominfo (UV parameters).
|
|
2260
|
+
|
|
2261
|
+
Args:
|
|
2262
|
+
mesh: NGSolve Mesh object
|
|
2263
|
+
radius: Cylinder radius
|
|
2264
|
+
boundary_names: Single boundary name (str) or list of boundary names
|
|
2265
|
+
order: Polynomial order for deformation field (2 is sufficient)
|
|
2266
|
+
axis: Cylinder axis ('x', 'y', or 'z')
|
|
2267
|
+
center: Center point of cylinder axis (x, y, z)
|
|
2268
|
+
|
|
2269
|
+
Returns:
|
|
2270
|
+
GridFunction: The deformation field (already applied to mesh)
|
|
2271
|
+
|
|
2272
|
+
Example:
|
|
2273
|
+
>>> mesh = Mesh(ngmesh)
|
|
2274
|
+
>>> mesh.Curve(1) # Linear mesh only
|
|
2275
|
+
>>> deform = apply_cylinder_deformation(mesh, R=0.5,
|
|
2276
|
+
... boundary_names=['face_0', 'face_1'], order=4)
|
|
2277
|
+
>>> # mesh.SetDeformation is called automatically
|
|
2278
|
+
"""
|
|
2279
|
+
from ngsolve import VectorH1, GridFunction, CF, sqrt, IfPos
|
|
2280
|
+
from ngsolve import x as ng_x, y as ng_y, z as ng_z
|
|
2281
|
+
|
|
2282
|
+
# Import coordinate functions
|
|
2283
|
+
x, y, z = ng_x, ng_y, ng_z
|
|
2284
|
+
|
|
2285
|
+
# Shift to cylinder center
|
|
2286
|
+
cx, cy, cz = center
|
|
2287
|
+
x_shifted = x - cx
|
|
2288
|
+
y_shifted = y - cy
|
|
2289
|
+
z_shifted = z - cz
|
|
2290
|
+
|
|
2291
|
+
# Create deformation field
|
|
2292
|
+
fes = VectorH1(mesh, order=order)
|
|
2293
|
+
deform = GridFunction(fes)
|
|
2294
|
+
|
|
2295
|
+
# Calculate radial distance and deformation based on axis
|
|
2296
|
+
epsilon = 0.01 # Avoid division by zero near axis
|
|
2297
|
+
|
|
2298
|
+
if axis.lower() == 'z':
|
|
2299
|
+
r = sqrt(x_shifted*x_shifted + y_shifted*y_shifted)
|
|
2300
|
+
scale_factor = IfPos(r - epsilon, scale * (radius/r - 1), 0)
|
|
2301
|
+
deform_cf = CF((scale_factor * x_shifted, scale_factor * y_shifted, 0))
|
|
2302
|
+
elif axis.lower() == 'x':
|
|
2303
|
+
r = sqrt(y_shifted*y_shifted + z_shifted*z_shifted)
|
|
2304
|
+
scale_factor = IfPos(r - epsilon, scale * (radius/r - 1), 0)
|
|
2305
|
+
deform_cf = CF((0, scale_factor * y_shifted, scale_factor * z_shifted))
|
|
2306
|
+
elif axis.lower() == 'y':
|
|
2307
|
+
r = sqrt(x_shifted*x_shifted + z_shifted*z_shifted)
|
|
2308
|
+
scale_factor = IfPos(r - epsilon, scale * (radius/r - 1), 0)
|
|
2309
|
+
deform_cf = CF((scale_factor * x_shifted, 0, scale_factor * z_shifted))
|
|
2310
|
+
else:
|
|
2311
|
+
raise ValueError(f"Invalid axis '{axis}'. Must be 'x', 'y', or 'z'.")
|
|
2312
|
+
|
|
2313
|
+
# Handle single boundary or list
|
|
2314
|
+
if isinstance(boundary_names, str):
|
|
2315
|
+
boundary_names = [boundary_names]
|
|
2316
|
+
|
|
2317
|
+
# Apply deformation to specified boundaries
|
|
2318
|
+
for bnd_name in boundary_names:
|
|
2319
|
+
region = mesh.Boundaries(bnd_name)
|
|
2320
|
+
deform.Set(deform_cf, definedon=region)
|
|
2321
|
+
|
|
2322
|
+
# Apply to mesh
|
|
2323
|
+
mesh.SetDeformation(deform)
|
|
2324
|
+
|
|
2325
|
+
return deform
|
|
2326
|
+
|
|
2327
|
+
|
|
2328
|
+
def apply_sphere_deformation(mesh: Any, radius: float, boundary_names: Any,
|
|
2329
|
+
order: int = 2, center: tuple = (0, 0, 0), scale: float = 2.0) -> Any:
|
|
2330
|
+
"""Apply SetDeformation to project boundary elements onto a sphere surface.
|
|
2331
|
+
|
|
2332
|
+
This function provides high-order curving for spherical surfaces when
|
|
2333
|
+
mesh.Curve() fails due to missing geominfo (UV parameters).
|
|
2334
|
+
|
|
2335
|
+
Args:
|
|
2336
|
+
mesh: NGSolve Mesh object
|
|
2337
|
+
radius: Sphere radius
|
|
2338
|
+
boundary_names: Single boundary name (str) or list of boundary names
|
|
2339
|
+
order: Polynomial order for deformation field (2 is sufficient)
|
|
2340
|
+
center: Center point of sphere (x, y, z)
|
|
2341
|
+
|
|
2342
|
+
Returns:
|
|
2343
|
+
GridFunction: The deformation field (already applied to mesh)
|
|
2344
|
+
|
|
2345
|
+
Example:
|
|
2346
|
+
>>> mesh = Mesh(ngmesh)
|
|
2347
|
+
>>> mesh.Curve(1)
|
|
2348
|
+
>>> deform = apply_sphere_deformation(mesh, R=1.0,
|
|
2349
|
+
... boundary_names='face_0', order=4)
|
|
2350
|
+
"""
|
|
2351
|
+
from ngsolve import VectorH1, GridFunction, CF, sqrt, IfPos
|
|
2352
|
+
from ngsolve import x as ng_x, y as ng_y, z as ng_z
|
|
2353
|
+
|
|
2354
|
+
x, y, z = ng_x, ng_y, ng_z
|
|
2355
|
+
cx, cy, cz = center
|
|
2356
|
+
x_shifted = x - cx
|
|
2357
|
+
y_shifted = y - cy
|
|
2358
|
+
z_shifted = z - cz
|
|
2359
|
+
|
|
2360
|
+
fes = VectorH1(mesh, order=order)
|
|
2361
|
+
deform = GridFunction(fes)
|
|
2362
|
+
|
|
2363
|
+
# Distance from center
|
|
2364
|
+
r = sqrt(x_shifted*x_shifted + y_shifted*y_shifted + z_shifted*z_shifted)
|
|
2365
|
+
epsilon = 0.01
|
|
2366
|
+
scale_factor = IfPos(r - epsilon, scale * (radius/r - 1), 0)
|
|
2367
|
+
deform_cf = CF((scale_factor * x_shifted, scale_factor * y_shifted, scale_factor * z_shifted))
|
|
2368
|
+
|
|
2369
|
+
if isinstance(boundary_names, str):
|
|
2370
|
+
boundary_names = [boundary_names]
|
|
2371
|
+
|
|
2372
|
+
for bnd_name in boundary_names:
|
|
2373
|
+
region = mesh.Boundaries(bnd_name)
|
|
2374
|
+
deform.Set(deform_cf, definedon=region)
|
|
2375
|
+
|
|
2376
|
+
mesh.SetDeformation(deform)
|
|
2377
|
+
|
|
2378
|
+
return deform
|
|
2379
|
+
|
|
2380
|
+
|
|
2381
|
+
def apply_cone_deformation(mesh: Any, apex: tuple, base_center: tuple,
|
|
2382
|
+
base_radius: float, boundary_names: Any,
|
|
2383
|
+
order: int = 2, scale: float = 2.0) -> Any:
|
|
2384
|
+
"""Apply SetDeformation to project boundary elements onto a cone surface.
|
|
2385
|
+
|
|
2386
|
+
Projects points onto a cone surface defined by apex, base center, and base radius.
|
|
2387
|
+
|
|
2388
|
+
Args:
|
|
2389
|
+
mesh: NGSolve Mesh object
|
|
2390
|
+
apex: Apex point of cone (x, y, z)
|
|
2391
|
+
base_center: Center of cone base (x, y, z)
|
|
2392
|
+
base_radius: Radius at base
|
|
2393
|
+
boundary_names: Single boundary name (str) or list of boundary names
|
|
2394
|
+
order: Polynomial order for deformation field (2 is sufficient)
|
|
2395
|
+
|
|
2396
|
+
Returns:
|
|
2397
|
+
GridFunction: The deformation field (already applied to mesh)
|
|
2398
|
+
|
|
2399
|
+
Example:
|
|
2400
|
+
>>> # Cone with apex at (0,0,2), base at (0,0,0), radius 1
|
|
2401
|
+
>>> deform = apply_cone_deformation(mesh, apex=(0,0,2),
|
|
2402
|
+
... base_center=(0,0,0), base_radius=1.0, boundary_names='face_0')
|
|
2403
|
+
"""
|
|
2404
|
+
from ngsolve import VectorH1, GridFunction, CF, sqrt, IfPos
|
|
2405
|
+
from ngsolve import x as ng_x, y as ng_y, z as ng_z
|
|
2406
|
+
import math
|
|
2407
|
+
|
|
2408
|
+
x, y, z = ng_x, ng_y, ng_z
|
|
2409
|
+
ax, ay, az = apex
|
|
2410
|
+
bx, by, bz = base_center
|
|
2411
|
+
|
|
2412
|
+
# Cone axis direction (from apex to base)
|
|
2413
|
+
dx = bx - ax
|
|
2414
|
+
dy = by - ay
|
|
2415
|
+
dz = bz - az
|
|
2416
|
+
h = math.sqrt(dx*dx + dy*dy + dz*dz) # Cone height
|
|
2417
|
+
|
|
2418
|
+
# Normalize axis
|
|
2419
|
+
dx, dy, dz = dx/h, dy/h, dz/h
|
|
2420
|
+
|
|
2421
|
+
fes = VectorH1(mesh, order=order)
|
|
2422
|
+
deform = GridFunction(fes)
|
|
2423
|
+
|
|
2424
|
+
# For z-axis aligned cone (apex at top, base at bottom)
|
|
2425
|
+
# Correct formula: r_expected = base_radius * (az - z) / h
|
|
2426
|
+
# At base (z=bz): r = R, At apex (z=az): r = 0
|
|
2427
|
+
if abs(dz) > 0.99: # Nearly z-aligned
|
|
2428
|
+
# r_expected at height z
|
|
2429
|
+
r_expected = base_radius * (az - z) / h
|
|
2430
|
+
|
|
2431
|
+
r_xy = sqrt((x - ax)*(x - ax) + (y - ay)*(y - ay))
|
|
2432
|
+
epsilon = 0.01
|
|
2433
|
+
# scale = r_expected/r_xy - 1, so new_r = r_xy * (1 + scale) = r_expected
|
|
2434
|
+
scale_factor = IfPos(r_xy - epsilon, scale * (r_expected/r_xy - 1), 0)
|
|
2435
|
+
deform_cf = CF((scale_factor * (x - ax), scale_factor * (y - ay), 0))
|
|
2436
|
+
else:
|
|
2437
|
+
# General case - more complex projection
|
|
2438
|
+
# For simplicity, assume y-axis aligned
|
|
2439
|
+
if abs(dy) > 0.99:
|
|
2440
|
+
r_expected = base_radius * (ay - y) / h
|
|
2441
|
+
r_xz = sqrt((x - ax)*(x - ax) + (z - az)*(z - az))
|
|
2442
|
+
epsilon = 0.01
|
|
2443
|
+
scale_factor = IfPos(r_xz - epsilon, scale * (r_expected/r_xz - 1), 0)
|
|
2444
|
+
deform_cf = CF((scale_factor * (x - ax), 0, scale_factor * (z - az)))
|
|
2445
|
+
else: # x-axis aligned
|
|
2446
|
+
r_expected = base_radius * (ax - x) / h
|
|
2447
|
+
r_yz = sqrt((y - ay)*(y - ay) + (z - az)*(z - az))
|
|
2448
|
+
epsilon = 0.01
|
|
2449
|
+
scale_factor = IfPos(r_yz - epsilon, scale * (r_expected/r_yz - 1), 0)
|
|
2450
|
+
deform_cf = CF((0, scale_factor * (y - ay), scale_factor * (z - az)))
|
|
2451
|
+
|
|
2452
|
+
if isinstance(boundary_names, str):
|
|
2453
|
+
boundary_names = [boundary_names]
|
|
2454
|
+
|
|
2455
|
+
for bnd_name in boundary_names:
|
|
2456
|
+
region = mesh.Boundaries(bnd_name)
|
|
2457
|
+
deform.Set(deform_cf, definedon=region)
|
|
2458
|
+
|
|
2459
|
+
mesh.SetDeformation(deform)
|
|
2460
|
+
|
|
2461
|
+
return deform
|
|
2462
|
+
|
|
2463
|
+
|
|
2464
|
+
def apply_torus_deformation(mesh: Any, major_radius: float, minor_radius: float,
|
|
2465
|
+
boundary_names: Any, order: int = 2,
|
|
2466
|
+
axis: str = 'z', center: tuple = (0, 0, 0), scale: float = 2.0) -> Any:
|
|
2467
|
+
"""Apply SetDeformation to project boundary elements onto a torus surface.
|
|
2468
|
+
|
|
2469
|
+
Projects points onto a torus (donut shape) defined by major and minor radii.
|
|
2470
|
+
|
|
2471
|
+
Args:
|
|
2472
|
+
mesh: NGSolve Mesh object
|
|
2473
|
+
major_radius: Distance from center of torus to center of tube (R)
|
|
2474
|
+
minor_radius: Radius of the tube (r)
|
|
2475
|
+
boundary_names: Single boundary name (str) or list of boundary names
|
|
2476
|
+
order: Polynomial order for deformation field (4+ recommended for torus)
|
|
2477
|
+
axis: Axis of revolution ('x', 'y', or 'z')
|
|
2478
|
+
center: Center point of torus (x, y, z)
|
|
2479
|
+
|
|
2480
|
+
Returns:
|
|
2481
|
+
GridFunction: The deformation field (already applied to mesh)
|
|
2482
|
+
|
|
2483
|
+
Example:
|
|
2484
|
+
>>> # Torus with R=1.0, r=0.3, centered at origin
|
|
2485
|
+
>>> deform = apply_torus_deformation(mesh, major_radius=1.0,
|
|
2486
|
+
... minor_radius=0.3, boundary_names='face_0', order=4)
|
|
2487
|
+
"""
|
|
2488
|
+
from ngsolve import VectorH1, GridFunction, CF, sqrt, IfPos
|
|
2489
|
+
from ngsolve import x as ng_x, y as ng_y, z as ng_z
|
|
2490
|
+
|
|
2491
|
+
x, y, z = ng_x, ng_y, ng_z
|
|
2492
|
+
cx, cy, cz = center
|
|
2493
|
+
x_shifted = x - cx
|
|
2494
|
+
y_shifted = y - cy
|
|
2495
|
+
z_shifted = z - cz
|
|
2496
|
+
|
|
2497
|
+
R = major_radius
|
|
2498
|
+
r = minor_radius
|
|
2499
|
+
|
|
2500
|
+
fes = VectorH1(mesh, order=order)
|
|
2501
|
+
deform = GridFunction(fes)
|
|
2502
|
+
|
|
2503
|
+
epsilon = 0.01
|
|
2504
|
+
|
|
2505
|
+
if axis.lower() == 'z':
|
|
2506
|
+
# Torus around z-axis
|
|
2507
|
+
# Distance from z-axis in xy-plane
|
|
2508
|
+
rho = sqrt(x_shifted*x_shifted + y_shifted*y_shifted)
|
|
2509
|
+
|
|
2510
|
+
# Distance from the tube center circle
|
|
2511
|
+
# tube_center is at (R*x/rho, R*y/rho, 0) from origin
|
|
2512
|
+
# We need to find vector from tube center to point
|
|
2513
|
+
tube_dist = sqrt((rho - R)*(rho - R) + z_shifted*z_shifted)
|
|
2514
|
+
|
|
2515
|
+
# Scale factor to project to tube surface
|
|
2516
|
+
scale_factor = IfPos(tube_dist - epsilon, scale * (r/tube_dist - 1), 0)
|
|
2517
|
+
|
|
2518
|
+
# Direction from tube center to point
|
|
2519
|
+
# In xy: (x/rho - R/rho*x/rho, y/rho - R/rho*y/rho) = ((1-R/rho)*x/rho, ...)
|
|
2520
|
+
# Simplified: the radial component is (rho-R)/tube_dist, z component is z/tube_dist
|
|
2521
|
+
dx_tube = IfPos(rho - epsilon, (rho - R) / tube_dist * (x_shifted / rho), 0)
|
|
2522
|
+
dy_tube = IfPos(rho - epsilon, (rho - R) / tube_dist * (y_shifted / rho), 0)
|
|
2523
|
+
dz_tube = z_shifted / IfPos(tube_dist - epsilon, tube_dist, 1)
|
|
2524
|
+
|
|
2525
|
+
deform_cf = CF((scale_factor * dx_tube * tube_dist,
|
|
2526
|
+
scale_factor * dy_tube * tube_dist,
|
|
2527
|
+
scale_factor * dz_tube * tube_dist))
|
|
2528
|
+
|
|
2529
|
+
elif axis.lower() == 'y':
|
|
2530
|
+
rho = sqrt(x_shifted*x_shifted + z_shifted*z_shifted)
|
|
2531
|
+
tube_dist = sqrt((rho - R)*(rho - R) + y_shifted*y_shifted)
|
|
2532
|
+
scale_factor = IfPos(tube_dist - epsilon, scale * (r/tube_dist - 1), 0)
|
|
2533
|
+
|
|
2534
|
+
dx_tube = IfPos(rho - epsilon, (rho - R) / tube_dist * (x_shifted / rho), 0)
|
|
2535
|
+
dy_tube = y_shifted / IfPos(tube_dist - epsilon, tube_dist, 1)
|
|
2536
|
+
dz_tube = IfPos(rho - epsilon, (rho - R) / tube_dist * (z_shifted / rho), 0)
|
|
2537
|
+
|
|
2538
|
+
deform_cf = CF((scale_factor * dx_tube * tube_dist,
|
|
2539
|
+
scale_factor * dy_tube * tube_dist,
|
|
2540
|
+
scale_factor * dz_tube * tube_dist))
|
|
2541
|
+
|
|
2542
|
+
else: # x-axis
|
|
2543
|
+
rho = sqrt(y_shifted*y_shifted + z_shifted*z_shifted)
|
|
2544
|
+
tube_dist = sqrt((rho - R)*(rho - R) + x_shifted*x_shifted)
|
|
2545
|
+
scale_factor = IfPos(tube_dist - epsilon, scale * (r/tube_dist - 1), 0)
|
|
2546
|
+
|
|
2547
|
+
dx_tube = x_shifted / IfPos(tube_dist - epsilon, tube_dist, 1)
|
|
2548
|
+
dy_tube = IfPos(rho - epsilon, (rho - R) / tube_dist * (y_shifted / rho), 0)
|
|
2549
|
+
dz_tube = IfPos(rho - epsilon, (rho - R) / tube_dist * (z_shifted / rho), 0)
|
|
2550
|
+
|
|
2551
|
+
deform_cf = CF((scale_factor * dx_tube * tube_dist,
|
|
2552
|
+
scale_factor * dy_tube * tube_dist,
|
|
2553
|
+
scale_factor * dz_tube * tube_dist))
|
|
2554
|
+
|
|
2555
|
+
if isinstance(boundary_names, str):
|
|
2556
|
+
boundary_names = [boundary_names]
|
|
2557
|
+
|
|
2558
|
+
for bnd_name in boundary_names:
|
|
2559
|
+
region = mesh.Boundaries(bnd_name)
|
|
2560
|
+
deform.Set(deform_cf, definedon=region)
|
|
2561
|
+
|
|
2562
|
+
mesh.SetDeformation(deform)
|
|
2563
|
+
|
|
2564
|
+
return deform
|
|
2565
|
+
|
|
2566
|
+
|
|
2567
|
+
def detect_cylinder_boundaries(mesh: Any, radius: float,
|
|
2568
|
+
axis: str = 'z', tol: float = 0.05) -> list:
|
|
2569
|
+
"""Detect which boundaries belong to a cylinder surface (not caps).
|
|
2570
|
+
|
|
2571
|
+
Automatically identifies boundary regions that are part of the cylinder
|
|
2572
|
+
surface based on vertex positions.
|
|
2573
|
+
|
|
2574
|
+
Args:
|
|
2575
|
+
mesh: NGSolve Mesh object
|
|
2576
|
+
radius: Expected cylinder radius
|
|
2577
|
+
axis: Cylinder axis ('x', 'y', or 'z')
|
|
2578
|
+
tol: Tolerance for radius matching
|
|
2579
|
+
|
|
2580
|
+
Returns:
|
|
2581
|
+
list: List of boundary names that are on the cylinder surface
|
|
2582
|
+
|
|
2583
|
+
Example:
|
|
2584
|
+
>>> boundaries = detect_cylinder_boundaries(mesh, radius=0.5, axis='z')
|
|
2585
|
+
>>> deform = apply_cylinder_deformation(mesh, 0.5, boundaries)
|
|
2586
|
+
"""
|
|
2587
|
+
import math
|
|
2588
|
+
from ngsolve import BND
|
|
2589
|
+
|
|
2590
|
+
boundaries = mesh.GetBoundaries()
|
|
2591
|
+
cylinder_boundaries = []
|
|
2592
|
+
|
|
2593
|
+
for bnd_name in boundaries:
|
|
2594
|
+
count = 0
|
|
2595
|
+
on_surface_count = 0
|
|
2596
|
+
|
|
2597
|
+
for el in mesh.Elements(BND):
|
|
2598
|
+
if mesh.GetBoundaries()[el.index] != bnd_name:
|
|
2599
|
+
continue
|
|
2600
|
+
count += 1
|
|
2601
|
+
|
|
2602
|
+
all_on_surface = True
|
|
2603
|
+
for v in el.vertices:
|
|
2604
|
+
p = mesh.vertices[v.nr].point
|
|
2605
|
+
if axis.lower() == 'z':
|
|
2606
|
+
r = math.sqrt(p[0]**2 + p[1]**2)
|
|
2607
|
+
elif axis.lower() == 'y':
|
|
2608
|
+
r = math.sqrt(p[0]**2 + p[2]**2)
|
|
2609
|
+
else: # x
|
|
2610
|
+
r = math.sqrt(p[1]**2 + p[2]**2)
|
|
2611
|
+
|
|
2612
|
+
if abs(r - radius) > tol:
|
|
2613
|
+
all_on_surface = False
|
|
2614
|
+
break
|
|
2615
|
+
|
|
2616
|
+
if all_on_surface:
|
|
2617
|
+
on_surface_count += 1
|
|
2618
|
+
|
|
2619
|
+
# Consider it cylinder surface if > 50% elements are on surface
|
|
2620
|
+
if count > 0 and on_surface_count / count > 0.5:
|
|
2621
|
+
cylinder_boundaries.append(bnd_name)
|
|
2622
|
+
|
|
2623
|
+
return cylinder_boundaries
|
|
2624
|
+
|
|
2625
|
+
|
|
2626
|
+
def detect_sphere_boundaries(mesh: Any, radius: float,
|
|
2627
|
+
center: tuple = (0, 0, 0), tol: float = 0.05) -> list:
|
|
2628
|
+
"""Detect which boundaries belong to a sphere surface.
|
|
2629
|
+
|
|
2630
|
+
Automatically identifies boundary regions that are part of the sphere
|
|
2631
|
+
surface based on vertex positions.
|
|
2632
|
+
|
|
2633
|
+
Args:
|
|
2634
|
+
mesh: NGSolve Mesh object
|
|
2635
|
+
radius: Expected sphere radius
|
|
2636
|
+
center: Center of sphere (x, y, z)
|
|
2637
|
+
tol: Tolerance for radius matching
|
|
2638
|
+
|
|
2639
|
+
Returns:
|
|
2640
|
+
list: List of boundary names that are on the sphere surface
|
|
2641
|
+
"""
|
|
2642
|
+
import math
|
|
2643
|
+
from ngsolve import BND
|
|
2644
|
+
|
|
2645
|
+
cx, cy, cz = center
|
|
2646
|
+
boundaries = mesh.GetBoundaries()
|
|
2647
|
+
sphere_boundaries = []
|
|
2648
|
+
|
|
2649
|
+
for bnd_name in boundaries:
|
|
2650
|
+
count = 0
|
|
2651
|
+
on_surface_count = 0
|
|
2652
|
+
|
|
2653
|
+
for el in mesh.Elements(BND):
|
|
2654
|
+
if mesh.GetBoundaries()[el.index] != bnd_name:
|
|
2655
|
+
continue
|
|
2656
|
+
count += 1
|
|
2657
|
+
|
|
2658
|
+
all_on_surface = True
|
|
2659
|
+
for v in el.vertices:
|
|
2660
|
+
p = mesh.vertices[v.nr].point
|
|
2661
|
+
r = math.sqrt((p[0]-cx)**2 + (p[1]-cy)**2 + (p[2]-cz)**2)
|
|
2662
|
+
if abs(r - radius) > tol:
|
|
2663
|
+
all_on_surface = False
|
|
2664
|
+
break
|
|
2665
|
+
|
|
2666
|
+
if all_on_surface:
|
|
2667
|
+
on_surface_count += 1
|
|
2668
|
+
|
|
2669
|
+
if count > 0 and on_surface_count / count > 0.5:
|
|
2670
|
+
sphere_boundaries.append(bnd_name)
|
|
2671
|
+
|
|
2672
|
+
return sphere_boundaries
|
|
2673
|
+
|
|
2674
|
+
|
|
2675
|
+
def detect_torus_boundaries(mesh: Any, major_radius: float, minor_radius: float,
|
|
2676
|
+
axis: str = 'z', center: tuple = (0, 0, 0),
|
|
2677
|
+
tol: float = 0.05) -> list:
|
|
2678
|
+
"""Detect which boundaries belong to a torus surface.
|
|
2679
|
+
|
|
2680
|
+
Automatically identifies boundary regions that are part of the torus
|
|
2681
|
+
surface based on vertex positions.
|
|
2682
|
+
|
|
2683
|
+
Args:
|
|
2684
|
+
mesh: NGSolve Mesh object
|
|
2685
|
+
major_radius: Distance from center of torus to center of tube (R)
|
|
2686
|
+
minor_radius: Radius of the tube (r)
|
|
2687
|
+
axis: Axis of revolution ('x', 'y', or 'z')
|
|
2688
|
+
center: Center of torus (x, y, z)
|
|
2689
|
+
tol: Tolerance for radius matching
|
|
2690
|
+
|
|
2691
|
+
Returns:
|
|
2692
|
+
list: List of boundary names that are on the torus surface
|
|
2693
|
+
"""
|
|
2694
|
+
import math
|
|
2695
|
+
from ngsolve import BND
|
|
2696
|
+
|
|
2697
|
+
cx, cy, cz = center
|
|
2698
|
+
R = major_radius
|
|
2699
|
+
r = minor_radius
|
|
2700
|
+
boundaries = mesh.GetBoundaries()
|
|
2701
|
+
torus_boundaries = []
|
|
2702
|
+
|
|
2703
|
+
for bnd_name in boundaries:
|
|
2704
|
+
count = 0
|
|
2705
|
+
on_surface_count = 0
|
|
2706
|
+
|
|
2707
|
+
for el in mesh.Elements(BND):
|
|
2708
|
+
if mesh.GetBoundaries()[el.index] != bnd_name:
|
|
2709
|
+
continue
|
|
2710
|
+
count += 1
|
|
2711
|
+
|
|
2712
|
+
all_on_surface = True
|
|
2713
|
+
for v in el.vertices:
|
|
2714
|
+
p = mesh.vertices[v.nr].point
|
|
2715
|
+
px, py, pz = p[0] - cx, p[1] - cy, p[2] - cz
|
|
2716
|
+
|
|
2717
|
+
# Calculate distance from torus surface
|
|
2718
|
+
if axis == 'z':
|
|
2719
|
+
r_major = math.sqrt(px*px + py*py)
|
|
2720
|
+
dist = math.sqrt((r_major - R)**2 + pz*pz)
|
|
2721
|
+
elif axis == 'y':
|
|
2722
|
+
r_major = math.sqrt(px*px + pz*pz)
|
|
2723
|
+
dist = math.sqrt((r_major - R)**2 + py*py)
|
|
2724
|
+
else: # x-axis
|
|
2725
|
+
r_major = math.sqrt(py*py + pz*pz)
|
|
2726
|
+
dist = math.sqrt((r_major - R)**2 + px*px)
|
|
2727
|
+
|
|
2728
|
+
if abs(dist - r) > tol:
|
|
2729
|
+
all_on_surface = False
|
|
2730
|
+
break
|
|
2731
|
+
|
|
2732
|
+
if all_on_surface:
|
|
2733
|
+
on_surface_count += 1
|
|
2734
|
+
|
|
2735
|
+
if count > 0 and on_surface_count / count > 0.5:
|
|
2736
|
+
torus_boundaries.append(bnd_name)
|
|
2737
|
+
|
|
2738
|
+
return torus_boundaries
|