From f666ad3bc2f3f6937af2515a2589b13bd766be97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 10 Mar 2022 22:26:00 +0100 Subject: [PATCH 1/7] some nastran fixes for continuation lines --- src/meshio/_helpers.py | 3 +- src/meshio/nastran/_nastran.py | 89 +++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/meshio/_helpers.py b/src/meshio/_helpers.py index 64b507b60..6cc7dac92 100644 --- a/src/meshio/_helpers.py +++ b/src/meshio/_helpers.py @@ -176,7 +176,8 @@ def write(filename, mesh: Mesh, file_format: str | None = None, **kwargs): if key in num_nodes_per_cell: if value.shape[1] != num_nodes_per_cell[key]: raise WriteError( - f"Unexpected cells array shape {value.shape} for {key} cells." + f"Unexpected cells array shape {value.shape} for {key} cells. " + + f"Expected shape [:, {num_nodes_per_cell[key]}]." ) else: # we allow custom keys and diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index 068a6384f..67431ae61 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -1,7 +1,10 @@ """ I/O for Nastran bulk data. """ +from __future__ import annotations + import numpy as np +from rich import print from ..__about__ import __version__ from .._common import num_nodes_per_cell, warn @@ -63,13 +66,12 @@ def read_buffer(f): cells = [] cells_id = [] cell = None - cell_type = None point_refs = [] cell_refs = [] cell_ref = None - def add_cell(nastran_type, cell, cell_type, cell_ref): - cell_type = nastran_to_meshio_type[keyword] + def add_cell(nastran_type, cell, cell_ref): + cell_type = nastran_to_meshio_type[nastran_type] cell = list(map(int, cell)) # Treat 2nd order CTETRA, CPYRA, CPENTA, CHEXA elements @@ -90,6 +92,7 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): cell = _convert_to_vtk_ordering(cell, nastran_type) + # decide if we should append cell or start a new cell block if len(cells) > 0 and cells[-1][0] == cell_type: cells[-1][1].append(cell) cells_id[-1].append(cell_id) @@ -114,24 +117,51 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): if next_line.startswith("ENDDATA"): break - # read line and merge with all continuation lines (starting with `+`) - chunks = _chunk_line(next_line) + # read line and merge with all continuation lines (starting with `+` or + # `*` or automatic continuation lines in fixed format) + chunks = [] + c, _ = _chunk_line(next_line) + chunks.append(c) while True: next_line = f.readline() if not next_line: raise ReadError("Premature EOF") - next_line = next_line.rstrip() # Blank lines or comments if len(next_line) < 4 or next_line.startswith(("$", "//", "#")): continue - elif next_line[0] == "+": - # skip the continuation chunk - chunks += _chunk_line(next_line)[1:] + elif next_line[0] in ["+", "*"]: + # From + # : + # You can manually specify a continuation by using a + # continuation identifier. A continuation identifier is a + # special character (+ or *) that indicates that the data + # continues on another line. + c, _ = _chunk_line(next_line) + chunks.append(c[1:]) + elif len(chunks[-1]) == 10 and chunks[-1][9] == " ": + # automatic continuation: last chunk of previous line and first + # chunk of current line are spaces + c, _ = _chunk_line(next_line) + if c[0] == " ": + chunks[-1] = chunks[-1][:9] + chunks.append(c[1:]) + else: + # not a continuation + pass else: break + # flatten + chunks = [item for sublist in chunks for item in sublist] + + # strip chunks chunks = [chunk.strip() for chunk in chunks] + # remove empty chunks + chunks = [c for c in chunks if c != ""] + + print("c", chunks) + keyword = chunks[0] # Points @@ -148,7 +178,7 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): if len(pref) > 0: point_refs.append(int(pref)) points_id.append(point_id) - chunks2 = _chunk_line(next_line) + chunks2, _ = _chunk_line(next_line) next_line = f.readline() points.append( [ @@ -174,13 +204,16 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): # This information is removed. cell = chunks[3:5] else: + print(keyword) + print(chunks) + exit(1) cell = chunks[3:] # remove empty chunks cell = [item for item in cell if item != ""] if cell is not None: - add_cell(keyword, cell, cell_type, cell_ref) + add_cell(keyword, cell, cell_ref) # Convert to numpy arrays points = np.array(points) @@ -209,10 +242,20 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): # There are two basic categories of input data formats in NX Nastran: # -# "Free" format data, in which the data fields are simply separated by commas. This type of data is known as free field data. -# "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: -# Small field format, in which a single line of data is divided into 10 fields that can contain eight characters each. -# 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. +# - "Free" format data, in which the data fields are simply separated by +# commas. This type of data is known as free field data. +# +# - "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: +# +# - Small field format, in which a single line of data is divided into 10 +# fields that can contain eight characters each. +# +# - 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. # # See: https://docs.plm.automation.siemens.com/data_services/resources/nxnastran/10/help/en_US/tdocExt/pdf/User.pdf @@ -356,14 +399,14 @@ def _nastran_string_to_float(string): return float(string[0] + string[1:].replace("+", "e+").replace("-", "e-")) -def _chunk_line(line): - if "," in line: # free format - chunks = line.split(",") - else: # fixed format - CHUNK_SIZE = 8 - chunks = [line[i : CHUNK_SIZE + i] for i in range(0, 72, CHUNK_SIZE)] - # everything after the 9th chunk is ignored - return chunks[:9] +def _chunk_line(line: str) -> tuple[list[str], bool]: + if "," in line: + # free format + return line.split(","), True + # fixed format + CHUNK_SIZE = 8 + chunks = [line[i : CHUNK_SIZE + i] for i in range(0, 80, CHUNK_SIZE)] + return chunks, False def _convert_to_vtk_ordering(cell, nastran_type): From 1c1ae2fc7cd8465b7f7028fd32f1c701a3da5519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 10 Mar 2022 22:37:29 +0100 Subject: [PATCH 2/7] more nastran fixes --- src/meshio/nastran/_nastran.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index 67431ae61..a28bbbbbc 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -4,7 +4,6 @@ from __future__ import annotations import numpy as np -from rich import print from ..__about__ import __version__ from .._common import num_nodes_per_cell, warn @@ -124,8 +123,10 @@ def add_cell(nastran_type, cell, cell_ref): chunks.append(c) while True: next_line = f.readline() + if not next_line: raise ReadError("Premature EOF") + # Blank lines or comments if len(next_line) < 4 or next_line.startswith(("$", "//", "#")): continue @@ -138,7 +139,7 @@ def add_cell(nastran_type, cell, cell_ref): # continues on another line. c, _ = _chunk_line(next_line) chunks.append(c[1:]) - elif len(chunks[-1]) == 10 and chunks[-1][9] == " ": + elif len(chunks[-1]) == 10 and chunks[-1][-1] == " ": # automatic continuation: last chunk of previous line and first # chunk of current line are spaces c, _ = _chunk_line(next_line) @@ -147,7 +148,7 @@ def add_cell(nastran_type, cell, cell_ref): chunks.append(c[1:]) else: # not a continuation - pass + chunks.append(c) else: break @@ -160,8 +161,6 @@ def add_cell(nastran_type, cell, cell_ref): # remove empty chunks chunks = [c for c in chunks if c != ""] - print("c", chunks) - keyword = chunks[0] # Points @@ -204,9 +203,6 @@ def add_cell(nastran_type, cell, cell_ref): # This information is removed. cell = chunks[3:5] else: - print(keyword) - print(chunks) - exit(1) cell = chunks[3:] # remove empty chunks @@ -227,6 +223,7 @@ def add_cell(nastran_type, cell, cell_ref): points_id_dict = dict(zip(points_id, np.arange(len(points), dtype=int))) points_id_get = np.vectorize(points_id_dict.__getitem__) for k, c in enumerate(cells): + print(c.data) cells[k] = CellBlock(c.type, points_id_get(c.data)) # Construct the mesh object From 61e18eeaeac1a05af00119a66097b41c68faf68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 10 Mar 2022 22:53:09 +0100 Subject: [PATCH 3/7] another nastran fix --- src/meshio/nastran/_nastran.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index a28bbbbbc..d05bd2045 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -130,6 +130,7 @@ def add_cell(nastran_type, cell, cell_ref): # Blank lines or comments if len(next_line) < 4 or next_line.startswith(("$", "//", "#")): continue + elif next_line[0] in ["+", "*"]: # From # : @@ -139,6 +140,7 @@ def add_cell(nastran_type, cell, cell_ref): # continues on another line. c, _ = _chunk_line(next_line) chunks.append(c[1:]) + elif len(chunks[-1]) == 10 and chunks[-1][-1] == " ": # automatic continuation: last chunk of previous line and first # chunk of current line are spaces @@ -148,7 +150,7 @@ def add_cell(nastran_type, cell, cell_ref): chunks.append(c[1:]) else: # not a continuation - chunks.append(c) + break else: break @@ -223,7 +225,6 @@ def add_cell(nastran_type, cell, cell_ref): points_id_dict = dict(zip(points_id, np.arange(len(points), dtype=int))) points_id_get = np.vectorize(points_id_dict.__getitem__) for k, c in enumerate(cells): - print(c.data) cells[k] = CellBlock(c.type, points_id_get(c.data)) # Construct the mesh object From abfbfac4709a9571f74a9caf7495b1880212c0c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 10 Mar 2022 22:53:26 +0100 Subject: [PATCH 4/7] version bump --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 6317ea158..4f69fbe07 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = meshio -version = 5.3.3 +version = 5.3.4 author = Nico Schlömer et al. author_email = nico.schloemer@gmail.com description = I/O for many mesh formats From 6ef8beebb224cccc15e88499767e55a6cdd8f941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 10 Mar 2022 23:19:54 +0100 Subject: [PATCH 5/7] nastran fixes for long format --- src/meshio/nastran/_nastran.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index d05bd2045..57896dc4a 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -75,7 +75,12 @@ def add_cell(nastran_type, cell, cell_ref): # Treat 2nd order CTETRA, CPYRA, CPENTA, CHEXA elements if len(cell) > num_nodes_per_cell[cell_type]: - assert cell_type in ["tetra", "pyramid", "wedge", "hexahedron"] + assert cell_type in [ + "tetra", + "pyramid", + "wedge", + "hexahedron", + ], f"Illegal cell type {cell_type}" if cell_type == "tetra": cell_type = "tetra10" nastran_type = "CTETRA_" @@ -160,31 +165,30 @@ def add_cell(nastran_type, cell, cell_ref): # strip chunks chunks = [chunk.strip() for chunk in chunks] - # remove empty chunks - chunks = [c for c in chunks if c != ""] - keyword = chunks[0] # Points if keyword == "GRID": + # remove empty chunks + chunks = [c for c in chunks if c != ""] point_id = int(chunks[1]) pref = chunks[2].strip() if len(pref) > 0: point_refs.append(int(pref)) points_id.append(point_id) points.append([_nastran_string_to_float(i) for i in chunks[3:6]]) + elif keyword == "GRID*": # large field format: 8 + 16*4 + 8 point_id = int(chunks[1] + chunks[2]) pref = (chunks[3] + chunks[4]).strip() if len(pref) > 0: point_refs.append(int(pref)) points_id.append(point_id) - chunks2, _ = _chunk_line(next_line) - next_line = f.readline() points.append( [ _nastran_string_to_float(i + j) - for i, j in [chunks[5:7], chunks[7:9], chunks2[1:3]] + # TODO why 10:12, not 9:11? check this in the manual + for i, j in [chunks[5:7], chunks[7:9], chunks[10:12]] ] ) From afcdfe0d033ff53f3bbabf44f5800775a18137b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Fri, 11 Mar 2022 13:15:34 +0100 Subject: [PATCH 6/7] more nastran fixes --- src/meshio/nastran/_nastran.py | 93 +++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index 57896dc4a..90bfb7f78 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -116,6 +116,9 @@ def add_cell(nastran_type, cell, cell_ref): else: break + print() + print() + print(repr(next_line)) while True: # End loop when ENDDATA detected if next_line.startswith("ENDDATA"): @@ -128,6 +131,7 @@ def add_cell(nastran_type, cell, cell_ref): chunks.append(c) while True: next_line = f.readline() + print(repr(next_line)) if not next_line: raise ReadError("Premature EOF") @@ -167,6 +171,8 @@ def add_cell(nastran_type, cell, cell_ref): keyword = chunks[0] + print(chunks) + # Points if keyword == "GRID": # remove empty chunks @@ -187,8 +193,7 @@ def add_cell(nastran_type, cell, cell_ref): points.append( [ _nastran_string_to_float(i + j) - # TODO why 10:12, not 9:11? check this in the manual - for i, j in [chunks[5:7], chunks[7:9], chunks[10:12]] + for i, j in [chunks[5:7], chunks[7:9], chunks[9:11]] ] ) @@ -307,7 +312,10 @@ def write(filename, mesh, point_format="fixed-large", cell_format="fixed-small") for point_id, x in enumerate(points): fx = [float_fmt(k) for k in x] pref = str(point_refs[point_id]) if point_refs is not None else "" - f.write(grid_fmt.format(point_id + 1, pref, fx[0], fx[1], fx[2])) + string = grid_fmt.format(point_id + 1, pref, fx[0], fx[1], fx[2]) + print(point_id + 1, repr(pref), x, fx) + print("s", repr(string)) + f.write(string) # CellBlock cell_id = 0 @@ -357,40 +365,62 @@ def _float_rstrip(x, n=8): def _float_to_nastran_string(value, length=16): """ - Return a value in NASTRAN scientific notation. + From + : + + Real numbers, including zero, must contain a decimal point. You can enter + real numbers in a variety of formats. For example, the following are all + acceptable versions of the real number, seven: + ``` + 7.0 .7E1 0.7+1 + .70+1 7.E+0 70.-1 + ``` + + This methods converts a float value into the corresponding string. Choose + the variant with `E` to make the file less ambigious when edited by a + human. (`5.-1` looks like 4.0, not 5.0e-1 = 0.5.) + Examples: - 1234.56789 --> "1.23456789+3" - -0.1234 --> "-1.234-1" - 3.1415926535897932 --> "3.14159265359+0" + 1234.56789 --> "1.23456789E+3" + -0.1234 --> "-1.234E-1" + 3.1415926535897932 --> "3.14159265359E+0" """ - aux = length - 2 - # sfmt = "{" + f":{length}s" + "}" - sfmt = "{" + ":s" + "}" - pv_fmt = "{" + f":{length}.{aux}e" + "}" + out = np.format_float_scientific(value, exp_digits=1, precision=11).replace( + "e", "E" + ) + assert len(out) <= 16 + return out + # The following is the manual float conversion. Keep it around for a while in case + # we still need it. + + # aux = length - 2 + # # sfmt = "{" + f":{length}s" + "}" + # sfmt = "{" + ":s" + "}" + # pv_fmt = "{" + f":{length}.{aux}e" + "}" - if value == 0.0: - return sfmt.format("0.") + # if value == 0.0: + # return sfmt.format("0.") - python_value = pv_fmt.format(value) # -1.e-2 - svalue, sexponent = python_value.strip().split("e") - exponent = int(sexponent) # removes 0s + # python_value = pv_fmt.format(value) # -1.e-2 + # svalue, sexponent = python_value.strip().split("e") + # exponent = int(sexponent) # removes 0s - sign = "-" if abs(value) < 1.0 else "+" + # sign = "-" if abs(value) < 1.0 else "+" - # the exponent will be added later... - sexp2 = str(exponent).strip("-+") - value2 = float(svalue) + # # the exponent will be added later... + # sexp2 = str(exponent).strip("-+") + # value2 = float(svalue) - # the plus 1 is for the sign - len_sexp = len(sexp2) + 1 - leftover = length - len_sexp - leftover = leftover - 3 if value < 0 else leftover - 2 - fmt = "{" + f":1.{leftover:d}f" + "}" + # # the plus 1 is for the sign + # len_sexp = len(sexp2) + 1 + # leftover = length - len_sexp + # leftover = leftover - 3 if value < 0 else leftover - 2 + # fmt = "{" + f":1.{leftover:d}f" + "}" - svalue3 = fmt.format(value2) - svalue4 = svalue3.strip("0") - field = sfmt.format(svalue4 + sign + sexp2) - return field + # svalue3 = fmt.format(value2) + # svalue4 = svalue3.strip("0") + # field = sfmt.format(svalue4 + sign + sexp2) + # return field def _nastran_string_to_float(string): @@ -402,12 +432,15 @@ def _nastran_string_to_float(string): def _chunk_line(line: str) -> tuple[list[str], bool]: + # remove terminal newline + assert line[-1] == "\n" + line = line[:-1] if "," in line: # free format return line.split(","), True # fixed format CHUNK_SIZE = 8 - chunks = [line[i : CHUNK_SIZE + i] for i in range(0, 80, CHUNK_SIZE)] + chunks = [line[i : CHUNK_SIZE + i] for i in range(0, len(line), CHUNK_SIZE)] return chunks, False From 9f32d06a01ba069657b7dee38b6ebc06dee8987d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Fri, 11 Mar 2022 14:05:22 +0100 Subject: [PATCH 7/7] nastran: better handling of large-fixed --- src/meshio/nastran/_nastran.py | 78 ++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index 90bfb7f78..06bb96717 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -116,9 +116,6 @@ def add_cell(nastran_type, cell, cell_ref): else: break - print() - print() - print(repr(next_line)) while True: # End loop when ENDDATA detected if next_line.startswith("ENDDATA"): @@ -131,7 +128,6 @@ def add_cell(nastran_type, cell, cell_ref): chunks.append(c) while True: next_line = f.readline() - print(repr(next_line)) if not next_line: raise ReadError("Premature EOF") @@ -147,36 +143,78 @@ def add_cell(nastran_type, cell, cell_ref): # continuation identifier. A continuation identifier is a # special character (+ or *) that indicates that the data # continues on another line. + assert len(chunks[-1]) <= 10 + if len(chunks[-1]) == 10: + # This is a continuation line, so the 10th chunk of the + # previous line must also be a continuation indicator. + # Sometimes its first character is a `+`, but it's not + # always present. Anyway, cut it off. + chunks[-1][-1] = None c, _ = _chunk_line(next_line) - chunks.append(c[1:]) + c[0] = None + chunks.append(c) elif len(chunks[-1]) == 10 and chunks[-1][-1] == " ": # automatic continuation: last chunk of previous line and first # chunk of current line are spaces c, _ = _chunk_line(next_line) if c[0] == " ": - chunks[-1] = chunks[-1][:9] - chunks.append(c[1:]) + chunks[-1][9] = None + c[0] = None + chunks.append(c) else: # not a continuation break else: break + # merge chunks according to large field format + # large field format: 8 + 16 + 16 + 16 + 16 + 8 + if chunks[0][0].startswith("GRID*"): + new_chunks = [] + for c in chunks: + d = [c[0]] + + if len(c) > 1: + d.append(c[1]) + if len(c) > 2: + d[-1] += c[2] + + if len(c) > 3: + d.append(c[3]) + if len(c) > 4: + d[-1] += c[4] + + if len(c) > 5: + d.append(c[5]) + if len(c) > 6: + d[-1] += c[6] + + if len(c) > 7: + d.append(c[7]) + if len(c) > 8: + d[-1] += c[8] + + if len(c) > 9: + d.append(c[9]) + + new_chunks.append(d) + + chunks = new_chunks + # flatten chunks = [item for sublist in chunks for item in sublist] + # remove None (continuation blocks) + chunks = [chunk for chunk in chunks if chunk is not None] + # strip chunks chunks = [chunk.strip() for chunk in chunks] keyword = chunks[0] - print(chunks) - # Points - if keyword == "GRID": - # remove empty chunks - chunks = [c for c in chunks if c != ""] + if keyword in ["GRID", "GRID*"]: point_id = int(chunks[1]) pref = chunks[2].strip() if len(pref) > 0: @@ -184,19 +222,6 @@ def add_cell(nastran_type, cell, cell_ref): points_id.append(point_id) points.append([_nastran_string_to_float(i) for i in chunks[3:6]]) - elif keyword == "GRID*": # large field format: 8 + 16*4 + 8 - point_id = int(chunks[1] + chunks[2]) - pref = (chunks[3] + chunks[4]).strip() - if len(pref) > 0: - point_refs.append(int(pref)) - points_id.append(point_id) - points.append( - [ - _nastran_string_to_float(i + j) - for i, j in [chunks[5:7], chunks[7:9], chunks[9:11]] - ] - ) - # CellBlock elif keyword in nastran_to_meshio_type: cell_id = int(chunks[1]) @@ -313,8 +338,6 @@ def write(filename, mesh, point_format="fixed-large", cell_format="fixed-small") fx = [float_fmt(k) for k in x] pref = str(point_refs[point_id]) if point_refs is not None else "" string = grid_fmt.format(point_id + 1, pref, fx[0], fx[1], fx[2]) - print(point_id + 1, repr(pref), x, fx) - print("s", repr(string)) f.write(string) # CellBlock @@ -338,6 +361,7 @@ def write(filename, mesh, point_format="fixed-large", cell_format="fixed-small") cell1 = cell + 1 cell1 = _convert_to_nastran_ordering(cell1, nastran_type) conn = sjoin.join(int_fmt.format(nid) for nid in cell1[:nipl1]) + if len(cell1) > nipl1: if cell_format == "free": cflag1 = cflag3 = ""