Source code for pyNastran.bdf.bdf_interface.utils

"""
Defines various utilities for BDF parsing including:
 - to_fields

"""
from __future__ import annotations
import os
from io import StringIO
from collections import defaultdict
from typing import List, Dict, Tuple, Optional, Any, TYPE_CHECKING

#import pyNastran
from pyNastran.bdf.errors import CardParseSyntaxError
if TYPE_CHECKING:  # pragma: no cover
    from pyNastran.bdf.bdf import BDF

_REMOVED_LINES = [
    '$EXECUTIVE CONTROL DECK',
    '$CASE CONTROL DECK',
    '$NODES', '$SPOINTS', '$ELEMENTS',
    '$PARAMS', '$PROPERTIES', '$ELEMENTS_WITH_PROPERTIES',
    '$ELEMENTS_WITH_NO_PROPERTIES (PID=0 and unanalyzed properties)',
    '$UNASSOCIATED_PROPERTIES',
    '$MATERIALS', '$THERMAL MATERIALS',
    '$CONSTRAINTS', '$SPCs', '$MPCs', '$RIGID ELEMENTS',
    '$LOADS', '$AERO', '$AERO CONTROL SURFACES',
    '$STATIC AERO', '$FLUTTER', '$GUST',
    '$DYNAMIC', '$OPTIMIZATION',
    '$COORDS', '$THERMAL', '$TABLES', '$RANDOM TABLES',
    '$SETS', '$CONTACT', '$REJECTS', '$REJECT_LINES',
    '$PROPERTIES_MASS', '$MASSES',
]
EXPECTED_HEADER_KEYS_CHECK = [
    'version', 'encoding', 'nnodes', 'nelements',
    'punch', 'dumplines', 'is_superelements', # booleans
]
EXPECTED_HEADER_KEYS_NO_CHECK = ['skip_cards', 'units', 'code-block']


def _to_fields_mntpnt1(card_lines: List[str]) -> List[str]:
    assert len(card_lines) == 2, card_lines
    line1, line2 = card_lines

    label = line1[:24]
    unused_comment = line1[24:]  # len=56 max
    #assert ',' not in label, f'base={label!r}'
    assert '\t' not in label, f'base={label!r}'

    fields = [
        line1[0:8],
        line1[8:16], line1[16:24], line1[24:32], line1[32:40], line1[40:48],
        line1[48:56], line1[56:64], line1[64:72],
    ]

    #assert ',' not in line2, card_lines
    assert '\t' not in line2, card_lines
    assert '*' not in line2, card_lines
    if ',' in line2:
        # drop off the first field of row2
        fields += line2.split(',')[1:]
    else:
        fields += [
            line2[8:16], line2[16:24], line2[24:32], line2[32:40], line2[40:48],
            line2[48:56], line2[56:64], line2[64:72],
        ]
    return fields


[docs]def to_fields(card_lines: List[str], card_name: str) -> List[str]: """ Converts a series of lines in a card into string versions of the field. Handles large, small, and CSV formatted cards. Parameters ---------- lines : List[str] the lines of the BDF card object card_name : str the card_name -> 'GRID' Returns ------- fields : List[str] the string formatted fields of the card .. warning:: this function is used by the reader and isn't intended to be called by a separate process .. code-block:: python >>> card_lines = ['GRID,1,,1.0,2.0,3.0'] >>> card_name = 'GRID' >>> fields = to_fields(lines, card_name) >>> fields ['GRID', '1', '', '1.0', '2.0', '3.0'] """ fields = [] # type: List[str] if card_name in ['MONPNT1']: return _to_fields_mntpnt1(card_lines) # first line line = card_lines[0].rstrip() if '=' in line: msg = 'card_name=%r\nequal signs are not supported...line=%r' % (card_name, line) raise CardParseSyntaxError(msg) if '\t' in line: line = expand_tabs(line) if '*' in line: # large field if ',' in line: # csv new_fields = line.split(',')[:5] for unused_i in range(5 - len(new_fields)): new_fields.append('') else: # standard new_fields = [line[0:8], line[8:24], line[24:40], line[40:56], line[56:72]] fields += new_fields assert len(fields) == 5, fields else: # small field if ',' in line: # csv new_fields = line.split(',')[:9] for unused_i in range(9 - len(new_fields)): new_fields.append('') else: # standard new_fields = [line[0:8], line[8:16], line[16:24], line[24:32], line[32:40], line[40:48], line[48:56], line[56:64], line[64:72]] fields += new_fields assert len(fields) == 9, fields for line in card_lines[1:]: # continuation lines if '=' in line and card_name != 'EIGRL': msg = 'card_name=%r\nequal signs are not supported...\nline=%r' % (card_name, line) raise CardParseSyntaxError(msg) if '\t' in line: line = expand_tabs(line) if '*' in line: # large field if ',' in line: # csv new_fields = line.split(',')[1:5] for unused_i in range(4 - len(new_fields)): new_fields.append('') else: # standard new_fields = [line[8:24], line[24:40], line[40:56], line[56:72]] assert len(new_fields) == 4, new_fields else: # small field if ',' in line: # csv new_fields = line.split(',')[1:9] for unused_i in range(8 - len(new_fields)): new_fields.append('') else: # standard new_fields = [line[8:16], line[16:24], line[24:32], line[32:40], line[40:48], line[48:56], line[56:64], line[64:72]] if len(new_fields) != 8: nfields = len(new_fields) msg = 'nfields=%s new_fields=%s' % (nfields, new_fields) raise RuntimeError(msg) fields += new_fields return fields
[docs]def expand_tabs(line: str) -> str: """expands the tabs; breaks if you mix commas and tabs""" line = line.expandtabs() if ',' in line: line = line.replace('\t', '') msg = f'tabs and commas in the same line are not supported...\nline={line!r}' raise CardParseSyntaxError(msg) return line
[docs]def parse_executive_control_deck( executive_control_lines: List[str]) -> Tuple[Optional[int], Optional[str], Optional[int]]: """Extracts the solution from the executive control deck""" sol = None method = None sol_iline = None for (i, eline) in enumerate(executive_control_lines): uline = eline.strip().upper() # uppercase line uline = uline.split('$')[0].expandtabs() if uline[:4] in ['SOL ']: if ',' in uline: sline = uline.split(',') # SOL 600,method sol_value = sline[0].strip() method = sline[1].strip() else: sol_value = uline method = None if sol is None: sol = sol_value[3:].strip(' \t=') if ',' not in sol: try: # SOL 101 sol = int(sol) except ValueError: # SOL SESTATIC pass else: raise ValueError('cannot overwrite solution existing=' f'|SOL {sol}| new={uline!r}') sol_iline = i return sol, method, sol_iline
def _parse_pynastran_header(line: str) -> Tuple[Optional[str], Optional[str]]: """ Parameters ---------- line : str the line to parse (e.g., '$ pyNastran: version=NX') Returns ------- key : str / None the key for the parameters str : valid (e.g., 'version') None : invalid value : str / None the key for the parameters str : valid (e.g., 'NX') None : invalid Search for data of the form: ..code-block :: python $ pyNastran: version=NX $ pyNastran: encoding=latin-1 $ pyNastran: punch=True $ pyNastran: dumplines=True $ pyNastran: nnodes=10 $ pyNastran: nelements=100 $ pyNastran: skip_cards=PBEAM,CBEAM $ pyNastran: units=in,lb,s $ pyNastran: skip elements=12345,6,7,8 If we find: ..code-block :: python $$ pyNastran: version=NX or a line without a valid pyNastran flag, we'll stop reading, even a valid header statement is on the following line. """ lline = line[1:].lower().strip() if len(lline) == 0 or lline[0] == '$': key = None value = None elif 'pynastran' in lline: base, word = lline.split(':', 1) if base.strip() != 'pynastran': msg = 'unrecognized pyNastran marker\n' msg += 'line=%r' % line raise SyntaxError(msg) try: key, value = word.strip().split('=', 1) except ValueError: msg = ( 'expected header of the form:\n' '$ pyNastran: version=NX\n' '$ pyNastran: encoding=latin-1\n' '$ pyNastran: punch=True\n' '$ pyNastran: dumplines=True\n' '$ pyNastran: nnodes=10\n' '$ pyNastran: nelements=100\n' '$ pyNastran: skip_cards=PBEAM,CBEAM\n' '$ pyNastran: units=in,lb,s\n' '$ pyNastran: skip elements=12345,6,7,8\n' ) raise SyntaxError(msg) key = key.strip() value = value.strip() if key in EXPECTED_HEADER_KEYS_CHECK: assert ' ' not in value, 'value=%r' % value elif key in EXPECTED_HEADER_KEYS_NO_CHECK: pass elif 'skip ' in key: pass else: msg = '\nunrecognized pyNastran key=%r type(key)=%s\n' % (key, type(key)) msg += 'line=%r\n' % line msg += 'expected_keys = [%s]\n' % ', '.join( EXPECTED_HEADER_KEYS_CHECK + EXPECTED_HEADER_KEYS_NO_CHECK) msg += 'type(key0) = %s' % type(EXPECTED_HEADER_KEYS_CHECK[0]) print(msg) raise KeyError(msg) else: key = None value = None return key, value #def clean_empty_lines(lines): ## type: (List[str]) -> List[str] #""" #Removes leading and trailing empty lines #don't remove internally blank lines #""" #found_lines = False #if len(lines) < 2: #return lines #for i, line in enumerate(lines): #if not found_lines and line: #found_lines = True #n1 = i #n2 = i + 1 #elif found_lines and line: #n2 = i + 1 #lines2 = lines[n1:n2] #return lines2 def _parse_dynamic_syntax(key: str, dict_of_vars: Dict[str, Any], log: Any) -> Dict[str, Any]: """ Applies the dynamic syntax for %varName Parameters ---------- key : str the uppercased key Returns ------- value : int/float/str the dynamic value defined by dict_of_vars .. seealso:: :func: `set_dynamic_syntax` """ key = key.strip()[1:] log.debug("dynamic key = %r" % key) #dict_of_vars = {'P5':0.5,'ONEK':1000.} if key not in dict_of_vars: msg = "key=%r not found in keys=%s" % (key, dict_of_vars.keys()) raise KeyError(msg) return dict_of_vars[key] def _get_card_name(lines: List[str], active_filename: str) -> Optional[str]: """ Returns the name of the card defined by the provided lines Parameters ---------- lines : list[str] the lines of the card Returns ------- card_name : str the name of the card """ card_name = lines[0][:8].rstrip('\t, ').split(',')[0].split('\t')[0].strip('*\t ') if len(card_name) == 0: return None if ' ' in card_name or len(card_name) == 0: msg = 'card_name=%r\nline=%r in filename=%r is invalid' \ % (card_name, lines[0], active_filename) print(msg) raise CardParseSyntaxError(msg) return card_name.upper()
[docs]def fill_dmigs(model: BDF) -> None: """fills the DMIx cards with the column data that's been stored""" for name, card_comments in model._dmig_temp.items(): card0, unused_comment0 = card_comments[0] card_name = card0[0] card_name = card_name.rstrip(' *').upper() if card_name == 'DMIG': # if field2 == 'UACCEL': # special DMIG card card = model.dmig[name] elif card_name == 'DMI': card = model.dmi[name] elif card_name == 'DMIJ': card = model.dmij[name] elif card_name == 'DMIJI': card = model.dmiji[name] elif card_name == 'DMIK': card = model.dmik[name] elif card_name == 'DMIAX': card = model.dmiax[name] else: # pragma: no cover raise NotImplementedError(card_name) for (card_obj, comment) in card_comments: card._add_column(card_obj, comment=comment) card.finalize() # empty the _dmig_temp variable model._dmig_temp = defaultdict(list)