Source code for pyNastran.bdf.bdf_interface.get_methods

"""defines various methods to access low level BDF data"""
from __future__ import annotations
from itertools import chain
from collections import defaultdict
from typing import Union, Optional, Iterable, Any, TYPE_CHECKING
import numpy as np

from pyNastran.bdf.bdf_interface.attributes import BDFAttributes
from pyNastran.utils.numpy_utils import integer_types
if TYPE_CHECKING:  # pragma: no cover
    from pyNastran.bdf.bdf import BDF
    from pyNastran.bdf.cards.coordinate_systems import Coord
    from pyNastran.bdf.cards.nodes import POINT, GRID, SPOINT, EPOINT # , SPOINTs, EPOINTs, SEQGP, GRIDB
    from pyNastran.bdf.cards.aero.aero import (
        #AECOMP, AECOMPL
        AEFACT, AELINK,
        AELIST,
        AEPARM, AESURF, # AESURFS,
        CAERO1, CAERO2, CAERO3, CAERO4, CAERO5,
        PAERO1, PAERO2, PAERO3, PAERO4, PAERO5,
        #MONPNT1, MONPNT2, MONPNT3,
        SPLINE1, SPLINE2, SPLINE3, SPLINE4, SPLINE5
    )
    from pyNastran.bdf.cards.aero.static_loads import AESTAT, AEROS # CSSCHD, TRIM, TRIM2, DIVERG
    from pyNastran.bdf.cards.aero.dynamic_loads import AERO, FLFACT, FLUTTER, GUST
    # MKAERO1, MKAERO2
    # ----------------------------------------------------

    from pyNastran.bdf.cards.elements.mass import CONM1, CONM2, CMASS1, CMASS2, CMASS3, CMASS4
    from pyNastran.bdf.cards.properties.mass import PMASS, NSM1, NSML, NSML1, NSM, NSMADD
    from pyNastran.bdf.cards.constraints import (SPCADD, SPC1, # SPC,
                                                 #MPC,
                                                 MPCADD) # SUPORT1, SUPORT
    #from pyNastran.bdf.cards.deqatn import DEQATN
    from pyNastran.bdf.cards.dynamic import (
        #DELAY, DPHASE,
        NLPARM)
    from pyNastran.bdf.cards.elements.rigid import RBAR, RBAR1, RBE1, RBE2, RBE3, RROD, RSPLINE, RSSCON
    from pyNastran.bdf.cards.loads.loads import SLOAD, DAREA
    from pyNastran.bdf.cards.loads.dloads import DLOAD, TLOAD1, TLOAD2, RLOAD1, RLOAD2
    from pyNastran.bdf.cards.loads.static_loads import (LOAD, GRAV, ACCEL, ACCEL1, FORCE,
                                                        FORCE1, FORCE2, MOMENT, MOMENT1, MOMENT2,
                                                        PLOAD, PLOAD1, PLOAD2, PLOAD4)

    from pyNastran.bdf.cards.materials import (MAT1, MAT2, MAT3, MAT4, MAT5,
                                               MAT8, MAT9, MAT10, MAT11, MAT3D,
                                               MATG, MATHE, MATHP, EQUIV)

    from pyNastran.bdf.cards.methods import EIGC, EIGR, EIGRL
    #from pyNastran.bdf.cards.nodes import GRID, SPOINTs, EPOINTs, POINT

    from pyNastran.bdf.cards.aero.zona import (
        #ACOORD, AEROZ, AESURFZ, BODY7, CAERO7, MKAEROZ, PAFOIL7, PANLST1, PANLST3,
        #SEGMESH, SPLINE1_ZONA, SPLINE2_ZONA, SPLINE3_ZONA, TRIMLNK, TRIMVAR, TRIM_ZONA,
        ZONA)

    from pyNastran.bdf.cards.optimization import (
        DCONADD, DCONSTR, DESVAR, DDVAL, # DOPTPRM, DLINK,
        DRESP1, DRESP2, DRESP3,
        DVCREL1, DVCREL2,
        DVMREL1, DVMREL2,
        DVPREL1, DVPREL2)
    from pyNastran.bdf.cards.bdf_sets import SET1, SET3
    from pyNastran.bdf.cards.thermal.thermal import PHBDY
    #from pyNastran.bdf.cards.thermal.loads import (QBDY1, QBDY2, QBDY3, QHBDY, TEMP, TEMPD, TEMPB3,
                                                   #TEMPRB, QVOL, QVECT)
    from pyNastran.bdf.cards.bdf_tables import (TABLED1, TABLED2, TABLED3, TABLED4,
                                                TABLEM1, TABLEM2, TABLEM3, TABLEM4,
                                                TABLES1, TABLEST, TABLEHT, TABLEH1,
                                                TABRND1, TABRNDG)


class GetMethods(BDFAttributes):
    """defines various methods to access low level BDF data"""
    def __init__(self):
        BDFAttributes.__init__(self)

    def EmptyNode(self, nid: int, msg: str='') -> Optional[Union[GRID, SPOINT, EPOINT]]:
        """
        Gets a GRID/SPOINT/EPOINT object, but allows for empty nodes
        (i.e., the CTETRA10 supports empty nodes, but the CTETRA4 does not).

        Parameters
        ----------
        nid : int / None
            the node id
            0, None : indicate blank
        msg : str; default=''
            a debugging message

        """
        if nid == 0 or nid is None:
            return None
        elif nid in self.nodes:
            return self.nodes[nid]
        elif nid in self.spoints:
            return self.spoints[nid]
        elif nid in self.epoints:
            return self.epoints[nid]
        else:
            assert isinstance(nid, integer_types), 'nid should be an integer; not %s' % type(nid)
            nid_list = _unique_keys(self.nodes)
            msg = 'nid=%s is not a GRID, SPOINT, or EPOINT%s\n' % (nid, msg)
            msg += 'nids=%s\n' % nid_list
            if self.spoints:
                msg += 'spoints=%s\n' % _unique_keys(self.spoints)
            if self.epoints:
                msg += 'epoints=%s\n' % _unique_keys(self.epoints)
            raise KeyError(msg)

    def Node(self, nid: int, msg: str='') -> Union[GRID, SPOINT, EPOINT]:
        """
        Gets a GRID/SPOINT/EPOINT object.  This method does not allow for empty nodes
        (i.e., the CTETRA10 supports empty nodes, but the CTETRA4 does not).

        Parameters
        ----------
        nid : int
            the node id
        msg : str; default=''
            a debugging message

        """
        #assert isinstance(nid, integer_types), 'nid=%s' % str(nid)
        if nid in self.nodes:
            return self.nodes[nid]
        elif nid in self.spoints:
            return self.spoints[nid]
        elif nid in self.epoints:
            return self.epoints[nid]
        else:
            assert isinstance(nid, integer_types), 'nid should be an integer; not %s' % type(nid)
            nid_list = _unique_keys(self.nodes)
            msg = 'nid=%s is not a GRID, SPOINT, or EPOINT%s\n' % (nid, msg)
            msg += 'nids=%s\n' % nid_list
            if self.spoints:
                msg += 'spoints=%s\n' % _unique_keys(self.spoints)
            if self.epoints:
                msg += 'epoints=%s\n' % _unique_keys(self.epoints)
            raise KeyError(msg)

    def EmptyNodes(self, nids: list[int], msg: str='') -> list[Optional[Union[GRID, SPOINT, EPOINT]]]:
        """
        Returns a series of node objects given a list of IDs

        """
        nodes = []
        bad_nids = []
        for nid in nids:
            try:
                nodes.append(self.EmptyNode(nid, msg=msg))
            except KeyError:
                bad_nids.append(nid)

        if bad_nids:
            nid_list = _unique_keys(self.nodes)
            msg = 'nids=%s are not a GRID, SPOINT, or EPOINT%s\n' % (bad_nids, msg)
            msg += 'nids=%s\n' % nid_list
            if self.spoints:
                msg += 'spoints=%s\n' % _unique_keys(self.spoints)
            if self.epoints:
                msg += 'epoints=%s\n' % _unique_keys(self.epoints)
            raise KeyError(msg)
        return nodes

    def Nodes(self, nids: list[int], msg: str='') -> list[Union[GRID, SPOINT, EPOINT]]:
        """
        Returns a series of node objects given a list of IDs

        """
        nodes = []
        #self.axic
        if self._is_axis_symmetric and self.axif is not None:
            # GRIDB is only active if AXIF exists
            for nid in nids:
                try:
                    gridb = self.gridb[nid]
                except KeyError:
                    assert isinstance(nid, integer_types), 'nid should be an integer; not %s' % type(nid)
                    nid_list = _unique_keys(self.gridb)
                    raise KeyError('nid=%s is not a GRIDB%s\n%s' % (nid, msg, nid_list))
                nodes.append(gridb)
        else:
            try:
                for nid in nids:
                    nodes.append(self.Node(nid, msg=msg))
            except AssertionError:
                print(msg)
                print(nids)
                raise
        return nodes

    def Point(self, nid: int, msg: str='') -> POINT:
        """Returns a POINT card"""
        if nid in self.points:
            return self.points[nid]
        else:
            assert isinstance(nid, integer_types), 'nid should be an integer; not %s' % type(nid)
            nid_list = _unique_keys(self.points)
            raise KeyError('nid=%s is not a POINT%s\n%s' % (nid, msg, nid_list))

    def Points(self, nids: list[int], msg: str='') -> list[POINT]:
        """
        Returns a series of POINT objects given a list of IDs
        """
        points = []
        for nid in nids:
            points.append(self.Point(nid, msg=msg))
        return points

    def Element(self, eid: int, msg: str='') -> Any:
        """
        Gets an element

        Doesn't get rigid (RROD, RBAR, RBE2, RBE3, RBAR, RBAR1, RSPLINE, RSSCON)
        or mass (CMASS1, CONM2)
        """
        try:
            return self.elements[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed elements=%s'
                           % (eid, msg, _unique_keys(self.elements)))

    def Elements(self, eids: list[int], msg: str='') -> list[Any]:
        """
        Gets an series of elements

        Doesn't get rigid (RROD, RBAR, RBE2, RBE3, RBAR, RBAR1, RSPLINE, RSSCON)
        or mass (CMASS1, CONM2)

        """
        elements = []
        bad_eids = []
        for eid in eids:
            try:
                elements.append(self.Element(eid, msg))
            except KeyError:
                bad_eids.append(eid)
        if bad_eids:
            msg = 'eids=%s not found%s.  Allowed elements=%s' % (
                bad_eids, msg, _unique_keys(self.elements))
            raise KeyError(msg)
        return elements

    def Mass(self, eid: int, msg: str='') -> Union[CMASS1, CMASS2, CMASS3, CMASS4, CONM1, CONM2]:
        """gets a mass element (CMASS1, CONM2)"""
        try:
            return self.masses[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed masses=%s'
                           % (eid, msg, _unique_keys(self.masses)))

    def RigidElement(self, eid: int, msg: str='') -> Union[RBAR, RBE2, RBE3, RBAR, RBAR1, RROD, RSPLINE, RSSCON]:
        """gets a rigid element (RBAR, RBE2, RBE3, RBAR, RBAR1, RROD, RSPLINE, RSSCON)"""
        try:
            return self.rigid_elements[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed rigid_elements=%s'
                           % (eid, msg, _unique_keys(self.rigid_elements)))

    #--------------------
    # PROPERTY CARDS
    def Property(self, pid: int, msg: str='') -> Any:
        """
        gets an elemental property (e.g. PSOLID, PLSOLID, PCOMP, PSHELL, PSHEAR);
        not mass property (PMASS)

        """
        try:
            return self.properties[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed Pids=%s'
                           % (pid, msg, self.property_ids))

    def Properties(self, pids: list[int], msg: str='') -> list[Any]:
        """
        gets one or more elemental property (e.g. PSOLID, PLSOLID,
        PCOMP, PSHELL, PSHEAR); not mass property (PMASS)

        """
        properties = []
        for pid in pids:
            properties.append(self.Property(pid, msg))
        return properties

    def PropertyMass(self, pid: int, msg: str='') -> PMASS:
        """
        gets a mass property (PMASS)
        """
        try:
            return self.properties_mass[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed Mass Pids=%s'
                           % (pid, msg, _unique_keys(self.properties_mass)))

    def Phbdy(self, pid: int, msg: str='') -> PHBDY:
        """gets a PHBDY"""
        try:
            return self.phbdys[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed PHBDY Pids=%s'
                           % (pid, msg, _unique_keys(self.phbdys)))

    #--------------------
    # MATERIAL CARDS

    def get_structural_material_ids(self) -> Iterable[int]:
        return self.materials.keys()

    def get_material_ids(self) -> Iterable[int]:
        """gets the material ids"""
        keys = chain(
            self.materials.keys(),
            self.thermal_materials.keys(),
            self.hyperelastic_materials.keys(),
        )
        return keys

    def get_thermal_material_ids(self) -> Iterable[int]:
        """gets the thermal material ids"""
        return self.thermal_materials.keys()

    def Material(self, mid: int, msg: str='') -> Union[MAT1, MAT2, MAT3, MAT4, MAT5, MAT8, MAT9, MAT10, MAT11, MAT3D, EQUIV, MATG]:
        """gets a structural or thermal material"""
        if mid in self.materials:
            return self.materials[mid]
        elif mid in self.thermal_materials:
            return self.thermal_materials[mid]
        else:
            msg = '\n' + msg
            msg2 = (
                'Invalid Material ID:  mid=%s%s\nAllowed=%s' % (
                    mid, msg, _unique_keys(self.materials)
                )
            )
            raise KeyError(msg2)

    def StructuralMaterial(self, mid, msg='') -> Union[MAT1, MAT2, MAT3, MAT8, MAT9, MAT10, MAT11, MAT3D, EQUIV, MATG]:
        """gets a structural material"""
        try:
            mat = self.materials[mid]
        except KeyError:
            msg = '\n' + msg
            raise KeyError('Invalid Structural Material ID:  mid=%s%s' % (mid, msg))
        return mat

    def ThermalMaterial(self, mid: int, msg: str='') -> Union[MAT4, MAT5]:
        """gets a thermal material"""
        try:
            mat = self.thermal_materials[mid]
        except KeyError:
            msg = '\n' + msg
            raise KeyError('Invalid Thermal Material ID:  mid=%s%s' % (mid, msg))
        return mat

    def HyperelasticMaterial(self, mid: int, msg: str='') -> Union[MATHE, MATHP]:
        """gets a hyperelastic material"""
        try:
            mat = self.hyperelastic_materials[mid]
        except KeyError:
            msg = '\n' + msg
            raise KeyError('Invalid Hyperelastic Material ID:  mid=%s%s' % (mid, msg))
        return mat

    def Materials(self, mids, msg='') -> list[Union[MAT1, MAT2, MAT3, MAT8, MAT9, MAT10, MAT11, MAT3D, EQUIV, MATG]]:
        """gets one or more Materials"""
        if isinstance(mids, integer_types):
            mids = [mids]
        materials = []
        for mid in mids:
            materials.append(self.Material(mid, msg))
        return materials

    #--------------------
    # LOADS

    def Load(self, sid: int, consider_load_combinations: bool=True,
             msg: str='') -> list[Union[LOAD, GRAV, ACCEL, ACCEL1, SLOAD,
                                        FORCE, FORCE1, FORCE2,
                                        MOMENT, MOMENT1, MOMENT2,
                                        PLOAD, PLOAD1, PLOAD2, PLOAD4]]:
        """
        Gets an LOAD or FORCE/PLOAD4/etc.

        Parameters
        ----------
        sid : int
            the LOAD id
        consider_load_combinations : bool; default=True
            LOADs should not be considered when referenced from an LOAD card
            from a case control, True should be used.
        msg : str
            additional message to print when failing

        """
        assert isinstance(sid, integer_types), 'sid=%s is not an integer; type=%s\n' % (sid, type(sid))
        load = []
        if consider_load_combinations and sid in self.load_combinations:
            load = self.load_combinations[sid]
        elif sid in self.loads:
            load = self.loads[sid]

        if sid in self.tempds:
            load.append(self.tempds[sid])

        if len(load) == 0:
            loads_ids = list(self.loads.keys())
            load_combination_ids = list(self.load_combinations.keys())
            raise KeyError('cannot find LOAD ID=%r%s.\nAllowed loads (e.g., FORCE)=%s; LOAD=%s' % (
                sid, msg, np.unique(loads_ids), np.unique(load_combination_ids)))
        return load

    def DLoad(self, sid: int, consider_dload_combinations: bool=True, msg: str='') -> DLOAD:
        """
        Gets a DLOAD, TLOAD1, TLOAD2, etc. associated with the
        Case Control DLOAD entry

        """
        assert isinstance(sid, integer_types), 'sid=%s is not an integer\n' % sid

        if consider_dload_combinations and sid in self.dloads:
            dload = self.dloads[sid]
        elif sid in self.dload_entries:
            dload = self.dload_entries[sid]
        else:
            dloads_ids = list(self.dload_entries.keys())
            dload_combination_ids = list(self.dloads.keys())
            raise KeyError('cannot find DLOAD ID=%r%s.\nAllowed dloads '
                           '(e.g., TLOAD1)=%s; DLOAD=%s' % (
                               sid, msg, np.unique(dloads_ids),
                               np.unique(dload_combination_ids)))
        return dload

    def get_dload_entries(self, sid: int, msg: str='') -> Union[TLOAD1, TLOAD2, RLOAD1, RLOAD2]:
        """gets the dload entries (e.g., TLOAD1, TLOAD2)"""
        self.deprecated(
            "get_dload_entries(sid, msg='')",
            "DLoad(sid, consider_dload_combinations=False, msg='')",
            '1.1')
        return self.DLoad(sid, consider_dload_combinations=False, msg=msg)

    def DAREA(self, darea_id: int, msg: str='') -> DAREA:
        """gets a DAREA"""
        assert isinstance(darea_id, integer_types), darea_id
        try:
            return self.dareas[darea_id]
        except KeyError:
            raise KeyError('darea_id=%s not found%s.  Allowed DAREA=%s'
                           % (darea_id, msg, list(self.dareas.keys())))

    def DELAY(self, delay_id: int, msg: str='') -> DELAY:
        """gets a DELAY"""
        assert isinstance(delay_id, integer_types), delay_id
        try:
            return self.delays[delay_id]
        except KeyError:
            raise KeyError('delay_id=%s not found%s.  Allowed DELAY=%s'
                           % (delay_id, msg, list(self.delays.keys())))

    def DPHASE(self, dphase_id: int, msg: str='') -> DPHASE:
        """gets a DPHASE"""
        assert isinstance(dphase_id, integer_types), dphase_id
        try:
            return self.dphases[dphase_id]
        except KeyError:
            raise KeyError('dphase_id=%s not found%s.  Allowed DPHASE=%s'
                           % (dphase_id, msg, list(self.dphases.keys())))

    #--------------------
    def MPC(self, mpc_id: int, consider_mpcadd: bool=True, msg: str='') -> Union[MPC, MPCADD]:
        """
        Gets an MPCADD or MPC

        Parameters
        ----------
        mpc_id : int
            the MPC id
        consider_mpcadd : bool; default=True
            MPCADDs should not be considered when referenced from an MPCADD
            from a case control, True should be used.
        msg : str
            additional message to print when failing

        """
        assert isinstance(mpc_id, integer_types), 'mpc_id=%s is not an integer\n' % mpc_id
        if consider_mpcadd and mpc_id in self.mpcadds:
            constraint = self.mpcadds[mpc_id]
        elif mpc_id in self.mpcs:
            constraint = self.mpcs[mpc_id]
        else:
            mpc_ids = list(self.mpcs.keys())
            mpcadd_ids = list(self.mpcadds.keys())
            raise KeyError('cannot find MPC ID=%r%s.\nAllowed MPCs=%s; MPCADDs=%s' % (
                mpc_id, msg, np.unique(mpc_ids), np.unique(mpcadd_ids)))
        return constraint

    def SPC(self, spc_id: int, consider_spcadd: bool=True, msg: str='') -> Union[SPC, SPC1, SPCADD]:
        """
        Gets an SPCADD or SPC

        Parameters
        ----------
        spc_id : int
            the SPC id
        consider_spcadd : bool; default=True
            SPCADDs should not be considered when referenced from an SPCADD
            from a case control, True should be used.
        msg : str
            additional message to print when failing

        """
        assert isinstance(spc_id, integer_types), 'spc_id=%s is not an integer\n' % spc_id
        if consider_spcadd and spc_id in self.spcadds:
            constraint = self.spcadds[spc_id]
        elif spc_id in self.spcs:
            constraint = self.spcs[spc_id]
        else:
            spc_ids = list(self.spcs.keys())
            spcadd_ids = list(self.spcadds.keys())
            raise KeyError('cannot find SPC ID=%r%s.\nAllowed SPCs=%s; SPCADDs=%s' % (
                spc_id, msg, np.unique(spc_ids), np.unique(spcadd_ids)))
        return constraint

    def NSM(self, nsm_id: int, consider_nsmadd: bool=True,
            msg: str='') -> Union[NSM, NSM1, NSML, NSML1, NSMADD]:
        """
        Gets an LOAD or FORCE/PLOAD4/etc.

        Parameters
        ----------
        nsm_id : int
            the LOAD id
        consider_nsmadd : bool; default=True
            NSMADDs should not be considered when referenced from an NSM card
            from a case control, True should be used.
        msg : str
            additional message to print when failing

        """
        assert isinstance(nsm_id, integer_types), 'nsm_id=%s is not an integer\n' % nsm_id
        if consider_nsmadd and nsm_id in self.nsmadds:
            nsm = self.nsmadds[nsm_id]
        elif nsm_id in self.nsms:
            nsm = self.nsms[nsm_id]
        else:
            nsm_ids = list(self.nsms.keys())
            nsmadd_ids = list(self.nsmadds.keys())
            raise KeyError('cannot find NSM ID=%r%s.\nAllowed NSMs (e.g., NSM1)=%s; '
                           'NSMADDs=%s; consider_nsmadd=%s' % (
                               nsm_id, msg, np.unique(nsm_ids), np.unique(nsmadd_ids),
                               consider_nsmadd))
        return nsm

    #--------------------
    # Sets
    def SET1(self, set_id: int, msg: str='') -> SET1:
        """gets a SET1"""
        assert isinstance(set_id, integer_types), 'set_id=%s is not an integer\n' % set_id
        if set_id in self.sets:
            set1 = self.sets[set_id]
        else:
            raise KeyError(f'cannot find SET1 ID={set_id}{msg}.\n{msg}')
        return set1

    #--------------------
    # COORDINATES CARDS
    def Coord(self, cid: int, msg: str='') -> Coord:
        """gets an COORDx"""
        try:
            return self.coords[cid]
        except KeyError:
            cids = np.array(list(self.coord_ids))
            raise KeyError(f'cid={cid} not found{msg}.  Allowed Cids={cids}')

    #--------------------
    # AERO CARDS
    def AEList(self, aelist: int, msg: str='') -> AELIST:
        """gets an AELIST"""
        try:
            return self.aelists[aelist]
        except KeyError:
            aelists = _unique_keys(self.aelists)
            raise KeyError(f'aelist={aelist} not found{msg}.  Allowed AELIST={aelists}')

    def AEFact(self, aefact: int, msg: str='') -> AEFACT:
        """gets an AEFACT"""
        try:
            return self.aefacts[aefact]
        except KeyError:
            aefacts = _unique_keys(self.aefacts)
            raise KeyError(f'aefact={aefact} not found{msg}.  Allowed AEFACT={aefacts}')

    def Trim(self, trim_id: int, msg: str='') -> TRIM:
        """gets an TRIM"""
        try:
            return self.trims[trim_id]
        except KeyError:
            raise KeyError('TRIM=%s not found%s.  Allowed TRIM=%s'
                           % (trim_id, msg, _unique_keys(self.trims)))

    def AESurf(self, aesurf_name: str, msg: str='') -> AESURF:
        """gets an AESURF"""
        #if isinstance(aesurf_name, integer_types):
            #aesurf_id = aesurf_name
            #try:
                #return self.aesurf[aesurf_id]
            #except KeyError:
                #raise KeyError('aesurf=%s not found%s.  Allowed AESURF=%s'
                               #% (aesurf_id, msg, _unique_keys(self.aesurf)))
        #else:
        assert isinstance(aesurf_name, str), f'aesurf_name={aesurf_name!r}'

        for aesurf_int, aesurf in self.aesurf.items():
            if aesurf.label == aesurf_name:
                return aesurf

        aesurf_names = list(aesurf.label for aesurf in self.aesurf.values())
        aeparam_names = list(aeparm.label for aeparm in self.aeparams.values())
        raise KeyError('aesurf=%r not found%s.  Allowed AESURF=%s.  Allowed AEPARM=%s.' % (
            aesurf_name, msg, aesurf_names, aeparam_names))

    def AESurf_int(self, aesurf_id: int, msg: str='') -> AESURF:
        """gets an AESURF"""
        try:
            return self.aesurf[aesurf_id]
        except KeyError:
            raise KeyError('aesurf=%s not found%s.  Allowed AESURF=%s'
                           % (aesurf_id, msg, _unique_keys(self.aesurf)))

    def Acsid(self, msg: str='') -> Coord:
        """gets the aerodynamic coordinate system"""
        if self.aero is not None:
            acsid_aero = self.aero.Acsid()
        if self.aeros is not None:
            acsid_aeros = self.aeros.Acsid()

        if self.aero is not None:
            if self.aeros is not None:
                assert acsid_aero == acsid_aeros, f'AERO acsid={acsid_aero}, AEROS acsid={acsid_aeros}'
            coord = self.Coord(acsid_aero, msg=msg)
        elif self.aeros is not None:
            coord = self.Coord(acsid_aeros, msg=msg)
        else:
            msg = 'neither AERO nor AEROS cards exist%s.' % msg
            raise RuntimeError(msg)
        return coord

    def safe_acsid(self, msg: str='') -> Optional[Coord]:
        """gets the aerodynamic coordinate system"""
        if self.aero is not None:
            acsid_aero = self.aero.Acsid()
        if self.aeros is not None:
            acsid_aeros = self.aeros.Acsid()

        if self.aero is not None:
            if self.aeros is not None:
                assert acsid_aero == acsid_aeros, f'AERO acsid={acsid_aero}, AEROS acsid={acsid_aeros}'
            coord = self.Coord(acsid_aero, msg=msg)
        elif self.aeros is not None:
            coord = self.Coord(acsid_aeros, msg=msg)
        else:
            ## TODO: consider changing this...
            self.log.error(f'neither AERO nor AEROS cards exist; assuming global (cid=0){msg}.')
            return self.Coord(0, msg=msg)
        return coord

    def Aero(self, msg: str='') -> AERO:
        """gets the AERO"""
        if self.aero is not None:
            return self.aero
        raise RuntimeError('no AERO card found%s.' % (msg))

    def Aeros(self, msg: str='') -> AEROS:
        """gets the AEROS"""
        if self.aeros is not None:
            return self.aeros
        raise RuntimeError('no AEROS card found%s.' % (msg))

    def Spline(self, eid: int, msg: str='') -> Union[SPLINE1, SPLINE2, SPLINE3, SPLINE4, SPLINE5]:
        """gets a SPLINEx"""
        try:
            return self.splines[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed SPLINEx=%s'
                           % (eid, msg, _unique_keys(self.splines)))

    def CAero(self, eid: int, msg: str='') -> Union[CAERO1, CAERO2, CAERO3, CAERO4, CAERO5]:
        """gets an CAEROx"""
        try:
            return self.caeros[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed CAEROx=%s'
                           % (eid, msg, _unique_keys(self.caeros)))

    def PAero(self, pid: int, msg: str='') -> Union[PAERO1, PAERO2, PAERO3, PAERO4, PAERO5]:
        """gets a PAEROx"""
        try:
            return self.paeros[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed PAEROx=%s'
                           % (pid, msg, _unique_keys(self.paeros)))

    def Gust(self, sid: int, msg: str='') -> GUST:
        """gets a GUST"""
        try:
            return self.gusts[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed GUSTs=%s'
                           % (sid, msg, _unique_keys(self.gusts)))

    #--------------------
    # AERO CONTROL SURFACE CARDS
    def AEStat(self, aestat_name: str, msg: str='') -> AESTAT:
        """gets an AESTAT"""
        #if isinstance(aesurf_name, integer_types):
            #aesurf_id = aesurf_name
            #try:
                #return self.aesurf[aesurf_id]
            #except KeyError:
                #raise KeyError('aesurf=%s not found%s.  Allowed AESURF=%s'
                               #% (aesurf_id, msg, _unique_keys(self.aesurf)))
        #else:
        assert isinstance(aestat_name, str), f'aestat_name={aestat_name!r}'

        for aestat_int, aestat in self.aestats.items():
            if aestat.label == aestat_name:
                return aestat

        aesurf_names = list(aesurf.label for aesurf in self.aesurf.values())
        aeparam_names = list(aeparm.label for aeparm in self.aeparams.values())
        aestat_names = list(aeparm.label for aeparm in self.aeparams.values())
        raise KeyError('aestat=%r not found%s.  Allowed AESURF=%s.  Allowed AEPARM=%s.  Allowed AESTAT=%s' % (
            aestat_name, msg, aesurf_names, aeparam_names, aestat_names))

    def AEStat_int(self, aid: int, msg: str='') -> AESTAT:
        """gets an AESTAT"""
        try:
            return self.aestats[aid]
        except KeyError:
            raise KeyError('aid=%s not found%s.  Allowed AESTATs=%s'
                           % (aid, msg, _unique_keys(self.aestats)))

    def AELIST(self, aid: int, msg: str='') -> AELIST:
        """gets an AELIST"""
        try:
            return self.aelists[aid]
        except KeyError:
            raise KeyError('id=%s not found%s.  Allowed AELISTs=%s'
                           % (aid, msg, _unique_keys(self.aelists)))

    def AELink(self, link_id: int, msg: str='') -> AELINK:
        """gets an AELINK"""
        try:
            return self.aelinks[link_id]
        except KeyError:
            raise KeyError('link_id=%s not found%s.  Allowed AELINKs=%s'
                           % (link_id, msg, _unique_keys(self.aelinks)))

    def AEParam(self, aeparm_name: str, msg: str='') -> AEPARM:
        """gets an AEPARM"""
        #if isinstance(aeparam_name, integer_types):
            #aeparam_id = aeparam_name
            #try:
                #return self.aeparams[aesurf_id]
            #except KeyError:
                #raise KeyError('aesurf=%s not found%s.  Allowed AEPARM=%s'
                               #% (aesurf_id, msg, _unique_keys(self.aesurf)))
        #else:
        assert isinstance(aeparm_name, str), f'aeparm_name={aeparm_name!r}'

        for aeparam_int, aeparam in self.aeparams.items():
            if aeparam.label == aeparm_name:
                return aeparam

        aesurf_names = list(aesurf.label for aesurf in self.aesurf.values())
        aeparam_names = list(aeparm.label for aeparm in self.aeparams.values())
        raise KeyError('aeparam=%r not found%s.  Allowed AESURF=%s.  Allowed AEPARM=%s.' % (
            aeparm_name, msg, aesurf_names, aeparam_names))

    def AEParam_int(self, aid: int, msg: str='') -> AEPARM:
        """gets an AEPARM"""
        try:
            return self.aeparams[aid]
        except KeyError:
            raise KeyError('aid=%s not found%s.  Allowed AEPARMs=%s'
                           % (aid, msg, _unique_keys(self.aeparams)))

    #--------------------
    # FLUTTER CARDS

    def FLFACT(self, sid: int, msg: str='') -> FLFACT:
        """gets an FLFACT"""
        try:
            return self.flfacts[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed FLFACTs=%s'
                           % (sid, msg, _unique_keys(self.flfacts)))

    def Flutter(self, fid: int, msg: str='') -> FLUTTER:
        """gets a FLUTTER"""
        try:
            return self.flutters[fid]
        except KeyError:
            raise KeyError('fid=%s not found%s.  Allowed FLUTTERs=%s'
                           % (fid, msg, _unique_keys(self.flutters)))

    #--------------------
    # OPTIMIZATION CARDS

    def DConstr(self, oid: int, msg: str='') -> list[Union[DCONSTR, DCONADD]]:
        """gets a DCONSTR"""
        try:
            return self.dconstrs[oid]
        except KeyError:
            raise KeyError('oid=%s not found%s.  Allowed DCONSTRs=%s'
                           % (oid, msg, _unique_keys(self.dconstrs)))

    def DResp(self, dresp_id: int, msg: str='') -> Union[DRESP1, DRESP2, DRESP3]:
        """gets a DRESPx"""
        try:
            return self.dresps[dresp_id]
        except KeyError:
            raise KeyError('dresp_id=%s not found%s.  Allowed DRESPx=%s'
                           % (dresp_id, msg, _unique_keys(self.dresps)))

    def Desvar(self, desvar_id: int, msg: int='') -> DESVAR:
        """gets a DESVAR"""
        try:
            return self.desvars[desvar_id]
        except KeyError:
            raise KeyError('desvar_id=%s not found%s.  Allowed DESVARs=%s'
                           % (desvar_id, msg, _unique_keys(self.desvars)))

    def DDVal(self, oid: int, msg: str='') -> DDVAL:
        """gets a DDVAL"""
        try:
            return self.ddvals[oid]
        except KeyError:
            raise KeyError('oid=%s not found%s.  Allowed DDVALs=%s'
                           % (oid, msg, _unique_keys(self.ddvals)))

    def DVcrel(self, dv_id: int, msg: str='') -> Union[DVCREL1, DVCREL2]:
        """gets a DVCREL1/DVCREL2"""
        try:
            return self.dvcrels[dv_id]
        except KeyError:
            raise KeyError('dv_id=%s not found%s.  Allowed DVCRELx=%s'
                           % (dv_id, msg, _unique_keys(self.dvcrels)))

    def DVmrel(self, dv_id: int, msg: str='') -> Union[DVMREL1, DVMREL2]:
        """gets a DVMREL1/DVMREL2"""
        try:
            return self.dvmrels[dv_id]
        except KeyError:
            raise KeyError('dv_id=%s not found%s.  Allowed DVMRELx=%s'
                           % (dv_id, msg, _unique_keys(self.dvmrels)))

    def DVprel(self, dv_id: int, msg: str='') -> Union[DVPREL1, DVPREL2]:
        """gets a DVPREL1/DVPREL2"""
        try:
            return self.dvprels[dv_id]
        except KeyError:
            raise KeyError('dv_id=%s not found%s.  Allowed DVPRELx=%s'
                           % (dv_id, msg, _unique_keys(self.dvprels)))

    #--------------------
    # SET CARDS

    def Set(self, sid: int, msg: str='') -> Union[SET1, SET3]:
        """gets a SET, SET1, SET2, or SET3 card"""
        try:
            return self.sets[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed SETx=%s'
                           % (sid, msg, _unique_keys(self.sets)))

    #--------------------
    # METHOD CARDS
    def Method(self, sid: int, msg: str='') -> Union[EIGR, EIGRL]:
        """gets a METHOD (EIGR, EIGRL)"""
        try:
            return self.methods[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed METHODs=%s'
                           % (sid, msg, _unique_keys(self.methods)))

    def CMethod(self, sid: int, msg: str='') -> EIGC:
        """gets a METHOD (EIGC)"""
        try:
            return self.cMethods[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed CMETHODs=%s'
                           % (sid, msg, _unique_keys(self.cMethods)))

    #--------------------
    # TABLE CARDS
    def Table(self, tid, msg='') -> Union[TABLES1, TABLEST, TABLEH1, TABLEHT]:
        """gets a TABLES1, TABLEST, TABLEH1, TABLEHT"""
        try:
            return self.tables[tid]
        except KeyError:
            table_keys = _unique_keys(self.tables)
            raise KeyError('tid=%s not found%s.  Allowed TABLEs=%s'
                           % (tid, msg, table_keys))

    def TableD(self, tid: int, msg: str='') -> Union[TABLED1, TABLED2, TABLED3, TABLED4]:
        """gets a TABLEDx (TABLED1, TABLED2, TABLED3, TABLED4)"""
        try:
            return self.tables_d[tid]
        except KeyError:
            table_keys = _unique_keys(self.tables)
            tabled_keys = _unique_keys(self.tables_d)
            tablem_keys = _unique_keys(self.tables_m)
            raise KeyError('tid=%s not found%s.  Allowed TABLEDs=%s; TABLEs=%s; TABLEMs=%s'
                           % (tid, msg, tabled_keys, table_keys, tablem_keys))

    def TableM(self, table_id: int, msg: str='') -> Union[TABLEM1, TABLEM2, TABLEM3, TABLEM4]:
        """gets a TABLEx (TABLEM1, TABLEM2, TABLEM3, TABLEM4)"""
        try:
            return self.tables_m[table_id]
        except KeyError:
            table_keys = _unique_keys(self.tables)
            tabled_keys = _unique_keys(self.tables_d)
            tablem_keys = _unique_keys(self.tables_m)
            raise KeyError('table_id=%s not found%s.  Allowed TABLEMs=%s; TABLEs=%s; TABLEDs=%s'
                           % (table_id, msg, tablem_keys, table_keys, tabled_keys))

    def RandomTable(self, table_id: int, msg: str='') -> Union[TABRND1, TABRNDG]:
        """gets a TABRND1 / TABRNDG"""
        try:
            return self.random_tables[table_id]
        except KeyError:
            raise KeyError('table_id=%s not found%s.  Allowed TABLEs=%s'
                           % (table_id, msg, _unique_keys(self.random_tables)))

    #--------------------
    # NONLINEAR CARDS

    def NLParm(self, nlparm_id: int, msg: str='') -> NLPARM:
        """gets an NLPARM"""
        try:
            return self.nlparms[nlparm_id]
        except KeyError:
            raise KeyError('nlparm_id=%s not found%s.  Allowed NLPARMs=%s'
                           % (nlparm_id, msg, _unique_keys(self.nlparms)))

    #--------------------
    # MATRIX ENTRY CARDS
    def DMIG(self, dname: str, msg: str='') -> DMIG:
        """gets a DMIG"""
        try:
            return self.dmigs[dname]
        except KeyError:
            raise KeyError('dname=%s not found%s.  Allowed DMIGs=%s'
                           % (dname, msg, _unique_keys(self.dmigs)))

    def DEQATN(self, equation_id: int, msg: str='') -> DEQATN:
        """gets a DEQATN"""
        try:
            return self.dequations[equation_id]
        except KeyError:
            raise KeyError('equation_id=%s not found%s.  Allowed DEQATNs=%s'
                           % (equation_id, msg, _unique_keys(self.dequations)))

[docs] def get_pid_to_eid_map(model: BDF) -> dict[int, list[int]]: pid_to_eid_map = defaultdict(set) for eid, elem in model.elements.items(): pid = elem.pid pid_to_eid_map[pid].add(eid) return pid_to_eid_map
[docs] def get_pid_to_nid_map(model: BDF) -> dict[int, list[int]]: """TODO: doesn't support CONROD""" from collections import defaultdict get_pid_to_eid_map(model) property_to_nodes_map = defaultdict(set) for eid, elem in model.elements.items(): pid = elem.pid property_to_nodes_map[pid].update(elem.node_ids) property_to_nodes_map2 = {} for pid, nodes in property_to_nodes_map.items(): nodes_list = list(nodes) nodes_list.sort() property_to_nodes_map2[pid] = nodes_list return property_to_nodes_map2
[docs] def _unique_keys(mydict: dict[int, Any]) -> str: """helper method""" return np.unique(list(mydict.keys()))