1
1
"""
2
2
I/O for Nastran bulk data.
3
3
"""
4
+ from __future__ import annotations
5
+
4
6
import numpy as np
5
7
6
8
from ..__about__ import __version__
@@ -63,18 +65,22 @@ def read_buffer(f):
63
65
cells = []
64
66
cells_id = []
65
67
cell = None
66
- cell_type = None
67
68
point_refs = []
68
69
cell_refs = []
69
70
cell_ref = None
70
71
71
- def add_cell (nastran_type , cell , cell_type , cell_ref ):
72
- cell_type = nastran_to_meshio_type [keyword ]
72
+ def add_cell (nastran_type , cell , cell_ref ):
73
+ cell_type = nastran_to_meshio_type [nastran_type ]
73
74
cell = list (map (int , cell ))
74
75
75
76
# Treat 2nd order CTETRA, CPYRA, CPENTA, CHEXA elements
76
77
if len (cell ) > num_nodes_per_cell [cell_type ]:
77
- assert cell_type in ["tetra" , "pyramid" , "wedge" , "hexahedron" ]
78
+ assert cell_type in [
79
+ "tetra" ,
80
+ "pyramid" ,
81
+ "wedge" ,
82
+ "hexahedron" ,
83
+ ], f"Illegal cell type { cell_type } "
78
84
if cell_type == "tetra" :
79
85
cell_type = "tetra10"
80
86
nastran_type = "CTETRA_"
@@ -90,6 +96,7 @@ def add_cell(nastran_type, cell, cell_type, cell_ref):
90
96
91
97
cell = _convert_to_vtk_ordering (cell , nastran_type )
92
98
99
+ # decide if we should append cell or start a new cell block
93
100
if len (cells ) > 0 and cells [- 1 ][0 ] == cell_type :
94
101
cells [- 1 ][1 ].append (cell )
95
102
cells_id [- 1 ].append (cell_id )
@@ -114,48 +121,106 @@ def add_cell(nastran_type, cell, cell_type, cell_ref):
114
121
if next_line .startswith ("ENDDATA" ):
115
122
break
116
123
117
- # read line and merge with all continuation lines (starting with `+`)
118
- chunks = _chunk_line (next_line )
124
+ # read line and merge with all continuation lines (starting with `+` or
125
+ # `*` or automatic continuation lines in fixed format)
126
+ chunks = []
127
+ c , _ = _chunk_line (next_line )
128
+ chunks .append (c )
119
129
while True :
120
130
next_line = f .readline ()
131
+
121
132
if not next_line :
122
133
raise ReadError ("Premature EOF" )
123
- next_line = next_line . rstrip ()
134
+
124
135
# Blank lines or comments
125
136
if len (next_line ) < 4 or next_line .startswith (("$" , "//" , "#" )):
126
137
continue
127
- elif next_line [0 ] == "+" :
128
- # skip the continuation chunk
129
- chunks += _chunk_line (next_line )[1 :]
138
+
139
+ elif next_line [0 ] in ["+" , "*" ]:
140
+ # From
141
+ # <https://docs.plm.automation.siemens.com/data_services/resources/nxnastran/10/help/en_US/tdocExt/pdf/User.pdf>:
142
+ # You can manually specify a continuation by using a
143
+ # continuation identifier. A continuation identifier is a
144
+ # special character (+ or *) that indicates that the data
145
+ # continues on another line.
146
+ assert len (chunks [- 1 ]) <= 10
147
+ if len (chunks [- 1 ]) == 10 :
148
+ # This is a continuation line, so the 10th chunk of the
149
+ # previous line must also be a continuation indicator.
150
+ # Sometimes its first character is a `+`, but it's not
151
+ # always present. Anyway, cut it off.
152
+ chunks [- 1 ][- 1 ] = None
153
+ c , _ = _chunk_line (next_line )
154
+ c [0 ] = None
155
+ chunks .append (c )
156
+
157
+ elif len (chunks [- 1 ]) == 10 and chunks [- 1 ][- 1 ] == " " :
158
+ # automatic continuation: last chunk of previous line and first
159
+ # chunk of current line are spaces
160
+ c , _ = _chunk_line (next_line )
161
+ if c [0 ] == " " :
162
+ chunks [- 1 ][9 ] = None
163
+ c [0 ] = None
164
+ chunks .append (c )
165
+ else :
166
+ # not a continuation
167
+ break
130
168
else :
131
169
break
132
170
171
+ # merge chunks according to large field format
172
+ # large field format: 8 + 16 + 16 + 16 + 16 + 8
173
+ if chunks [0 ][0 ].startswith ("GRID*" ):
174
+ new_chunks = []
175
+ for c in chunks :
176
+ d = [c [0 ]]
177
+
178
+ if len (c ) > 1 :
179
+ d .append (c [1 ])
180
+ if len (c ) > 2 :
181
+ d [- 1 ] += c [2 ]
182
+
183
+ if len (c ) > 3 :
184
+ d .append (c [3 ])
185
+ if len (c ) > 4 :
186
+ d [- 1 ] += c [4 ]
187
+
188
+ if len (c ) > 5 :
189
+ d .append (c [5 ])
190
+ if len (c ) > 6 :
191
+ d [- 1 ] += c [6 ]
192
+
193
+ if len (c ) > 7 :
194
+ d .append (c [7 ])
195
+ if len (c ) > 8 :
196
+ d [- 1 ] += c [8 ]
197
+
198
+ if len (c ) > 9 :
199
+ d .append (c [9 ])
200
+
201
+ new_chunks .append (d )
202
+
203
+ chunks = new_chunks
204
+
205
+ # flatten
206
+ chunks = [item for sublist in chunks for item in sublist ]
207
+
208
+ # remove None (continuation blocks)
209
+ chunks = [chunk for chunk in chunks if chunk is not None ]
210
+
211
+ # strip chunks
133
212
chunks = [chunk .strip () for chunk in chunks ]
134
213
135
214
keyword = chunks [0 ]
136
215
137
216
# Points
138
- if keyword == "GRID" :
217
+ if keyword in [ "GRID" , "GRID*" ] :
139
218
point_id = int (chunks [1 ])
140
219
pref = chunks [2 ].strip ()
141
220
if len (pref ) > 0 :
142
221
point_refs .append (int (pref ))
143
222
points_id .append (point_id )
144
223
points .append ([_nastran_string_to_float (i ) for i in chunks [3 :6 ]])
145
- elif keyword == "GRID*" : # large field format: 8 + 16*4 + 8
146
- point_id = int (chunks [1 ] + chunks [2 ])
147
- pref = (chunks [3 ] + chunks [4 ]).strip ()
148
- if len (pref ) > 0 :
149
- point_refs .append (int (pref ))
150
- points_id .append (point_id )
151
- chunks2 = _chunk_line (next_line )
152
- next_line = f .readline ()
153
- points .append (
154
- [
155
- _nastran_string_to_float (i + j )
156
- for i , j in [chunks [5 :7 ], chunks [7 :9 ], chunks2 [1 :3 ]]
157
- ]
158
- )
159
224
160
225
# CellBlock
161
226
elif keyword in nastran_to_meshio_type :
@@ -180,7 +245,7 @@ def add_cell(nastran_type, cell, cell_type, cell_ref):
180
245
cell = [item for item in cell if item != "" ]
181
246
182
247
if cell is not None :
183
- add_cell (keyword , cell , cell_type , cell_ref )
248
+ add_cell (keyword , cell , cell_ref )
184
249
185
250
# Convert to numpy arrays
186
251
points = np .array (points )
@@ -209,10 +274,20 @@ def add_cell(nastran_type, cell, cell_type, cell_ref):
209
274
210
275
# There are two basic categories of input data formats in NX Nastran:
211
276
#
212
- # "Free" format data, in which the data fields are simply separated by commas. This type of data is known as free field data.
213
- # "Fixed" format data, in which your data must be aligned in columns of specific width. There are two subcategories of fixed format data that differ based on the size of the fixed column width:
214
- # Small field format, in which a single line of data is divided into 10 fields that can contain eight characters each.
215
- # Large field format, in which a single line of input is expanded into two lines The first and last fields on each line are eight columns wide, while the intermediate fields are sixteen columns wide. The large field format is useful when you need greater numerical accuracy.
277
+ # - "Free" format data, in which the data fields are simply separated by
278
+ # commas. This type of data is known as free field data.
279
+ #
280
+ # - "Fixed" format data, in which your data must be aligned in columns of
281
+ # specific width. There are two subcategories of fixed format data that differ
282
+ # based on the size of the fixed column width:
283
+ #
284
+ # - Small field format, in which a single line of data is divided into 10
285
+ # fields that can contain eight characters each.
286
+ #
287
+ # - Large field format, in which a single line of input is expanded into
288
+ # two lines The first and last fields on each line are eight columns wide,
289
+ # while the intermediate fields are sixteen columns wide. The large field
290
+ # format is useful when you need greater numerical accuracy.
216
291
#
217
292
# See: https://docs.plm.automation.siemens.com/data_services/resources/nxnastran/10/help/en_US/tdocExt/pdf/User.pdf
218
293
@@ -262,7 +337,8 @@ def write(filename, mesh, point_format="fixed-large", cell_format="fixed-small")
262
337
for point_id , x in enumerate (points ):
263
338
fx = [float_fmt (k ) for k in x ]
264
339
pref = str (point_refs [point_id ]) if point_refs is not None else ""
265
- f .write (grid_fmt .format (point_id + 1 , pref , fx [0 ], fx [1 ], fx [2 ]))
340
+ string = grid_fmt .format (point_id + 1 , pref , fx [0 ], fx [1 ], fx [2 ])
341
+ f .write (string )
266
342
267
343
# CellBlock
268
344
cell_id = 0
@@ -285,6 +361,7 @@ def write(filename, mesh, point_format="fixed-large", cell_format="fixed-small")
285
361
cell1 = cell + 1
286
362
cell1 = _convert_to_nastran_ordering (cell1 , nastran_type )
287
363
conn = sjoin .join (int_fmt .format (nid ) for nid in cell1 [:nipl1 ])
364
+
288
365
if len (cell1 ) > nipl1 :
289
366
if cell_format == "free" :
290
367
cflag1 = cflag3 = ""
@@ -312,40 +389,62 @@ def _float_rstrip(x, n=8):
312
389
313
390
def _float_to_nastran_string (value , length = 16 ):
314
391
"""
315
- Return a value in NASTRAN scientific notation.
392
+ From
393
+ <https://docs.plm.automation.siemens.com/data_services/resources/nxnastran/10/help/en_US/tdocExt/pdf/User.pdf>:
394
+
395
+ Real numbers, including zero, must contain a decimal point. You can enter
396
+ real numbers in a variety of formats. For example, the following are all
397
+ acceptable versions of the real number, seven:
398
+ ```
399
+ 7.0 .7E1 0.7+1
400
+ .70+1 7.E+0 70.-1
401
+ ```
402
+
403
+ This methods converts a float value into the corresponding string. Choose
404
+ the variant with `E` to make the file less ambigious when edited by a
405
+ human. (`5.-1` looks like 4.0, not 5.0e-1 = 0.5.)
406
+
316
407
Examples:
317
- 1234.56789 --> "1.23456789 +3"
318
- -0.1234 --> "-1.234 -1"
319
- 3.1415926535897932 --> "3.14159265359 +0"
408
+ 1234.56789 --> "1.23456789E +3"
409
+ -0.1234 --> "-1.234E -1"
410
+ 3.1415926535897932 --> "3.14159265359E +0"
320
411
"""
321
- aux = length - 2
322
- # sfmt = "{" + f":{length}s" + "}"
323
- sfmt = "{" + ":s" + "}"
324
- pv_fmt = "{" + f":{ length } .{ aux } e" + "}"
412
+ out = np .format_float_scientific (value , exp_digits = 1 , precision = 11 ).replace (
413
+ "e" , "E"
414
+ )
415
+ assert len (out ) <= 16
416
+ return out
417
+ # The following is the manual float conversion. Keep it around for a while in case
418
+ # we still need it.
419
+
420
+ # aux = length - 2
421
+ # # sfmt = "{" + f":{length}s" + "}"
422
+ # sfmt = "{" + ":s" + "}"
423
+ # pv_fmt = "{" + f":{length}.{aux}e" + "}"
325
424
326
- if value == 0.0 :
327
- return sfmt .format ("0." )
425
+ # if value == 0.0:
426
+ # return sfmt.format("0.")
328
427
329
- python_value = pv_fmt .format (value ) # -1.e-2
330
- svalue , sexponent = python_value .strip ().split ("e" )
331
- exponent = int (sexponent ) # removes 0s
428
+ # python_value = pv_fmt.format(value) # -1.e-2
429
+ # svalue, sexponent = python_value.strip().split("e")
430
+ # exponent = int(sexponent) # removes 0s
332
431
333
- sign = "-" if abs (value ) < 1.0 else "+"
432
+ # sign = "-" if abs(value) < 1.0 else "+"
334
433
335
- # the exponent will be added later...
336
- sexp2 = str (exponent ).strip ("-+" )
337
- value2 = float (svalue )
434
+ # # the exponent will be added later...
435
+ # sexp2 = str(exponent).strip("-+")
436
+ # value2 = float(svalue)
338
437
339
- # the plus 1 is for the sign
340
- len_sexp = len (sexp2 ) + 1
341
- leftover = length - len_sexp
342
- leftover = leftover - 3 if value < 0 else leftover - 2
343
- fmt = "{" + f":1.{ leftover :d} f" + "}"
438
+ # # the plus 1 is for the sign
439
+ # len_sexp = len(sexp2) + 1
440
+ # leftover = length - len_sexp
441
+ # leftover = leftover - 3 if value < 0 else leftover - 2
442
+ # fmt = "{" + f":1.{leftover:d}f" + "}"
344
443
345
- svalue3 = fmt .format (value2 )
346
- svalue4 = svalue3 .strip ("0" )
347
- field = sfmt .format (svalue4 + sign + sexp2 )
348
- return field
444
+ # svalue3 = fmt.format(value2)
445
+ # svalue4 = svalue3.strip("0")
446
+ # field = sfmt.format(svalue4 + sign + sexp2)
447
+ # return field
349
448
350
449
351
450
def _nastran_string_to_float (string ):
@@ -356,14 +455,17 @@ def _nastran_string_to_float(string):
356
455
return float (string [0 ] + string [1 :].replace ("+" , "e+" ).replace ("-" , "e-" ))
357
456
358
457
359
- def _chunk_line (line ):
360
- if "," in line : # free format
361
- chunks = line .split ("," )
362
- else : # fixed format
363
- CHUNK_SIZE = 8
364
- chunks = [line [i : CHUNK_SIZE + i ] for i in range (0 , 72 , CHUNK_SIZE )]
365
- # everything after the 9th chunk is ignored
366
- return chunks [:9 ]
458
+ def _chunk_line (line : str ) -> tuple [list [str ], bool ]:
459
+ # remove terminal newline
460
+ assert line [- 1 ] == "\n "
461
+ line = line [:- 1 ]
462
+ if "," in line :
463
+ # free format
464
+ return line .split ("," ), True
465
+ # fixed format
466
+ CHUNK_SIZE = 8
467
+ chunks = [line [i : CHUNK_SIZE + i ] for i in range (0 , len (line ), CHUNK_SIZE )]
468
+ return chunks , False
367
469
368
470
369
471
def _convert_to_vtk_ordering (cell , nastran_type ):
0 commit comments