Source code for pyNastran.op2.fortran_format
"""
Defines:
- FortranFormat
"""
from __future__ import annotations
from typing import Optional, TYPE_CHECKING
from pyNastran.utils import object_attributes
from pyNastran.utils.numpy_utils import integer_types
#from pyNastran.op2.errors import FortranMarkerError, SortCodeError
from pyNastran.op2.errors import EmptyRecordError
from pyNastran.op2.tables.oef_forces.oef import OEF
from pyNastran.op2.tables.oef_forces.oefpk import OEFPK
from pyNastran.op2.tables.oes_stressStrain.oes import OES
from pyNastran.op2.tables.ogs_grid_point_stresses.ogs import OGS
from pyNastran.op2.tables.oee_energy.onr import ONR
from pyNastran.op2.tables.ogf_gridPointForces.ogpf import OGPF
#from pyNastran.op2.tables.oes_stressStrain.oesm import OESM
from pyNastran.op2.tables.opg_appliedLoads.opg import OPG
from pyNastran.op2.tables.oqg_constraintForces.oqg import OQG
from pyNastran.op2.tables.oqg_constraintForces.obc import OBC
from pyNastran.op2.tables.oug.oug import OUG
from pyNastran.op2.tables.oug.otemp import OTEMP
from pyNastran.op2.tables.oug.ougpk import OUGPK
from pyNastran.op2.tables.contact.oslide import OSLIDE
from pyNastran.op2.tables.contact.obolt import OBOLT
from pyNastran.op2.tables.contact.ofcon3d import OFCON3D
from pyNastran.op2.tables.contact.ougstrs import OUGSTRS
from pyNastran.op2.tables.lama_eigenvalues.lama import LAMA
from pyNastran.op2.tables.onmd import ONMD
from pyNastran.op2.tables.opr import OPR
from pyNastran.op2.tables.ogpwg import OGPWG
if TYPE_CHECKING: # pragma: no cover
from pyNastran.op2.op2 import OP2
[docs]
class Op2Tables:
def __init__(self, op2: OP2):
#self.op2 = op2
self.reader_onmd = ONMD(op2)
self.reader_ogpwg = OGPWG(op2)
self.reader_lama = LAMA(op2)
self.reader_oes = OES(op2)
self.reader_opg = OPG(op2)
self.reader_oqg = OQG(op2)
self.reader_opr = OPR(op2)
self.reader_ogs = OGS(op2)
self.reader_onr = ONR(op2)
self.reader_ogpf = OGPF(op2)
# OUG - displacement, velocity, acceleration, eigenvector, temperature
self.reader_oug = OUG(op2)
self.reader_otemp = OTEMP(op2) # Siemens
self.reader_ougpk = OUGPK(op2) # STK
# bolt
self.reader_obolt = OBOLT(op2) # NX 2019.2 Bolt output
# contact
self.reader_obc = OBC(op2)
self.reader_oslide = OSLIDE(op2) # Incremental and total slide output for contact/glue
self.reader_ofcon3d = OFCON3D(op2)
self.reader_ougstrs = OUGSTRS(op2)
# OEF - element force, heat flux
self.reader_oef = OEF(op2)
self.reader_oefpk = OEFPK(op2) # STK
[docs]
class FortranFormat:
"""defines basic methods for reading Fortran formatted data files"""
def __init__(self):
self.n = 0
self.f = None
self.obj = None
self.table_name = None
self.isubcase = None
self.binary_debug = None
self.read_mode = 1
self._endian = None
self._table_mapper = {}
self._nastran_format = None
self._data_factor = 1
#: stores if the user entered [] for isubcases
self.is_all_subcases = True
self.valid_subcases = []
#self.op2_reader = OP2Reader()
self.IS_TESTING = False
self._op2_readers = Op2Tables(self)
[docs]
def show(self, n: int, types: str='ifs', endian=None, force: bool=False): # pragma: no cover
"""Shows binary data"""
return self.op2_reader.show(n, types=types, endian=endian, force=force)
[docs]
def show_data(self, data, types: str='ifs', endian=None, force: bool=False): # pragma: no cover
"""Shows binary data"""
return self.op2_reader.show_data(data, types=types, endian=endian, force=force)
[docs]
def show_ndata(self, n: int, types: str='ifs', force: bool=False, endian=None): # pragma: no cover
self.op2_reader.show_ndata(n, types=types, force=force, endian=endian)
#def passer(self, data):
#"""
#dummy function used for unsupported tables
#"""
#pass
def _get_table_mapper(self):
raise NotImplementedError('this should be overwritten')
def _finish(self):
raise NotImplementedError('overwrite this')
def _read_subtable_results(self, table4_parser, record_len: int) -> Optional[int]:
"""
# if reading the data
# 1 - 1st pass to size the array (vectorized)
# 2 - 2nd pass to read the data (vectorized)
Parameters
----------
table4_parser : function
the parser function for table 4
record_len : int
the length of the record block
Returns
-------
n : None / int
None : an error occurred or we're in read_mode=1/array sizing (???)
int : the number of bytes that have been read
"""
op2_reader = self.op2_reader # type: OP2Reader
#datai = b''
n = 0
if self.read_mode == 2:
self.ntotal = 0
data, ndata = op2_reader._read_record_ndata()
n = table4_parser(data, ndata)
assert isinstance(n, integer_types), self.table_name
self._reset_vector_counter()
elif self.read_mode == 1:
# if we're checking the array size
#n = op2_reader._skip_record()
#n = table4_parser(datai, 300000)
#self.show(100, types='ifs', endian=None, force=False)
if self.table_name in {b'R1TABRG', b'ONRGY1', b'PVT', b'PVT0', b'PVTS'}:
# these tables are always fully parsed
# PVT/PVTS - we want to know what the PARAM cards are,
# so we can determine the NXVER
data, ndata = op2_reader._read_record_ndata()
else:
try:
data, ndata = op2_reader._skip_record_ndata()
except EmptyRecordError:
self.log.error('error round 2...')
raise
op2_reader.read_markers([1, 0], macro_rewind=False)
marker146 = op2_reader.get_nmarkers4(1, rewind=True)
if marker146 == 146:
#print('marker146', marker146)
return n
self._cleanup_data_members()
#n = self.n
#return n
raise
n = table4_parser(data, ndata)
if not isinstance(n, integer_types):
msg = 'n is not an integer; table_name=%s n=%s table4_parser=%s' % (
self.table_name, n, table4_parser)
raise TypeError(msg)
#op2_reader._goto(n)
#n = op2_reader._skip_record()
self._init_vector_counter(record_len)
else:
raise RuntimeError(self.read_mode)
self._cleanup_data_members()
return n
def _reset_vector_counter(self) -> None:
"""
if reading the data
0 - non-vectorized
1 - 1st pass to size the array (vectorized)
2 - 2nd pass to read the data (vectorized)
vectorized objects are stored as self.obj
they have obj.itime which is their table3 counter
"""
if not(hasattr(self, 'obj') and hasattr(self.obj, 'itime')):
#print(f'self.obj.name={self.obj.__class__.__name__!r} doesnt have itime')
return
#ntotal = record_len // (self.num_wide * 4) * self._data_factor
# we reset the itime counter when we fill up the
# total number of nodes/elements/layers in the
# result, where ntotal is the critical length of
# interest. This let's us start back at the correct
# spot the next time we read table3
#
# For displacements, ntotal=nnodes
#
# For a CBAR, it's ntotal=nelements*2, where 2 is
# the number of nodes; points A/B
#
# For a CTRIA3 / linear CQUAD4, it's
# ntotal=nelements*2, where 2 is the number of
# layers (top/btm) and we only get a centroidal
# result.
#
# For a CQUAD4 bilinear, it's
# ntotal=nelements*(nnodes+1)*2, where 2 is the
# number of layers and nnodes is 4 (we get an extra
# result at the centroid).
#
# For a PCOMP, it's ntotal=sum(nelements*nlayers),
# where each element can have a different number
# of layers
#
# TODO: the or 1 flag breaks tests...that could be why shapes are bad...
#
if self.obj.ntotal == self.obj.data.shape[1] or 1:
#if self.table_name_str in ['OESRMS2', 'OESNO2', 'OSTRRMS2', 'OSTRNO2', 'OESATO2']:
#print('resetting %r indicies; itime=%s; shape=%s' % (
#self.obj.class_name, self.obj.itime, self.obj.data.shape))
self.obj._reset_indices()
self.obj.words = self.words
self.obj.itime += 1
else:
# This happens when self._data_factor hasn't been reset
# or is set wrong.
# can it happen any other time?
#
# yup, when you have sort2...
msga = f'self.obj.name={self.obj.__class__.__name__!r} has itime'
self.log.debug(msga)
msgb = 'ntotal=%s shape=%s shape[1]=%s _data_factor=%s\n' % (
self.obj.ntotal, str(self.obj.data.shape),
self.obj.data.shape[1], self._data_factor)
msgb += f'obj._ntotals={self.obj._ntotals}'
self.log.error(msgb)
raise RuntimeError(msga + '\n' + msgb)
def _init_vector_counter(self, record_len: int) -> None:
"""
Sets the table size
Parameters
----------
record_len : int
the length of the record block
"""
if not(hasattr(self, 'obj') and self.obj is not None):
return
if hasattr(self.obj, 'ntimes'):
if not hasattr(self.obj, '_reset_indices'):
#methods = '\ndir(obj)=%s' % ', '.join(sorted(dir(self.obj)))
#msg = 'is %s vectorized because its missing _reset_indices...%s' % (
#self.obj.__class__.__name__, methods)
return None
#raise RuntimeError(msg)
self.obj._reset_indices()
self.obj.ntimes += 1
#ntotal = record_len // (self.num_wide * 4) * self._data_factor
ntotal = record_len // (self.num_wide * self.size) * self._data_factor
#if self._data_factor == 2 and self.num_wide == 77:
#print(f'record_len={record_len} num_wide={self.num_wide}')
# this has a problem with XYPLOT data if there is a result
# request in the same format (e.g. OESNLXD/OES1X1 tables
# if they both have the same element ID)
#
#class_name = self.obj.__class__.__name__
#if class_name == 'RealBush1DStressArray':
#print('%s.ntotal = %s' % (class_name, ntotal))
#print('num_wide=%s factor=%s len=%s ntotal=%s' % (
#self.num_wide, self._data_factor, record_len, ntotal))
self.obj.ntotal = ntotal
self.obj._ntotals.append(ntotal)
assert isinstance(self.obj.ntotal, integer_types), type(self.obj.ntotal)
else:
self.log.warning('obj=%s doesnt have ntimes' % self.obj.__class__.__name__)
return
def _cleanup_data_members(self) -> None:
"""deletes variables from previous tables"""
del_words = [
'words',
#'Title',
#'ID',
'analysis_code',
#'result_names',
#'labels',
#'data_names',
]
msg = ''
if hasattr(self, 'words'):
if len(self.words) not in [0, 28]:
msg = 'table_name=%r len(self.words)=%s words=%s' % (
self.table_name, len(self.words), self.words)
raise RuntimeError(msg)
for word in self.words:
if word in ['???', 'Title']:
continue
if not hasattr(self, word):
continue
delattr(self, word)
self.words = []
if hasattr(self, 'analysis_code'):
del self.analysis_code
#if hasattr(self, 'data_names') and self.data_names is not None:
#print(object_attributes(self))
if hasattr(self, 'data_code'):
del self.data_code
if hasattr(self, 'mode'):
del self.mode
for word in del_words:
if hasattr(self, word):
val = getattr(self, word)
if isinstance(val, list) and len(val) == 0:
continue
msg += f' {word}={val}\n'
if msg:
print(object_attributes(self, filter_properties=True))
print(msg)