text: """
  defines readers for BDF objects in the OP2 EPT/EPTS table
  """
  #pylint: disable=C0103,R0914
  from __future__ import annotations
  from struct import unpack, Struct
  from functools import partial
  from typing import Tuple, List, TYPE_CHECKING
  
  import numpy as np
  
  #from pyNastran import is_release
  from pyNastran.bdf.cards.properties.mass import PMASS, NSM, NSML
  from pyNastran.bdf.cards.properties.bars import PBAR, PBARL, PBEND, PBEAM3
  from pyNastran.bdf.cards.properties.beam import PBEAM, PBEAML, PBCOMP
  from pyNastran.bdf.cards.properties.bush import PBUSH, PBUSHT
  from pyNastran.bdf.cards.properties.damper import PDAMP, PVISC
  from pyNastran.bdf.cards.properties.properties import PFAST, PGAP
  from pyNastran.bdf.cards.properties.rods import PROD, PTUBE
  from pyNastran.bdf.cards.properties.shell import PSHEAR, PSHELL, PCOMP
  from pyNastran.bdf.cards.properties.solid import PSOLID
  from pyNastran.bdf.cards.properties.springs import PELAS, PELAST
  
  from pyNastran.bdf.cards.thermal.thermal import PCONV, PHBDY, PCONVM
  # PCOMPG, PBUSH1D, PBEAML, PBEAM3
  from pyNastran.op2.op2_interface.op2_reader import (
      mapfmt, reshape_bytes_block_size) # reshape_bytes_block,
  from .utils import get_minus1_start_end
  from .geom2 import DoubleCardError
  if TYPE_CHECKING:  # pragma: no cover
      from pyNastran.op2.op2_geom import OP2Geom
  
  
  class EPT:
      """defines methods for reading op2 properties"""
  
      @property
      def size(self) -> int:
          return self.op2.size
      @property
      def factor(self) -> int:
          return self.op2.factor
  
      def _read_fake(self, data: bytes, n: int) -> int:
          return self.op2._read_fake(data, n)
  
      def read_ept_4(self, data: bytes, ndata: int):
          return self.op2._read_geom_4(self.ept_map, data, ndata)
  
      def __init__(self, op2: OP2Geom):
          self.op2 = op2
          self.ept_map = {
              (3201, 32, 55): ['NSM', self._read_nsm],          # record 2
              (52, 20, 181): ['PBAR', self._read_pbar],         # record 11 - buggy
              (9102, 91, 52): ['PBARL', self._read_pbarl],      # record 12 - almost there...
              (2706, 27, 287): ['PCOMP', self._read_pcomp],     # record 22 - buggy
              (302, 3, 46): ['PELAS', self._read_pelas],        # record 39
              (2102, 21, 121): ['PGAP', self._read_pgap],       # record 42
              (902, 9, 29): ['PROD', self._read_prod],          # record 49
              (1002, 10, 42): ['PSHEAR', self._read_pshear],    # record 50
              (2402, 24, 281): ['PSOLID', self._read_psolid],   # record 51
              (2302, 23, 283): ['PSHELL', self._read_pshell],   # record 52
              (1602, 16, 30): ['PTUBE', self._read_ptube],      # record 56
  
              (5402, 54, 262): ['PBEAM', self._read_pbeam],      # record 14 - not done
              (9202, 92, 53): ['PBEAML', self._read_pbeaml],     # record 15
              (2502, 25, 248): ['PBEND', self._read_pbend],      # record 16 - not done
              (1402, 14, 37): ['PBUSH', self._read_pbush],       # record 19 - not done
              (3101, 31, 219): ['PBUSH1D', self._read_pbush1d],  # record 20 - not done
              (152, 19, 147): ['PCONEAX', self._read_pconeax],   # record 24 - not done
              (11001, 110, 411): ['PCONV', self._read_pconv],    # record 25 - not done
              # record 26
              (202, 2, 45): ['PDAMP', self._read_pdamp],      # record 27 - not done
              (2802, 28, 236): ['PHBDY', self._read_phbdy],   # record 43 - not done
              (402, 4, 44): ['PMASS', self._read_pmass],      # record 48
              (1802, 18, 31): ['PVISC', self._read_pvisc],    # record 59
              (10201, 102, 400): ['PVAL', self._read_pval],   # record 58 - not done
              (2606, 26, 289): ['VIEW', self._read_view],     # record 62 - not done
              (3201, 32, 991) : ['NSM', self._read_nsm_2],  # record
              (3301, 33, 992) : ['NSM1', self._read_nsm1],  # record
              (3701, 37, 995) : ['NSML1', self._read_nsml1_nx],    # record
              (3601, 36, 62): ['NSML1', self._read_nsml1_msc],  # record 7
              (15006, 150, 604): ['PCOMPG', self._read_pcompg],  # record
  
              (702, 7, 38): ['PBUSHT', self._read_pbusht],  # record 1
              (3301, 33, 56): ['NSM1', self._read_fake],  # record 3
              (3401, 34, 57) : ['NSMADD', self._read_fake],    # record 5
              (3501, 35, 58): ['NSML', self._read_fake],  # record 6
              (3501, 35, 994) : ['NSML', self._read_nsml],
              (1502, 15, 36): ['PAABSF', self._read_fake],  # record 8
              (8300, 83, 382): ['PACABS', self._read_fake],  # record 9
              (8500, 85, 384): ['PACBAR', self._read_fake],  # record 10
              (5403, 55, 349): ['PBCOMP', self._read_pbcomp],  # record 13
              (13301, 133, 509): ['PBMSECT', self._read_fake],  # record 17
              (2902, 29, 420): ['PCONVM', self._read_pconvm],  # record 26
              (1202, 12, 33): ['PDAMPT', self._read_pdampt],  # record 28
              (8702, 87, 412): ['PDAMP5', self._read_pdamp5],  # record 29
              (6802, 68, 164): ['PDUM8', self._read_fake],  # record 37
              (6902, 69, 165): ['PDUM9', self._read_fake],  # record 38
              (1302, 13, 34): ['PELAST', self._read_pelast],  # record 41
              (12001, 120, 480): ['PINTC', self._read_fake],  # record 44
              (12101, 121, 484): ['PINTS', self._read_fake],  # record 45
              (4606, 46, 375): ['PLPLANE', self._read_plplane],  # record 46
              (4706, 47, 376): ['PLSOLID', self._read_plsolid],  # record 47
              (10301, 103, 399): ['PSET', self._read_pset],  # record 57
              (3002, 30, 415): ['VIEW3D', self._read_fake],  # record 63
  
              (13501, 135, 510) : ['PFAST', self._read_pfast_msc],  # MSC-specific
              (3601, 36, 55) : ['PFAST', self._read_pfast_nx],  # NX-specific
              (3801, 38, 979) : ['PPLANE', self._read_pplane],
              (11801, 118, 560) : ['PWELD', self._read_fake],
              (3401, 34, 993) : ['NSMADD', self._read_nsmadd],
              (9300, 93, 684) : ['ELAR', self._read_fake],
              (9400, 94, 685) : ['ELAR2', self._read_fake],
              (16006, 160, 903) : ['PCOMPS', self._read_fake],
  
              # MSC-specific
              (14602, 146, 692): ['PSLDN1', self._read_fake],
              (16502, 165, 916): ['PAXSYMH', self._read_fake],
              (13201, 132, 513): ['PBRSECT', self._read_fake],
  
              (13701, 137, 638): ['PWSEAM', self._read_fake],
              (7001, 70, 632): ['???', self._read_fake],
              (15106, 151, 953): ['PCOMPG1', self._read_fake],
              (3901, 39, 969): ['PSHL3D', self._read_fake],
              (17006, 170, 901): ['MATCID', self._read_fake],
  
              (9601, 96, 691): ['PJOINT', self._read_fake],
              (16502, 165, 916): ['???', self._read_fake],
  
              (9701, 97, 692): ['PJOINT2', self._read_fake],
              (13401, 134, 611): ['PBEAM3', self._read_pbeam3],
              (8901, 89, 905): ['PSOLCZ', self._read_fake],
              (9801, 98, 698): ['DESC', self._read_desc],
              #(9701, 97, 692): ['???', self._read_fake],
              #(9701, 97, 692): ['???', self._read_fake],
              #(9701, 97, 692): ['???', self._read_fake],
  
          }
  
      def _add_op2_property(self, prop):
          """helper method for op2"""
          op2 = self.op2
          #if prop.pid > 100000000:
              #raise RuntimeError('bad parsing; pid > 100000000...%s' % str(prop))
          #print(str(prop)[:-1])
          ntables = op2.table_names.count(b'EPT') + op2.table_names.count(b'EPTS')
          pid = prop.pid
          allow_overwrites = (
              ntables > 1 and
              pid in op2.properties and
              op2.properties[pid].type == prop.type)
          op2._add_methods._add_property_object(prop, allow_overwrites=allow_overwrites)
  
      def _add_op2_property_mass(self, prop):
          """helper method for op2"""
          op2 = self.op2
          #if prop.pid > 100000000:
              #raise RuntimeError('bad parsing; pid > 100000000...%s' % str(prop))
          #print(str(prop)[:-1])
          ntables = op2.table_names.count(b'EPT') + op2.table_names.count(b'EPTS')
          pid = prop.pid
          allow_overwrites = (
              ntables > 1 and
              pid in op2.properties_mass and
              op2.properties_mass[pid].type == prop.type)
          op2._add_methods._add_property_mass_object(prop, allow_overwrites=allow_overwrites)
  
      def _add_pconv(self, prop: PCONV) -> None:
          if prop.pconid > 100000000:
              raise RuntimeError('bad parsing pconid > 100000000...%s' % str(prop))
          self.op2._add_methods._add_convection_property_object(prop)
  
  # HGSUPPR
  
      def _read_desc(self, data: bytes, n: int) -> int:
          """
          RECORD – DESC(9801,98,698)
  
          Word Name Type Description
          1 DID        I Description identification number
          2 NWORDS     I Number of words for the description string
          3 DESC   CHAR4 Description
          Words 3 repeats NWORDS times
  
          data = (1, 14, 'FACE CONTACT(1)                                         ')
          """
          op2 = self.op2
          assert self.size == 4, 'DESC size={self.size} is not supported'
          #op2.show_data(data[n:], types='ifs')
          struct_2i = Struct(op2._endian + b'2i')
          while n < len(data):
  
              datai = data[n:n+8]
              desc_id, nwords = struct_2i.unpack(datai)
              #print(desc_id, nwords)
              ndatai = 8 + nwords * 4
              word_bytes = data[n+8:n+ndatai]
              word = word_bytes.decode('ascii').rstrip()
              assert len(word_bytes) == nwords * 4
              #print('word_bytes =', word_bytes)
              op2.log.warning(f'geom skipping DESC={desc_id}: {word!r}')
              n += ndatai
          assert n == len(data), n
          return n
  
      def _read_nsml(self, data: bytes, n: int) -> int:
          """
          NX 2019.2
          RECORD – NSML(3501, 35, 994)
  
          Defines a set of lumped nonstructural mass by ID.
          Word Name Type Description
          1 SID         I Set identification number
          2 PROP(2) CHAR4 Set of properties or elements
          4 ID          I Property of element identification number
          5 VALUE      RS Lumped nonstructural mass value
          Words 4 and 5 repeat until -1 occurs
  
            ints    = (3, ELEMENT, 0,   200, 0.7, -1, 4, PSHELL, 0, 6401, 4.2, -1)
            floats  = (3, ELEMENT, 0.0, 200, 0.7, -1, 4, PSHELL, 0.0, 6401, 4.2, -1)
  
          """
          op2 = self.op2
          n0 = n
          #op2.show_data(data[n:])
          ints = np.frombuffer(data[n:], op2.idtype8).copy()
          floats = np.frombuffer(data[n:], op2.fdtype8).copy()
          istart, iend = get_minus1_start_end(ints)
  
          ncards = 0
          size = self.size
          for (i0, i1) in zip(istart, iend):
              #data = (4, ELEMENT, 2.1, 1, 3301, -1, -2)
              assert ints[i1] == -1, ints[i1]
              sid = ints[i0]
              prop_bytes = data[n0+(i0+1)*size:n0+(i0+3)*size]
              #print(sid, prop_bytes)
              ids = ints[i0+4:i1:2].tolist()
              values = floats[i0+5:i1:2].tolist()
              #print(ids, values)
              assert len(ids) == len(values)
              nsm_type = prop_bytes.decode('latin1').rstrip()
              nsml = op2.add_nsml(sid, nsm_type, ids, values)
              #print(nsml)
              str(nsml)
              n += (i1 - i0 + 1) * size
              ncards += 1
          op2.card_count['NSML'] = ncards
          return n
  
      def _read_nsmadd(self, data: bytes, n: int) -> int:
          """
          NX 2019.2
          (3401, 34, 993)
  
          RECORD – NSMADD(3401,34,993)
          Combines the nonstructural mass inputs.
  
          Word Name Type Description
          1 SID I Set identification number
          2 ID  I Set of properties or elements
          Word 2 repeats until End of Record
  
          (1, 2, 3, 4, -1)
          """
          op2 = self.op2
          ints = np.frombuffer(data[n:], op2.idtype8).copy()
          istart, iend = get_minus1_start_end(ints)
  
          ncards = 0
          istart = [0] + list(iend + 1)
          size = self.size
          for (i0, i1) in zip(istart, iend):
              assert ints[i1] == -1, ints[i1]
              sid, *nsms = ints[i0:i1]
              nsmadd = op2.add_nsmadd(sid, nsms)
              #print(nsmadd)
              str(nsmadd)
              n += (i1 - i0 + 1) * size
              ncards += 1
          op2.card_count['NSMADD'] = ncards
          return n
  
      def _read_nsml1_nx(self, data: bytes, n: int) -> int:
          """
          NSML1(3701, 37, 995)
          Alternate form of NSML entry. Defines lumped nonstructural mass entries by VALUE, ID list.
  
          Word Name Type Description
          1 SID      I Set identification number
          2 PROP CHAR4 Set of properties
          3 TYPE CHAR4 Set of elements
          4 VALUE   RS Lumped nonstructural mass value
          5 SPECOPT  I Specification option
          SPECOPT=1 By IDs
            6 ID I Property of element identification number
            Word 6 repeats until -1 occurs
          SPECOPT=2 All
            6 ALL(2) CHAR4 Keyword ALL
            Words 6 and 7 repeat until -1 occurs
          SPECOPT=3 Thru range
            6 ID1         I Starting identification number
            7 THRU(2) CHAR4 Keyword THRU
            9 ID2         I Ending identification number
            Words 6 through 9 repeat until -1 occurs
          SPECOPT=4 Thru range with by
            6 ID1         I Starting identification number
            7 THRU(2) CHAR4 Keyword THRU
            9 ID2         I Ending identification number
            10 BY(2)  CHAR4 Keyword BY
            12 N I Increment
            Words 6 through 12 repeat until -1 occurs
  
          data = (
              3701, 37, 995,
              1, ELEMENT, 466.2,
              3, 249311, THRU, 250189, -1,
              3, 250656, THRU, 251905, -1,
              3, 270705, THRU, 275998, -1,
              3, 332687, THRU, 334734, -1,
              -2,
  
              2, ELEMENT, 77.7,
              3, 225740, THRU 227065, -1,
              3, 227602, THRU, 228898, -1,
              3, 229435, THRU, 230743, -1,
              3, 231280, THRU, 233789, -1,
              3, 233922, THRU, 235132, -1,
              3, 235265, THRU, 236463, -1,
              3, 338071, THRU, 341134, -1, -2)
          """
          #ints    = (1, ELEMENT, 466.2,
          #           3, 249311, THRU, 250189, -1,
          #           3, 250656, THRU, 251905, -1,
          #           3, 270705, THRU, 275998, -1,
          #           3, 332687, THRU, 334734, -1,
          #           -2,
          #
          #           2, ELEMENT, 77.7,
          #           3, 225740, THRU 227065, -1,
          #           3, 227602, THRU, 228898, -1,
          #           3, 229435, THRU, 230743, -1,
          #           3, 231280, THRU, 233789, -1,
          #           3, 233922, THRU, 235132, -1,
          #           3, 235265, THRU, 236463, -1,
          #           3, 338071, THRU, 341134, -1, -2)
          op2 = self.op2
          n0 = n
          #op2.show_data(data[n:])
          ints = np.frombuffer(data[n:], op2.idtype8).copy()
          floats = np.frombuffer(data[n:], op2.fdtype8).copy()
          iminus2 = np.where(ints == -2)[0]
          istart = [0] + list(iminus2[:-1] + 1)
          iend = iminus2
          #print(istart, iend)
          assert len(data[n:]) > 12, data[n:]
          #op2.show_data(data[n:], types='ifs')
  
          ncards = 0
          istart = [0] + list(iend + 1)
          size = self.size
          for (i0, i1) in zip(istart, iend):
              #data = (4, ELEMENT, 2.1, 1, 3301, -1, -2)
              assert ints[i1] == -2, ints[i1]
              sid = ints[i0]
              nsm_type = data[n0+(i0+1)*size:n0+(i0+2)*size].decode('latin1').rstrip()
              value = float(floats[i0+3])
              #print(f'sid={sid} nsm_type={nsm_type} value={value}')
  
              iminus1 = i0 + np.where(ints[i0:i1] == -1)[0]
              #print('-1', iminus1)
              #print('-2', iminus2)
              istart2 = [i0 + 4] + list(iminus1[:-1] + 1)
              iend2 = iminus1
              #print(istart2, iend2)
  
              for istarti, iendi in zip(istart2, iend2):
                  #print(istarti, iendi)
                  spec_opt = ints[istarti] # 4
                  #print(f'ints[{istarti}] = spec_opt = {spec_opt}')
                  if spec_opt == 1:
                      # 6 ID I Property of element identification number
  
                      ivalues = list(range(istarti, iendi))
                      #print('ivalues =', ivalues)
                      pid_eids = ints[ivalues].tolist()
                      #print('pid_eids =', pid_eids)
                  elif spec_opt == 3:
                      # datai = (3, 249311, 'THRU    ', 250189)
                      #print(f'i0={i0}')
                      #datai = data[n0+(i0+6)*size:n0+i1*size]
                      #op2.show_data(datai)
                      ids = ints[istarti:iendi]
                      istart = ids[1]
                      iend = ids[-1]
                      pid_eids = list(range(istart, iend+1))
                  else:
                      raise NotImplementedError(spec_opt)
  
              if nsm_type == 'ELEM':
                  nsm_type = 'ELEMENT'
              #for pid_eid in pid_eids:
              #nsml = op2.add_nsml1(sid, nsm_type, pid_eids, [value])
              assert len(pid_eids) > 0, pid_eids
              nsml1 = op2.add_nsml1(sid, nsm_type, value, pid_eids)
              #print(nsml1)
              str(nsml1)
              n += (i1 - i0 + 1) * size
              ncards += 1
          op2.card_count['NSML'] = ncards
          return n
  
      def _read_nsml1_msc(self, data: bytes, n: int) -> int:
          r"""
          NSML1(3601, 36, 62)
  
          Word Name Type Description
          1 SID      I Set identification number
          2 PROP CHAR4 Set of property or elements
          3 VALUE   RS Lumped nonstructural mass value
          4 SPECOPT  I Specification option
          SPECOPT=1 By IDs
            5 IDs , =FLG1LIST in ixidlst.prm
            6 ID I Property or element ID
            Word 6 repeats until End of Record
          SPECOPT=2 means ALL, =FLG1ALL in ixidlst.prm
            5 ALL(2) CHAR4 Keyword ALL
            Words 5 through 6 repeat until End of Record
          SPECOPT=3 means THRU range, =FLG1THRU in ixidlst.prm
            5 ID1 I Starting ID
            6 THRU(2) CHAR4 Keyword THRU
            8 ID2 I Ending ID
            Words 5 through 8 repeat until End of Record
          SPECOPT=4 means THRU range with BY, =FLG1THBY in ixidlst.prm
            5 ID1 I Starting ID
            6 THRU(2) CHAR4 Keyword THRU
            8 ID2 I Ending ID
            9 BY(2) CHAR4 Keyword BY
            11 N I Increment
            Words 5 through 11 repeat until End of Record
          End SPECOPT
          Words 4 through max repeat until End of Record
  
          C:\MSC.Software\simcenter_nastran_2019.2\tpl_post2\elsum15.op2
  
          data = (4, ELEMENT, 2.1, 1, 3301, -1, -2)
  
          """
          op2 = self.op2
          op2.log.info(f'geom skipping NSML1 in {op2.table_name}; ndata={len(data)-12}')
          #op2.show_data(data[n:], types='ifs')
          #bbb
          return len(data)
  
      def _read_nsm1(self, data: bytes, n: int) -> int:
          """
          NSM1(3301, 33, 992)
  
          Defines the properties of a nonstructural mass.
          Word Name Type Description
          1 SID      I Set identification number
          2 PROP CHAR4 Set of properties
          3 TYPE CHAR4 Set of elements
          4 ORIGIN   I Entry origin
          5 VALUE   RS Nonstructural mass value
          6 SPECOPT  I Specification option
          SPECOPT=1 By IDs
            7 ID I
            Word 7 repeats until -1 occurs
          SPECOPT=2 All
            7 ALL(2) CHAR4
            Words 7 and 8 repeat until -1 occurs
          SPECOPT=3 Thru range
            7 ID          I
            8 THRU(2) CHAR4
            10 ID         I
            Words 7 through 10 repeat until -1 occurs
          SPECOPT=4 Thru range with by
            7 ID I
            8 THRU(2) CHAR4
            10 ID        I
            11 BY(2) CHAR4
            13 N         I
            Words 7 through 13 repeat until -1 occurs
  
          data = (3, PCOMP,   0, 0.37, 2, ALL, -1,
                  4, ELEMENT, 2, 2.1, 1, 3301, -1)
  
          """
          op2 = self.op2
          #op2.show_data(data[n:], types='ifs')
          n0 = n
          #op2.show_data(data[n:])
          ints = np.frombuffer(data[n:], op2.idtype8).copy()
          floats = np.frombuffer(data[n:], op2.fdtype8).copy()
          istart, iend = get_minus1_start_end(ints)
  
          ncards = 0
          size = self.size
          for (i0, i1) in zip(istart, iend):
              assert ints[i1] == -1, ints[i1]
              # 1 SID      I Set identification number
              sid = ints[i0]
  
              # 2 PROP CHAR4 Set of properties
              # 3 TYPE CHAR4 Set of elements
              # 4 ORIGIN   I Entry origin
              # 5 VALUE   RS Nonstructural mass value
              # 6 SPECOPT  I Specification option
              nsm_type = data[n0+(i0+1)*size:n0+(i0+3)*size].decode('latin1').rstrip()
              zero_two = ints[i0+3]
              value = float(floats[i0+4])
              spec_opt = ints[i0+5]
              assert zero_two in [0, 2], zero_two
              #nii = 6
              #print(ints[i0+nii:i1])
              #print(floats[i0+nii:i1])
              #print(sid, nsm_type, value, spec_opt)
  
              iminus1 = i0 + np.where(ints[i0:i1] == -1)[0]
              #print('-1', iminus1)
              #print('-2', iminus2)
              istart2 = [i0 + 5] + list(iminus1[:-1] + 1)
              iend2 = iminus1
              #print(istart2, iend2)
  
              if spec_opt == 1:
                  # 7 ID I
                  ids = ints[i0+6:i1]
              elif spec_opt == 2:
                  word = data[n0+(i0+6)*size:n0+i1*size]
                  ids = word
              elif spec_opt == 3:  # thru
                  # datai = (249311, 'THRU    ', 250189)
                  #datai = data[n0+(i0+6)*size:n0+i1*size]
                  ids = ints[i0+6:i1]
                  istart = ids[0]
                  iend = ids[-1]
                  ids = list(range(istart, iend+1))
              else:
                  raise NotImplementedError(spec_opt)
              #print(sid, nsm_type, zero_two, value, ids)
              #if nsm_type == 'ELEM':
                  #nsm_type = 'ELEMENT'
              #for pid_eid in pid_eids:
              #nsml = self.add_nsml1(sid, nsm_type, pid_eids, [value])
              nsm1 = op2.add_nsm1(sid, nsm_type, value, ids)
              #print(nsm1)
              str(nsm1)
              n += (i1 - i0 + 1) * size
              ncards += 1
          op2.card_count['NSM1'] = ncards
          return n
  
      def _read_nsm(self, data: bytes, n: int) -> int:
          """NSM"""
          op2 = self.op2
          n = op2.reader_geom2._read_dual_card(
              data, n,
              self._read_nsm_nx, self._read_nsm_msc,
              'NSM', op2._add_methods._add_nsm_object)
          return n
  
      def _read_nsm_2(self, data: bytes, n: int) -> int:
          """
          NX 2019.2
          NSM(3201, 32, 991)
  
          RECORD – NSM(3201,32,991)
          Defines the properties of a nonstructural mass.
  
          Word Name Type Description
          1 SID      I Set identification number
          2 PROP CHAR4 Set of properties
          3 TYPE CHAR4 Set of elements   <---- not right...it's an integer and not used...
          4 ID       I Property or element identification number
          5 VALUE   RS Nonstructural mass value
          Words 5 through 6 repeat until End of Record
  
          NSM,2,conrod,1007,0.3
  
          data    = (2, CONROD,  0, 1007, 0.3, -1,
                     2, ELEMENT, 0,  200, 0.20, -1,
                     3, PSHELL,  0, 3301, 0.20, -1,
                     3, ELEMENT, 2,  200, 1.0, -1,
                     4, PSHELL,  2, 6401, 4.2, -1)
          """
          op2 = self.op2
          n0 = n
          ints = np.frombuffer(data[n:], op2.idtype8).copy()
          floats = np.frombuffer(data[n:], op2.fdtype8).copy()
          istart, iend = get_minus1_start_end(ints)
  
          ncards = 0
          size = self.size
          for (i0, i1) in zip(istart, iend):
              #data = (4, ELEMENT, 2.1, 1, 3301, -1, -2)
              assert ints[i1] == -1, ints[i1]
              sid = ints[i0]
              prop_type = data[n0+(i0+1)*size:n0+(i0+3)*size]
              elem_type = data[n0+(i0+3)*size:n0+(i0+4)*size]
              nsm_type = prop_type.decode('latin1').rstrip()
              dunno_int = ints[i0+3]
              #print(ints[i0+4:i1])
              #print(floats[i0+4:i1])
              ids = ints[i0+4:i1:2].tolist()
              values = floats[i0+5:i1:2].tolist()
              assert len(ids) == len(values)
              assert dunno_int in [0, 2], (sid, prop_type, (ints[i0+3], floats[i0+4]), ids, values)
              #print(sid, prop_type, (ints[i0+3], floats[i0+4]), ids, values)
              nsm = op2.add_nsm(sid, nsm_type, ids, values)
              #print(nsm[0])
              str(nsm)
              n += (i1 - i0 + 1) * size
              ncards += 1
          op2.card_count['NSM'] = ncards
          return n
  
      def _read_nsm_msc(self, data: bytes, n: int) -> int:
          """
          NSM(3201,32,55) - the marker for Record 2
  
          MSC
          1 SID       I Set identification number
          2 PROP  CHAR4 Set of property or elements
          3 ID        I Property or element identification number
          4 VALUE    RS Nonstructural mass value
          ORIGIN=0 NSM Bulk Data entry
            5 ID I Property or element ID
            6 VALUE RS Nonstructural mass value
            Words 5 through 6 repeat until End of Record
          ORIGIN=2 NSML Bulk Data entry
            5 ID I Property or element ID
            6 VALUE RS Nonstructural mass value
            Words 5 through 6 repeat until End of Record
          Words 3 through 4 repeat until End of Record
          """
          op2 = self.op2
          properties = []
          struct1 = Struct(op2._endian + b'i 4s if')
          ndelta = 16
  
          i = 0
          ints = np.frombuffer(data[n:], op2.idtype).copy()
          floats = np.frombuffer(data[n:], op2.fdtype).copy()
  
          while n < len(data):
              edata = data[n:n+ndelta]
              out = struct1.unpack(edata)
              (sid, prop_set, pid, value) = out
              #            538976312
              assert pid < 100000000
              i += 4
              n += ndelta
  
              prop_set = prop_set.decode('utf8').rstrip(' ') # \x00
              values = [value]
              #print('ints[i:]=', ints[i:])
              while ints[i] != -1:
                  value2 = floats[i]
                  values.append(value2)
                  n += 4
                  i += 1
              op2.log.info("MSC: NSM-sid=%s prop_set=%s pid=%s values=%s" % (
                  sid, prop_set, pid, values))
              prop = NSM.add_op2_data([sid, prop_set, pid, value])
              #op2._add_methods._add_nsm_object(prop)
              properties.append(prop)
  
              # handle the trailing -1
              i += 1
              n += 4
          return n, properties
  
      def _read_nsm_nx(self, data: bytes, n: int) -> int:
          """
          NSM(3201,32,55) - the marker for Record 2
  
          1 SID         I Set identification number
          2 PROP(2) CHAR4 Set of properties or elements
          4 ORIGIN      I  Entry origin
          5 ID          I  Property or element identification number
          6 VALUE      RS Nonstructural mass value
          Words 5 through 6 repeat until End of Record
          """
          op2 = self.op2
          properties = []
  
          #NX: C:\Users\sdoyle\Dropbox\move_tpl\nsmlcr2s.op2
          struct1 = Struct(op2._endian + b'i 8s ii f')
          ndelta = 24
          #op2.show_data(data[12:], 'ifs')
  
          i = 0
          ints = np.frombuffer(data[n:], op2.idtype).copy()
          floats = np.frombuffer(data[n:], op2.fdtype).copy()
  
          unused_packs = break_by_minus1(ints)
          #for pack in packs:
              #print(pack)
  
          #ipack = 0
          while n < len(data):
              #print('ints[i:]=', ints[i:].tolist())
              #i1, i2 = packs[ipack]
              #print('idata=%s' % idata[i1:i2])
              #print('fdata=%s' % fdata[i1:i2])
              #print(idata[i1:i2])
              edata = data[n:n+ndelta]
              out = struct1.unpack(edata)
              (sid, prop_set, origin, pid, value) = out
              #            538976312
              assert pid < 100000000
              i += 6
              n += ndelta
  
              prop_set = prop_set.decode('utf8').rstrip(' ') # \x00
              pids = [pid]
              values = [value]
              #print('ints[i:]=', ints[i:].tolist())
              while ints[i] != -1:
                  pid = ints[i]
                  value2 = floats[i+1]
                  assert pid != -1
                  pids.append(pid)
                  values.append(value2)
                  n += 8
                  i += 2
  
              for pid, value in zip(pids, values):
                  if origin == 0:
                      #op2.log.info("NX: NSM-sid=%s prop_set=%s pid=%s values=%s" % (
                          #sid, prop_set, pid, values))
                      prop = NSM.add_op2_data([sid, prop_set, pid, value])
                  elif origin == 2:
                      #op2.log.info("NX: NSML-sid=%s prop_set=%s pid=%s values=%s" % (
                          #sid, prop_set, pid, values))
                      prop = NSML.add_op2_data([sid, prop_set, pid, value])
  
                  #print(prop.rstrip(), pid, value)
                  #op2._add_methods._add_nsm_object(prop)
                  properties.append(prop)
              #print('----')
  
              # handle the trailing -1
              i += 1
              n += 4
              #ipack += 1
          return n, properties
  
  # NSM1
  # NSML1
  # NSMADD
  # NSML
  # NSML1
  # PAABSF
  # PACABS
  # PACBAR
  
      def _read_pbar(self, data: bytes, n: int) -> int:
          """
          PBAR(52,20,181) - the marker for Record 11
          .. warning:: this makes a funny property...
  
          MSC 2016/NX10
  
          Word Name Type Description
          1  PID  I Property identification number
          2  MID  I Material identification number
          3  A   RS Area
          4  I1  RS Area moment of inertia in plane 1
          5  I2  RS Area moment of inertia in plane 2
          6  J   RS Torsional constant
          7  NSM RS Nonstructural mass per unit length
          8  FE  RS
          9  C1  RS Stress recovery location at point C in element y-axis
          10 C2  RS Stress recovery location at point C in element z-axis
          11 D1  RS Stress recovery location at point D in element y-axis
          12 D2  RS Stress recovery location at point D in element z-axis
          13 E1  RS Stress recovery location at point E in element y-axis
          14 E2  RS Stress recovery location at point E in element z-axis
          15 F1  RS Stress recovery location at point F in element y-axis
          16 F2  RS Stress recovery location at point F in element z-axis
          17 K1  RS Area factor for shear in plane 1
          18 K2  RS Area factor for shear in plane 2
          19 I12 RS Area product of inertia for plane 1 and 2
          """
          op2 = self.op2
          ntotal = 76 * self.factor  # 19*4
          struct1 = Struct(mapfmt(op2._endian + b'2i17f', self.size))
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              #(pid, mid, a, I1, I2, J, nsm, fe, c1, c2, d1, d2,
               #e1, e2, f1, f2, k1, k2, I12) = out
              prop = PBAR.add_op2_data(out)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PBAR'] = nentries
          return n
  
      def _read_pbarl(self, data: bytes, n: int) -> int:
          """
          PBARL(9102,91,52) - the marker for Record 12
          TODO: buggy
          It's possible to have a PBARL and a PBAR at the same time.
          NSM is at the end of the element.
          """
          op2 = self.op2
          valid_types = {
              'ROD': 1,
              'TUBE': 2,
              'TUBE2': 2,
              'I': 6,
              'CHAN': 4,
              'T': 4,
              'BOX': 4,
              'BAR': 2,
              'CROSS': 4,
              'H': 4,
              'T1': 4,
              'I1': 4,
              'CHAN1': 4,
              'Z': 4,
              'CHAN2': 4,
              "T2": 4,
              'BOX1': 6,
              'HEXA': 3,
              'HAT': 4,
              'HAT1': 5,
              'DBOX': 10,  # was 12
              #'MLO TUBE' : 2,
          }  # for GROUP="MSCBML0"
  
          size = self.size
          ntotal = 28 * self.factor  # 7*4 - ROD - shortest entry...could be buggy... # TODO fix this
          if size == 4:
              struct1 = Struct(op2._endian + b'2i 8s 8s f')
          else:
              struct1 = Struct(op2._endian + b'2q 16s 16s d')
  
          #nentries = (len(data) - n) // ntotal
          #print(self.show_ndata(80))
          ndata = len(data)
  
          while ndata - n > ntotal:
              edata = data[n:n+ntotal]
              n += ntotal
  
              out = struct1.unpack(edata)
              (pid, mid, group, beam_type, value) = out
              if pid > 100000000 or pid < 1:
                  op2.log.debug("  pid=%s mid=%s group=%r beam_type=%r value=%s" % (
                      pid, mid, group, beam_type, value))
                  raise RuntimeError('bad parsing...')
  
              beam_type = reshape_bytes_block_size(beam_type, size=size)
              group = reshape_bytes_block_size(group, size=size)
              data_in = [pid, mid, group, beam_type, value]
  
              expected_length = valid_types[beam_type]
              iformat = op2._endian + b'%if' % expected_length
  
              ndelta = expected_length * 4
              dims_nsm = list(unpack(iformat, data[n:n+ndelta]))
              data_in += dims_nsm
              #print("  pid=%s mid=%s group=%r beam_type=%r value=%s dims_nsm=%s" % (
                  #pid, mid, group, beam_type, value, dims_nsm))
  
              # TODO why do i need the +4???
              #      is that for the nsm?
              #min_len =  expected_length * 4 + 4
              #if len(data)
              #data = data[n + expected_length * 4 + 4:]
              n += ndelta
  
              #prin( "len(out) = ",len(out)))
              #print("PBARL = %s" % data_in)
              prop = PBARL.add_op2_data(data_in)  # last value is nsm
              pid = prop.pid
              if pid in op2.properties:
                  #op2.log.debug("removing:\n%s" % op2.properties[pid])
                  op2._type_to_id_map['PBAR'].remove(pid)
                  del op2.properties[pid]
              self._add_op2_property(prop)
              #op2.properties[pid] = prop
              #print(prop.get_stats())
              #print(op2.show_data(data[n-8:-100]))
  
              # the PBARL ends with a -1 flag
              #value, = unpack(op2._endian + b'i', data[n:n+4])
              n += 4 * self.factor
          if len(op2._type_to_id_map['PBAR']) == 0 and 'PBAR' in op2.card_count:
              del op2._type_to_id_map['PBAR']
              del op2.card_count['PBAR']
          op2.increase_card_count('PBARL')
          #assert len(data) == n
          if self.size == 8:
              n += 16
              #n += 8  # same for 32/64 bit - not 100% that it's always active
          return n
  
      def _read_pbcomp(self, data: bytes, n: int) -> int:
          """
          PBCOMP(5403, 55, 349)
  
                      pid      mid  A      I1      I2         I12 J           NSM
          PBCOMP         3       2 2.00E-4 6.67E-9 1.67E-9    0.0 4.58E-9     0.0 +
                   pid mid
          floats = (3, 2, 0.0002, 6.67e-09, 1.67e-09, 0.0, 4.58e-09, 0.0, 1.0, 1.0,
          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
          ints   = (3, 2, 0.0002, 6.67E-9, 1.67E-9, 0, 4.58E-9, 0, 1.0, 1.0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
  
          """
          op2 = self.op2
          struct1 = Struct(mapfmt(op2._endian + b'2i 12f i', self.size))
          struct2 = Struct(mapfmt(op2._endian + b'3f 2i', self.size))
          nproperties = 0
          ntotal1 = 60 * self.factor  # 4*15
          ntotal2 = 20 * self.factor
  
          ndata = len(data)
          #print(ntotal1, ntotal2)
          if self.factor == 2:
              op2.show_data(data[12*self.factor:], types='qd')
          #print(len(data[12*self.factor:]))
          while n < ndata:
              #op2.log.debug(f"n={n} ndata={ndata}")
              edata = data[n:n+ntotal1]
              #if len(edata) == ntotal1:
              data1 = struct1.unpack(edata)
              #else:
                  #op2.show_data(edata, types='qdi')
                  #n += ntotal2
                  #continue
              nsections = data1[-1]
              if op2.is_debug_file:
                  (pid, mid, a, i1, i2, i12, j, nsm, k1, k2,
                   m1, m2, n1, n2, unused_nsections) = data1
                  op2.log.info(f'PBCOMP pid={pid} mid={mid} nsections={nsections} '
                                f'k1={k1} k2={k2} m=({m1},{m2}) n=({n1},{n2})\n')
              #if pid > 0 and nsections == 0:
                  #print('n1')
                  #n += ntotal1
                  #continue
              #if pid == 0 and nsections == 0:
                  #print('n2')
                  #n += ntotal2
                  #continue
  
              data2 = []
              n += ntotal1
              if nsections in [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]:
                  # 16 Y   RS    Lumped area location along element's y-axis
                  # 17 Z   RS    Lumped area location along element's z-axis
                  # 18 C   RS    Fraction of the total area for the lumped area
                  # 19 MID I     Material identification number
                  # 20     UNDEF None
                  # Words 16 through 20 repeat NSECT times
                  for unused_i in range(nsections):
                      datai = data[n:n+ntotal2]
                      xi, yi, ci, mid, unused_null = struct2.unpack(datai)
                      data2.append((xi, yi, ci, mid))
                      n += ntotal2
              else:
                  op2.log.error(f'PBCOMP={data1[0]} has no sections; check your bdf')
                  return n
                  #raise NotImplementedError('PBCOMP nsections=%r' % nsections)
  
              if op2.is_debug_file:
                  op2.binary_debug.write('     PBCOMP: %s\n' % str([data1, data2]))
                  msg = (
                      '    i=%-2s so=%s xxb=%.1f a=%g i1=%g i2=%g i12=%g j=%g nsm=%g '
                      'c=[%s,%s] d=[%s,%s] e=[%s,%s] f=[%s,%s]' % (
                          nsections, None, -9999., a, i1, i2, i12, j, nsm,
                          None, None, None, None, None, None, None, None,)
                  )
                  op2.log.debug(msg)
              #op2.log.debug(data1)
              #op2.log.debug(data2)
  
              data_in = [data1, data2]
              prop = PBCOMP.add_op2_data(data_in)
              pid = data1[0]
              if pid in op2.properties:
                  op2._type_to_id_map['PBEAM'].remove(pid)
                  del op2.properties[pid]
  
              self._add_op2_property(prop)
              nproperties += 1
          #print(f"n={n} ndata={ndata}")
          assert nproperties > 0, 'PBCOMP nproperties=%s' % (nproperties)
          if len(op2._type_to_id_map['PBEAM']) == 0 and 'PBEAM' in op2.card_count:
              del op2._type_to_id_map['PBEAM']
              del op2.card_count['PBEAM']
          op2.card_count['PBCOMP'] = nproperties
          return n
  
      def _read_pbeam(self, data: bytes, n: int) -> int:
          """
          PBEAM(5402,54,262) - the marker for Record 14
          .. todo:: add object
          """
          op2 = self.op2
          cross_section_type_map = {
              0 : 'variable',
              1 : 'constant',
              2 : '???',
          }
  
          struct1 = Struct(mapfmt(op2._endian + b'4if', self.size))
          struct2 = Struct(mapfmt(op2._endian + b'16f', self.size))
          struct3 = Struct(mapfmt(op2._endian + b'16f', self.size))
          unused_ntotal = 768 # 4*(5+16*12)
          #nproperties = (len(data) - n) // ntotal
          #assert nproperties > 0, 'ndata-n=%s n=%s datai\n%s' % (len(data)-n, n, op2.show_data(data[n:100+n]))
          ndata = len(data)
          #op2.show_data(data[12:], 'if')
          #assert ndata % ntotal == 0, 'ndata-n=%s n=%s ndata%%ntotal=%s' % (len(data)-n, n, ndata % ntotal)
          nproperties = 0
  
          ntotal1 = 20 * self.factor
          ntotal2 = 64 * self.factor
          while n < ndata:
          #while 1: #for i in range(nproperties):
              edata = data[n:n+ntotal1]
              n += ntotal1
              data_in = list(struct1.unpack(edata))
              #if op2.is_debug_file:
                  #op2.log.info('PBEAM pid=%s mid=%s nsegments=%s ccf=%s x=%s\n' % tuple(data_in))
              (pid, unused_mid, unused_nsegments, ccf, unused_x) = data_in
              #op2.log.info('PBEAM pid=%s mid=%s nsegments=%s ccf=%s x=%s' % tuple(data_in))
  
              # Constant cross-section flag: 1=yes and 0=no
              # what is 2?
              if ccf not in [0, 1, 2]:
                  msg = ('  PBEAM pid=%s mid=%s nsegments=%s ccf=%s x=%s; '
                         'ccf must be in [0, 1, 2]\n' % tuple(data_in))
                  raise ValueError(msg)
  
              cross_section_type = cross_section_type_map[ccf]
              #print('cross_section_type = %s' % cross_section_type)
  
              is_pbcomp = False
              is_bad_so = False
  
              so = []
              xxb = []
              for i in range(11):
                  edata = data[n:n+ntotal2]
                  if len(edata) != ntotal2:
                      endpack = []
                      raise RuntimeError(f'PBEAM unexpected length i={i:d}...')
                  n += ntotal2
                  pack = struct2.unpack(edata)
                  (soi, xxbi, a, i1, i2, i12, j, nsm, c1, c2,
                   d1, d2, e1, e2, f1, f2) = pack
                  xxb.append(xxbi)
                  so.append(soi)
  
                  if soi == 0.0:
                      so_str = 'NO'
                  elif soi == 1.0:
                      so_str = 'YES'
                  else:
                      so_str = str(soi)
                      is_bad_so = True
                      #msg = 'PBEAM pid=%s i=%s x/xb=%s soi=%s; soi not in 0.0 or 1.0' % (
                          #pid, i, xxb, soi)
                      #raise NotImplementedError(msg)
  
                  #if xxb != 0.0:
                      #msg = 'PBEAM pid=%s i=%s x/xb=%s soi=%s; xxb not in 0.0 or 1.0' % (
                          #pid, i, xxb, soi)
                      #raise NotImplementedError(msg)
  
                  pack2 = (so_str, xxbi, a, i1, i2, i12, j, nsm, c1, c2,
                           d1, d2, e1, e2, f1, f2)
                  data_in.append(pack2)
                  if op2.is_debug_file:
                      op2.binary_debug.write(f'     {pack}\n')
                      msg = (
                          '    i=%-2s' % i + ' so=%s xxb=%.1f a=%g i1=%g i2=%g i12=%g j=%g nsm=%g '
                          'c=[%s,%s] d=[%s,%s] e=[%s,%s] f=[%s,%s]' % (tuple(pack2))
                      )
                      op2.binary_debug.write(msg)
                  #msg = (
                      #'    i=%-2s' % i + ' so=%s xxb=%.1f a=%g i1=%g i2=%g i12=%g j=%g nsm=%g '
                      #'c=[%s,%s] d=[%s,%s] e=[%s,%s] f=[%s,%s]' % (tuple(pack2))
                  #)
                  #print(msg)
  
              edata = data[n:n+ntotal2]
              if len(edata) != ntotal2:
                  endpack = []
                  raise RuntimeError('PBEAM unexpected length 2...')
              endpack = struct3.unpack(edata)
              n += ntotal2
  
              assert len(endpack) == 16, endpack
              #(k1, k2, s1, s2, nsia, nsib, cwa, cwb, # 8
               #m1a, m2a, m1b, m2b, n1a, n2a, n1b, n2b) = endpack # 8 -> 16
              if op2.is_debug_file:
                  op2.binary_debug.write('    k=[%s,%s] s=[%s,%s] nsi=[%s,%s] cw=[%s,%s] '
                                          'ma=[%s,%s] mb=[%s,%s] na=[%s,%s] nb=[%s,%s]' % (
                                              tuple(endpack)))
              data_in.append(endpack)
  
              if is_bad_so:
              #if soi < 0.:
                  xxb_str = ', '.join(['%g' % xxbi for xxbi in xxb])
                  so_str = ', '.join(['%g' % soi for soi in so])
                  msg = (f'PBEAM pid={pid} i={i} soi=[{so_str}]; '
                         'soi not 0.0 or 1.0; assuming PBCOMP & dropping')
                  op2.log.error(msg)
                  is_pbcomp = True
  
              if min(xxb) < 0.0 or max(xxb) > 1.0:
                  xxb_str = ', '.join(['%g' % xxbi for xxbi in xxb])
                  msg = (f'PBEAM pid={pid} i={i} x/xb=[{xxb_str}]; '
                         'x/xb must be between 0.0 and 1.0; assuming PBCOMP & dropping')
                  op2.log.error(msg)
                  is_pbcomp = True
  
              if is_pbcomp:
                  continue
              if pid in op2.properties:
                  if op2.properties[pid].type == 'PBCOMP':
                      continue
  
              prop = PBEAM.add_op2_data(data_in)
              nproperties += 1
              self._add_op2_property(prop)
          if nproperties:
              op2.card_count['PBEAM'] = nproperties
          return n
  
      def _read_pbeaml(self, data: bytes, n: int) -> int:
          """
          PBEAML(9202,92,53)
  
          Word Name Type Description
          1 PID        I   Property identification number
          2 MID        I   Material identification number
          3 GROUP(2) CHAR4 Cross-section group name
          5 TYPE(2)  CHAR4 Cross section type
          7 VALUE      RS  Cross section values for XXB, SO, NSM, and dimensions
          Word 7 repeats until (-1) occurs
          """
          op2 = self.op2
          #strs = numpy.core.defchararray.reshapesplit(data, sep=",")
          #ints = np.frombuffer(data[n:], self._uendian + 'i').copy()
          #floats = np.frombuffer(data[n:], self._uendian + 'f').copy()
          ints = np.frombuffer(data[n:], op2.idtype8).copy()
          floats = np.frombuffer(data[n:], op2.fdtype8).copy()
          istart, iend = get_minus1_start_end(ints)
  
          size = self.size
          nproperties = len(istart)
          if size == 4:
              struct1 = Struct(op2._endian + b'2i 8s 8s')
          else:
              struct1 = Struct(op2._endian + b'2q 16s 16s')
  
          for unused_i, (istarti, iendi) in enumerate(zip(istart, iend)):
              idata = data[n+istarti*size : n+(istarti+6)*size]
              pid, mid, group, beam_type = struct1.unpack(idata)
              group = group.decode('latin1').strip()
              beam_type = beam_type.decode('latin1').strip()
              fvalues = floats[istarti+6: iendi]
              if op2.is_debug_file:
                  op2.binary_debug.write('     %s\n' % str(fvalues))
                  op2.log.debug(f'pid={pid:d} mid={mid:d} group={group} beam_type={beam_type}')
                  op2.log.debug(fvalues)
              #op2.log.debug(f'pid={pid:d} mid={mid:d} group={group} beam_type={beam_type}')
              data_in = [pid, mid, group, beam_type, fvalues]
              prop = PBEAML.add_op2_data(data_in)
              if pid in op2.properties:
                  # this is a fake PSHELL
                  propi = op2.properties[pid]
                  assert propi.type in ['PBEAM'], propi.get_stats()
                  nproperties -= 1
                  continue
              self._add_op2_property(prop)
          if nproperties:
              op2.card_count['PBEAML'] = nproperties
          return len(data)
  
      def _read_pbend(self, data: bytes, n: int) -> int:
          """PBEND"""
          op2 = self.op2
          n = op2.reader_geom2._read_dual_card(
              data, n,
              self._read_pbend_nx, self._read_pbend_msc,
              'PBEND', op2._add_methods._add_property_object)
          return n
  
      def _read_pbend_msc(self, data: bytes, n: int) -> int:
          """
          PBEND
  
          1 PID     I  Property identification number
          2 MID     I  Material identification number
          3 A       RS Area
          4 I1      RS Area moment of inertia in plane 1
          5 I2      RS Area moment of inertia in plane 2
          6 J       RS Torsional constant
          7 FSI     I  flexibility and stress intensification factors
          8 RM      RS Mean cross-sectional radius of the curved pipe
          9 T       RS Wall thickness of the curved pipe
          10 P      RS Internal pressure
          11 RB     RS Bend radius of the line of centroids
          12 THETAB RS Arc angle of element
          13 C1     RS Stress recovery location at point C in element y-axis
          14 C2     RS Stress recovery location at point C in element z-axis
          15 D1     RS Stress recovery location at point D in element y-axis
          16 D2     RS Stress recovery location at point D in element z-axis
          17 E1     RS Stress recovery location at point E in element y-axis
          18 E2     RS Stress recovery location at point E in element z-axis
          19 F1     RS Stress recovery location at point F in element y-axis
          20 F2     RS Stress recovery location at point F in element z-axis
          21 K1     RS Area factor for shear in plane 1
          22 K2     RS Area factor for shear in plane 2
          23 NSM    RS Nonstructural mass per unit length
          24 RC     RS Radial offset of the geometric centroid
          25 ZC     RS Offset of the geometric centroid
          26 DELTAN  I Radial offset of the neutral axis from the geometric
                       centroid
          """
          op2 = self.op2
          ntotal = 104  # 26*4
          struct1 = Struct(op2._endian + b'2i 4f i 18f f')  # delta_n is a float, not an integer
          nproperties = (len(data) - n) // ntotal
          assert (len(data) - n) % ntotal == 0
          assert nproperties > 0, 'table=%r len=%s' % (op2.table_name, len(data) - n)
          properties = []
          for unused_i in range(nproperties):
              edata = data[n:n+104]
              out = struct1.unpack(edata)
              (pid, mid, area, i1, i2, j, fsi, rm, t, p, rb, theta_b,
               c1, c2, d1, d2, e1, e2, f1, f2, k1, k2, nsm, rc, zc,
               delta_n) = out
              beam_type = fsi
  
              if (area, rm, t, p) == (0., 0., 0., 0.):
                  area = None
                  rm = None
                  t = None
                  p = None
                  delta_n = None
                  beam_type = 2
              if delta_n == 0:
                  #: Radial offset of the neutral axis from the geometric
                  #: centroid, positive is toward the center of curvature
                  delta_n = None
              pbend = PBEND(pid, mid, beam_type, area, i1, i2, j,
                            c1, c2, d1, d2, e1, e2, f1, f2, k1, k2,
                            nsm, rc, zc, delta_n, fsi, rm, t, p, rb, theta_b)
              #print(pbend)
              pbend.validate()
  
              properties.append(pbend)
              n += ntotal
          return n, properties
  
      def _read_pbend_nx(self, data: bytes, n: int) -> int:
          """
          PBEND
  
          1 PID     I  Property identification number
          2 MID     I  Material identification number
          3 A       RS Area
          4 I1      RS Area moment of inertia in plane 1
          5 I2      RS Area moment of inertia in plane 2
          6 J       RS Torsional constant
          7 FSI     I  Flexibility and stress intensification factors
          8 RM      RS Mean cross-sectional radius of the curved pipe
          9 T       RS Wall thickness of the curved pipe
          10 P      RS Internal pressure
          11 RB     RS Bend radius of the line of centroids
          12 THETAB RS Arc angle of element
          13 C1     RS Stress recovery location at point C in element y-axis
          14 C2     RS Stress recovery location at point C in element z-axis
          15 D1     RS Stress recovery location at point D in element y-axis
          16 D2     RS Stress recovery location at point D in element z-axis
          17 E1     RS Stress recovery location at point E in element y-axis
          18 E2     RS Stress recovery location at point E in element z-axis
          19 F1     RS Stress recovery location at point F in element y-axis
          20 F2     RS Stress recovery location at point F in element z-axis
          21 K1     RS Area factor for shear in plane 1
          22 K2     RS Area factor for shear in plane 2
          23 NSM    RS Nonstructural mass per unit length
          24 RC     RS Radial offset of the geometric centroid
          25 ZC     RS Offset of the geometric centroid
          26 DELTAN RS Radial offset of the neutral axis from the geometric
                       centroid
          27 SACL   RS Miter spacing at center line.
          28 ALPHA  RS One-half angle between the adjacent miter axis
                       (Degrees).
          29 FLANGE I  For FSI=5, defines the number of flanges attached.
          30 KX     RS For FSI=6, the user defined flexibility factor for the
                    torsional moment.
          31 KY     RS For FSI=6, the user defined flexibility factor for the
                    out-of-plane bending moment.
          32 KZ     RS For FSI=6, the user defined flexbility factor for the
                    in-plane bending moment.
          33 Not used
          """
          op2 = self.op2
          #op2.log.info('geom skipping PBEND in EPT')
          #return len(data)
          ntotal = 132  # 33*4
          struct1 = Struct(op2._endian + b'2i 4f i 21f i 4f')
          nproperties = (len(data) - n) // ntotal
          assert (len(data) - n) % ntotal == 0
          assert nproperties > 0, 'table=%r len=%s' % (op2.table_name, len(data) - n)
          properties = []
          for unused_i in range(nproperties):
              edata = data[n:n+132]
              out = struct1.unpack(edata)
              (pid, mid, area, i1, i2, j, fsi, rm, t, p, rb, theta_b,
               c1, c2, d1, d2, e1, e2, f1, f2, k1, k2, nsm, rc, zc,
               delta_n, unused_sacl, unused_alpha, unused_flange,
               unused_kx, unused_ky, unused_kz, unused_junk,) = out
              beam_type = fsi
  
              pbend = PBEND(pid, mid, beam_type, area, i1, i2, j,
                            c1, c2, d1, d2, e1, e2, f1, f2, k1, k2,
                            nsm, rc, zc, delta_n, fsi, rm, t, p, rb, theta_b)
              pbend.validate()
              properties.append(pbend)
              n += ntotal
          return n, properties
  
  # PBMSECT
  # PBRSECT
  
      def _read_pbush(self, data: bytes, n: int) -> int:
          """
          The PBUSH card is different between MSC and NX Nastran.
  
          DMAP NX 11
          ----------
          NX has 23 fields in NX 11
          NX has 18 fields in the pre-2001 format
  
          DMAP MSC 2005
          -------------
          MSC has 23 fields in 2005
          MSC has 18 fields in the pre-2001 format
  
          DMAP MSC 2016
          -------------
          MSC has 24 fields in 2016.1
          MSC has 18 fields in the pre-2001 format
  
          DMAP MSC 2021
          -------------
          MSC has 27 fields in 2021
  
          """
          op2 = self.op2
          card_name = 'PBUSH'
          card_obj = PBUSH
          methods = {
              72 : self._read_pbush_nx_72,  # 72=4*18
              92 : self._read_pbush_msc_92, # 92=4*23
              96 : self._read_pbush_msc_96, # 96=4*24
              108 : self._read_pbush_msc_108, # 108=4*27
          }
          try:
              n = op2.reader_geom2._read_double_card(
                  card_name, card_obj, self._add_op2_property,
                  methods, data, n)
          except DoubleCardError:
              nx_method = partial(self._read_pbush_nx_72, card_obj)
              msc_method = partial(self._read_pbush_msc_92, card_obj)
              n = op2.reader_geom2._read_dual_card(
                  data, n,
                  nx_method, msc_method,
                  card_name, self._add_op2_property)
  
          # we're listing nx twice because NX/MSC used to be consistent
          # the new form for MSC is not supported
          #n = self._read_dual_card(data, n, self._read_pbush_nx, self._read_pbush_msc,
                                   #'PBUSH', self._add_op2_property)
          return n
  
      def _read_pbush_nx_72(self, card_obj: PBUSH, data: bytes, n: int) -> Tuple[int, List[PBUSH]]:
          """PBUSH(1402,14,37) - 18 fields"""
          op2 = self.op2
          ntotal = 72 * self.factor
          struct1 = Struct(mapfmt(op2._endian + b'i17f', self.size))
          ndata = len(data) - n
          nentries = ndata // ntotal
          assert nentries > 0, 'table={op2.table_name} len={ndata}'
          assert ndata % ntotal == 0, f'table={op2.table_name} leftover = {ndata} % {ntotal} = {ndata % ntotal}'
          props = []
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              (pid,
               k1, k2, k3, k4, k5, k6,
               b1, b2, b3, b4, b5, b6,
               g1, sa, st, ea, et) = out
              #op2.log.debug(out)
              assert pid > 0, pid
              g2 = g3 = g4 = g5 = g6 = g1
              data_in = (pid, k1, k2, k3, k4, k5, k6, b1, b2, b3, b4, b5, b6,
                         g1, g2, g3, g4, g5, g6, sa, st, ea, et)
              prop = PBUSH.add_op2_data(data_in)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pbush_msc_92(self, card_obj: PBUSH, data: bytes, n: int) -> Tuple[int, List[PBUSH]]:
          """PBUSH(1402,14,37) - 23 fields
  
          MSC 2005r2 to <MSC 2016
          """
          op2 = self.op2
          ntotal = 92 * self.factor # 23*4
          struct1 = Struct(mapfmt(op2._endian + b'i22f', self.size))
  
          ndata = len(data) - n
          nentries = ndata // ntotal
          assert nentries > 0, 'table={op2.table_name} len={ndata}'
          assert ndata % ntotal == 0, f'table={op2.table_name} leftover = {ndata} % {ntotal} = {ndata % ntotal}'
  
          props = []
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              #(pid, k1, k2, k3, k4, k5, k6, b1, b2, b3, b4, b5, b6,
               #g1, g2, g3, g4, g5, g6, sa, st, ea, et) = out
              pid = out[0]
              assert pid > 0, pid
              prop = PBUSH.add_op2_data(out)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pbush_msc_96(self, card_obj: PBUSH, data: bytes, n: int) -> Tuple[int, List[PBUSH]]:
          """PBUSH(1402,14,37) - 24 fields
  
          MSC 2016.1? to 2020
          """
          op2 = self.op2
          ntotal = 96 * self.factor # 24*4
          struct1 = Struct(mapfmt(op2._endian + b'i22f f', self.size))
  
          ndata = len(data) - n
          nentries = ndata // ntotal
          assert nentries > 0, 'table={op2.table_name} len={ndata}'
          assert ndata % ntotal == 0, f'table={op2.table_name} leftover = {ndata} % {ntotal} = {ndata % ntotal}'
  
          props = []
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              #(pid, k1, k2, k3, k4, k5, k6, b1, b2, b3, b4, b5, b6,
               #g1, g2, g3, g4, g5, g6, sa, st, ea, et, mass) = out
              pid = out[0]
              assert pid > 0, pid
              prop = PBUSH.add_op2_data(out)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pbush_msc_108(self, card_obj: PBUSH, data: bytes, n: int) -> Tuple[int, List[PBUSH]]:
          """
          PBUSH(1402,14,37) - 27 fields
          MSC 2021 to current
  
          ints    = (1402, 14, 37, 2, 100000.0, 200000.0, 300000.0, 0.15, 0.25, 0.35, 1000.0, 2000.0, 3000.0, 0.0015, 0.0025, 0.0035, 0,
                     -1577048263, -1577048263, -1577048263, -1577048263, -1577048263, 1065353216, 1065353216, 1065353216, 1065353216, 0, 0, 0, 0)
          floats  = (1402, 14, 37,
                     2, 100000.0, 200000.0, 300000.0, 0.15, 0.25, 0.35, 1000.0, 2000.0, 3000.0, 0.0015, 0.0025, 0.0035, 0.0,
                     -1.7367999061094683e-18, -1.7367999061094683e-18, -1.7367999061094683e-18, -1.7367999061094683e-18, -1.7367999061094683e-18, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0)
          """
          op2 = self.op2
          ntotal = 108 * self.factor # 27*4
          struct1 = Struct(mapfmt(op2._endian + b'i22f 4f', self.size))
          #op2.show_data(data, types='ifs')
  
          ndata = len(data) - n
          nentries = ndata // ntotal
          assert nentries > 0, 'table={op2.table_name} len={ndata}'
          assert ndata % ntotal == 0, f'table={op2.table_name} leftover = {ndata} % {ntotal} = {ndata % ntotal}'
  
          props = []
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              #(pid, k1, k2, k3, k4, k5, k6, b1, b2, b3, b4, b5, b6,
               #g1, g2, g3, g4, g5, g6, sa, st, ea, et) = out
              pid = out[0]
              assert pid > 0, pid
              prop = PBUSH.add_op2_data(out)
              str(prop)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pbush1d(self, data: bytes, n: int) -> int:
          """
          Record 18 -- PBUSH1D(3101,31,219)
  
          1  PID    I  Property identification number
          2  K      RS Stiffness
          3  C      RS Viscous Damping
          4  M      RS Mass
          5  ALPHA  RS Temperature coefficient
          6  SA     RS Stress recovery coefficient
          7  EA/SE  RS Strain recovery coefficient
  
          8  TYPEA  I  Shock data type:0=Null, 1=Table, 2=Equation
          9  CVT    RS Coefficient of translation velocity tension
          10 CVC    RS Coefficient of translation velocity compression
          11 EXPVT  RS Exponent of velocity tension
          12 EXPVC  RS Exponent of velocity compression
          13 IDTSU  I  TABLEDi or DEQATN entry identification number for scale factor vs displacement
          14 IDTCU  I  DEQATN entry identification number for scale factor vs displacement
          15 IDTSUD I  DEQATN entry identification number for derivative tension
          16 IDCSUD I  DEQATN entry identification number for derivative compression
  
          17 TYPES  I  Spring data type: 0=Null, 1=Table, 2=Equation
          18 IDTS   I  TABLEDi or DEQATN entry identification number for tension compression
          19 IDCS   I  DEQATN entry identification number for compression
          20 IDTDU  I  DEQATN entry identification number for scale factor vs displacement
          21 IDCDU  I  DEQATN entry identification number for force vs displacement
  
          22 TYPED  I  Damper data type: 0=Null, 1=Table, 2=Equation
          23 IDTD   I  TABLEDi or DEQATN entry identification number for tension compression
          24 IDCD   I  DEQATN entry identification number for compression
          25 IDTDV  I  DEQATN entry identification number for scale factor versus velocity
          26 IDCDV  I  DEQATN entry identification number for force versus velocity
  
          27 TYPEG  I  General data type: 0=Null, 1=Table, 2=Equation
          28 IDTG   I  TABLEDi or DEQATN entry identification number for tension compression
          29 IDCG   I  DEQATN entry identification number for compression
          30 IDTDU  I  DEQATN entry identification number for scale factor versus displacement
          31 IDCDU  I  DEQATN entry identification number for force versus displacement
          32 IDTDV  I  DEQATN entry identification number for scale factor versus velocity
          33 IDCDV  I  DEQATN entry identification number for force vs velocity
  
          34 TYPEF  I  Fuse data type: 0=Null, 1=Table
          35 IDTF   I  TABLEDi entry identification number for tension
          36 IDCF   I  TABLEDi entry identification number for compression
  
          37 UT     RS Ultimate tension
          38 UC     RS Ultimate compression
          """
          op2 = self.op2
          type_map = {
              0 : None,  # NULL
              1 : 'TABLE',
              2 : 'EQUAT',
          }
          ntotal = 152 * self.factor  # 38*4
          struct1 = Struct(mapfmt(op2._endian + b'i 6f i 4f 24i 2f', self.size))
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              (pid, k, c, m, unused_alpha, sa, se,
               typea, cvt, cvc, expvt, expvc, idtsu, idtcu, idtsud, idcsud,
               types, idts, idcs, idtdus, idcdus,
               typed, idtd, idcd, idtdvd, idcdvd,
               typeg, idtg, idcg, idtdug, idcdug, idtdvg, idcdvg,
               typef, idtf, idcf,
               unused_ut, unused_uc) = out
              #  test_op2_other_05
              #pbush1d, 204, 1.e+5, 1000., , , , , , +pb1
              #+pb1, spring, table, 205, , , , , , +pb2
              #+pb2, damper, table, 206
              #pid=204 k=100000.0 c=1000.0 m=0.0 sa=nan se=nan
  
  
              msg = f'PBUSH1D pid={pid} k={k} c={c} m={m} sa={sa} se={se}'
              optional_vars = {}
              typea_str = type_map[typea]
              types_str = type_map[types]
              typed_str = type_map[typed]
              unused_typeg_str = type_map[typeg]
              unused_typef_str = type_map[typef]
  
              if min([typea, types, typed, typeg, typef]) < 0:
                  raise RuntimeError(f'typea={typea} types={types} typed={typed} typeg={typeg} typef={typef}')
              if typea in [1, 2]:  # SHOCKA?
                  #pbush1d, 204, 1.e+5, 1000., , , , , , +pb4
                  #+pb4, shocka, table, 1000., , 1., , 214, , +pb41
                  #+pb41, spring, table, 205
  
                  idts = idtsu # if typea_str == 'TABLE' else 0
                  idets = idtsu # if typea_str == 'EQUAT' else 0
                  optional_vars['SHOCKA'] = [typea_str, cvt, cvc, expvt, expvc,
                                             idts, idets, idtcu, idtsud, idcsud]
                  #(shock_type, shock_cvt, shock_cvc, shock_exp_vt, shock_exp_vc,
                   #shock_idts, shock_idets, shock_idecs, shock_idetsd, shock_idecsd
                  #)
                  #print('shock_idts, shock_idets', typea_str, idtsu, idtsu)
                  msg += (
                      f'  SHOCKA type={typea} cvt={cvt} cvc={cvc} expvt={expvt} expvc={expvc}\n'
                      f'    idtsu={idtsu} (idts={idts} idets={idets}) idtcu={idtcu} idtsud={idtsud} idcsud={idcsud}')
              if types in [1, 2]: # SPRING: Spring data type: 0=Null, 1=Table, 2=Equation
                  #(spring_type, spring_idt, spring_idc, spring_idtdu, spring_idcdu) = values
                  # SPRING, TYPE IDT IDC IDTDU IDCDU
                  optional_vars['SPRING'] = [types_str, idts, idcs, idtdus, idcdus]
                  msg += f'  SPRING type={types} idt={idts} idc={idcs} idtdu={idtdus} idcdu={idcdus}'
              if typed in [1, 2]: # Damper data type: 0=Null, 1=Table, 2=Equation
                  optional_vars['DAMPER'] = [typed_str, idtd, idcd, idtdvd, idcdvd]
                  msg += f'  DAMPER type={typed} idt={idtd} idc={idtd} idtdv={idtdvd} idcdv={idcdvd}'
              if typeg in [1, 2]: # general, GENER?: 0=Null, 1=Table 2=Equation
                  # C:\NASA\m4\formats\git\examples\move_tpl\ar29scbt.bdf
                  #pbush1d, 206, 1.e+3, 10., , , , , , +pb6
                  #+pb6, gener, equat, 315, , 3015, , 3016
                  msg += f'  GENER  type={typeg} idt={idtg} idc={idcg} idtdu={idtdug} idcdu={idcdug} idtdv={idtdvg} idcdv={idcdvg}'
                  optional_vars['GENER'] = [idtg, idcg, idtdug, idcdug, idtdvg, idcdvg]
              if typef in [1, 2]: # Fuse data type: 0=Null, 1=Table
                  raise NotImplementedError(f'typef={typef} idtf={idtf} idcf={idcf}')
  
              if op2.is_debug_file:
                  op2.binary_debug.write(msg)
  
              pbush1d = op2.add_pbush1d(pid, k=k, c=c, m=m, sa=sa, se=se,
                                        optional_vars=optional_vars,)
              str(pbush1d)
              n += ntotal
          op2.card_count['PBUSH1D'] = nentries
          return n
  
      #def _read_pbusht(self, data: bytes, n: int) -> int:
          #"""reads the PBUSHT(702, 7, 38)"""
          #n, props = self._read_pbusht_nx(data, n)
          #for prop in props:
              ##print(prop)
              #op2._add_pbusht_object(prop)
          #return n
  
      def _read_pbusht(self, data: bytes, n: int) -> int:
          """
          NX 12 / MSC 2005
          Word Name Type Description
          1 PID       I Property identification number
          2 TKID(6)   I TABLEDi entry identification numbers for stiffness
          8 TBID(6)   I TABLEDi entry identification numbers for viscous damping
          14 TGEID(6) I TABLEDi entry identification number for structural damping
          20 TKNID(6) I TABLEDi entry identification numbers for force versus deflection
  
          old style
          Word Name Type Description
          1 PID       I Property identification number
          2 TKID(6)   I TABLEDi entry identification numbers for stiffness
          8 TBID(6)   I TABLEDi entry identification numbers for viscous damping
          14 TGEID    I TABLEDi entry identification number for structural damping
          15 TKNID(6) I TABLEDi entry IDs for force versus deflection
          """
          op2 = self.op2
          card_name = 'PBUSHT'
          card_obj = PBUSHT
          methods = {
              80 : self._read_pbusht_80,
              100 : self._read_pbusht_100,
              136 : self._read_pbusht_136,
          }
          try:
              n = op2.reader_geom2._read_double_card(
                  card_name, card_obj, op2._add_methods._add_pbusht_object,
                  methods, data, n)
          except DoubleCardError:
              raise
              op2.log.warning(f'try-except {card_name}')
              #n = self._read_split_card(data, n,
                                        #self._read_cquad8_current, self._read_cquad8_v2001,
                                        #card_name, self.add_op2_element)
          #nelements = op2.card_count['CQUAD8']
          #op2.log.debug(f'nCQUAD8 = {nelements}')
  
          #n = self._read_dual_card(data, n, self._read_ctriax_8, self._read_ctriax_9,
                                   #'CTRIAX', self.add_op2_element)
          return n
  
      def _read_pbusht_nx_old(self, data: bytes, n: int) -> int:
          op2 = self.op2
          #op2.show_data(data[12:])
          ndata = (len(data) - n) // self.factor
  
          if ndata % 100 == 0 and ndata % 80 == 0:
              op2.log.warning(f"skipping PBUSHT in EPT because nfields={ndata//4}, which is "
                               'nproperties*25 or nproperties*20')
              return len(data), []
          if ndata % 100 == 0:
              n, props = self._read_pbusht_100(data, n)
          elif ndata % 80 == 0:
              n, props = self._read_pbusht_80(data, n)
          else:
              # C:\MSC.Software\msc_nastran_runs\mbsh14.op2
              # ints = (1,
              #         51, 51, 0, 0, 0, 0,
              #         61, 61, 0, 0, 0, 0,
              #         0,  0,  0, 0, 0, 0,
              #         0, '', '', 0, 0, '', '', 0, 0, 925353388, 0, 0, 0, 0, 0,
              #         7,
              #         51, 51, 0, 0, 0, 0,
              #         61, 61, 0, 0, 0, 0,
              #         0,  0,  0, 0, 0, 0,
              #         0, '', '', 0, 0, '', '', 0, 0, 925353388, 0, 0, 0, 0, 0)
              # strings = (b"1 51 51 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x00\x00\x00=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00        \x00\x00\x00\x00\x00\x00\x00\x00        \x00\x00\x00\x00\x00\x00\x00\x00\xac\xc5'7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x003\x00\x00\x003\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\x00\x00\x00=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00        \x00\x00\x00\x00\x00\x00\x00\x00        \x00\x00\x00\x00\x00\x00\x00\x00\xac\xc5'7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",)
              # ints    = (1, 51, 51, 0,   0,   0,   0,   61, 61, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   '    ', '    ', 0,   0,   '    ', '    ', 0,   0,   1e-5, 0,   0,   0,   0  , 0,
              #
              # 7, 51, 51, 0,   0,   0,   0,   61, 61, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, '    ', '    ', 0, 0, '    ', '    ', 0, 0, 1e-5, 0, 0, 0, 0, 0)
              #op2.show_data(data[n:], types='is')
              raise NotImplementedError('You have blank lines in your PBUSHT')
          return n, props
  
      def _read_pbusht_80(self, card_obj, data: bytes, n: int) -> int:
          """
          Word Name Type Description
          1 PID     I Property identification number
          2 TKID(6) I TABLEDi entry identification numbers for stiffness
          8 TBID(6) I TABLEDi entry identification numbers for viscous damping
          14 TGEID  I TABLEDi entry identification number for structural damping
          15 TKNID(6) I TABLEDi entry identification numbers for force versus deflection
          16,17,18,19,20
          ???
          """
          op2 = self.op2
          ntotal = 80 * self.factor
          struct1 = Struct(op2._endian + b'20i')
          nentries = (len(data) - n) // ntotal
          assert nentries > 0, 'table=%r len=%s' % (op2.table_name, len(data) - n)
  
          props = []
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              #(pid,
               #k1, k2, k3, k4, k5, k6,
               #b1, b2, b3, b4, b5, b6,
               #g1, sa, st, ea, et) = out
              (pid,
               k1, k2, k3, k4, k5, k6,
               b1, b2, b3, b4, b5, b6,
               g1,
               n1, n2, n3, n4, n5, n6) = out
              g2 = g3 = g4 = g5 = g6 = g1
              k_tables = [k1, k2, k3, k4, k5, k6]
              b_tables = [b1, b2, b3, b4, b5, b6]
              ge_tables = [g1, g2, g3, g4, g5, g6]
              kn_tables = [n1, n2, n3, n4, n5, n6]
              prop = PBUSHT(pid, k_tables, b_tables, ge_tables, kn_tables)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pbusht_100(self, card_obj, data: bytes, n: int) -> int:
          op2 = self.op2
          props = []
          ntotal = 100 * self.factor
          struct1 = Struct(mapfmt(op2._endian + b'25i', self.size))
          nentries = (len(data) - n) // ntotal
          assert nentries > 0, 'table=%r len=%s' % (op2.table_name, len(data) - n)
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              (pid,
               k1, k2, k3, k4, k5, k6,
               b1, b2, b3, b4, b5, b6,
               g1, g2, g3, g4, g5, g6,
               n1, n2, n3, n4, n5, n6) = out
              k_tables = [k1, k2, k3, k4, k5, k6]
              b_tables = [b1, b2, b3, b4, b5, b6]
              ge_tables = [g1, g2, g3, g4, g5, g6]
              kn_tables = [n1, n2, n3, n4, n5, n6]
              prop = PBUSHT(pid, k_tables, b_tables, ge_tables, kn_tables)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pbusht_136(self, card_obj, data: bytes, n: int) -> int:
          r"""not 100%
  
          1  PID           I Property identification number
          2  TKID(6)       I TABLEDi entry identification numbers for stiffness
          8  TBID(6)       I TABLEDi entry identification numbers for viscous damping
          14 TGEID(6)      I TABLEDi entry identification number for structural damping
          20 TKNID(6)      I TABLEDi entry IDs for force vs. deflection
          26 FDC(2)    CHAR4 Force deflection curve rule
          28 FUSE          I Failure level
          29 DIR           I Fuse direction
          30 OPTION(2) CHAR4 Failure mode
          32 LOWER        RS Lower failure bound
          33 UPPER        RS Upper failure bound
          34 FRATE        RS FACTOR of scales the stiffness
          35 LRGR          I Controls large rotation
          36 UNDEF(4)        none
  
          # C:\MSC.Software\msc_nastran_runs\mbsh14.op2
          PBUSHT	1	 K	51	51
                   B	61	61
          PBUSHT	7	 K	51	51
                   B	61	61
  
          538976288 = '    '
          ints    = (
              702, 7, 38,
              1, (51, 51, 0, 0, 0, 0), (61, 61, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0), 0, 538976288, 538976288, 0, 0, 538976288, 538976288, 0, 0, 925353388, 0, 0, 0, 0, 0,
              7, (51, 51, 0, 0, 0, 0), (61, 61, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0), 0, 538976288, 538976288, 0, 0, 538976288, 538976288, 0, 0, 925353388, 0, 0, 0, 0, 0)
          floats  = (
              702, 7, 38,
              1, 51, 51, 0.0, 0.0, 0.0, 0.0, 61, 61, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 538976288, 538976288, 0.0, 0.0, 538976288, 538976288, 0.0, 0.0, 1.e-7, 0.0, 0.0, 0.0, 0.0, 0.0,
              7, 51, 51, 0.0, 0.0, 0.0, 0.0, 61, 61, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 538976288, 538976288, 0.0, 0.0, 538976288, 538976288, 0.0, 0.0, 1.e-7, 0.0, 0.0, 0.0, 0.0, 0.0)
          """
          op2 = self.op2
          props = []
          ntotal = 136 * self.factor              #  k  b  g  n  fdc
          struct1 = Struct(mapfmt(op2._endian + b'i 6i 6i 6i 6i 4s  2i i 5i', self.size))
          nentries = (len(data) - n) // ntotal
          assert nentries > 0, 'table=%r len=%s' % (op2.table_name, len(data) - n)
          for unused_i in range(nentries):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              (pid,
               k1, k2, k3, k4, k5, k6,
               b1, b2, b3, b4, b5, b6,
               g1, g2, g3, g4, g5, g6,
               n1, n2, n3, n4, n5, n6,
               word1, a, word2, c, *other) = out
  
  
              k_tables = [ki if ki != 538976288 else 0
                          for ki in [k1, k2, k3, k4, k5, k6]]
  
              b_tables = [bi if bi != 538976288 else 0
                          for bi in [b1, b2, b3, b4, b5, b6]]
              ge_tables = [gei if gei != 538976288 else 0
                          for gei in [g1, g2, g3, g4, g5, g6]]
              kn_tables = [kni if kni != 538976288 else 0
                          for kni in [n1, n2, n3, n4, n5, n6]]
              op2.log.warning(
                  f'PBUSHT: pid={pid} '
                  f'k={k_tables} '
                  f'b={b_tables} '
                  f'ge={ge_tables} '
                  f'n={kn_tables} ' +
                  'words=' + str([word1, a, word2, c]) +
                  f' other={other}')
              assert sum(other) == 0, other
              prop = PBUSHT(pid, k_tables, b_tables, ge_tables, kn_tables)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pcomp(self, data: bytes, n: int) -> int:
          r"""PCOMP(2706,27,287) - the marker for Record 22
  
          standard:
            EPTS; 64-bit: C:\MSC.Software\simcenter_nastran_2019.2\tpl_post1\cqrdbxdra3lg.op2
  
          optistruct:
            ints    = (2706, 27, 287,
                       5,
                       3, -2.75, 0, 0, 1, 0,   0,
                       2,  0.25, 0, 2, # why is sout=2?
                       3,  5.0,  0, 3, # why is sout=3?
                       2,  0.25, 0, 2, # why is sout=2?
  
                       6, 5, -3.0, 0, 0, 1, 0, 0,
                       2, 0.25, 0, 2,
                       2, 0.25, 0, 2,
                       3, 5.0,  0, 3,
                       2, 0.25, 0, 2,
                       2, 0.25, 0, 2, 7, 7, -1068498944, 0, 0, 1, 0, 0, 2, 0.25, 0, 2, 2, 0.25, 0, 2, 2, 0.25, 0, 2, 3, 5.0, 0, 3, 2, 0.25, 0, 2, 2, 0.25, 0, 2, 2, 0.25, 0, 2)
            floats  = (2706, 27, 287,
                       5, 3, -2.75, 0.0, 0.0, 1, 0.0, 0.0, 2, 0.25, 0.0, 2, 3, 5.0, 0.0, 3, 2, 0.25, 0.0, 2, 6, 5, -3.0, 0.0, 0.0, 1, 0.0, 0.0, 2, 0.25, 0.0, 2, 2, 0.25, 0.0, 2, 3, 5.0, 0.0, 3, 2, 0.25, 0.0, 2, 2, 0.25, 0.0, 2, 9.80908925027372e-45, 9.80908925027372e-45, -3.25, 0.0, 0.0, 1, 0.0, 0.0, 2, 0.25, 0.0, 2, 2, 0.25, 0.0, 2, 2, 0.25, 0.0, 2, 3, 5.0, 0.0, 3, 2, 0.25, 0.0, 2, 2, 0.25, 0.0, 2, 2, 0.25, 0.0, 2)
          """
          op2 = self.op2
          if self.size == 4:
              n2, props = self._read_pcomp_32_bit(data, n)
              nproperties = len(props)
              for prop in props:
                  self._add_op2_property(prop)
              op2.card_count['PCOMP'] = nproperties
          else:
              n2 = op2._read_dual_card(data, n, self._read_pcomp_32_bit,
                                       self._read_pcomp_64_bit,
                                       'PCOMP', self._add_op2_property)
          return n2
  
      def _read_pcomp_64_bit(self, data: bytes, n: int) -> Tuple[int, List[PCOMP]]:
          """
          PCOMP(2706,27,287) - the marker for Record 22
  
          1  PID   I  Property identification number
          2  N(C)  I  Number of plies
          3  Z0    RS Distance from the reference plane to the bottom surface
          4  NSM   RS Nonstructural mass per unit area
          5  SB    RS Allowable shear stress of the bonding material
          6  FT    I  Failure theory
          7  TREF  RS Reference temperature
          8  GE    RS Damping coefficient
  
          9  MID   I  Material identification number
          10 T     RS Thicknesses of the ply
          11 THETA RS Orientation angle of the longitudinal direction of the ply
          12 SOUT  I Stress or strain output request of the ply
          Words 9 through 12 repeat N times
  
          TODO:
             64-bit bug: why is the number of plies 0???
  
            doubles (float64) = (
            1, 0.0, 1.7368e-18, 0.0, 1.0, 1.5e-323, 0.0, 0.0,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
            -1, -1, -1, -1,
            21, 0.0, 1.7368e-18, 0.0, 1.0, 1.5e-323, 0.0, 0.0,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
            -1, -1, -1, -1)
            long long (int64) = (
            1, 0,   1.7368e-18, 0,   1.0, 3, 0, 0, 1, 4592590756007337001, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
            -1, -1, -1, -1,
            21, 0, 4341475431749739292, 0, 4607182418800017408, 3, 0, 0,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
              1, 0.11, 0, 1,
            -1, -1, -1, -1)
  
            doubles (float64) = (5e-324, 0.0, -0.005, 0.0, 0.0, 0.0, 0.0, 0.0,
                                 4e-323, 0.005, 0.0, 5e-324,
                                 4e-323, 0.005, 0.0, 5e-324,
                                 nan, nan, nan, nan)
            long long (int64) = (1, 0, -4650957407178058629, 0, 0, 0, 0, 0,
                                    8, 4572414629676717179, 0, 1,
                                    8, 4572414629676717179, 0, 1,
                                 -1, -1, -1, -1)
  
          """
          op2 = self.op2
          op2.to_nx(' because PCOMP-64 was found')
          nproperties = 0
          s1 = Struct(mapfmt(op2._endian + b'2i3fi2f', self.size))
          ntotal1 = 32 * self.factor
          s2 = Struct(mapfmt(op2._endian + b'i2fi', self.size))
  
          four_minus1 = Struct(mapfmt(op2._endian + b'4i', self.size))
          ndata = len(data)
          ntotal2 = 16 * self.factor
          props = []
          while n < (ndata - ntotal1):
              out = s1.unpack(data[n:n+ntotal1])
              (pid, nlayers, z0, nsm, sb, ft, tref, ge) = out
              assert pid > 0
              if op2.binary_debug:
                  op2.binary_debug.write(f'PCOMP pid={pid} nlayers={nlayers} z0={z0} nsm={nsm} '
                                          f'sb={sb} ft={ft} Tref={tref} ge={ge}')
              assert isinstance(nlayers, int), out
              #print(f'PCOMP pid={pid} nlayers={nlayers} z0={z0} nsm={nsm} '
                    #f'sb={sb} ft={ft} Tref={tref} ge={ge}')
              n += ntotal1
  
              # None, 'SYM', 'MEM', 'BEND', 'SMEAR', 'SMCORE', 'NO'
              is_symmetrical = 'NO'
              #if nlayers < 0:
                  #is_symmetrical = 'SYM'
                  #nlayers = abs(nlayers)
  
              mids = []
              T = []
              thetas = []
              souts = []
              edata2 = data[n:n+ntotal2]
              idata = four_minus1.unpack(edata2)
              while idata != (-1, -1, -1, -1):
                  (mid, t, theta, sout) = s2.unpack(edata2)
                  mids.append(mid)
                  T.append(t)
                  thetas.append(theta)
                  souts.append(sout)
                  if op2.is_debug_file:
                      op2.binary_debug.write(f'      mid={mid} t={t} theta={theta} sout={sout}\n')
                  n += ntotal2
                  #print(f'      mid={mid} t={t} theta={theta} sout={sout}')
                  edata2 = data[n:n+ntotal2]
                  idata = four_minus1.unpack(edata2)
  
              if self.size == 4:
                  assert 0 < nlayers < 400, 'pid=%s nlayers=%s z0=%s nms=%s sb=%s ft=%s Tref=%s ge=%s' % (
                      pid, nlayers, z0, nsm, sb, ft, tref, ge)
              else:
                  assert nlayers == 0, nlayers
                  nlayers = len(mids)
  
              data_in = [
                  pid, z0, nsm, sb, ft, tref, ge,
                  is_symmetrical, mids, T, thetas, souts]
              prop = PCOMP.add_op2_data(data_in)
              nproperties += 1
              n += ntotal2
              props.append(prop)
          return n, props
  
      def _read_pcomp_32_bit(self, data: bytes, n: int) -> Tuple[int, List[PCOMP]]:  # pragma: no cover
          """PCOMP(2706,27,287) - the marker for Record 22"""
          op2 = self.op2
          nproperties = 0
          s1 = Struct(mapfmt(op2._endian + b'2i3fi2f', self.size))
          ntotal1 = 32 * self.factor
          s2 = Struct(mapfmt(op2._endian + b'i2fi', self.size))
  
          ndata = len(data)
          ntotal2 = 16 * self.factor
          props = []
          while n < (ndata - ntotal1):
              out = s1.unpack(data[n:n+ntotal1])
              (pid, nlayers, z0, nsm, sb, ft, tref, ge) = out
              assert pid > 0
  
              if op2.binary_debug:
                  op2.binary_debug.write(f'PCOMP pid={pid} nlayers={nlayers} z0={z0} nsm={nsm} '
                                          f'sb={sb} ft={ft} Tref={tref} ge={ge}')
              assert isinstance(nlayers, int), out
              #print(f'PCOMP pid={pid} nlayers={nlayers} z0={z0} nsm={nsm} '
                    #f'sb={sb} ft={ft} Tref={tref} ge={ge}')
              n += ntotal1
  
              mids = []
              T = []
              thetas = []
              souts = []
  
              # None, 'SYM', 'MEM', 'BEND', 'SMEAR', 'SMCORE', 'NO'
              is_symmetrical = 'NO'
              if nlayers < 0:
                  is_symmetrical = 'SYM'
                  nlayers = abs(nlayers)
              assert nlayers > 0, out
  
              assert 0 < nlayers < 400, 'pid=%s nlayers=%s z0=%s nsm=%s sb=%s ft=%s Tref=%s ge=%s' % (
                  pid, nlayers, z0, nsm, sb, ft, tref, ge)
  
              if op2.is_debug_file:
                  op2.binary_debug.write('    pid=%s nlayers=%s z0=%s nsm=%s sb=%s ft=%s Tref=%s ge=%s\n' % (
                      pid, nlayers, z0, nsm, sb, ft, tref, ge))
              #if op2._nastran_format == 'optistruct':
                  #print('    pid=%s nlayers=%s z0=%s nsm=%s sb=%s ft=%s Tref=%s ge=%s' % (
                      #pid, nlayers, z0, nsm, sb, ft, tref, ge))
              for unused_ilayer in range(nlayers):
                  (mid, t, theta, sout) = s2.unpack(data[n:n+ntotal2])
                  if op2._nastran_format == 'optistruct':
                      #print(f'      mid={mid} t={t} theta={theta} sout={sout}')
                      if sout in [2, 3]: # TODO: Why is this 2/3?
                          sout = 1 # YES
  
                  mids.append(mid)
                  assert mid > 0
  
                  T.append(t)
                  thetas.append(theta)
                  souts.append(sout)
                  if op2.is_debug_file:
                      op2.binary_debug.write(f'      mid={mid} t={t} theta={theta} sout={sout}\n')
                  n += ntotal2
  
              data_in = [
                  pid, z0, nsm, sb, ft, tref, ge,
                  is_symmetrical, mids, T, thetas, souts]
              prop = PCOMP.add_op2_data(data_in)
              #print(prop)
              props.append(prop)
              nproperties += 1
          return n, props
  
      def _read_pcompg(self, data: bytes, n: int) -> int:
          """
          PCOMP(2706,27,287)
  
          1 PID      I  Property identification number
          2 LAMOPT   I  Laminate option
          3 Z0       RS Distance from the reference plane to the bottom surface
          4 NSM      RS Nonstructural mass per unit area
          5 SB       RS Allowable shear stress of the bonding material
          6 FT       I  Failure theory
          7 TREF     RS Reference temperature
          8 GE       RS Damping coefficient
  
          9  GPLYIDi I  Global ply IDs.
          10 MID     I  Material identification number
          11 T       RS Thicknesses of the ply
          12 THETA   RS Orientation angle of the longitudinal direction of the ply
          13 SOUT    I  Stress or strain output request of the ply
          Words 9 through 13 repeat N times (until -1, -1, -1, -1, -1 as Nplies doesn't exist...)
  
          float = (15006, 150, 604,
                   5, 0.0, 1.7368e-18, 0.0, 0.0, 0.0, 20.0, 0.0,
                       5e-324, 5e-324, 2.0, 0.0, 0.0,
                       1e-323, 1e-323, 3.0, 0.0, 0.0,
                       1.5e-323, 1e-323, 3.0, 0.0, 0.0,
                       2e-323, 5e-324, 2.0, 0.0, 0.0,
                       nan, nan, nan, nan, nan)
          int   = (15006, 150, 604,
                   5, 0,   1.7368e-18, 0,   0,   0,   20.0, 0,
                       1, 1, 4611686018427387904, 0, 0,
                       2, 2, 4613937818241073152, 0, 0,
                       3, 2, 4613937818241073152, 0, 0,
                       4, 1, 4611686018427387904, 0, 0,
                       -1, -1, -1, -1, -1)
  
          """
          op2 = self.op2
          nproperties = 0
          s1 = Struct(mapfmt(op2._endian + b'2i 3f i 2f', self.size))
          s2 = Struct(mapfmt(op2._endian + b'2i 2f i', self.size))
          struct_i5 = Struct(mapfmt(op2._endian + b'5i', self.size))
  
          # lam - SYM, MEM, BEND, SMEAR, SMCORE, None
          lam_map = {
              0 : None,
              # MEM
              # BEND
              # SMEAR
              # SMCORE
          }
  
          # ft - HILL, HOFF, TSAI, STRN, None
          ft_map = {
              0 : None,
              # HILL
              # HOFF
              3 : 'TSAI',
              # STRN
          }
          # sout - YES, NO
          sout_map = {
              0 : 'NO',
              1 : 'YES',
          }
          ndata = len(data)
          #op2.show_data(data, types='qd')
          ntotal1 = 32 * self.factor
          ntotal2 = 20 * self.factor
          while n < (ndata - ntotal1):
              out = s1.unpack(data[n:n+ntotal1])
              (pid, lam_int, z0, nsm, sb, ft_int, tref, ge) = out
              if op2.binary_debug:
                  op2.binary_debug.write(f'PCOMPG pid={pid} lam_int={lam_int} z0={z0} nsm={nsm} '
                                          f'sb={sb} ft_int={ft_int} tref={tref} ge={ge}')
              #print(f'PCOMPG pid={pid} lam_int={lam_int} z0={z0} nsm={nsm} sb={sb} '
                    #f'ft_int={ft_int} tref={tref} ge={ge}')
              assert isinstance(lam_int, int), out
              assert pid > -1, out
              n += ntotal1
  
              mids = []
              thicknesses = []
              thetas = []
              souts = []
              global_ply_ids = []
  
              # None, 'SYM', 'MEM', 'BEND', 'SMEAR', 'SMCORE', 'NO'
              #is_symmetrical = 'NO'
              #if nlayers < 0:
                  #is_symmetrical = 'SYM'
                  #nlayers = abs(nlayers)
              #assert nlayers > 0, out
  
              #assert 0 < nlayers < 400, 'pid=%s nlayers=%s z0=%s nms=%s sb=%s ft=%s tref=%s ge=%s' % (
                  #pid, nlayers, z0, nsm, sb, ft, tref, ge)
  
              #if op2.is_debug_file:
                  #op2.binary_debug.write('    pid=%s nlayers=%s z0=%s nms=%s sb=%s ft=%s tref=%s ge=%s\n' % (
                      #pid, nlayers, z0, nsm, sb, ft, tref, ge))
              ilayer = 0
              while ilayer < 1000:
                  ints5 = struct_i5.unpack(data[n:n+ntotal2])
                  if ints5 == (-1, -1, -1, -1, -1):
                      if op2.is_debug_file:
                          op2.binary_debug.write('      global_ply=%-1 mid=%-1 t=%-1 theta=%-1 sout=-1\n')
                      break
                  (global_ply, mid, t, theta, sout_int) = s2.unpack(data[n:n+ntotal2])
                  #print('  ', (global_ply, mid, t, theta, sout_int))
                  try:
                      sout = sout_map[sout_int]
                  except KeyError:
                      op2.log.error('cant parse global_ply=%s sout=%s; assuming 0=NO' % (
                          global_ply, sout_int))
                      sout = 'NO'
  
                  global_ply_ids.append(global_ply)
                  mids.append(mid)
                  thicknesses.append(t)
                  thetas.append(theta)
                  souts.append(sout)
                  if op2.is_debug_file:
                      op2.binary_debug.write('      global_ply=%s mid=%s t=%s theta=%s sout_int=%s sout=%r\n' % (
                          global_ply, mid, t, theta, sout_int, sout))
                  n += ntotal2
                  ilayer += 1
              n += ntotal2
  
              try:
                  ft = ft_map[ft_int]
              except KeyError:
                  op2.log.error('pid=%s cant parse ft=%s; should be HILL, HOFF, TSAI, STRN'
                                 '...skipping' % (pid, ft_int))
                  continue
  
              try:
                  lam = lam_map[lam_int]
              except KeyError:
                  op2.log.error('pid=%s cant parse lam=%s; should be HILL, HOFF, TSAI, STRN'
                                 '...skipping' % (pid, lam_int))
                  continue
  
              # apparently Nastran makes duplicate property ids...
              if pid in op2.properties and op2.properties[pid].type == 'PCOMP':
                  del op2.properties[pid]
  
              op2.add_pcompg(pid, global_ply_ids, mids, thicknesses, thetas=thetas, souts=souts,
                             nsm=nsm, sb=sb, ft=ft, tref=tref, ge=ge, lam=lam, z0=z0, comment='')
              nproperties += 1
          op2.card_count['PCOMPG'] = nproperties
          return n
  
  # PCOMPA
  
      def _read_pconeax(self, data: bytes, n: int) -> int:
          """
          (152,19,147) - Record 24
          """
          self.op2.log.info('geom skipping PCONEAX in EPT')
          return len(data)
  
      def _read_pconv(self, data: bytes, n: int) -> int:
          """common method for reading PCONVs"""
          op2 = self.op2
          #n = self._read_dual_card(data, n, self._read_pconv_nx, self._read_pconv_msc,
                                   #'PCONV', self._add_pconv)
  
          card_name = 'PCONV'
          card_obj = PCONV
          methods = {
              16 : self._read_pconv_nx_16,  # 16=4*4
              56 : self._read_pconv_msc_56, # 56=4*14
          }
          try:
              n, elements = op2.reader_geom2._read_double_card_load(
                  card_name, card_obj,
                  methods, data, n)
          except DoubleCardError:
              nx_method = partial(self._read_pconv_nx_16, card_obj)
              msc_method = partial(self._read_pconv_msc_56, card_obj)
              n, elements = op2._read_dual_card_load(
                  data, n,
                  nx_method, msc_method,
                  card_name, self._add_op2_property)
  
          nelements = len(elements)
          for prop in elements:
              key = prop.pconid
              if key in op2.convection_properties:
                  prop_old = op2.convection_properties[key]
                  if prop != prop_old:
                      op2.log.warning(prop.raw_fields())
                      op2.log.warning(prop_old.raw_fields())
                      op2.log.warning(f'PCONV pconid={key}; old, new\n{prop_old}{prop}')
                      # this will fail due to a duplicate id
                      self._add_pconv(prop)
                  #else:
                      # already exists
              else:
                  self._add_pconv(prop)
          op2.card_count['PCONV'] = nelements
  
          return n
  
      def _read_pconv_nx_16(self, card_obj: PCONV, data: bytes, n: int) -> int:
          """
          (11001,110,411)- NX version
          """
          op2 = self.op2
          ntotal = 16  # 4*4
          struct_3if = Struct(op2._endian + b'3if')
          nentries = (len(data) - n) // ntotal
          assert (len(data) - n) % ntotal == 0
          props = []
          for unused_i in range(nentries):
              out = struct_3if.unpack(data[n:n+ntotal])
              (pconid, mid, form, expf) = out
              ftype = tid = chlen = gidin = ce = e1 = e2 = e3 = None
              data_in = (pconid, mid, form, expf, ftype, tid, chlen,
                         gidin, ce, e1, e2, e3)
  
              prop = PCONV.add_op2_data(data_in)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pconv_msc_56(self, card_obj: PCONV, data: bytes, n: int) -> int:
          """
          (11001,110,411)- MSC version - Record 25
          """
          op2 = self.op2
          ntotal = 56  # 14*4
          s = Struct(op2._endian + b'3if 4i fii 3f')
          nentries = (len(data) - n) // ntotal
          assert (len(data) - n) % ntotal == 0
          props = []
          for unused_i in range(nentries):
              out = s.unpack(data[n:n+ntotal])
              (pconid, mid, form, expf, ftype, tid, unused_undef1, unused_undef2, chlen,
               gidin, ce, e1, e2, e3) = out
              data_in = (pconid, mid, form, expf, ftype, tid, chlen,
                         gidin, ce, e1, e2, e3)
  
              prop = PCONV.add_op2_data(data_in)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pconvm(self, data: bytes, n: int) -> int:
          """Record 24 -- PCONVM(2902,29,420)
  
          1 PID    I Property identification number
          2 MID    I Material identification number
          3 FORM   I Type of formula used for free convection
          4 FLAG   I Flag for mass flow convection
          5 COEF  RS Constant coefficient used for forced convection
          6 EXPR  RS Reynolds number convection exponent
          7 EXPPI RS Prandtl number convection exponent into the working fluid
          8 EXPPO RS Prandtl number convection exponent out of the working fluid
          """
          op2 = self.op2
          ntotal = 32  # 8*4
          structi = Struct(op2._endian + b'4i 4f')
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              out = structi.unpack(data[n:n+ntotal])
              if out != (0, 0, 0, 0, 0., 0., 0., 0.):
                  (pconid, mid, form, flag, coeff, expr, expri, exppo) = out
                  #print(out)
                  prop = PCONVM(pconid, mid, coeff, form=form, flag=flag,
                                expr=expr, exppi=expri, exppo=exppo, comment='')
                  op2._add_methods._add_convection_property_object(prop)
              n += ntotal
          op2.card_count['PCONVM'] = nentries
          return n
  
      def _read_pdamp(self, data: bytes, n: int) -> int:
          """
          PDAMP(202,2,45) - the marker for Record ???
          """
          op2 = self.op2
          ntotal = 8 * self.factor # 2*4
          struct_if = Struct(mapfmt(op2._endian + b'if', self.size))
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              out = struct_if.unpack(data[n:n+ntotal])
              #(pid, b) = out
              prop = PDAMP.add_op2_data(out)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PDAMP'] = nentries
          return n
  
      def _read_pdampt(self, data: bytes, n: int) -> int:  # 26
          self.op2.log.info('geom skipping PDAMPT in EPT')
          return len(data)
  
      def _read_pdamp5(self, data: bytes, n: int) -> int:  # 26
          self.op2.log.info('geom skipping PDAMP5 in EPT')
          return len(data)
  
  # PDUM1
  # PDUM2
  # PDUM3
  # PDUM4
  # PDUM5
  # PDUM6
  # PDUM7
  # PDUM8
  # PDUM9
  
      def _read_pelas(self, data: bytes, n: int) -> int:
          """PELAS(302,3,46) - the marker for Record 39"""
          op2 = self.op2
          struct_i3f = Struct(mapfmt(op2._endian + b'i3f', self.size))
          ntotal = 16 * self.factor # 4*4
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct_i3f.unpack(edata)
              #(pid, k, ge, s) = out
              if op2.is_debug_file:
                  op2.binary_debug.write('  PELAS=%s\n' % str(out))
              prop = PELAS.add_op2_data(out)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PELAS'] = nproperties
          return n
  
      def _read_pfast_msc(self, data: bytes, n: int) -> int:
          r"""
          Word Name Type Description
          1 PID       I Property identification number
          2 MID       I Material property identification number
          3 D        RS Diameter of the fastener
          4 CONNBEH   I Connection behavior (0=FF/F, 1=FR, 10=RF/R, 11=RR)
          5 CONNTYPE  I Connection type (0=clamp, 1=hinge, 2=bolt)
          6 EXTCON    I External constraint flag (0=off, 1=on)
          7 CONDTYPE  I Condition type (0=rigid, 1=equivalent)
          8 WELDTYPE  I Weld type (0=spot weld, 1=but seam, 2=T-seam)
  
          9 MINLEN   RS Minimum length of spot weld
          10 MAXLEN  RS Maximum length of spot weld
          11 GMCHK    I Perform geometry check
          12 SPCGS    I SPC the master grid GS
          13 CMASS   RS Concentrated mass
          14 GE      RS Structureal Damping
  
          15 UNDEF(3) none Not used
          18 MCID    I Element stiffness coordinate system
          19 MFLAG   I Defined the coordinate system type
          20 KT(3)  RS Stiffness values in direction 1
          23 KR(3)  RS Rotation stiffness values in direction 1
  
          C:\MSC.Software\msc_nastran_runs\cfmass.op2
                     pid mid  D    con  con  ext  cond weld min max  chk  spc  cmass ge  und  und  und  mcid mfag kt1      kt2       kt3       kr1    kr2      kr3
          ints    = (99, 0,   0.1, 0,   0,   0,   0,   -1, 0.2, 5.0, 0,   0,   7.9, 0,   0,   0,   0,   -1, 0,   471200.0, 181200.0, 181200.0, 226.6, 45610.0, 45610.0)
          floats  = (99, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, -1, 0.2, 5.0, 0.0, 0.0, 7.9, 0.0, 0.0, 0.0, 0.0, -1, 0.0, 471200.0, 181200.0, 181200.0, 226.6, 45610.0, 45610.0)
          """
          op2 = self.op2
          #op2.show_data(data[n:], types='ifs')
          #ntotal = 92 * self.factor # 26*4
          #struct1 = Struct(op2._endian + b'ifii 3f')
  
          ntotal = 100 * self.factor # 25*4
          struct1 = Struct(op2._endian + b'2if 5i 2f2i2f 3i 2i 6f')
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PFAST=%s\n' % str(out))
              (pid, d, mcid, unused_connbeh, unused_conntype, unused_extcon,
               unused_condtype, unused_weldtype, unused_minlen, unused_maxlen,
               unused_gmcheck, unused_spcgs, mass, ge,
               unused_aa, unused_bb, unused_cc, mcid, mflag,
               kt1, kt2, kt3, kr1, kr2, kr3) = out
  
              data_in = (pid, d, mcid, mflag, kt1, kt2, kt3,
                         kr1, kr2, kr3, mass, ge)
              prop = PFAST.add_op2_data(data_in)
              str(prop)
              #print(prop)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PFAST'] = nproperties
          return n
  
      def _read_pfast_nx(self, data: bytes, n: int) -> int:
          """
          PFAST(3601,36,55)
          NX only
          """
          op2 = self.op2
          op2.to_nx(' because PFAST-NX was found')
          ntotal = 48
          struct1 = Struct(op2._endian + b'ifii 8f')
          nproperties = (len(data) - n) // ntotal
          delta = (len(data) - n) % ntotal
          assert delta == 0, 'len(data)-n=%s n=%s' % (len(data) - n, (len(data) - n) / 48.)
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct1.unpack(edata)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PFAST=%s\n' % str(out))
              (pid, d, mcid, mflag, kt1, kt2, kt3, kr1, kr2, kr3, mass, ge) = out
  
              data_in = (pid, d, mcid, mflag, kt1, kt2, kt3,
                         kr1, kr2, kr3, mass, ge)
              prop = PFAST.add_op2_data(data_in)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PFAST'] = nproperties
          return n
  
      def _read_pelast(self, data: bytes, n: int) -> int:
          """
          Record 41 -- PELAST(1302,13,34)
  
          1 PID   I Property identification number
          2 TKID  I TABLEDi entry identification number for stiffness
          3 TGEID I TABLEDi entry identification number for structural
                    damping
          4 TKNID I TABLEDi entry
          """
          op2 = self.op2
          ntotal = 16 * self.factor
          struct_4i = Struct(mapfmt(op2._endian + b'4i', self.size))
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct_4i.unpack(edata)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PELAST=%s\n' % str(out))
              #(pid, tkid, tgeid, tknid) = out
              prop = PELAST.add_op2_data(out)
              op2._add_methods._add_pelast_object(prop)
              n += ntotal
          op2.card_count['PELAST'] = nproperties
          return n
  
      def _read_pgap(self, data: bytes, n: int) -> int:
          """
          PGAP(2102,21,121) - the marker for Record 42
          """
          op2 = self.op2
          ntotal = 44 * self.factor
          struct_i10f = Struct(mapfmt(op2._endian + b'i10f', self.size))
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct_i10f.unpack(edata)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PGAP=%s\n' % str(out))
              #(pid,u0,f0,ka,kb,kt,mu1,mu2,tmax,mar,trmin) = out
              prop = PGAP.add_op2_data(out)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PGAP'] = nproperties
          return n
  
      def _read_phbdy(self, data: bytes, n: int) -> int:
          """
          PHBDY(2802,28,236) - the marker for Record 43
          """
          op2 = self.op2
          struct_i3f = Struct(op2._endian + b'ifff')
          nproperties = (len(data) - n) // 16
          for unused_i in range(nproperties):
              edata = data[n:n+16]
              out = struct_i3f.unpack(edata)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PHBDY=%s\n' % str(out))
              #(pid, af, d1, d2) = out
              prop = PHBDY.add_op2_data(out)
              op2._add_methods._add_phbdy_object(prop)
              n += 16
          op2.card_count['PHBDY'] = nproperties
          return n
  
      def _read_pintc(self, data: bytes, n: int) -> int:
          self.op2.log.info('geom skipping PINTC in EPT')
          return len(data)
  
      def _read_pints(self, data: bytes, n: int) -> int:
          self.op2.log.info('geom skipping PINTS in EPT')
          return len(data)
  
      def _read_pbeam3(self, data: bytes, n: int) -> int:
          op2 = self.op2
          card_name = 'PBUSHT'
          card_obj = PBUSHT
          methods = {
              264 : self._read_pbeam3_264,
              456 : self._read_pbeam3_456,
          }
          try:
              n = op2.reader_geom2._read_double_card(
                  card_name, card_obj, self._add_op2_property,
                  methods, data, n)
          except DoubleCardError:
              raise
              op2.log.warning(f'try-except {card_name}')
          return n
  
      def _read_pbeam3_456(self, card_obj, data: bytes, n: int) -> int:
          r"""
  
          # per C:\MSC.Software\msc_nastran_runs\b3plod3.op2
          ints    = (2201, 1, 1.0, 0.1833, 0.0833, 0, -1.0, 0, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5,
                           2, 1.0, 0.1833, 0.0833, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           2, 1.0, 0.1833, 0.0833, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           1.0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     2901, 2, 0.1, 0.1, 0.1, 0, 0.2, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5,
                           2, 0.1, 0.1, 0.1, 0, 0.2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           2, 0.1, 0.1, 0.1, 0, 0.2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           1.0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
          floats  = (2201, 1, 1.0, 0.1833, 0.0833, 0.0, -1.0, 0.0, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5,
                           2, 1.0, 0.1833, 0.0833, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                           2, 1.0, 0.1833, 0.0833, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                           1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                     2901, 2, 0.1, 0.1, 0.1, 0.0, 0.2, 0.0, 0.5, 0.0, 0.0, 0.5, -0.5, 0.0, 0.0, -0.5,
                           2, 0.1, 0.1, 0.1, 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                           2, 0.1, 0.1, 0.1, 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                           1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
          """
          op2 = self.op2
          #op2.show_data(data[n:])
          ntotal = 456 * self.factor # 114*4
          #
          struct1 = Struct(mapfmt(op2._endian +
                                  b'2i' # pid, mid
                                  b'3f' # A, Iy, Iz
                                  b'5f'  # # a, b, c, d, e
                                  b'5f fi  14f i' #fj ki  14f i
                                  b'2i3f' #aa-ee - good
                                  b'5f'   #ff-jj
                                  b'5f'   #kk-oo
                                  b'5f'   #pp-tt
                                  b'6f'   #uu-zz
                                  b'5f'   #aaa-eee
                                  b'4i'   #fff-iii
                                  # jjj-ooo
                                  b'2f iii f'
                                  # ppp-ttt
                                  b'5f'
                                  # uuu-zzz
                                  b'6f'
                                  b'30f', self.size))
  
          ndatai = len(data) - n
          nentries = ndatai // ntotal
          assert ndatai % ntotal == 0
  
          props = []
          for unused_i in range(nentries):
              #print(n, ntotal)
              datai = data[n:n+ntotal]
              #op2.show_data(datai, types='ifqd')
              n += ntotal
  
              (pid, mid, A, iz, iy,
               a, b, c, d, e,
               f, g, h, i, j,
               k, inta, l, m, ni, o, p, q, r, s, t, u, v, w, x, y, z,
               aa, bb, cc, dd, ee,
               ff, gg, hh, ii, jj,
               kk, ll, mm, nn, oo,
               pp, qq, rr, ss, tt,
               uu, vv, ww, xx, yy, zz,
               aaa, bbb, ccc, ddd, eee,
               fff, ggg, hhh, iii,
               jjj, kkk, lll, mmm, nnn, ooo,
               ppp, qqq, rrr, sss, ttt,
               uuu, vvv, www, xxx, yyy, zzz,
               *other) = struct1.unpack(datai)
              #print(pid, mid, A, iz, iy)
              #print('a-e', (a, b, c, d, e))
              #print('f-j', (f, g, h, i, j))
              #print(k, inta, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)
              #print('aa-ee', (aa, bb, cc, dd, ee))
              #print('ff-jj', (ff, gg, hh, ii, jj))
              #print('kk-oo', (kk, ll, mm, nn, oo))
              #print('pp-tt', (pp, qq, rr, ss, tt))
              #print('uu-zz', (uu, vv, ww, xx, yy, zz))
              #print('aaa-eee', (aaa, bbb, ccc, ddd, eee))
              #print('fff-jjj', (fff, ggg, hhh, iii))
              #print('jjj-ooo', (jjj, kkk, lll, mmm, nnn, ooo))
              #print('ppp-ttt', (ppp, qqq, rrr, sss, ttt))
              #print('uuu-zzz', (uuu, vvv, www, xxx, yyy, zzz))
  
              if mid == 0:
                  continue
              #assert sum(other) < 100, other
              prop = PBEAM3(
                  pid, mid, A, iz, iy, iyz=None, j=None, nsm=0.,
                  so=None,
                  cy=None, cz=None,
                  dy=None, dz=None,
                  ey=None, ez=None,
                  fy=None, fz=None,
                  ky=1., kz=1.,
                  ny=None, nz=None, my=None, mz=None,
                  nsiy=None, nsiz=None, nsiyz=None,
                  cw=None, stress='GRID',
                  w=None, wy=None, wz=None, comment='')
              assert pid > 0, prop.get_stats()
              assert mid > 0, prop.get_stats()
              str(prop)
              props.append(prop)
              #self._add_op2_property(prop)
          #op2.card_count['PBEAM3'] = nentries
          return n, props
  
      def _read_pbeam3_264(self, card_obj, data: bytes, n: int) -> int:
          """
          TODO: partial
          # per test_cbeam_cbeam3???
          ints    = (2901, 2, 0.1, 0.1, 0.1, 0,   0.02, 0,   0.5, 0,   0,   0.5, -0.5, 0,   0,   -0.5, 2, 0.1, 0.1, 0.1, 0,   0.02, 0,   0,   0,   0,   0,   0,   0,   0,   0,   2, 0.1, 0.1, 0.1, 0,   0.02, 0,   0,   0,   0,   0,   0,   0,   0,   0,   1.0, 1.0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   -2,   0,   0)
          floats  = (2901, 2, 0.1, 0.1, 0.1, 0.0, 0.02, 0.0, 0.5, 0.0, 0.0, 0.5, -0.5, 0.0, 0.0, -0.5, 2, 0.1, 0.1, 0.1, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2, 0.1, 0.1, 0.1, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, nan, 0.0, 0.0)
          """
          op2 = self.op2
          ntotal = 264 * self.factor # 66*4
          #                                       p/m ayz ae fj ki  14f i
          struct1 = Struct(mapfmt(op2._endian + b'2i 3f  5f 5f fi  14f i 30f 4i', self.size))
  
          ndatai = len(data) - n
          nentries = ndatai // ntotal
          assert ndatai % ntotal == 0
  
          props = []
          for unused_i in range(nentries):
              pid, mid, A, iz, iy, a, b, c, d, e, f, g, h, i, j, k, inta, *other = struct1.unpack(data[n:n+ntotal])
              #print(pid, mid, A, iz, iy)
              #print((a, b, c, d, e))
              #print((f, g, h, i, j))
              #print(k, inta)
              assert sum(other) < 100, other
              prop = PBEAM3(
                  pid, mid, A, iz, iy, iyz=None, j=None, nsm=0.,
                  so=None,
                  cy=None, cz=None,
                  dy=None, dz=None,
                  ey=None, ez=None,
                  fy=None, fz=None,
                  ky=1., kz=1.,
                  ny=None, nz=None, my=None, mz=None,
                  nsiy=None, nsiz=None, nsiyz=None,
                  cw=None, stress='GRID',
                  w=None, wy=None, wz=None, comment='')
              assert pid > 0, prop.get_stats()
              assert mid > 0, prop.get_stats()
              str(prop)
              props.append(prop)
              n += ntotal
          return n, props
  
      def _read_pplane(self, data: bytes, n: int) -> int:
          """
          RECORD – PPLANE(3801,38,979)
          Word Name Type Description
          1 PID    I Property identification number
          2 MID    I Material identification number
          3 T     RS Default membrane thickness for Ti on the connection entry
          4 NSM   RS Nonstructural mass per unit area
          5 FOROPT I Formulation option number
          6 CSOPT  I Reserved for coordinate system definition of plane
          7 UNDEF(2) None
  
          ints    = (1, 1, 1.0, 0,   0,   0,   0,   0,   2, 2, 1.0, 0, 0, 0, 0, 0)
          floats  = (1, 1, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2, 2, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
          """
          op2 = self.op2
          ntotal = 32 * self.factor # 8*4
          struct1 = Struct(mapfmt(op2._endian + b'2i 2f 4i', self.size))
  
          ndatai = len(data) - n
          nentries = ndatai // ntotal
          assert ndatai % ntotal == 0
          for unused_i in range(nentries):
              out = struct1.unpack(data[n:n+ntotal])
              pid, mid, t, nsm, foropt, csopt = out[:6]
              #print(out)
              assert csopt == 0, csopt
              pplane = op2.add_pplane(pid, mid, t=t, nsm=nsm,
                                       formulation_option=foropt)
              pplane.validate()
              #print(pplane)
              str(pplane)
              n += ntotal
          op2.card_count['PLPLANE'] = nentries
          return n
  
      def _read_plplane(self, data: bytes, n: int) -> int:
          """
          PLPLANE(4606,46,375)
  
          NX 10
          1 PID     I Property identification number
          2 MID     I Material identification number
          3 CID     I Coordinate system identification number
          4 STR CHAR4 Location of stress and strain output
          5 T      RS Default membrane thickness for Ti on the connection entry
          6 CSOPT  I  Reserved for coordinate system definition of plane
          7 UNDEF(5) None
  
          MSC 2016
          PID       I Property identification number
          2 MID     I Material identification number
          3 CID     I Coordinate system identification number
          4 STR CHAR4 Location of stress and strain output
          5 UNDEF(7 ) none Not used
  
          .. warning:: CSOPT ad T are not supported
          """
          op2 = self.op2
          ntotal = 44 * self.factor  # 4*11
          if self.size == 4:
              s = Struct(op2._endian + b'3i 4s f 6i')
          else:
              s = Struct(op2._endian + b'3q 8s d 6q')
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              out = s.unpack(data[n:n+ntotal])
              pid, mid, cid, location, unused_t, unused_csopt = out[:6]
              location = location.decode('latin1')
              #op2.show_data(data[n:n+ntotal], 'ifs')
              op2.add_plplane(pid, mid, cid=cid, stress_strain_output_location=location)
              n += ntotal
          op2.card_count['PLPLANE'] = nentries
          return n
  
      def _read_plsolid(self, data: bytes, n: int) -> int:
          """
          MSC 2016
          1 PID I Property identification number
          2 MID I Material identification number
          3 STR CHAR4 Location of stress and strain output
          4 UNDEF(4 ) none Not used
  
          NX 10
          1 PID I Property identification number
          2 MID I Material identification number
          3 STR CHAR4 Location of stress and strain output
          4 CSOPT I Reserved for coordinate system definition of plane
          5 UNDEF(3) None
  
          .. warning:: CSOPT is not supported
          """
          op2 = self.op2
          ntotal = 28 * self.factor  # 4*7
          if self.size == 4:
              struct1 = Struct(op2._endian + b'2i 4s 4i')
          else:
              struct1 = Struct(op2._endian + b'2q 8s 4q')
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              out = struct1.unpack(data[n:n+ntotal])
              pid, mid, location, unused_csopt, unused_null_a, unused_null_b, unused_null_c = out
              location = location.decode('latin1')
              #op2.show_data(data[n:n+ntotal], 'ifs')
              op2.add_plsolid(pid, mid, stress_strain=location, ge=0.)
              n += ntotal
          op2.card_count['PLSOLID'] = nentries
          return n
  
      def _read_pmass(self, data: bytes, n: int) -> int:
          """
          PMASS(402,4,44) - the marker for Record 48
          """
          op2 = self.op2
          ntotal = 8 * self.factor # 2*4
          struct_if = Struct(mapfmt(op2._endian + b'if', self.size))
          nentries = (len(data) - n) // ntotal
          for unused_i in range(nentries):
              edata = data[n:n + ntotal]
              out = struct_if.unpack(edata)
              #out = (pid, mass)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PMASS=%s\n' % str(out))
              prop = PMASS.add_op2_data(out)
              self._add_op2_property_mass(prop)
              n += ntotal
          return n
  
      def _read_prod(self, data: bytes, n: int) -> int:
          """
          PROD(902,9,29) - the marker for Record 49
          """
          op2 = self.op2
          ntotal = 24 * self.factor  # 6*4
          struct_2i4f = Struct(mapfmt(op2._endian + b'2i4f', self.size))
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct_2i4f.unpack(edata)
              #(pid, mid, a, j, c, nsm) = out
              prop = PROD.add_op2_data(out)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PROD=%s\n' % str(out))
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PROD'] = nproperties
          return n
  
      def _read_pshear(self, data: bytes, n: int) -> int:
          """
          PSHEAR(1002,10,42) - the marker for Record 50
          """
          op2 = self.op2
          ntotal = 24 * self.factor
          struct_2i4f = Struct(mapfmt(op2._endian + b'2i4f', self.size))
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct_2i4f.unpack(edata)
              #(pid, mid, t, nsm, f1, f2) = out
              if op2.is_debug_file:
                  op2.binary_debug.write('  PSHEAR=%s\n' % str(out))
              prop = PSHEAR.add_op2_data(out)
              self._add_op2_property(prop)
              n += ntotal
          op2.card_count['PSHEAR'] = nproperties
          return n
  
      def _read_pshell(self, data: bytes, n: int) -> int:
          """
          PSHELL(2302,23,283) - the marker for Record 51
          """
          op2 = self.op2
          ntotal = 44 * self.factor  # 11*4
          s = Struct(mapfmt(op2._endian + b'iififi4fi', self.size))
          nproperties = (len(data) - n) // ntotal
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = s.unpack(edata)
              (pid, mid1, unused_t, mid2, unused_bk, mid3, unused_ts,
               unused_nsm, unused_z1, unused_z2, mid4) = out
              if op2.is_debug_file:
                  op2.binary_debug.write('  PSHELL=%s\n' % str(out))
              prop = PSHELL.add_op2_data(out)
              n += ntotal
  
              if pid in op2.properties:
                  # this is a fake PSHELL
                  propi = op2.properties[pid]
                  if prop == propi:
                      op2.log.warning(f'Fake PSHELL {pid:d} (skipping):\n{propi}')
                      nproperties -= 1
                      continue
                  #assert propi.type in ['PCOMP', 'PCOMPG'], propi.get_stats()
                  op2.log.error(f'PSHELL {pid:d} is also {propi.type} (skipping PSHELL):\n{propi}{prop}')
                  nproperties -= 1
                  continue
              #continue
              #if max(pid, mid1, mid2, mid3, mid4) > 1e8:
                  #self.big_properties[pid] = prop
              #else:
              self._add_op2_property(prop)
          if nproperties:
              op2.card_count['PSHELL'] = nproperties
          return n
  
      def _read_psolid(self, data: bytes, n: int) -> int:
          """
          PSOLID(2402,24,281) - the marker for Record 52
          """
          op2 = self.op2
          #print("reading PSOLID")
          if self.size == 4:
              ntotal = 28  # 7*4
              struct_6i4s = Struct(op2._endian + b'6i4s')
          else:
              ntotal = 28 * 2
              struct_6i4s = Struct(op2._endian + b'6q8s')
  
          nproperties = (len(data) - n) // ntotal
          nproperties_found = 0
          for unused_i in range(nproperties):
              edata = data[n:n+ntotal]
              out = struct_6i4s.unpack(edata)
              #(pid, mid, cid, inp, stress, isop, fctn) = out
              #data_in = [pid, mid, cid, inp, stress, isop, fctn]
              if op2.is_debug_file:
                  op2.binary_debug.write('  PSOLID=%s\n' % str(out))
  
              n += ntotal
              fctn = out[-1]
              if fctn == b'FAKE':
                  op2.log.warning('    PSOLID=%s; is this a PCOMPLS?' % str(out))
                  continue
              prop = PSOLID.add_op2_data(out)
              self._add_op2_property(prop)
              nproperties_found += 1
          op2.card_count['PSOLID'] = nproperties_found
          return n
  
  # PSOLIDL
  # PTRIA6
  # PTSHELL
  
      def _read_ptube(self, data: bytes, n: int) -> int:
          """
          PTUBE(1602,16,30) - the marker for Record 56
  
          .. todo:: OD2 only exists for heat transfer...
                    how do i know if there's heat transfer at this point?
                    I could store all the tubes and add them later,
                    but what about themal/non-thermal subcases?
  
          .. warning:: assuming OD2 is not written (only done for thermal)
          """
          op2 = self.op2
          struct_2i3f = Struct(op2._endian + b'2i3f')
          nproperties = (len(data) - n) // 20
          for unused_i in range(nproperties):
              edata = data[n:n+20]  # or 24???
              out = struct_2i3f.unpack(edata)
              (pid, mid, OD, t, nsm) = out
              data_in = [pid, mid, OD, t, nsm]
              if op2.is_debug_file:
                  op2.binary_debug.write('  PTUBE=%s\n' % str(out))
              prop = PTUBE.add_op2_data(data_in)
              self._add_op2_property(prop)
              n += 20
          op2.card_count['PTUBE'] = nproperties
          return n
  
      def _read_pset(self, data: bytes, n: int) -> int:
          op2 = self.op2
          struct_5i4si = Struct(op2._endian + b'5i4si')
          nentries = 0
          while  n < len(data):
              edata = data[n:n+28]
              out = struct_5i4si.unpack(edata)
              #print(out)
              idi, poly1, poly2, poly3, cid, typei, typeid = out
              typei = typei.rstrip().decode('latin1')
              assert typei in ['SET', 'ELID'], (idi, poly1, poly2, poly3, cid, typei, typeid)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PVAL=%s\n' % str(out))
              #print(idi, poly1, poly2, poly3, cid, typei, typeid)
              typeids = []
              n += 28
              while typeid != -1:
                  typeids.append(typeid)
                  typeid, = op2.struct_i.unpack(data[n:n+4])
                  n += 4
                  #print(val)
              #print(typeids)
              # PSET ID POLY1 POLY2 POLY3 CID SETTYP ID
              if len(typeids) == 1:
                  typeids = typeids[0]
              op2.add_pset(idi, poly1, poly2, poly3, cid, typei, typeids)
          op2.card_count['PSET'] = nentries
          return n
  
      def _read_pval(self, data: bytes, n: int) -> int:
          """
          PVAL(10201,102,400)
  
          Word Name Type Description
          1 ID       I p-value set identification number
          2 POLY1    I Polynomial order in 1 direction of the CID system
          3 POLY2    I Polynomial order in 2 direction of the CID system
          4 POLY3    I Polynomial order in 2 direction of the CID system
          5 CID      I Coordinate system identification number
          6 TYPE CHAR4 Type of set provided: "SET" or "ELID"
          7 TYPEID   I SET identification number or element identification
                       number with this p-value specification.
          Words 1 through 7 repeat until End of Record
          """
          op2 = self.op2
          #op2.show_data(data[n:])
          if self.size == 4:
              struct_5i4si = Struct(op2._endian + b'5i 4s i')
              struct_i = op2.struct_i
          else:
              struct_5i4si = Struct(op2._endian + b'5q 8s q')
              struct_i = op2.struct_q
  
          nentries = 0
          ntotal = 28 * self.factor
          size = self.size
          while  n < len(data):
              edata = data[n:n+ntotal]
              out = struct_5i4si.unpack(edata)
              #print(out)
              idi, poly1, poly2, poly3, cid, typei, typeid = out
              typei = typei.rstrip().decode('latin1')
              assert typei in ['SET', 'ELID'], f'idi={idi} poly1={poly1} poly2={poly2} poly3={poly3} cid={cid} typei={typei} typeid={typeid}'
              if op2.is_debug_file:
                  op2.binary_debug.write('  PVAL=%s\n' % str(out))
              #print(idi, poly1, poly2, poly3, cid, typei, typeid)
              typeids = []
              n += ntotal
              while typeid != -1:
                  typeids.append(typeid)
                  typeid, = struct_i.unpack(data[n:n+size])
                  n += size
                  #print(val)
              #print(typeids)
              # PVAL ID POLY1 POLY2 POLY3 CID SETTYP ID
              op2.add_pval(idi, poly1, poly2, poly3, cid, typei, typeids)
          op2.card_count['PVAL'] = nentries
          return n
  
      def _read_pvisc(self, data: bytes, n: int) -> int:
          """PVISC(1802,18,31) - the marker for Record 39"""
          op2 = self.op2
          struct_i2f = Struct(op2._endian + b'i2f')
          nproperties = (len(data) - n) // 12
          for unused_i in range(nproperties):
              edata = data[n:n+12]
              out = struct_i2f.unpack(edata)
              if op2.is_debug_file:
                  op2.binary_debug.write('  PVISC=%s\n' % str(out))
              #(pid, ce, cr) = out
              prop = PVISC.add_op2_data(out)
              self._add_op2_property(prop)
              n += 12
          op2.card_count['PVISC'] = nproperties
          return n
  
  # PWELD
  # PWSEAM
      def _read_view(self, data: bytes, n: int) -> int:
          self.op2.log.info('geom skipping VIEW in EPT')
          return len(data)
  
      def _read_view3d(self, data: bytes, n: int) -> int:
          self.op2.log.info('geom skipping VIEW3D in EPT')
          return len(data)
  
  def break_by_minus1(idata):
      """helper for ``read_nsm_nx``"""
      i1 = 0
      i = 0
      i2 = None
      packs = []
      for idatai in idata:
          #print('data[i:] = ', data[i:])
          if idatai == -1:
              i2 = i
              packs.append((i1, i2))
              i1 = i2 + 1
              i += 1
              continue
          i += 1
      #print(packs)
      return packs
  