Source code for pyNastran.bdf.bdf_interface.cross_reference

"""
Links up the various cards in the BDF.

For example, with cross referencing...

.. code-block:: python

   >>> model = BDF()
   >>> model.read_bdf(bdf_filename, xref=True)

   >>> nid1 = 1
   >>> node1 = model.nodes[nid1]
   >>> node.nid
   1

   >>> node.xyz
   [1., 2., 3.]

   >>> node.Cid()
   3

   >>> node.cid
   3

   >>> node.cid_ref
   CORD2S, 3, 1, 0., 0., 0., 0., 0., 1.,
           1., 0., 0.
   # get the position in the global frame
   >>> node.get_position()
   [4., 5., 6.]

   # get the position with respect to another frame
   >>> node.get_position_wrt(model, cid=2)
   [4., 5., 6.]


Without cross referencing...

.. code-block:: python

   >>> model = BDF()
   >>> model.read_bdf(bdf_filename, xref=True)

   >>> nid1 = 1
   >>> node1 = model.nodes[nid1]
   >>> node.nid
   1

   >>> node.xyz
   [1., 2., 3.]

   >>> node.Cid()
   3

   >>> node.cid
   3

   >>> node.cid_ref
   None

   # get the position in the global frame
   >>> node.get_position()
   Error!

Cross-referencing allows you to easily jump across cards and also helps
with calculating things like position, area, and mass.  The BDF is designed
around the idea of cross-referencing, so it's recommended that you use it.

"""
# pylint: disable=R0902,R0904,R0914
from collections import defaultdict
import traceback
from typing import List, Dict, Any

from numpy import zeros, argsort, arange, array_equal, array
from pyNastran.bdf.bdf_interface.attributes import BDFAttributes

class XrefMesh(BDFAttributes):
    """Links up the various cards in the BDF."""
    def __init__(self) -> None:
        """The main BDF class defines all the parameters that are used."""
        BDFAttributes.__init__(self)
        self._nxref_errors = 100
        self._stop_on_xref_error = True

    # def geom_check(self):
        # """
        # Performs various geometry checks
          # 1.  nodal uniqueness on elements
        # """
        # for elem in model.elements:
            # elem.check_unique_nodes()

    def cross_reference(self,
                        xref: bool=True,
                        xref_nodes: bool=True,
                        xref_elements: bool=True,
                        xref_nodes_with_elements: bool=False,
                        xref_properties: bool=True,
                        xref_masses: bool=True,
                        xref_materials: bool=True,
                        xref_loads: bool=True,
                        xref_constraints: bool=True,
                        xref_aero: bool=True,
                        xref_sets: bool=True,
                        xref_optimization: bool=True,
                        word: str='') -> None:
        """
        Links up all the cards to the cards they reference

        Parameters
        ----------
        xref : bool; default=True
           cross references the model
        xref_nodes : bool; default=True
           set cross referencing of nodes/coords
        xref_element : bool; default=True
           set cross referencing of elements
        xref_properties : bool; default=True
           set cross referencing of properties
        xref_masses : bool; default=True
           set cross referencing of CMASS/PMASS
        xref_materials : bool; default=True
           set cross referencing of materials
        xref_loads : bool; default=True
            set cross referencing of loads
        xref_constraints : bool; default=True
            set cross referencing of constraints
        xref_aero : bool; default=True
            set cross referencing of CAERO/SPLINEs
        xref_sets : bool; default=True
            set cross referencing of SETx
        word : str; default=''
            model flag

        To only cross-reference nodes:

        .. code-block:: python

           model = BDF()
           model.read_bdf(bdf_filename, xref=False)
           model.cross_reference(xref=True, xref_loads=False, xref_constraints=False,
                                            xref_materials=False, xref_properties=False,
                                            xref_aero=False, xref_masses=False,
                                            xref_sets=False)

        .. warning:: be careful if you call this method with False values
        """
        if not xref:
            return
        self.log.debug("Cross Referencing%s..." % word)
        if xref_nodes:
            self._cross_reference_nodes()
            self._cross_reference_coordinates()

        if xref_elements:
            self._cross_reference_elements()
        if xref_properties:
            self._cross_reference_properties()
        if xref_masses:
            self._cross_reference_masses()
        if xref_materials:
            self._cross_reference_materials()

        if xref_aero:
            self._cross_reference_aero()
        if xref_constraints:
            self._cross_reference_constraints()
        if xref_loads:
            self._cross_reference_loads()
        if xref_sets:
            self._cross_reference_sets()
        if xref_optimization:
            self._cross_reference_optimization()
        if xref_nodes_with_elements:
            self._cross_reference_nodes_with_elements()
        self._cross_reference_contact()
        self._cross_reference_superelements()
        #self.case_control_deck.cross_reference(self)
        self.pop_xref_errors()

        for super_id, superelement in sorted(self.superelement_models.items()):
            superelement.cross_reference(
                xref=xref, xref_nodes=xref_nodes, xref_elements=xref_elements,
                xref_nodes_with_elements=xref_nodes_with_elements,
                xref_properties=xref_properties, xref_masses=xref_masses,
                xref_materials=xref_materials, xref_loads=xref_loads,
                xref_constraints=xref_constraints, xref_aero=xref_aero,
                xref_sets=xref_sets, xref_optimization=xref_optimization,
                word=' (Superelement %i)' % super_id)

    def _cross_reference_constraints(self) -> None:
        """
        Links the SPCADD, SPC, SPCAX, SPCD, MPCADD, MPC, SUPORT,
        SUPORT1, SESUPORT cards.
        """
        for spcadds in self.spcadds.values():
            for spcadd in spcadds:
                spcadd.cross_reference(self)
        for spcs in self.spcs.values():
            for spc in spcs:
                spc.cross_reference(self)
        for spcoffs in self.spcoffs.values():
            for spcoff in spcoffs:
                spcoff.cross_reference(self)

        for mpcadds in self.mpcadds.values():
            for mpcadd in mpcadds:
                mpcadd.cross_reference(self)
        for mpcs in self.mpcs.values():
            for mpc in mpcs:
                mpc.cross_reference(self)

        for suport in self.suport:
            suport.cross_reference(self)

        for unused_suport1_id, suport1 in self.suport1.items():
            suport1.cross_reference(self)

        for se_suport in self.se_suport:
            se_suport.cross_reference(self)

    def _cross_reference_coordinates(self) -> None:
        """
        Links up all the coordinate cards to other coordinate cards and nodes
         - CORD1R, CORD1C, CORD1S
         - CORD2R, CORD2C, CORD2S
        """
        # CORD2x: links the rid to coordinate systems
        # CORD1x: links g1,g2,g3 to grid points
        for coord in self.coords.values():
            coord.cross_reference(self)

        for coord in self.coords.values():
            coord.setup()

    def _cross_reference_aero(self, check_caero_element_ids: bool=False) -> None:
        """
        Links up all the aero cards
          - CAEROx, PAEROx, SPLINEx, AECOMP, AELIST, AEPARAM, AESTAT, AESURF, AESURFS
        """
        self.zona.cross_reference()
        for caero in self.caeros.values():
            caero.cross_reference(self)

        for paero in self.paeros.values():
            paero.cross_reference(self)

        for trim in self.trims.values():
            trim.cross_reference(self)

        for csschd in self.csschds.values():
            csschd.cross_reference(self)

        for spline in self.splines.values():
            spline.cross_reference(self)

        for aecomp in self.aecomps.values():
            aecomp.cross_reference(self)

        for aelist in self.aelists.values():
            aelist.cross_reference(self)

        for aeparam in self.aeparams.values():
            aeparam.cross_reference(self)

        #for aestat in self.aestats.values():
            #aestat.cross_reference(self)

        for aesurf in self.aesurf.values():
            aesurf.cross_reference(self)

        for aesurfs in self.aesurfs.values():
            aesurfs.cross_reference(self)

        for flutter in self.flutters.values():
            flutter.cross_reference(self)

        for monitor_point in self.monitor_points:
            monitor_point.cross_reference(self)

        if self.aero:
            self.aero.cross_reference(self)
        if self.aeros:
            self.aeros.cross_reference(self)

        if check_caero_element_ids:  # only support CAERO1
            ncaeros = len(self.caeros)
            if ncaeros > 1:
                # we don't need to check the ncaeros=1 case
                i = 0
                min_maxs = zeros((ncaeros, 2), dtype='int32')
                for unused_eid, caero in sorted(self.caeros.items()):
                    min_maxs[i, :] = caero.min_max_eid
                    i += 1
                isort = argsort(min_maxs.ravel())
                expected = arange(ncaeros * 2, dtype='int32')
                if not array_equal(isort, expected):
                    msg = 'CAERO element ids are inconsistent\n'
                    msg += 'isort = %s' % str(isort)
                    raise RuntimeError(msg)

            #'AERO',     ## aero
            #'AEROS',    ## aeros
            #'GUST',     ## gusts
            #'FLUTTER',  ## flutters
            #'FLFACT',   ## flfacts
            #'MKAERO1', 'MKAERO2',  ## mkaeros
            #'AECOMP',   ## aecomps
            #'AEFACT',   ## aefacts
            #'AELINK',   ## aelinks
            #'AELIST',   ## aelists
            #'AEPARAM',  ## aeparams
            #'AESTAT',   ## aestats
            #'AESURF',  ## aesurfs

    def _cross_reference_nodes(self) -> None:
        """Links the nodes to coordinate systems"""
        grdset = self.grdset
        for node in self.nodes.values():
            try:
                node.cross_reference(self, grdset)
            except Exception:
                self.log.error("Couldn't cross reference GRID.\n%s" % (str(node)))
                raise

        for point in self.points.values():
            try:
                point.cross_reference(self)
            except Exception:
                self.log.error("Couldn't cross reference POINT.\n%s" % (str(point)))
                raise

        # SPOINTs, EPOINTs don't need xref

        # GRDPNT for mass calculations
        #if model.has_key()
        #for param_key, param in self.params:
            #if

    def _cross_reference_elements(self) -> None:
        """
        Links the elements to nodes, properties (and materials depending on
        the card).
        """
        for elem in self.elements.values():
            try:
                elem.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, elem)

        for elem in self.masses.values():
            try:
                elem.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, elem)

        for elem in self.rigid_elements.values():
            try:
                elem.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, elem)

        for elem in self.plotels.values():
            try:
                elem.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, elem)

    def _store_xref_error(self, error, card) -> None:
        self._ixref_errors += 1
        var = traceback.format_exception_only(type(error), error)
        self._stored_xref_errors.append((card, var))
        if self._ixref_errors > self._nxref_errors:
            self.pop_xref_errors()

    def _cross_reference_nodes_with_elements(self) -> None:
        """Links the nodes to all connected elements"""
        nodes = defaultdict(list)  # type: Dict[int, List[Any]]
        for element in self.elements.values():
            #if element.type in ['CONM2']:
            #    pass
            #else:
            if element.nodes is not None:
                for nid in element.node_ids:
                    if nid is None:
                        continue
                    nodes[nid].append(element)
                    #except AttributeError:
                        #print(element)
                        #print('node = %s' % str(node))
                        #raise
        for node in self.nodes.values():
            node.elements_ref = nodes[node.nid]

    def _cross_reference_masses(self) -> None:
        """
        Links the mass to nodes, properties (and materials depending on
        the card).
        """
        for mass in self.masses.values():
            try:
                mass.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, mass)

        for prop in self.properties_mass.values():
            try:
                prop.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, prop)

    def _cross_reference_properties(self) -> None:
        """Links the properties to materials"""
        for prop in self.properties.values():
            try:
                prop.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, prop)

    def _cross_reference_materials(self) -> None:
        """
        Links the materials to materials (e.g. MAT1, CREEP)
        often this is a pass statement
        """
        for mat in self.materials.values():  # MAT1
            try:
                mat.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, mat)

        for mat in self.creep_materials.values():  # CREEP
            try:
                mat.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, mat)

        # CREEP - depends on MAT1
        data = [self.MATS1, self.MATS3, self.MATS8,
                self.MATT1, self.MATT2, self.MATT3, self.MATT4, self.MATT5,
                self.MATT8, self.MATT9]
        for material_deps in data:
            for mat in material_deps.values():
                try:
                    mat.cross_reference(self)
                except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                    self._store_xref_error(error, mat)

    def _cross_reference_loads(self) -> None:
        """Links the loads to nodes, coordinate systems, and other loads."""
        for (unused_lid, load_combinations) in self.load_combinations.items():
            for load_combination in load_combinations:
                try:
                    load_combination.cross_reference(self)
                except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                    self._store_xref_error(error, load_combination)

        for (unused_lid, loads) in self.loads.items():
            for load in loads:
                try:
                    load.cross_reference(self)
                except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                    self._store_xref_error(error, load)

        for (unused_lid, sid) in self.dloads.items():
            for load in sid:
                #self.log.debug("  dloadi load=%s" % (load))
                try:
                    load.cross_reference(self)
                except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                    self._ixref_errors += 1
                    var = traceback.format_exception_only(type(error), error)
                    self._stored_xref_errors.append((load, var))
                    if self._ixref_errors > self._nxref_errors:
                        self.pop_xref_errors()

        for unused_lid, sid in self.dload_entries.items():
            for load in sid:
                #self.log.debug("  dloadi load=%s" % (load))
                try:
                    load.cross_reference(self)
                except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                    #raise
                    self._store_xref_error(error, load)

        for unused_key, darea in self.dareas.items():
            try:
                darea.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, darea)

        for unused_key, tic in self.tics.items():
            try:
                tic.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, tic)

        for unused_key, dphase in self.dphases.items():
            try:
                dphase.cross_reference(self)
            except (SyntaxError, RuntimeError, AssertionError, KeyError, ValueError) as error:
                self._store_xref_error(error, dphase)

    def _cross_reference_sets(self) -> None:
        """cross references the SET objects"""
        for set_obj in self.asets:
            set_obj.cross_reference(self)
        for set_obj in self.omits:
            set_obj.cross_reference(self)
        for set_obj in self.bsets:
            set_obj.cross_reference(self)
        for set_obj in self.csets:
            set_obj.cross_reference(self)
        for set_obj in self.qsets:
            set_obj.cross_reference(self)
        for unused_name, set_objs in self.usets.items():
            for set_obj in set_objs:
                set_obj.cross_reference(self)

        # superelements
        for unused_key, set_obj in self.se_sets.items():
            set_obj.cross_reference(self)
        for set_obj in self.se_bsets:
            set_obj.cross_reference(self)
        for set_obj in self.se_csets:
            set_obj.cross_reference(self)
        for set_obj in self.se_qsets:
            set_obj.cross_reference(self)
        for set_obj in self.se_usets:
            set_obj.cross_reference(self)

    def _cross_reference_optimization(self) -> None:
        """cross references the optimization objects"""
        for unused_key, deqatn in self.dequations.items():
            deqatn.cross_reference(self)
        for unused_key, dresp in self.dresps.items():
            dresp.cross_reference(self)
        for unused_key, dconstrs in self.dconstrs.items():
            for dconstr in dconstrs:
                dconstr.cross_reference(self)

        for unused_key, dvcrel in self.dvcrels.items():
            dvcrel.cross_reference(self)
        for unused_key, dvmrel in self.dvmrels.items():
            dvmrel.cross_reference(self)
        for unused_key, dvprel in self.dvprels.items():
            dvprel.cross_reference(self)
        for unused_key, desvar in self.desvars.items():
            desvar.cross_reference(self)

    def _safe_cross_reference_contact(self) -> None:
        """cross references the contact objects"""
        self._cross_reference_contact()

    def _cross_reference_contact(self) -> None:
        """cross references the contact objects"""
        for blseg in self.blseg.values():
            blseg.cross_reference(self)
        for bconp in self.bconp.values():
            bconp.cross_reference(self)

    def _uncross_reference_contact(self) -> None:
        """uncross references the contact objects"""
        for blseg in self.blseg.values():
            blseg.uncross_reference()
        for bconp in self.bconp.values():
            bconp.uncross_reference()

    def _cross_reference_superelements(self) -> None:
        """cross references the superelement objects"""
        for unused_seid, csuper in self.csuper.items():
            csuper.cross_reference(self)
        for unused_seid, csupext in self.csupext.items():
            csupext.cross_reference(self)

        for unused_seid, sebulk in self.sebulk.items():
            sebulk.cross_reference(self)
        for unused_seid, sebndry in self.sebndry.items():
            sebndry.cross_reference(self)
        for unused_seid, seconct in self.seconct.items():
            seconct.cross_reference(self)
        for unused_seid, seelt in self.seelt.items():
            seelt.cross_reference(self)
        for unused_seid, seexcld in self.seexcld.items():
            seexcld.cross_reference(self)
        for unused_seid, selabel in self.selabel.items():
            selabel.cross_reference(self)
        for unused_seid, seloc in self.seloc.items():
            seloc.cross_reference(self)
        for unused_seid, seload in self.seload.items():
            seload.cross_reference(self)
        for unused_seid, sempln in self.sempln.items():
            sempln.cross_reference(self)
        for unused_seid, setree in self.setree.items():
            setree.cross_reference(self)

        #'senqset',
        #'se_sets', 'se_usets',

    def _safe_cross_reference_superelements(
            self, create_superelement_geometry: bool=False) -> None:
        xref_errors = {}
        seloc_missing = []
        for seid, seloc in self.seloc.items():
            if seid in self.superelement_models:
                superelement = self.superelement_models[seid]
                seloc.safe_cross_reference(self, xref_errors)
                #seloc.transform(self)
            else:
                seloc_missing.append(seid)

        try:
            for unused_seid, sempln in sorted(self.sempln.items()):
                sempln.safe_cross_reference(self, xref_errors)
            for unused_seid, csuper in self.csuper.items():
                csuper.safe_cross_reference(self, xref_errors)
            for unused_seid, csupext in self.csupext.items():
                csupext.safe_cross_reference(self, xref_errors)

            if self.sebulk and create_superelement_geometry:
                #print('sebulk...')
                import os
                # we have to create the superelement in order to transform it...
                for seid, sebulk in self.sebulk.items():
                    super_filename = 'super_%i.bdf' % seid
                    if os.path.exists(super_filename):
                        os.remove(super_filename)
                    #print(sebulk)
                    rseid = sebulk.rseid
                    sebulk.safe_cross_reference(self, xref_errors)
                    mirror_model = self._create_superelement_from_sebulk(sebulk, seid, rseid)
                    if mirror_model is None:
                        continue
                    self.log.debug('made superelement %i' % seid)
                    self.superelement_models[seid] = mirror_model
                    mirror_model.write_bdf(super_filename)
            for unused_seid, sebndry in self.sebndry.items():
                sebndry.safe_cross_reference(self, xref_errors)
            for unused_seid, seconct in self.seconct.items():
                seconct.safe_cross_reference(self, xref_errors)
            for unused_seid, seelt in self.seelt.items():
                seelt.safe_cross_reference(self, xref_errors)
            for unused_seid, seexcld in self.seexcld.items():
                seexcld.safe_cross_reference(self, xref_errors)
            for unused_seid, selabel in self.selabel.items():
                selabel.safe_cross_reference(self, xref_errors)
            for seid in seloc_missing:
                seloc = self.seloc[seid]
                seloc.safe_cross_reference(self, xref_errors)
            for unused_seid, seload in self.seload.items():
                seload.safe_cross_reference(self, xref_errors)
            for unused_seid, setree in self.setree.items():
                setree.safe_cross_reference(self, xref_errors)
        except KeyError:
            if not create_superelement_geometry:
                raise
            self.write_bdf('superelement_xref.bdf')
            self.log.error('check superelement_xref.bdf')
            raise

    def _create_superelement_from_sebulk(self, sebulk, seid: int, rseid: int) -> None:
        """helper for sebulk"""
        #C:\MSC.Software\MSC.Nastran\msc20051\nast\tpl\see103q4.dat
        ref_model = self.superelement_models[rseid]
        if sebulk.superelement_type == 'MIRROR':
            from pyNastran.bdf.mesh_utils.mirror_mesh import bdf_mirror_plane
            #print('creating superelement %s from %s' % (seid, rseid))
            sempln = self.sempln[seid]
            plane = array([node.get_position() for node in sempln.nodes_ref])

            # What about seloc on the primary and sempln+seloc on the secondary?
            #  - move the primary
            #  - then apply the mirror to make the secondary
            #  - then move the secondary
            #
            # Or what about sempln+seloc on the tertiary?
            #
            # this is fine for the secondary
            if rseid in self.seloc:
                # I think this is wrong...
                seloc = self.seloc[rseid]
                plane = seloc.transform(self, plane)

            ref_model, mirror_model, unused_nid_offset, unused_eid_offset = bdf_mirror_plane(
                ref_model, plane, mirror_model=None, log=None, debug=True, use_nid_offset=False)
            mirror_model.properties = ref_model.properties
            mirror_model.materials = ref_model.materials
            new_model = mirror_model
        elif sebulk.Type in ['MANUAL', 'PRIMARY', 'COLLCTR', 'EXTERNAL']:
            self.log.info('skipping:\n%s' % sebulk)
            new_model = None
        else:  # pragma: no cover
            raise NotImplementedError(sebulk)
        return new_model

    def _uncross_reference_superelements(self) -> None:
        """cross references the superelement objects"""
        for unused_seid, csuper in self.csuper.items():
            csuper.uncross_reference()
        for unused_seid, csupext in self.csupext.items():
            csupext.uncross_reference()

        for unused_seid, sebulk in self.sebulk.items():
            sebulk.uncross_reference()
        for unused_seid, sebndry in self.sebndry.items():
            sebndry.uncross_reference()
        for unused_seid, seconct in self.seconct.items():
            seconct.uncross_reference()
        for unused_seid, seelt in self.seelt.items():
            seelt.uncross_reference()
        for unused_seid, seexcld in self.seexcld.items():
            seexcld.uncross_reference()
        for unused_seid, selabel in self.selabel.items():
            selabel.uncross_reference()
        for unused_seid, seloc in self.seloc.items():
            seloc.uncross_reference()
        for unused_seid, seload in self.seload.items():
            seload.uncross_reference()
        for unused_seid, sempln in self.sempln.items():
            sempln.uncross_reference()
        for unused_seid, setree in self.setree.items():
            setree.uncross_reference()

    def get_point_grids(self, nodes: List[Any], msg: str='') -> None:
        """gets GRID, POINT cards"""
        nodes_ref = []
        missing_nids = []
        for nid in nodes:
            if nid in self.nodes:
                node = self.nodes[nid]
            elif nid in self.points:
                node = self.points[nid]
            else:
                missing_nids.append(nid)
                continue
            nodes_ref.append(node)
        if missing_nids:
            raise KeyError('missing GRID/POINT nids=%s%s' % (missing_nids, msg))
        return nodes_ref

    def superelement_nodes(self, seid: int, nodes: List[Any], msg: str='') -> None:
        if seid == 0:
            return self.Nodes(nodes, msg=msg)
        try:
            superelement = self.superelement_models[seid]
        except KeyError:
            keys = list(self.superelement_models.keys())
            raise KeyError('cant find superelement=%i%s; seids=%s' % (seid, msg, keys))
        return superelement.Nodes(nodes, msg=msg)

    def geom_check(self, geom_check: bool, xref: bool) -> None:  # pragma: no cover
        """
        what about xref?
        """
        if geom_check:
            if xref:
                for unused_eid, element in self.elements.values():
                    #element.Mass()
                    element._verify(xref=True)
                #if 'GEOMCHECK' in self.params:  # should this be an executive control parameter?
                    #for eid, element in model.elements:
                        #element._verify()
            else:
                for unused_eid, element in self.elements.values():
                    element.verify_unique_node_ids()
                    element._verify(xref=False)

            # aspect ratio - ratio between element edges
            # warping - how planar is a face
            # taper - split a quad into 2 triangles and compare the area
            # skew - an angle, measures how skewed an element face is by drawing lines
            #        between midpoints of elements edges, finding the smallest angle
            #        between the intersecting lines and subtracting that from 90 degrees
            # Jacobian - how much does element deviate from the ideal shape by taking the
            #            determinant of the Jacobian matrix
            # quad skew <= 30.
            # quad warp >= 0.05
            # quad taper >= 0.5
            # quad iamin <= 30.
            # quad iamax >= 150.

            # tria skew <= 10.
            # tria iamax <= 160.

            # tetra ar >= 100.
            # tetra elpr <= 0.5
            # tetra detj <= 0.

            # hex ar >= 100.
            # hex elpr <= 0.5
            # hex detj <= 0.
            # hex warp <= 0.707

            # penta ar >= 100.
            # penta elpr <= 0.5
            # penta detj <= 0.
            # penta warp <= 0.707

            # pyram ar >= 100.
            # pyram elpr <= 0.5
            # pyram detj <= 0.
            # pyram warp <= 0.707