Source code for pyNastran.bdf.bdf

# coding: utf-8
# pylint: disable=W0201,R0915,R0912
"""
Main BDF class.  Defines:
  - BDF

"""
# TABLE3D TID X0 Y0 Z0 F0
# X1 Y1 Z1 F1 X2 Y2 Z2 F2
# X3 Y3 Z3 F3 X4 Y4 Z4 F4
# -etc.- ENDT

# see https://docs.plm.automation.siemens.com/tdoc/nxnastran/10/help/#uid:index
from __future__ import annotations
import os
import sys
from copy import deepcopy
from collections import Counter
from io import StringIO, IOBase
from pathlib import PurePath
from functools import wraps
#from functools import partial
from collections import defaultdict
import traceback

from typing import (
    Sequence, Optional, Union, Any, cast, TYPE_CHECKING)
from pickle import load, dump, dumps  # type: ignore

import numpy as np  # type: ignore
in1d = np.in1d
#in1d = np.in1d if hasattr(np, 'in1d') else getattr(np, 'in')
from cpylog import get_logger2

from pyNastran.utils import PathLike, object_attributes, check_path, deprecated as _deprecated
from .utils import parse_patran_syntax
from .bdf_interface.utils import (
    _parse_pynastran_header, to_fields, parse_executive_control_deck,
    fill_dmigs, _get_card_name, _parse_dynamic_syntax,
)
from pyNastran.bdf.bdf_interface.add_card import CARD_MAP
from .bdf_interface.replication import (
    to_fields_replication, get_nrepeats, int_replication, float_replication,
    _field, repeat_cards)

from .field_writer_8 import print_card_8
from .field_writer_16 import print_card_16, print_field_16

from .cards.base_card import _format_comment
from .cards.utils import wipe_empty_fields

#from .write_path import write_include
from .bdf_interface.assign_type import (integer,
                                        integer_or_string, string)

from pyNastran.bdf.bdf_interface.model_group import ModelGroup
from .cards.elements.elements import CFAST, CGAP, CRAC2D, CRAC3D, PLOTEL, GENEL
from .cards.properties.properties import PFAST, PGAP, PRAC2D, PRAC3D
from .cards.properties.solid import PLSOLID, PSOLID, PIHEX, PCOMPS, PCOMPLS
from .cards.cyclic import CYAX, CYJOIN
from .cards.msgmesh import CGEN

from .cards.elements.springs import CELAS1, CELAS2, CELAS3, CELAS4
from .cards.properties.springs import PELAS, PELAST

from .cards.elements.solid import (
    #CTETRA, CPYRAM, CPENTA, CHEXA,
    CIHEX1, CIHEX2, CHEXA1, CHEXA2,
    CTETRA4, CPYRAM5, CPENTA6, CHEXA8,
    CTETRA10, CPYRAM13, CPENTA15, CHEXA20,
)
from .cards.elements.rigid import RBAR, RBAR1, RBE1, RBE2, RBE3, RROD, RSPLINE, RSSCON
from .cards.bolt import BOLT, BOLTLD, BOLTFOR, BOLTSEQ, BOLTFRC, BOLT_MSC

from .cards.axisymmetric.axisymmetric import (
    AXIF, RINGFL,
    AXIC, RINGAX, POINTAX, CCONEAX, PCONEAX, )
from .cards.axisymmetric.loads import PLOADX1, FORCEAX, PRESAX, TEMPAX
from .cards.elements.axisymmetric_shells import (
    CTRAX3, CTRAX6, CTRIAX, CTRIAX6, CQUADX, CQUADX4, CQUADX8)
from pyNastran.bdf.cards.elements.shell import (
    CQUAD, CQUAD4, CQUAD8, CQUADR, CSHEAR,
    CTRIA3, CTRIA6, CTRIAR,
    CPLSTN3, CPLSTN4, CPLSTN6, CPLSTN8,
    CPLSTS3, CPLSTS4, CPLSTS6, CPLSTS8,
    SNORM,)
from pyNastran.bdf.cards.elements.shell_nasa95 import (
    CTRSHL, CQUAD1, PQUAD1)

from .cards.properties.shell import PSHELL, PCOMP, PCOMPG, PSHEAR, PLPLANE, PPLANE, PTRSHL
from .cards.elements.acoustic import (
    CHACAB, CAABSF, CHACBR, PACABS, PAABSF, PACBAR,
    ACMODL, PMIC, ACPLNW, AMLREG, MATPOR, MICPNT)
from .cards.elements.bush import CBUSH, CBUSH1D, CBUSH2D
from .cards.properties.bush import PBUSH, PBUSH1D, PBUSHT, PBUSH_OPTISTRUCT
from .cards.elements.damper import (CVISC, CDAMP1, CDAMP2, CDAMP3, CDAMP4,
                                    CDAMP5)
from .cards.properties.damper import PVISC, PDAMP, PDAMP5, PDAMPT
from .cards.elements.rods import CROD, CONROD, CTUBE
from .cards.elements.bars import CBAR, BAROR, CBARAO, CBEAM3, CBEND
from .cards.elements.beam import CBEAM, BEAMOR
from .cards.properties.rods import PROD, PTUBE
from .cards.properties.bars import PBAR, PBARL, PBRSECT, PBEND, PBEAM3
from .cards.properties.beam import PBEAM, PBEAML, PBCOMP, PBMSECT
# CMASS5
from .cards.elements.mass import CONM1, CONM2, CMASS1, CMASS2, CMASS3, CMASS4
from .cards.properties.mass import PMASS, NSM, NSM1, NSML, NSML1, NSMADD
from .cards.constraints import (SPC, SPCADD, SPCAX, SPC1, SPCOFF, SPCOFF1,
                                MPC, MPCADD, SUPORT1, SUPORT, SESUP,
                                GMSPC)
from .cards.coordinate_systems import (MATCID,
                                       CORD1R, CORD1C, CORD1S,
                                       CORD2R, CORD2C, CORD2S, #CORD3G,
                                       transform_coords_vectorized,
                                       CORDx)
#from .cards.coordinate_systems.msgmesh import CGEN, GMCORD, GMLOAD
from .cards.deqatn import DEQATN
from .cards.dynamic import (
    DELAY, DPHASE, FREQ, FREQ1, FREQ2, FREQ3, FREQ4, FREQ5,
    TSTEP, TSTEP1, TSTEPNL, NLPARM, NLPCI, TF, ROTORG, ROTORD, TIC)
from .cards.loads.loads import (
    LSEQ, SLOAD, DAREA, RFORCE, RFORCE1, SPCD, DEFORM, LOADCYN, LOADCYH)
from .cards.loads.dloads import ACSRCE, DLOAD, TLOAD1, TLOAD2, RLOAD1, RLOAD2
from .cards.loads.static_loads import (LOAD, CLOAD, GRAV, ACCEL, ACCEL1, FORCE,
                                       FORCE1, FORCE2, MOMENT, MOMENT1, MOMENT2,
                                       PLOAD, PLOAD1, PLOAD2, PLOAD4)

from .cards.loads.random_loads import RANDPS, RANDT1

from .cards.materials import (MAT1, MAT2, MAT3, MAT4, MAT5,
                              MAT8, MAT9, MAT10, MAT11, MAT3D,
                              MATG, MATHE, MATHP, MATEV,
                              CREEP, EQUIV, NXSTRAT)
from .cards.material_deps import (
    MATT1, MATT2, MATT3, MATT4, MATT5, MATT8, MATT9, MATS1, MATDMG)

from .cards.methods import EIGB, EIGC, EIGR, EIGP, EIGRL, MODTRAK
from .cards.nodes import GRID, GRDSET, SPOINTs, EPOINTs, POINT, SEQGP, GRIDB
from .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, MONDSP1,
    SPLINE1, SPLINE2, SPLINE3, SPLINE4, SPLINE5)
from .cards.aero.static_loads import AESTAT, AEROS, CSSCHD, TRIM, TRIM2, DIVERG
from .cards.aero.dynamic_loads import AERO, FLFACT, FLUTTER, GUST, MKAERO1, MKAERO2
from .cards.optimization import (
    DCONADD, DCONSTR, DESVAR, TOPVAR, DDVAL, DOPTPRM, DLINK,
    DRESP1, DRESP2, DRESP3,
    DVCREL1, DVCREL2,
    DVMREL1, DVMREL2,
    DVPREL1, DVPREL2,
    DVGRID, DSCREEN)
from .cards.optimization_nx import (
    DVTREL1, GROUP, DMNCON,
)
from .cards.superelements import (
    RELEASE, SEBNDRY, SEBULK, SECONCT, SEELT, SEEXCLD,
    SELABEL, SELOAD, SELOC, SEMPLN, SENQSET, SETREE,
    CSUPER, CSUPEXT,
)
from .cards.bdf_sets import (
    ASET, BSET, CSET, QSET, USET,
    ASET1, BSET1, CSET1, QSET1, USET1,
    OMIT, OMIT1,
    SET1, SET2, SET3,
    SEBSET, SECSET, SEQSET, # SEUSET
    SEBSET1, SECSET1, SEQSET1, # SEUSET1
    SESET, #SEQSEP
    RADSET,
)
from .cards.params import PARAM, PARAM_MYSTRAN, PARAM_NASA95, MDLPRM
from .cards.dmig import DMIG, DMI, DMIJ, DMIK, DMIJI, DMIG_UACCEL, DTI, DTI_UNITS, DMIAX
from .cards.thermal.loads import (QBDY1, QBDY2, QBDY3, QHBDY, TEMP, TEMPD, TEMPB3,
                                  TEMPRB, QVOL, QVECT)
from .cards.thermal.thermal import (CHBDYE, CHBDYG, CHBDYP, PCONV, PCONVM,
                                    PHBDY, CONV, CONVM, TEMPBC)
from .cards.thermal.radiation import RADM, RADBC, RADCAV, RADLST, RADMTX, VIEW, VIEW3D
from .cards.bdf_tables import (TABLED1, TABLED2, TABLED3, TABLED4,
                               TABLEM1, TABLEM2, TABLEM3, TABLEM4,
                               TABLES1, TABDMP1, TABLEST, TABLEHT, TABLEH1,
                               TABRND1, TABRNDG,
                               DTABLE)
from .cards.contact import (
    BCRPARA, BCTADD, BCTSET, BSURF, BSURFS, BCPARA, BCTPARA, BCONP, BLSEG, BFRIC,
    BCTPARM, BGADD, BGSET, BCBODY)
from .cards.parametric.geometry import PSET, PVAL, FEEDGE, FEFACE, GMCURV, GMSURF

from .case_control_deck import CaseControlDeck, Subcase
from .bdf_methods import BDFMethods
from .bdf_interface.get_card import GetCard
from .bdf_interface.add_card import AddCards
from .bdf_interface.bdf_card import BDFCard
from .bdf_interface.write_mesh_file import WriteMeshs
from .bdf_interface.uncross_reference import UnXrefMesh
from .bdf_interface.verify_validate import verify_bdf, validate_bdf
from .bdf_interface.stats import get_bdf_stats

from .errors import (CrossReferenceError, DuplicateIDsError,
                                  CardParseSyntaxError, UnsupportedCard, DisabledCardError,
                                  SuperelementFlagError, ReplicationError)
from .bdf_interface.pybdf import (
    BDFInputPy, _clean_comment, _clean_comment_bulk, _check_for_spaces,
    add_superelements_from_deck_lines,
)

#from .bdf_interface.add_card import CARD_MAP
if TYPE_CHECKING:  # pragma: no cover
    from cpylog import SimpleLogger

CORD = Union[CORD1R, CORD1C, CORD1S,
             CORD2R, CORD2C, CORD2S]

REMOVED_CARDS = {
    'ADAPT',
    'PVAL', 'GMCURV', 'GMSURF', 'FEEDGE', 'FEFACE', 'GMSPC', 'GMLOAD',
    #'OUTPUT'
    #'OUTRCV'
    'GMBNDS', 'GMINTS', 'PINTS',
    'GMBNDC', 'GMINTC', 'PINTC'
    'CGEN', 'EGRID', 'GRIDG', 'SPCG',
}

SOL_700 = {
    ## Explicit Nonlinear (SOL 700)
    'ABINFL', 'AIRBAG', 'ATBACC', 'ATBJNT', 'ATBSEG',
    'BARRIER',
    'BCBODY', 'BCBODY1', 'BCBOX', 'BCELIPS', 'BCGRID', 'BCMATL', 'BCONECT',
    'BCONPRG', 'BCONPRP', 'BCPROP', 'BCSEG', 'BCTABL1', 'BCTABLE',
    'BIAS', 'BJOIN', 'BSURF',
    'CDAMP1D', 'CDAMP2D', 'CELAS1D', 'CELAS2D', 'CMARKB2', 'CMARKN1', 'COHFRIC',
    'COMPUDS', 'CORD3R',
    'COUCOHF', 'COUOPT', 'COUP1FL', 'COUPINT', 'COUPLE',
    'CSPR', 'CYLINDR', 'DETSPH', 'DYFSISW', 'DYPARAM',
    'EOSDEF', 'EOSGAM', 'EOSGRUN', 'EOSIG', 'EOSJWL',
    'EOSMG', 'EOSNA', 'EOSPOL', 'EOSUDS',
    'EOSTAIT', 'EULFOR', 'EULFOR1', 'EULFREG',
    'FAILJC', 'FAILMPS', 'FAILUDS', 'FFCONTR',
    'FLOW', 'FLOWC', 'FLOWDEF', 'FLOWT', 'FLOWUDS', 'FORCE2', 'FORCUDS',
    'GBAG', 'GBAGCOU',
    'HEATLOS', 'HGSUPPR', 'HTRCONV', 'HTRRAD', 'HYDSTAT',
    'INFLCG', 'INFLFRC', 'INFLGAS', 'INFLHB', 'INFLTNK', 'INFLTR', 'INITGAS', 'LEAKAGE',
    'MATBV', 'MATDEUL', 'MATEP', 'MATF', 'MATFAB', 'MATHE', 'MATORT', 'MATRIG', 'MATVE',
    'MESH', 'MOMENT2', 'NLOUTUD',
    'PBEAML', 'PBELT', 'PCOMPA', 'PELAS1', 'PERMEAB', 'PERMGBG', 'PEULER', 'PEULER1', 'PMARKER', 'PMINC',
    'PORFCPL', 'PORFGBG', 'PORFLOW', 'PORFLWT', 'PORHOLE', 'PORHYDS', 'PORUDS',
    'PSHELL1', 'PSPRMAT', 'PVISC1', 'RBJOINT', 'RELEX',
    'SHREL', 'SHRPOL', 'SHRUDS',
    'SPHERE', 'SURFINI',
    'TABLUDS', 'TIC3', 'TICEL', 'TICEUDS', 'TICEUL1', 'TICREG', 'TICVAL',
    'TODYNA',
    'WALL',
    'YLDHY', 'YLDJC', 'YLDMC', 'YLDMSS', 'YLDPOL', 'YLDRPL',
    'YLDSG', 'YLDTM', 'YLDUDS', 'YLDVM', 'YLDZA',
}
MISSING_CARDS = {
    # msgmesh
    'CGEN', 'GMSPC', 'GMCURV', 'GMLOAD', 'FEFACE', 'GMSURF', 'GMINTS', 'PVAL', 'PINTS',
    'EGRID', 'ADAPT', 'GRIDG', 'MESHOPT', 'OUTPUT', 'OUTRCV', 'SPCG', 'GRIDU',
    'GMINTC', 'PINTC', 'GMBNDS', 'GMBC', 'GMCONV', 'PGEN', 'GMQVOL',
    'CNGRNT',

    # slot
    'GRIDF',
    'CSLOT3', 'CSLOT4', 'CAXIF2', 'CAXIF3', 'AXSLOT', 'SLBDY',

    'EQUIV', 'EXTRN', 'DSCONS', 'DVGEOM', 'DVAR', 'DVSET',
    'ADUM1', 'ADUM8', 'ADUM9', 'GRIDS',

    'CFLUID2', 'CFLUID3', 'CFLUID4', 'FSLIST', 'BNDGRID', 'BDYLIST', 'PRESPT',
    'FREEPT', 'FLSYM',
    # ----------------------------
    'RJOINT', 'RTRPLT', 'RTRPLT1', 'DYNRED',

    ## fatigue
    'FTGDEF', 'FTGPARM', 'FTGEVNT', 'FTGLOAD', 'FTGSEQ',
    'MATFTG', 'PFTG', 'TABLFTG', 'UDNAME', 'SET4',
    'TOPSTR', 'TOPDMG',

    ## acoustic
    'CACINF3', 'CACINF4',

    ## Non Linear (SOL 400)
    'BCBODY', 'BSURF', 'BCTABLE', 'BCPARA', 'PSLDN1',

    ## Implicit Nonlinear (SOL 600) - marc
    'PARAMARC', 'MARCIN', 'MARCOUT',
    'RESTART',
    'MATD001', 'MATD003', 'MATD005','MATD006', 'MATD007', 'MATD009',
    'MATD012', 'MATD013', 'MATD014', 'MATD015', 'MATD018', 'MATD019',
    'MATD020', 'MATD022', 'MATD024', 'MATD026', 'MATD027', 'MATD028',
    'MATD030', 'MATD031', 'MATD034', 'MATD036',
    'MATD054', 'MATD055', 'MATD057', 'MATD059',
    'MATD062', 'MATD063', 'MATD064', 'MATD077',
    'MATD080', 'MATD081', 'MATD127', 'MATD181',
    'MATD20M',
    'MATD2AN', 'MATD2OR',
    'MATORT', 'MATTORT',
    'MATEP', 'MATTEP',
    'MATHE', 'MATTHE',
    'MATVP',
    'MATSMA',
    'MATVE', 'MATTVE',
    'MATG', 'MATTG'
    'MATF',
    'MATVB',
    'MATHED',
    'COHSEIV',
    'DEACTEL', 'ACTIVAT',
    # properties
    'PCOMPF', 'MSTACK', 'GASKET',
    # control
    'NLAUTO', 'NLDAMP', 'NLSTRAT',
    # solid -> shell
    'CSSHLH', 'CSSHLP',
    # solid element connector
    'CSSHL', 'PSSHL',
    # contact bodies
    'BCBODY', 'BCHANGE',
    'GMNURB', 'BSURF', 'BCBOX', 'BCPROP', 'BCMATL',
    # contact parameters
    'BCONTACT', 'BCPARA',
    # contact table
    'BCTABLE',
    # contact movement
    'BCMOVE',
    # thermal contact
    'MPHEAT', 'NLHEAT', 'MCHSTAT', 'MINSTAT',
    'MHEATSHL', 'MTHERM',

    ## nx bolts
    'BOLT', 'BOLTFRC', 'BOLTFOR', 'BOLTLD', 'BOLTSEQ',

    ## msc bolts?
    'MBOLT', 'MBOLTUS', # 'BOLT'

    ## uds
    'PORUDS', 'YLDUDS', 'SHRUDS', 'FAILUDS', 'COMPUDS',
    'FLOWUDS','BCONUDS', 'ELEMUDS', 'UDSESV', 'MATUDS',
    'TICEUDS', 'EOSUDS', 'TABLUDS', 'GENUDS', 'ENTUDS',

    # explosives
    'EXPLSV', 'PLBLAST',

    # rotor
    'ROTOR', 'ROTORB', 'ROTPARM', 'ROTORAX', 'ROTORSE',

    # elements/properties
    'CELAS1D',
    'PRODN1',
    'CBARG',
    'PBEMN1',
    'PSHELL1', 'PSHL3D',
    'PSLDN1', 'PSHELLD', 'PSOLIDD',
    'PSHLN1', 'PSHLN2',
    'PCOMPG1', 'PCOMPLS', 'PLCOMP',
    'PBUSH2D', 'PBSH2DT',
    'CYSYM', 'CSEAM', 'PSEAM', 'CWSEAM', 'PWSEAM',
    'PACINF', 'PAXSYMH',
    'CBEAR', 'PBEAR',
    'CSPOT', 'CFILLET',
    'CBUTT',
    'PCOHE',
    'CWELD', 'PWELD',

    ## rigid_elements
    'RSPINT', 'RSPINR', 'RBE2GS',

    ## materials
    'MATF', 'MATEP', 'MATVE', 'MCOHE', 'MATM',
    'MATDT01', 'MATDIGI', 'MATUSR', 'MATTC',
    'MATORT', 'MATTORT', 'MATTHE', 'MATPLCY',
    'MATSMA', 'MAT8A', 'MATTEP',
    'MATPOR', # 'MATDMG',
    'MAT2F', 'MAT8F',

    ## loads
    'FORCDST', 'PLOADX', 'PLOADB3', 'PLOADG', 'QBDY4', 'SLOADN1',
    'TEMPP1', 'TTEMP', 'RGYRO', 'PLOADE1', 'RCROSSC', 'LOADCYT',
    'TEMPN1',

    ## boundaries
    'BNDFREE', 'BNDFREE1',
    'BNDFIX', 'BNDFIX1',
    'SPCR',

    ## aero
    'UXVEC', 'GUST2', 'RVDOF', 'RVDOF1', 'AEFORCE', 'AEPRESS',

    ## acoustics
    'MICPNT',

    ## coords
    'CORD1RX', 'CORD3G',

    ## brakes
    'BRKSQL',

    ## d2r
    'D2R0000', 'D2RAUTO', 'D2RINER',

    'MATD016', 'MATD029', 'MATD053', 'MATD066', 'MATD067', 'MATD069',
    'MATD070', 'MATD078', 'MATD093', 'MATD094', 'MATD095', 'MATD097',
    'MATD098', 'MATD099',
    'MATDS01', 'MATDS02','MATDS03','MATDS04', 'MATDS05','MATDS06','MATDS07','MATDS08',
    'MATDS13','MATDS14','MATDS15',
    'MATDSW1', 'MATDSW2', 'MATDSW3', 'MATDSW4', 'MATDSW5',

    # ???
    'MONGRP', 'THPAD', 'TABLEDR',
    'DMIGOUT', 'MPCOUT', 'BCBZIER', 'BCPATCH', 'MDBULK', 'BCSCAP',
    'PRJCON',
    'TABLRPC', 'CIFQDX', 'NLADAPT', 'EOSTAB', 'EOSTABC',
    'TABLED5',
    'TIMNAT', 'ROTHYBD',
    'GRIA',
    'PANEL', 'TRMCPL',
    'DAMPING', 'DAMPGBL',
    'MONCARL',
    'PLCYISO', 'PLCYKIN', 'PLCYRUP',
    'BCTABLE', 'BCTABL1', 'BCAUTOP', 'BCBODY1', 'BCPROP', 'BCPROPS', 'BCBMRAD',
    'BGPARM', 'BCHANGE', 'BOUTPUT', 'TCNTPRM', 'BSQUEAL',
    'DELETE', 'RENAME',
    'ITER', 'GROUP', 'LIST',
    'MATRIG',
    'DVSHAP', 'DVBSHAP',
    'DISTORT', 'UNGLUE',
    'NLSTEP', 'NLAUTO', 'NLMOPTS', 'NLOUT', 'ERPPNL',
    'RBAX3D', 'ACPEMCP',
    'FTGDEF', 'CAMPBLL', 'UNBALNC', 'ECHO',
    'CINTC', 'GMBNDC',
    'SET4', 'PFTG', 'FTGPARM', 'UDNAME', 'FTGSEQ',
    'ELIST', 'MFLUID',
    'TEMPF', 'TEMPG', 'HADAPTL', 'HADACRI',
    'CIFPENT',
    'ELAR2', 'EBDSET', 'TMCPARA',
    'PEULER1', 'MATDEUL', 'EOSPOL', 'SHREL', 'YLDMC', 'PMINC', 'COUPLE', 'COUCOHF',
    'COHFRIC', 'MESH', 'TICVAL', 'TICEUL1', 'TICREG', 'CYLINDR',
    'SWLDPRM', 'PLOTOPT', 'PLOTE', 'PLOTG',
    'FTGEVNT', 'RADBND', 'RADMT', 'NOLIN1', 'NOLIN2', 'NOLIN3', 'NOLIN4',
    'NLFREQ1', 'NLRGAP',
    'ACADAPT', 'ACORDER', 'AMLREG',
    'NLCNTL', 'NLCNTLG', 'NLCNTL2', 'NLSTRAT', 'NLRSFD', 'NLHARM',
    'DMRLAW',
    'TABLE3D', 'TABL3D0',
    'CONTRLT',
    'MPOINT',
    'CFTUBE', 'CHBDY', 'MONSUM', 'TMPSET', 'HYBDAMP',
    'TOMVAR', 'STOCHAS', 'NHRMPRM', 'MONSUM', 'CYLINDER', 'EOSGAM',
    'SPHERE', 'BJOIN', 'PMARKER', 'TICEUL', 'PEULER', 'BCBOX',
    'NLOUTUD', 'YLDVM', 'MATBV', 'SEDRSP2', 'SEDRSP3', 'SEDLINK', 'MFUN',
    'MTAB', 'TIC3', 'TABSCTL', 'SPLINEX', 'BCONECT', 'BCONPRG',
    'BCSURF', 'BCGRID', 'PAXISYM', 'BEADVAR', 'BLDOUT', 'GRIDMOD',
    'IPSTRAIN', 'RESTART', 'TICD',
    'RCROSS', 'CYSUP', 'FBODYLD', 'BDYOR', 'RANDVAR',
    'L16MOD', 'SECTAX',
    'ACIFPRM', 'BCBDPRP', 'MDMIOUT', 'MPROCS', 'FBODYSB',
    'CCRSFIL', 'COMBWLD',
    'CIFHEX',
    'FSICTRL', 'FLOW', 'FLOWDEF', 'BCSEG', 'CMARKN1', 'LEAKAGE', 'TICEL',
    'EOSMG', 'YLDJC', 'MAT1A', 'HGSUPPR',
    'EOSTAIT', 'EOSJWL', 'SURFINI', 'WALL', 'MPCY',
    'TIMNVH', 'CIFQUAD', 'VCCT',
    'DYTIMHS', 'PRESTRS', 'SEQROUT', 'BCRIGID', 'BCMOVE', 'ISTRESS',
    'WETLOAD', 'WETELMG', 'COUP1FL', 'PORFLOW', 'PERMEAB', 'ENDDYNA',
    'COUOPT', 'COUPLE1', 'COUP1INT', 'FAILJC',
    'DETSPH', 'BIAS', 'PORFCPL', 'RELAX', 'FLOW', 'ISTRSSH',
    'NTHICK', 'TIM2PSD', 'WETSURF', 'WETELME', 'BCNURBS',
    'BCTRIM', 'IMPGEOM', 'IMPCASE', 'SPCD2', 'SPRBCK', 'BCRGSRF', 'BCNURB2',
    'CAXISYM', 'RADC', 'VIEWEX',
    'ACLOAD', 'PBARN1',
    'TABL3D1', 'AEGRID', 'AEQUAD4', 'SPBLND1', 'CONCTL',
    'MAT1F', 'HYDROS', 'HYDROC', 'DVLREL1', 'DTABLE2', 'DVPSURF', 'PFASTT',
    'FRFRELS', 'FRFCONN', 'FRFXIT1', 'FBALOAD', 'MATS8', 'METADATA',
    'PRIM1', 'PRIM7', 'CONV3', 'GRIDA', 'MAT10F', 'RADCOL', 'SPLINRB',
    'TABL3D2', 'DYMAT24', 'FBAPHAS', 'FRFFLEX', 'FBADLAY', 'FRFXIT',
    'FRFCOMP', 'FRFSPC1', 'PCOMPFQ', 'PDISTB', 'MASSSET', 'PSLDN2', 'MATSORT',
    'NLHEATC', 'MDRBE2', 'MDRBE3', 'MDEXCLD', 'MDWELD', 'MDBCNCT', 'MDCONCT',
    'MDBCTB1', 'MDMPC', 'MDRROD', 'ACCSSPT', 'MDLOC', 'MDMPLN', 'MDMOVE', 'MDWELD',
    'DEFUSET', 'MDFAST', 'MDBOLT', 'MDSEAM', 'ALIASM', 'POSTBUK', 'MATDB01',
    'PBEAM71', 'PSHEARN', 'MATD010', 'PBDISCR', 'PBELTD', 'CORD3RX', 'RBE2A',
    'ACCMETR', 'CBELT', 'RBJSTIF', 'MDRJNT', 'BCPFLG', 'MDLABEL', 'MDBNDRY',
    'CHEXCZ', 'CPENTCZ', 'BEDGE', 'DVEREL1', 'DMNCON', 'DVTREL1', 'NLCNTL',
    'MATCRP', 'TLOAD3', 'MATSR', 'DTEMP',
    'MDDMIG', 'MDTRAN', 'MDROT1', 'MDROT2', 'MDMIR1', 'MDMIR2', 'MDMIAUX',
    'MPCREEP',
    'COHESIV', 'CSSHLM', 'PBMARB6', 'PBMNUM6', 'DMIGROT', 'GRNDSPR', 'SUPORT6',
    'MARPRN', 'MATNLE2', 'MATNLE3', 'MATNLE4', 'MATNLE5', 'MATNLE6', 'MATPDR',
    'MGRSPR', 'MIXTURE', 'MNF600', 'MT16SEL', 'MTABRV', 'NLBSH3D', 'MONCNCM',
    'FREQV', 'VATVFS', 'PMIC', 'MAT10C', 'ALOAD', 'ELAR', 'ATVBULK', 'AMLREG',
    'ATVFS', 'BOLTLD', 'BCTPAR2', 'MATFT', 'PLOTEL4', 'CYCADD', 'MATT11'
}


[docs] def load_bdf_object(obj_filename:str, xref: bool=True, log=None, debug: bool=True): model = BDF(log=log, debug=debug) model.load(obj_filename=obj_filename) model.cross_reference(xref=xref, xref_nodes=True, xref_elements=True, xref_nodes_with_elements=True, xref_properties=True, xref_masses=True, xref_materials=True, xref_loads=True, xref_constraints=True, xref_aero=True, xref_sets=True, xref_optimization=True) return model
class BDF_(BDFMethods, GetCard, AddCards, WriteMeshs, UnXrefMesh): """ Base class for the BDF Reader/Writer/Editor class that's used by the main BDF object and (temporarily) the in-development vectorized object to keep things working. If you add very few methods and attributes to this, you get the ``BDF`` class. The point of this class is to break out a attributes, so the names (e.g., nodes) can be reused when vectorize the data. """ #: required for sphinx bug #: http://stackoverflow.com/questions/11208997/autoclass-and-instance-attributes #__slots__ = ['_is_dynamic_syntax'] def __init__(self, debug: Union[str, bool, None]=True, log: Optional[SimpleLogger]=None, mode: str='msc') -> None: """ Initializes the BDF_ object Parameters ---------- debug : bool/None; default=True used to set the logger if no logger is passed in True: logs debug/info/error messages False: logs info/error messages None: logs error messages log : logging module object / None if log is set, debug is ignored and uses the settings the logging object has mode : str; default='msc' the type of Nastran valid_modes = {'msc', 'nx', 'mystran', 'nasa95', 'zona'} """ assert debug in [True, False, None], f'debug={debug!r}' self.echo = False self.read_includes = True self._remove_disabled_cards = False self.use_new_deck_parser = False # file management parameters self.active_filenames = [] # type: list[str] self.active_filename = None # type: Optional[str] self.include_dir = '' self.dumplines = False self.log = get_logger2(log=log, debug=debug) # list of all read in cards - useful in determining if entire BDF # was read & really useful in debugging self.card_count: dict[str, int] = {} # stores the card_count of cards that have been rejected self.reject_count: dict[str, int] = {} # allows the BDF variables to be scoped properly (i think...) GetCard.__init__(self) AddCards.__init__(self) BDFMethods.__init__(self) WriteMeshs.__init__(self) UnXrefMesh.__init__(self) # useful in debugging errors in input self.debug = debug # flag that allows for OpenMDAO-style optimization syntax to be used self._is_dynamic_syntax = False # lines that were rejected b/c they were for a card that isn't supported self.reject_lines: list[list[str]] = [] # cards that were created, but not processed self.reject_cards: list[str] = [] self.include_filenames = defaultdict(list) # list[str] # self.__init_attributes() cards_to_read = [ '/', 'ECHOON', 'ECHOOFF', 'PARAM', 'MDLPRM', ## nodes 'GRID', 'GRDSET', 'SPOINT', 'EPOINT', 'SEQGP', 'GRIDB', # points 'POINT', #'GRIDG' ## ringfl 'RINGFL', ## ringaxs 'RINGAX', 'POINTAX', ## masses 'CONM1', 'CONM2', 'CMASS1', 'CMASS2', 'CMASS3', 'CMASS4', ## nsms 'NSM', 'NSM1', 'NSML', 'NSML1', ## nsmadds 'NSMADD', ## elements # springs 'CELAS1', 'CELAS2', 'CELAS3', 'CELAS4', # 'CELAS5', # bushings 'CBUSH', 'CBUSH1D', 'CBUSH2D', # dampers 'CDAMP1', 'CDAMP2', 'CDAMP3', 'CDAMP4', 'CDAMP5', 'CFAST', 'CBAR', 'CBARAO', 'BAROR', 'CROD', 'CTUBE', 'CBEAM', 'CBEAM3', 'CONROD', 'CBEND', 'BEAMOR', 'CTRIA3', 'CTRIA6', 'CTRIAR', 'CQUAD4', 'CQUAD8', 'CQUADR', 'CQUAD', 'CTRAX3', 'CTRAX6', 'CTRIAX', 'CTRIAX6', 'CQUADX', 'CQUADX4', 'CQUADX8', 'CTRSHL', 'CQUAD1', 'SNORM', 'CPLSTN3', 'CPLSTN4', 'CPLSTN6', 'CPLSTN8', # plate strain 'CPLSTS3', 'CPLSTS4', 'CPLSTS6', 'CPLSTS8', # plate stress # acoustic 'CHACAB', 'CAABSF', 'CHACBR', 'PACABS', 'PAABSF', 'PACBAR', 'ACMODL', 'CTETRA', 'CPYRAM', 'CPENTA', 'CHEXA', 'CIHEX1', 'CIHEX2', 'CHEXA1', 'CHEXA2', 'CSHEAR', 'CVISC', 'CRAC2D', 'CRAC3D', 'CGAP', 'GENEL', ## rigid_elements 'RBAR', 'RBAR1', 'RBE1', 'RBE2', 'RBE3', 'RROD', 'RSPLINE', 'RSSCON', ## plotels 'PLOTEL', ## properties 'PMASS', 'PELAS', 'PGAP', 'PFAST', 'PLPLANE', 'PPLANE', 'PBUSH', 'PBUSH1D', 'PDAMP', 'PDAMP5', 'PROD', 'PBAR', 'PBARL', 'PBEAM', 'PTUBE', 'PBCOMP', 'PBRSECT', 'PBEND', 'PBEAML', 'PBMSECT', # not fully supported 'PBEAM3', # v1.3 'PSHELL', 'PCOMP', 'PCOMPG', 'PSHEAR', 'PSOLID', 'PLSOLID', 'PVISC', 'PRAC2D', 'PRAC3D', 'PCOMPS', 'PCOMPLS', 'PMIC', # nastran 95 'PTRSHL', 'PQUAD1', 'PIHEX', # PQUAD4 # axixsymmetric 'CCONEAX', # element 'PCONEAX', # property 'AXIC', # axic 'AXIF', # axif 'FORCEAX', # loads ## pdampt 'PDAMPT', ## pelast 'PELAST', ## pbusht 'PBUSHT', ## creep_materials 'CREEP', ## materials 'MAT1', 'MAT2', 'MAT3', 'MAT8', 'MAT9', 'MAT10', 'MAT11', 'MAT3D', 'MATG', 'MATHE', 'MATHP', 'MATEV', ## Material dependence - MATT1/MATT2/etc. 'MATT1', 'MATT2', 'MATT3', 'MATT4', 'MATT5', 'MATT8', 'MATT9', 'MATS1', #'MATS3', 'MATS8', 'MATDMG', # 'MATHE' #'EQUIV', # testing only, should never be activated... ## nxstrats 'NXSTRAT', ## thermal_materials 'MAT4', 'MAT5', ## spcs 'SPC', 'SPCADD', 'SPC1', 'SPCAX', 'SPCOFF', 'SPCOFF1', ## mpcs 'MPC', 'MPCADD', ## suport/suport1/se_suport 'SUPORT', 'SUPORT1', 'SESUP', ## dloads 'DLOAD', ## dload_entries 'ACSRCE', 'TLOAD1', 'TLOAD2', 'RLOAD1', 'RLOAD2', 'QVECT', 'RANDPS', 'RANDT1', # random ## loads 'LOAD', 'CLOAD', 'LSEQ', 'LOADCYN', 'LOADCYH', 'SLOAD', 'FORCE', 'FORCE1', 'FORCE2', 'MOMENT', 'MOMENT1', 'MOMENT2', 'GRAV', 'ACCEL', 'ACCEL1', 'PLOAD', 'PLOAD1', 'PLOAD2', 'PLOAD4', 'PLOADX1', 'RFORCE', 'RFORCE1', 'SPCD', 'DEFORM', ## acoustic 'ACPLNW', 'AMLREG', 'MATPOR', 'MICPNT', # msgmesh #'GMLOAD', # loads #'GMCORD', # coords # axisymmetric 'PRESAX', #thermal 'QVOL', # aero cards 'AERO', ## aero 'AEROS', ## aeros 'GUST', ## gusts 'FLUTTER', ## flutters 'FLFACT', ## flfacts 'MKAERO1', 'MKAERO2', ## mkaeros 'AECOMP', 'AECOMPL', ## aecomps 'AEFACT', ## aefacts 'AELINK', ## aelinks 'AELIST', ## aelists 'AEPARM', ## aeparams 'AESTAT', ## aestats 'AESURF', ## aesurf 'AESURFS', ## aesurfs 'CAERO1', 'CAERO2', 'CAERO3', 'CAERO4', 'CAERO5', ## caeros 'PAERO1', 'PAERO2', 'PAERO3', 'PAERO4', 'PAERO5', ## paeros 'MONPNT1', 'MONPNT2', 'MONPNT3', 'MONDSP1', ## monitor_points 'SPLINE1', 'SPLINE2', 'SPLINE3', 'SPLINE4', 'SPLINE5', ## splines 'SPLINE6', 'SPLINE7', 'TRIM', 'TRIM2', ## trims 'CSSCHD', ## csschds 'DIVERG', ## divergs ## coords 'CORD1R', 'CORD1C', 'CORD1S', 'CORD2R', 'CORD2C', 'CORD2S', 'MATCID', # temperature cards 'TEMP', 'TEMPD', 'TEMPB3', 'TEMPAX', 'QBDY1', 'QBDY2', 'QBDY3', 'QHBDY', 'CHBDYE', 'CHBDYG', 'CHBDYP', 'PCONV', 'PCONVM', 'PHBDY', 'RADBC', 'CONV', 'RADM', 'VIEW', 'VIEW3D', # TODO: not validated 'RADCAV', ## radcavs # ---- dynamic cards ---- # 'DAREA', ## dareas 'DPHASE', ## dphases 'DELAY', ## delays 'NLPARM', ## nlparms 'ROTORG', 'ROTORD', ## rotors 'NLPCI', ## nlpcis 'TSTEP', ## tsteps 'TSTEPNL', 'TSTEP1', ## tstepnls 'TF', ## transfer_functions 'TIC', ## initial conditions - sid (set ID) ## frequencies 'FREQ', 'FREQ1', 'FREQ2', 'FREQ3', 'FREQ4', 'FREQ5', # direct matrix input cards 'DMIG', 'DMIJ', 'DMIJI', 'DMIK', 'DMI', 'DTI', 'DMIAX', # optimization cards 'DEQATN', 'DTABLE', 'DCONSTR', 'DESVAR', 'TOPVAR', 'DDVAL', 'DRESP1', 'DRESP2', 'DRESP3', 'DVCREL1', 'DVCREL2', 'DVPREL1', 'DVPREL2', 'DVMREL1', 'DVMREL2', 'DOPTPRM', 'DLINK', 'DCONADD', 'DVGRID', 'DSCREEN', # nx optimization 'DVTREL1', 'GROUP', 'DMNCON', # sets 'SET1', 'SET2', 'SET3', ## sets 'ASET', 'ASET1', ## asets 'OMIT', 'OMIT1', ## omits 'BSET', 'BSET1', ## bsets 'CSET', 'CSET1', ## csets 'QSET', 'QSET1', ## qsets 'USET', 'USET1', ## usets 'RADSET', # radset # superelements 'SETREE', 'SENQSET', 'SEBULK', 'SEBNDRY', 'SEELT', 'SELOC', 'SEMPLN', 'SECONCT', 'SELABEL', 'SEEXCLD', 'CSUPER', 'CSUPEXT', 'SELOAD', 'RELEASE', # super-element sets 'SESET', ## se_sets 'SEBSET', 'SEBSET1', ## se_bsets 'SECSET', 'SECSET1', ## se_csets 'SEQSET', 'SEQSET1', ## se_qsets #'SEUSET', 'SEUSET1', ## se_usets 'SEQSEP', #------------------------------------------------------------------ ## parametric 'PSET', 'PVAL', 'GMCURV', 'GMSURF', 'FEEDGE', 'FEFACE', 'GMSPC', # spcs #------------------------------------------------------------------ ## tables 'TABLED1', 'TABLED2', 'TABLED3', 'TABLED4', # dynamic tables - freq/time loads 'TABLEM1', 'TABLEM2', 'TABLEM3', 'TABLEM4', # material tables - temperature # nonlinear elastic temperature dependent materials (e.g. creep) # sees TABLES1 'TABLEST', # material tables - stress (MATS1, CREEP, MATHP) 'TABLES1', ## modal damping table - tables_sdamping 'TABDMP1', ## random_tables # PSD=func(freq); used by RANDPS card 'TABRND1', # gust for aeroelastic response; used by RANDPS card 'TABRNDG', # ??? 'TABLEHT', 'TABLEH1', #------------------------------------------------------------------ #: methods 'EIGB', 'EIGR', 'EIGRL', #: cMethods 'EIGC', 'EIGP', # : modtrak 'MODTRAK', #: contact 'BCBODY', ## bcbody 'BCPARA', ## bcpara 'BCTPARA', ## bctpara 'BCRPARA', ## bcrpara 'BCTPARM', ## bctparm 'BGADD', ## bgadds 'BGSET', ## bgsets 'BCTADD', ## bctadds 'BCTSET', ## bctsets 'BSURF', ## bsurf 'BSURFS', ## bsurfs 'BCONP', ## bconp 'BLSEG', ## blseg 'BFRIC', ## bfric 'TEMPBC', #'RADMT', 'RADLST', 'RADMTX', #'RADBND', #'TEMPP1', 'TEMPRB', 'CONVM', ## ??? #'PANEL', 'SWLDPRM', #'CWELD', 'PWELD', # 'PWSEAM', 'CWSEAM', 'CSEAM', 'PSEAM', 'DVSHAP', #'CYSYM', 'CYJOIN', 'MODTRAK', 'DSCONS', 'DVAR', 'DVSET', 'DYNRED', #'AEFORCE', 'UXVEC', 'GUST2', #'BNDGRID', #'BNDFREE', 'BNDFREE1', #'BNDFIX', 'BNDFIX1', # cyclic 'CYJOIN', 'CYAX', # bolt nx 'BOLT', 'BOLTSEQ', 'BOLTLD', 'BOLTFOR', # other 'INCLUDE', # '=' 'ENDDATA', ] set_cards_to_read = set(cards_to_read) if len(cards_to_read) != len(set_cards_to_read): # pragma: no cover bad_cards = [key for key, value in Counter(cards_to_read).items() if value > 1] raise RuntimeError(f'duplicate cards in cards_to_read={bad_cards}') # the list of possible cards that will be parsed self.cards_to_read = set_cards_to_read self._xref = False #case_control_cards = {'FREQ', 'GUST', 'MPC', 'SPC', 'NLPARM', 'NSM', #'TEMP', 'TSTEPNL', 'INCLUDE'} #self._unique_bulk_data_cards = self.cards_to_read.difference(CASE_CONTROL_CARDS) #: / is the delete from restart card self.special_cards = ['DEQATN', '/'] self._make_card_parser() self._nastran_format = mode map_version(self, mode) def __getstate__(self): """clears out a few variables in order to pickle the object""" # Copy the object's state from self.__dict__ which contains # all our instance attributes. Always use the dict.copy() # method to avoid modifying the original state. state = self.__dict__.copy() # Remove the unpicklable entries. del state['_card_parser'], state['log'] if hasattr(self, '_card_parser_b'): del state['_card_parser_b'] if hasattr(self, '_card_parser_prepare'): del state['_card_parser_prepare'] return state def get_h5attrs(self) -> list[str]: """helper method for dict_to_h5py""" attrs = self.object_attributes(mode='both', keys_to_skip=None) return attrs def export_hdf5_filename(self, hdf5_filename: str) -> None: """ Converts the BDF objects into hdf5 object Parameters ---------- hdf5_filename : str the path to the hdf5 file TODO: doesn't support: - BucklingEigenvalues """ import h5py self.log.debug('starting export_hdf5_file of %r' % hdf5_filename) try: with h5py.File(hdf5_filename, 'w') as hdf5_file: self.export_hdf5_file(hdf5_file) except OSError: self.log.error(f'failed to export {hdf5_filename!r}') raise def export_hdf5_file(self, hdf5_file, exporter=None) -> None: """ Converts the BDF objects into hdf5 object Parameters ---------- hdf5_file : H5File() an h5py object exporter : HDF5Exporter; default=None unused """ from pyNastran.bdf.bdf_interface.hdf5_exporter import export_bdf_to_hdf5_file export_bdf_to_hdf5_file(hdf5_file, self) def load_hdf5_filename(self, hdf5_filename: str) -> None: """ Loads a BDF object from an hdf5 filename Parameters ---------- hdf5_filename : str the path to the hdf5 file """ import h5py self.log.debug('starting load_hdf5_file of %r' % hdf5_filename) with h5py.File(hdf5_filename, 'r') as hdf5_file: self.load_hdf5_file(hdf5_file) def load_hdf5_file(self, h5_file) -> None: """ Loads a BDF object from an hdf5 object Parameters ---------- hdf5_file : H5File() an h5py object exporter : HDF5Exporter; default=None unused """ from pyNastran.bdf.bdf_interface.hdf5_loader import load_bdf_from_hdf5_file load_bdf_from_hdf5_file(h5_file, self) def saves(self, unxref: bool=True) -> str: """Saves a pickled string""" if unxref: self.uncross_reference() return dumps(self) def save(self, obj_filename: str='model.obj', unxref: bool=True) -> None: """Saves a pickleable object""" #del self.log #del self._card_parser, self._card_parser_prepare #try: #del self.log #except AttributeError: #pass #self.case_control_lines = str(self.case_control_deck).split('\n') #del self.case_control_deck if unxref: self.uncross_reference() self.log.info(f'saving BDF obj {obj_filename}') with open(obj_filename, 'wb') as obj_file: dump(self, obj_file) def load(self, obj_filename: str='model.obj') -> None: """Loads a pickleable object""" #del self.log #lines = print(self.case_control_deck) #self.case_control_lines = lines.split('\n') #del self.case_control_deck #self.uncross_reference() #import types self.log.info(f'loading BDF obj {obj_filename}') with open(obj_filename, 'rb') as obj_file: obj = load(obj_file) # these are properties, functions, etc. keys_to_skip = [ 'case_control_deck', 'log', 'node_ids', 'coord_ids', 'element_ids', 'property_ids', 'material_ids', 'caero_ids', 'is_long_ids', 'nnodes', 'npoints', 'ncoords', 'nelements', 'nproperties', 'nmaterials', 'ncaeros', 'nid_map', 'is_bdf_vectorized', 'type_slot_str', #'dmigs', 'dmijs', 'dmiks', 'dmijis', 'dtis', 'dmis', 'point_ids', 'subcases', '_card_parser', '_card_parser_b', '_card_parser_prepare', 'wtmass', ] attrs = object_attributes(self, mode='all', keys_to_skip=keys_to_skip) for key in attrs: if key.startswith('__') and key.endswith('__'): continue val = getattr(obj, key) #print(key) #if isinstance(val, types.FunctionType): #continue try: setattr(self, key, val) except AttributeError: # pragma: no cover raise AttributeError(f'key={key!r} val={val}\nupdate ~line 1050 of bdf.py and ' f'add the new key ({key})') self.case_control_deck = CaseControlDeck(self.case_control_lines, log=self.log) #self.log.debug('done loading!') for model in self.superelement_models.values(): model.log = self.log def replace_cards(self, replace_model) -> None: """ Replaces the common cards from the current (self) model from the ones in the new replace_model. The intention is that you're going to replace things like PSHELLs and DESVARs from a pch file in order to update your BDF with the optimized geometry. .. todo:: only does a subset of cards. Notes ----- loads/spcs (not supported) are tricky because you can't replace cards one-to-one...not sure what to do """ for nid, node in replace_model.nodes.items(): self.nodes[nid] = node for eid, elem in self.elements.items(): self.elements[eid] = elem for eid, elem in replace_model.rigid_elements.items(): self.rigid_elements[eid] = elem for pid, prop in replace_model.properties.items(): self.properties[pid] = prop for mid, mat in replace_model.materials.items(): self.materials[mid] = mat for dvid, desvar in replace_model.desvars.items(): self.desvars[dvid] = desvar for dvid, dvprel in replace_model.dvprels.items(): self.dvprels[dvid] = dvprel for dvid, dvmrel in replace_model.dvmrels.items(): self.dvmrels[dvid] = dvmrel for dvid, dvgrid in replace_model.dvgrids.items(): self.dvgrids[dvid] = dvgrid def disable_cards(self, cards: Sequence[str]) -> None: """ Method for removing broken cards from the reader Parameters ---------- cards : list[str]; set[str] a list/set of cards that should not be read .. python :: bdf_model.disable_cards(['DMIG', 'PCOMP']) """ if cards is None: return elif isinstance(cards, str): disable_set = set([cards]) else: disable_set = set(cards) self.cards_to_read = self.cards_to_read.difference(disable_set) def enable_cards(self, cards: Sequence[str]) -> None: """ Method for setting the cards that will be processed Parameters ---------- cards : list[str]; set[str] a list/set of cards that should not be read .. python :: bdf_model.enable_cards(['GRID', 'CTRIA3']) """ if cards is None: return elif isinstance(cards, str): enable_set = set([cards]) else: enable_set = set(cards) self.cards_to_read = enable_set def deprecated(old_name: str, new_name: str, version: str, levels=None): """deprecates methods""" if levels is None: levels = [0, 1, 2] def decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): _deprecated(old_name, new_name, version) return func(self, *args, **kwargs) return wrapper return decorator @deprecated('set_cards', 'enable_cards', '1.4') def set_cards(self, cards: Sequence[str]) -> None: self.enable_cards(cards) def set_error_storage(self, nparse_errors: int=100, stop_on_parsing_error: bool=True, nxref_errors: int=100, stop_on_xref_error: bool=True) -> None: """ Catch parsing errors and store them up to print them out all at once (not all errors are caught). Parameters ---------- nparse_errors : int how many parse errors should be stored (default=0; all=None; no storage=0) stop_on_parsing_error : bool should an error be raised if there are parsing errors (default=True) nxref_errors : int how many cross-reference errors should be stored (default=0; all=None; no storage=0) stop_on_xref_error : bool should an error be raised if there are cross-reference errors (default=True) """ assert isinstance(nparse_errors, int), type(nparse_errors) assert isinstance(nxref_errors, int), type(nxref_errors) self._nparse_errors = nparse_errors self._nxref_errors = nxref_errors self._stop_on_parsing_error = stop_on_parsing_error self._stop_on_xref_error = stop_on_xref_error def validate(self) -> None: """runs some checks on the input data beyond just type checking""" validate_bdf(self) def _verify_bdf(self, xref: Optional[bool]=None) -> None: """Cross reference verification method.""" if xref is None: xref = self._xref verify_bdf(self, xref) def include_zip(self, bdf_filename: Optional[str]=None, encoding: Optional[str]=None, make_ilines: bool=True) -> tuple[list[str], Any]: """ Read a bdf without perform any other operation, except (optionally) insert the INCLUDE files in the bdf Parameters ---------- bdf_filename : str / None the input bdf (default=None; popup a dialog) encoding : str; default=None -> system default the unicode encoding make_ilines : bool; default=True flag for ilines Returns ------- all_lines : list[str] all the lines packed into a single line stream ilines : (nlines, 2) int ndarray if make_ilines = True: the [ifile, iline] pair for each line in the file if make_ilines = False: ilines = None .. note:: Setting read_includes to False is kind of pointless if called directly; it's useful for ``read_bdf`` """ punch = False # doesn't really matter read_includes = True self._read_bdf_helper(bdf_filename, encoding, punch, read_includes) self._parse_primary_file_header(bdf_filename) obj = BDFInputPy(self.read_includes, self.dumplines, self._encoding, nastran_format=self.nastran_format, consider_superelements=self.is_superelements, log=self.log, debug=self.debug) main_lines = obj.get_main_lines(self.bdf_filename) all_lines, ilines = obj.lines_to_deck_lines(main_lines, make_ilines=make_ilines) self._set_pybdf_attributes(obj, save_file_structure=False) return all_lines, ilines def _set_pybdf_attributes(self, obj: BDFInputPy, save_file_structure: bool=False) -> None: """common method for all functions that use BDFInputPy""" # these are include line pairs #print(obj.include_lines) self.active_filenames = [] self.reject_lines = [] include_filenames = defaultdict(list) for ifile, include_lines_filename_pairs in obj.include_lines.items(): assert len(include_lines_filename_pairs) > 0, include_lines_filename_pairs for include_lines, bdf_filename2 in include_lines_filename_pairs: #print(ifile, include_lines) include_filenames [ifile].append(bdf_filename2) if not save_file_structure and not obj.read_includes: self.reject_lines += include_lines self.include_filenames: dict[int, list[str]] = dict(include_filenames) #print('-------------ssett (end)----------') self.active_filenames += obj.active_filenames self.active_filename = obj.active_filename self.include_dir = obj.include_dir def read_bdf(self, bdf_filename: Optional[PathLike]=None, validate: bool=True, xref: bool=True, punch: bool=False, read_includes: bool=True, save_file_structure: bool=False, encoding: Optional[str]=None) -> None: """ Read method for the bdf files Parameters ---------- bdf_filename : str / None the input bdf (default=None; popup a dialog) validate : bool; default=True runs various checks on the BDF xref : bool; default=True should the bdf be cross referenced punch : bool; default=False indicates whether the file is a punch file read_includes : bool; default=True indicates whether INCLUDE files should be read save_file_structure : bool; default=False enables the ``write_bdfs`` method encoding : str; default=None -> system default the unicode encoding .. code-block:: python >>> bdf = BDF() >>> bdf.read_bdf(bdf_filename, xref=True) >>> g1 = bdf.Node(1) >>> print(g1.get_position()) [10.0, 12.0, 42.0] >>> bdf.write_card(bdf_filename2) >>> print(bdf.card_stats()) ---BDF Statistics--- SOL 101 bdf.nodes = 20 bdf.elements = 10 etc. """ self.save_file_structure = save_file_structure if bdf_filename and not isinstance(bdf_filename, (StringIO, list)): check_path(bdf_filename, 'bdf_filename') self._read_bdf_helper(bdf_filename, encoding, punch, read_includes) self.log.debug(f'---starting BDF.read_bdf of {self.bdf_filename}---') self._parse_primary_file_header(bdf_filename) obj = BDFInputPy(self.read_includes, self.dumplines, self._encoding, nastran_format=self.nastran_format, consider_superelements=self.is_superelements, log=self.log, debug=self.debug) obj.use_new_parser = self.use_new_deck_parser out = obj.get_lines(bdf_filename, punch=self.punch, make_ilines=True) (system_lines, executive_control_lines, case_control_lines, bulk_data_lines, bulk_data_ilines, additional_deck_lines) = out self._set_pybdf_attributes(obj, save_file_structure) #assert system_lines == [], system_lines #assert executive_control_lines == [], executive_control_lines #assert case_control_lines == [], case_control_lines self.system_command_lines = system_lines self.executive_control_lines = executive_control_lines self.case_control_lines = case_control_lines sol, method, sol_iline, app = parse_executive_control_deck(executive_control_lines) self.app = app self.update_solution(sol, method, sol_iline) self.case_control_deck = CaseControlDeck(case_control_lines, self.log) self.case_control_deck.solmap_to_value = self._solmap_to_value self.case_control_deck.rsolmap_to_str = self.rsolmap_to_str try: self._parse_all_cards(bulk_data_lines, bulk_data_ilines) except SuperelementFlagError: if self.is_superelements: raise self.clear_attributes() self.log.error('Attempting to use is_superelements=True') self.is_superelements = True self.read_bdf(bdf_filename=bdf_filename, validate=validate, xref=xref, punch=punch, read_includes=read_includes, save_file_structure=save_file_structure, encoding=encoding) return if additional_deck_lines: add_superelements_from_deck_lines(self, BDF, additional_deck_lines) self.pop_parse_errors() fill_dmigs(self) if validate: self.validate() if self._remove_disabled_cards: all_cards = set(self.card_count.keys()) union_cards = all_cards.intersection(REMOVED_CARDS) if union_cards: raise DisabledCardError(f'the following cards have been removed: {list(union_cards)}') self.cross_reference(xref=xref) self._xref = xref self.log.debug('---finished BDF.read_bdf of %s---' % self.bdf_filename) def _parse_all_cards(self, bulk_data_lines: list[str], bulk_data_ilines: Any) -> None: """creates and loads all the cards the bulk data section""" strict = True cards_list = [] cards_dict = {} if self._is_cards_dict: cards_dict, card_count = self.get_bdf_cards_dict( bulk_data_lines, bulk_data_ilines) #if 0: #with open('dump.bdf', 'w') as bdf_file_obj: #bdf_file_obj.write('\n'.join(executive_control_lines)) #bdf_file_obj.write(str(case_control_deck)) #for cardname, cards in cards.items(): #for (comment, cardlines) in cards: ##bdf_file_obj.write(comment + '\n') #bdf_file_obj.write('\n'.join(cardlines) + '\n') #bdf_file_obj.write('\n') else: cards_list, cards_dict, card_count = self.get_bdf_cards( bulk_data_lines, bulk_data_ilines) #for card in cards_list: #card_name = card[0] #if card_name == 'CBAR': #print(card) self._parse_cards(cards_list, cards_dict, card_count, strict=strict) if self.values_to_skip: for key, values in self.values_to_skip.items(): dict_values = getattr(self, key) if not isinstance(dict_values, dict): msg = f'{key!r} is an invalid type; only dictionaries are supported' raise TypeError(msg) for value in values: try: del dict_values[value] except KeyError: pass # TODO: redo get_card_ids_by_card_types & card_count def _read_bdf_helper(self, bdf_filename: Optional[PathLike], encoding: str, punch: bool, read_includes: bool): """creates the file loading if bdf_filename is None""" #self.set_error_storage(nparse_errors=None, stop_on_parsing_error=True, # nxref_errors=None, stop_on_xref_error=True) if encoding is None: encoding = sys.getdefaultencoding() self._encoding = encoding self.read_includes = read_includes self.active_filenames = [] # turns bdf_filename -> bdf_filename2 if bdf_filename is None: from pyNastran.utils.gui_io import load_file_dialog wildcard_wx = "Nastran BDF (*.bdf; *.dat; *.nas; *.pch, *.ecd)|" \ "*.bdf;*.dat;*.nas;*.pch;*.ecd|" \ "All files (*.*)|*.*" wildcard_qt = "Nastran BDF (*.bdf *.dat *.nas *.pch *.ecd);;All files (*)" title = 'Please select a BDF/DAT/PCH/ECD to load' bdf_filename2 = load_file_dialog(title, wildcard_wx, wildcard_qt)[0] assert bdf_filename2 is not None, bdf_filename2 elif isinstance(bdf_filename, (str, PurePath)): bdf_filename2 = bdf_filename elif isinstance(bdf_filename, (StringIO, IOBase)): self.bdf_filename = bdf_filename self.punch = punch return else: raise NotImplementedError(bdf_filename) #------------------------------- check_path(bdf_filename2, 'bdf_filename') ext = os.path.splitext(bdf_filename2)[1] if ext == '.pch': # .. todo:: should this be removed??? punch = True #: the active filename (string) self.bdf_filename = bdf_filename2 #: is this a punch file (no executive control deck) self.punch = punch assert ext != '.op2', bdf_filename2 def pop_parse_errors(self) -> None: """raises an error if there are parsing errors""" if self._stop_on_parsing_error: if self._iparse_errors == 1 and self._nparse_errors == 0: raise is_error = False msg = '' if self._duplicate_elements: duplicate_eids = [elem.eid for elem in self._duplicate_elements] uduplicate_eids = np.unique(duplicate_eids) msg += 'self.elements IDs are not unique=%s\n' % uduplicate_eids for eid in uduplicate_eids: msg += 'old_element=\n%s\n' % str(self.elements[eid]) msg += 'new_elements=\n' for elem, eidi in zip(self._duplicate_elements, duplicate_eids): if eidi == eid: msg += str(elem) msg += '\n' is_error = True raise DuplicateIDsError(msg) if self._duplicate_properties: duplicate_pids = [prop.pid for prop in self._duplicate_properties] uduplicate_pids = np.unique(duplicate_pids) msg += 'self.properties IDs are not unique=%s\n' % uduplicate_pids for pid in duplicate_pids: msg += 'old_property=\n%s\n' % str(self.properties[pid]) msg += 'new_properties=\n' for prop, pidi in zip(self._duplicate_properties, duplicate_pids): if pidi == pid: msg += str(prop) msg += '\n' is_error = True if self._duplicate_masses: duplicate_eids = [elem.eid for elem in self._duplicate_masses] uduplicate_eids = np.unique(duplicate_eids) msg += 'self.massses IDs are not unique=%s\n' % uduplicate_eids for eid in uduplicate_eids: msg += 'old_mass=\n%s\n' % str(self.masses[eid]) msg += 'new_masses=\n' for elem, eidi in zip(self._duplicate_masses, duplicate_eids): if eidi == eid: msg += str(elem) msg += '\n' is_error = True if self._duplicate_materials: duplicate_mids = [mat.mid for mat in self._duplicate_materials] uduplicate_mids = np.unique(duplicate_mids) msg += 'self.materials IDs are not unique=%s\n' % uduplicate_mids for mid in uduplicate_mids: msg += 'old_material=\n%s\n' % str(self.materials[mid]) msg += 'new_materials=\n' for mat, midi in zip(self._duplicate_materials, duplicate_mids): if midi == mid: msg += str(mat) msg += '\n' is_error = True if self._duplicate_thermal_materials: duplicate_mids = [mat.mid for mat in self._duplicate_thermal_materials] uduplicate_mids = np.unique(duplicate_mids) msg += 'self.thermal_materials IDs are not unique=%s\n' % uduplicate_mids for mid in uduplicate_mids: msg += 'old_thermal_material=\n%s\n' % str(self.thermal_materials[mid]) msg += 'new_thermal_materials=\n' for mat, midi in zip(self._duplicate_thermal_materials, duplicate_mids): if midi == mid: msg += str(mat) msg += '\n' is_error = True if self._duplicate_coords: duplicate_cids = [coord.cid for coord in self._duplicate_coords] uduplicate_cids = np.unique(duplicate_cids) msg += 'self.coords IDs are not unique=%s\n' % uduplicate_cids for cid in uduplicate_cids: msg += 'old_coord=\n%s\n' % str(self.coords[cid]) msg += 'new_coords=\n' for coord, cidi in zip(self._duplicate_coords, duplicate_cids): if cidi == cid: msg += str(coord) msg += '\n' is_error = True if is_error: msg = 'There are duplicate cards.\n\n' + msg if self._stop_on_xref_error: msg += 'There are parsing errors.\n\n' for (card, an_error) in self._stored_parse_errors: msg += '%scard=%s\n' % (an_error[0], card) msg += 'xref error: %s\n\n'% an_error[0] is_error = True if is_error: print('%s' % msg) raise DuplicateIDsError(msg.rstrip()) def pop_xref_errors(self) -> None: """raises an error if there are cross-reference errors""" is_error = False if self._stop_on_xref_error: if self._ixref_errors == 1 and self._nxref_errors == 0: raise if self._stored_xref_errors: filename_note = '' if self.bdf_filename and not isinstance(self.bdf_filename, StringIO): filename_note = f' in {os.path.abspath(self.bdf_filename)!r}' msg = f'There are cross-reference errors{filename_note}.\n\n' for (card, an_error) in self._stored_xref_errors: msg += '%scard=%s\n' % (an_error[0], card) is_error = True if is_error and self._stop_on_xref_error: raise CrossReferenceError(msg.rstrip()) def get_bdf_cards(self, bulk_data_lines: list[str], bulk_data_ilines: Optional[Any]=None) -> tuple[Any, Any, Any]: """Parses the BDF lines into a list of card_lines""" if bulk_data_ilines is None: bulk_data_ilines = np.zeros((len(bulk_data_lines), 2), dtype='int32') cards_list = [] # type: list[Any] cards_dict: dict[str, list[Any]] = defaultdict(list) dict_cards = ['BAROR', 'BEAMOR'] #cards = defaultdict(list) card_count: dict[str, int] = defaultdict(int) full_comment = '' card_lines = [] old_ifile_iline = None old_card_name = None backup_comment = '' nlines = len(bulk_data_lines) if len(bulk_data_lines) != len(bulk_data_ilines): msg = 'len(bulk_data_lines)=%s len(bulk_data_ilines)=%s' % ( len(bulk_data_lines), len(bulk_data_ilines)) self.log.warning(msg) for iline_bulk, line in enumerate(bulk_data_lines): ifile_iline = bulk_data_ilines[iline_bulk, :] #print(iline_bulk, ifile_iline, line) #print(' backup=%r' % backup_comment) comment = '' if '$' in line: line, comment = line.split('$', 1) strip_comment = comment.strip() if strip_comment.lower().startswith('group:'): #'group: name="ULFuseCanardAtch MainFuseStruct Fixed Gridpoints"; nodes=1' strip_comment2 = strip_comment.split(':', 1)[1].strip() if ';' in strip_comment2: group = ModelGroup.create_from_line(strip_comment2) name = group.name if name in self.model_groups: og_group = self.model_groups[name] #print(og_group) og_group.union(group) #print('->', og_group) del og_group continue self.model_groups[name] = group else: self.log.warning(f'unknown group={strip_comment}') card_name = line.split(',', 1)[0].split('\t', 1)[0][:8].rstrip().upper() if card_name and card_name[0] not in ['+', '*']: if old_card_name: if self.echo and not self.force_echo_off: self.log.info('Reading %s:\n' % old_card_name + full_comment + ''.join(card_lines)) # old dictionary version # cards_list[old_card_name].append([full_comment, card_lines, ifile_iline]) # new list version #if full_comment: #print('full_comment = ', full_comment) if old_card_name in dict_cards: cards_dict[old_card_name].append([_prep_comment(full_comment), card_lines, ifile_iline]) else: cards_list.append([old_card_name, _prep_comment(full_comment), card_lines, old_ifile_iline]) card_count[old_card_name] += 1 card_lines = [] full_comment = '' if old_card_name == 'ECHOON': self.echo = True elif old_card_name == 'ECHOOFF': self.echo = False old_ifile_iline = ifile_iline old_card_name = card_name.rstrip(' *') if old_card_name == 'ENDDATA': self.card_count['ENDDATA'] = 1 if nlines - iline_bulk > 1: nleftover = nlines - iline_bulk - 1 msg = 'exiting due to ENDDATA found with %i lines left' % nleftover self.log.debug(msg) return cards_list, cards_dict, card_count #print("card_name = %s" % card_name) comment = _clean_comment(comment) #TODO: these additional \n need to be there for rejected cards # but not parsed cards if line.rstrip(): card_lines.append(line) if backup_comment: if comment: full_comment += backup_comment + comment + '\n' else: full_comment += backup_comment backup_comment = '' elif comment: full_comment += comment + '\n' backup_comment = '' elif comment: backup_comment += comment + '\n' #elif comment: #backup_comment += '$' + comment + '\n' if card_lines: if self.echo and not self.force_echo_off: self.log.info('Reading %s:\n' % old_card_name + full_comment + ''.join(card_lines)) #print('end_add %s' % card_lines) # old dictionary version #cards[old_card_name].append([backup_comment + full_comment, card_lines]) # new list version #if backup_comment + full_comment: #print('backup_comment + full_comment = ', backup_comment + full_comment) if old_card_name in dict_cards: cards_dict[old_card_name].append([_prep_comment( backup_comment + full_comment), card_lines, ifile_iline]) else: cards_list.append([old_card_name, _prep_comment( backup_comment + full_comment), card_lines, ifile_iline]) card_count[old_card_name] += 1 self.echo = False return cards_list, cards_dict, card_count def get_bdf_cards_dict(self, bulk_data_lines, bulk_data_ilines=None): """Parses the BDF lines into a list of card_lines""" if bulk_data_ilines is None: bulk_data_ilines = np.zeros((len(bulk_data_lines), 2), dtype='int32') cards_dict = defaultdict(list) card_count = defaultdict(int) full_comment = '' card_lines = [] old_card_name = None backup_comment = '' nlines = len(bulk_data_lines) for iline_bulk, line in enumerate(bulk_data_lines): ifile_iline = bulk_data_ilines[iline_bulk, :] #print(' backup=%r' % backup_comment) comment = '' if '$' in line: line, comment = line.split('$', 1) card_name = line.split(',', 1)[0].split('\t', 1)[0][:8].rstrip().upper() if card_name and card_name[0] not in ['+', '*']: if old_card_name: if self.echo and not self.force_echo_off: self.log.info('Reading %s:\n' % old_card_name + full_comment + ''.join(card_lines)) # old dictionary version cards_dict[old_card_name].append([full_comment, card_lines, ifile_iline]) # new list version #cards.append([old_card_name, full_comment, card_lines, ifile_iline]) card_count[old_card_name] += 1 card_lines = [] full_comment = '' if old_card_name == 'ECHOON': self.echo = True elif old_card_name == 'ECHOOFF': self.echo = False old_card_name = card_name.rstrip(' *') if old_card_name == 'ENDDATA': self.card_count['ENDDATA'] = 1 if nlines - iline_bulk > 1: nleftover = nlines - iline_bulk - 1 msg = f'exiting due to ENDDATA found with {nleftover:d} lines left' self.log.debug(msg) return cards_dict, card_count #print("card_name = %s" % card_name) comment = _clean_comment_bulk(comment) if line.rstrip(): card_lines.append(line) if backup_comment: if comment: full_comment += backup_comment + comment + '\n' else: full_comment += backup_comment backup_comment = '' elif comment: full_comment += comment + '\n' backup_comment = '' elif comment: backup_comment += comment + '\n' #print('add backup=%r' % backup_comment) #elif comment: #backup_comment += comment + '\n' if card_lines: if self.echo and not self.force_echo_off: self.log.info('Reading %s:\n' % old_card_name + full_comment + ''.join(card_lines)) #print('end_add %s' % card_lines) # old dictionary version cards_dict[old_card_name].append( [backup_comment + full_comment, card_lines, ifile_iline]) # new list version #cards.append([old_card_name, backup_comment + full_comment, card_lines]) card_count[old_card_name] += 1 return cards_dict, card_count def update_solution(self, sol: int, method: Optional[str], sol_iline: int) -> None: """ Updates the overall solution type (e.g. 101,200,600) Parameters ---------- sol : int the solution type (101, 103, etc) method : str the solution method (only for SOL=600) sol_iline : int the line to put the SOL/method on """ self.sol_iline = sol_iline # the integer of the solution type (e.g. SOL 101) if sol is None: self.sol = None self.sol_method = None return try: self.sol = int(sol) except ValueError: try: self.sol = self._solmap_to_value[sol] except KeyError: self.sol = sol if self.sol == 600: #: solution 600 method modifier if method is None: method = '' self.sol_method = method.strip() self.log.debug(f'sol={self.sol} method={self.sol_method!r}') else: # very common self.sol_method = None def update_card(self, card_name: str, icard: int, ifield: int, value: Union[int, float, str]) -> None: """ Updates a Nastran card based on standard Nastran optimization names Parameters ---------- card_name : str the name of the card (e.g. GRID) icard : int the unique 1-based index identifier for the card (e.g. the GRID id) ifield : int the index on the card (e.g. X on GRID card as an integer representing the field number) value : varies the value to assign Returns ------- obj : varies the corresponding object (e.g. the GRID object) # On GRID 100, set Cp (2) to 42 >>> model.update_card('GRID', 100, 2, 42) # On GRID 100, set X (3) to 43. >>> model.update_card('GRID', 100, 3, 43.) """ #rslot_map = self.get_rslot_map(reset_type_to_slot_map=False) for key in self.card_count: assert isinstance(key, str), f'key={key!r}' if key not in self._type_to_slot_map: msg = 'add %r to self._type_to_slot_map\n%s' % (key, str(self._type_to_slot_map)) raise RuntimeError(msg) #_slot_to_type_map['nodes'] : ['GRID'] #_type_to_slot_map['GRID'] : ['nodes'] # get the storage object try: field_str = self._type_to_slot_map[card_name] # 'nodes' except KeyError: msg = 'Updating card card_name=%r is not supported\nkeys=%s' % ( card_name, list(self._type_to_slot_map.keys())) raise KeyError(msg) objs = getattr(self, field_str) # self.nodes # get the specific card try: obj = objs[icard] except KeyError: msg = 'Could not find %s ID=%r' % (card_name, icard) raise KeyError(msg) # update the card obj.update_field(ifield, value) return obj def set_dynamic_syntax(self, dict_of_vars: dict[str, Union[int, float, str]]) -> None: """ Uses the OpenMDAO syntax of %varName in an embedded BDF to update the values for an optimization study. Parameters ---------- dict_of_vars : dict[str] = int/float/str dictionary of 7 character variable names to map. .. code-block:: python GRID, 1, %xVar, %yVar, %zVar >>> dict_of_vars = {'xVar': 1.0, 'yVar', 2.0, 'zVar':3.0} >>> bdf = BDF() >>> bdf.set_dynamic_syntax(dict_of_vars) >>> bdf.read_bdf(bdf_filename, xref=True) Notes ----- Case sensitivity is supported. Variables should be 7 characters or less to fit in an 8-character field. .. warning:: Type matters! """ self.dict_of_vars = {} assert len(dict_of_vars) > 0, f'nvars = {len(dict_of_vars):d}' for (key, value) in sorted(dict_of_vars.items()): assert len(key) <= 7, ('max length for key is 7; ' 'len(%s)=%s' % (key, len(key))) assert len(key) >= 1, ('min length for key is 1; ' 'len(%s)=%s' % (key, len(key))) if not isinstance(key, str): msg = 'key=%r must be a string. type=%s' % (key, type(key)) raise TypeError(msg) self.dict_of_vars[key] = value self._is_dynamic_syntax = True def is_reject(self, card_name: str) -> bool: """ Can the card be read. If the card is rejected, it's added to self.reject_count Parameters ---------- card_name : str the card_name -> 'GRID' """ if '=' in card_name: raise ReplicationError('unparsed replication format') if card_name.startswith('='): return False elif card_name in self.cards_to_read: return False if card_name: if card_name not in self.reject_count: self.reject_count[card_name] = 0 self.reject_count[card_name] += 1 return True def _process_card(self, card_lines: list[str]) -> list[str]: """ Converts card_lines into a card. Considers dynamic syntax and removes empty fields Parameters ---------- card_lines : list[str] list of strings that represent the card's lines Returns ------- fields : list[str] the parsed card's fields card_name : str the card's name .. code-block:: python >>> card_lines = ['GRID,1,,1.0,2.0,3.0,,'] >>> model = BDF() >>> fields, card_name = model._process_card(card_lines) >>> fields ['GRID', '1', '', '1.0', '2.0', '3.0'] >>> card_name 'GRID' """ card_name = _get_card_name(card_lines, self.active_filename) fields = to_fields(card_lines, card_name) if self._is_dynamic_syntax: fields = [ print_field_16(_parse_dynamic_syntax(field, self.dict_of_vars, self.log)) if '%' in field[0:1] else field for field in fields] card = wipe_empty_fields(fields) card[0] = card_name return card def create_card_object(self, card_lines: list[str], card_name: str, is_list: bool=True, has_none: bool=True): """ Creates a BDFCard object, which is really just a list that allows indexing past the last field Parameters ---------- card_lines: list[str] the list of the card fields input is list of card_lines -> ['GRID, 1, 2, 3.0, 4.0, 5.0'] card_name : str the card_name -> 'GRID' is_list : bool; default=True True : this is a list of fields False : this is a list of lines has_none : bool; default=True can there be trailing Nones in the card data (e.g. ['GRID, 1, 2, 3.0, 4.0, 5.0, ']) Returns ------- card_object : BDFCard() the card object representation of card card : list[str] the card with empty fields removed """ card_name = card_name.upper() self.increase_card_count(card_name) if card_name in ['DEQATN', 'PBRSECT', 'PBMSECT', 'GMCURV', 'GMSURF', 'OUTPUT', 'ADAPT', 'MONDSP1']: card_obj = card_lines card = card_lines else: if is_list: fields = card_lines else: fields = to_fields(card_lines, card_name) # apply OPENMDAO syntax if self._is_dynamic_syntax: fields = [ print_field_16(_parse_dynamic_syntax(field, self.dict_of_vars, self.log)) if '%' in field.strip()[0:1] else print_field_16(field) for field in fields] has_none = False if has_none: card = wipe_empty_fields([print_field_16(field) for field in fields]) else: #card = remove_trailing_fields(fields) card = wipe_empty_fields(fields) card_obj = BDFCard(card, has_none=False) return card_obj, card def _parse_dynamic_syntax(self, key: str) -> dict[str, Any]: return _parse_dynamic_syntax(key, self.dict_of_vars, self.log) def _make_card_parser(self) -> None: """creates the card parser variables that are used by add_card""" class Crash: """class for crashing on specific cards""" def __init__(self) -> None: """dummy init""" pass @classmethod def add_card(cls, card, comment=''): """the method that forces the crash""" #raise CardParseSyntaxError(card) msg = _format_comment(comment) + str(card) raise UnsupportedCard(msg) #class CrashIgnore: #"""class for crashing on specific cards""" #def __init__(self): #"""dummy init""" #pass #@classmethod #def add_card(cls, card, comment=''): #"""the method that forces the crash""" ##raise CardParseSyntaxError(card) #msg = _format_comment(comment) + str(card) #raise DisabledCardError(msg) #: a storage of card_name to (card_class, add_method) add_methods = self._add_methods self._card_parser = { #'=' : (Crash, None), '/' : (Crash, None), #'CGEN' : (CrashIgnore, None), 'SETREE' : (SETREE, add_methods._add_setree_object), 'SENQSET' : (SENQSET, add_methods._add_senqset_object), 'SEBULK' : (SEBULK, add_methods._add_sebulk_object), 'RELEASE': (RELEASE, add_methods._add_release_object), 'SEBNDRY' : (SEBNDRY, add_methods._add_sebndry_object), 'SEELT' : (SEELT, add_methods._add_seelt_object), 'SELOC' : (SELOC, add_methods._add_seloc_object), 'SEMPLN' : (SEMPLN, add_methods._add_sempln_object), 'SECONCT' : (SECONCT, add_methods._add_seconct_object), 'SELABEL' : (SELABEL, add_methods._add_selabel_object), 'SEEXCLD' : (SEEXCLD, add_methods._add_seexcld_object), 'CSUPER' : (CSUPER, add_methods._add_csuper_object), 'CSUPEXT' : (CSUPEXT, add_methods._add_csupext_object), 'SELOAD' : (SELOAD, add_methods._add_seload_object), ## acoustic 'CHACAB': (CHACAB, add_methods._add_element_object), 'CHACBR': (CHACBR, add_methods._add_element_object), 'CAABSF': (CAABSF, add_methods._add_element_object), 'PACABS': (PACABS, add_methods._add_acoustic_property_object), 'PAABSF': (PAABSF, add_methods._add_acoustic_property_object), 'PACBAR': (PACBAR, add_methods._add_acoustic_property_object), 'PMIC': (PMIC, add_methods._add_property_object), 'ACPLNW': (ACPLNW, add_methods._add_acplnw_object), 'AMLREG': (AMLREG, add_methods._add_amlreg_object), 'MATPOR': (MATPOR, add_methods._add_structural_material_object), 'MICPNT': (MICPNT, add_methods._add_micpnt_object), #'PANEL' : (Crash, None), 'BCONP' : (BCONP, add_methods._add_bconp_object), 'BLSEG' : (BLSEG, add_methods._add_blseg_object), 'BFRIC' : (BFRIC, add_methods._add_bfric_object), 'MODTRAK' : (MODTRAK, add_methods._add_modtrak_object), # nx contact 'BCPARA' : (BCPARA, add_methods._add_bcpara_object), 'BCTPARM' : (BCTPARM, add_methods._add_bctparam_object), 'BGADD' : (BGADD, add_methods._add_bgadd_object), 'BGSET' : (BGSET, add_methods._add_bgset_object), 'BCBODY' : (BCBODY, add_methods._add_bcbody_object), # 'BOLT', 'BOLTFOR', 'BOLTFRC', 'BOLTLD', 'BOLTSEQ' 'BOLTFOR' : (BOLTFOR, add_methods._add_boltfor_object), 'BOLTSEQ' : (BOLTSEQ, add_methods._add_boltseq_object), #'BOLTFRC' : (BOLTFRC, add_methods._add_boltfrc_object), #'BOLTLD' : (BOLTLD, add_methods._add_boltld_object), 'BOLTFRC': (Crash, None), 'BOLTLD': (Crash, None), # msc bolts # 'BOUTPUT', 'BOUTPUT': (Crash, None), #'CBEAR', 'PBEAR', 'ROTORB', 'CBEAR' : (Crash, None), 'PBEAR' : (Crash, None), 'ROTORB' : (Crash, None), #'SWLDPRM' : (Crash, None), #'CWELD' : (Crash, None), #'PWELD' : (Crash, None), #'PWSEAM' : (Crash, None), #'CWSEAM' : (Crash, None), #'CSEAM' : (Crash, None), #'PSEAM' : (Crash, None), #'DVSHAP' : (Crash, None), #'CYSYM' : (Crash, None), #'TEMPP1' : (Crash, None), #'DSCONS' : (Crash, None), #'DVAR' : (Crash, None), #'DVSET' : (Crash, None), #'DYNRED' : (Crash, None), #'BNDGRID' : (Crash, None), #'BNDFIX' : (Crash, None), #'BNDFIX1' : (Crash, None), #'AEFORCE' : (Crash, None), #'UXVEC' : (Crash, None), 'GUST2' : (Crash, None), #'RADBND' : (Crash, None), # nodes 'GRID' : (GRID, add_methods._add_node_object), 'SPOINT' : (SPOINTs, add_methods._add_spoint_object), 'EPOINT' : (EPOINTs, add_methods._add_epoint_object), 'RINGAX' : (RINGAX, add_methods._add_ringax_object), 'POINTAX' : (POINTAX, add_methods._add_ringax_object), 'POINT' : (POINT, add_methods._add_point_object), 'SEQGP' : (SEQGP, add_methods._add_seqgp_object), 'GRIDB' : (GRIDB, add_methods._add_gridb_object), 'PARAM' : (PARAM, add_methods._add_param_object), 'MDLPRM' : (MDLPRM, add_methods._add_mdlprm_object), 'CORD2R' : (CORD2R, add_methods._add_coord_object), 'CORD2C' : (CORD2C, add_methods._add_coord_object), 'CORD2S' : (CORD2S, add_methods._add_coord_object), 'MATCID' : (MATCID, add_methods._add_matcid_object), # parametric 'PSET' : (PSET, add_methods._add_pset), 'PVAL' : (PVAL, add_methods._add_pval), 'GMCURV' : (GMCURV, add_methods._add_gmcurv), 'GMSURF' : (GMSURF, add_methods._add_gmsurf), 'FEFACE' : (FEFACE, add_methods._add_feface), 'FEEDGE' : (FEEDGE, add_methods._add_feedge), # msgmesh #'GMCORD' : (GMCORD, add_methods._add_coord_object), # coords #'CGEN' : (CGEN, add_methods._add_element_object), # elements #'GMLOAD' : (GMLOAD, add_methods._add_load_object), # basic loads 'CONROD' : (CONROD, add_methods._add_element_object), 'CROD' : (CROD, add_methods._add_element_object), 'PROD' : (PROD, add_methods._add_property_object), 'CTUBE' : (CTUBE, add_methods._add_element_object), 'PTUBE' : (PTUBE, add_methods._add_property_object), 'BAROR' : (BAROR, add_methods._add_baror_object), 'CBARAO' : (CBARAO, add_methods._add_ao_object), 'PBAR' : (PBAR, add_methods._add_property_object), 'PBARL' : (PBARL, add_methods._add_property_object), 'PBRSECT' : (PBRSECT, add_methods._add_property_object), 'BEAMOR' : (BEAMOR, add_methods._add_beamor_object), 'PBEAM' : (PBEAM, add_methods._add_property_object), 'PBEAML' : (PBEAML, add_methods._add_property_object), 'PBCOMP' : (PBCOMP, add_methods._add_property_object), 'PBMSECT' : (PBMSECT, add_methods._add_property_object), 'CBEAM3' : (CBEAM3, add_methods._add_element_object), 'PBEAM3' : (PBEAM3, add_methods._add_property_object), 'CBEND' : (CBEND, add_methods._add_element_object), 'PBEND' : (PBEND, add_methods._add_property_object), 'CTRSHL' : (CTRSHL, add_methods._add_element_object), # nasa95 'CTRIA3' : (CTRIA3, add_methods._add_element_object), 'CQUAD1' : (CQUAD1, add_methods._add_element_object), # nasa95 'CQUAD4' : (CQUAD4, add_methods._add_element_object), 'CQUAD' : (CQUAD, add_methods._add_element_object), 'CQUAD8' : (CQUAD8, add_methods._add_element_object), 'CQUADX' : (CQUADX, add_methods._add_element_object), 'CQUADX4' : (CQUADX4, add_methods._add_element_object), 'CQUADX8' : (CQUADX8, add_methods._add_element_object), 'CQUADR' : (CQUADR, add_methods._add_element_object), 'CTRIA6' : (CTRIA6, add_methods._add_element_object), 'CTRIAR' : (CTRIAR, add_methods._add_element_object), 'CTRAX3' : (CTRAX3, add_methods._add_element_object), 'CTRAX6' : (CTRAX6, add_methods._add_element_object), 'CTRIAX' : (CTRIAX, add_methods._add_element_object), 'CTRIAX6' : (CTRIAX6, add_methods._add_element_object), 'SNORM' : (SNORM, add_methods._add_normal_object), 'PCOMP' : (PCOMP, add_methods._add_property_object), 'PCOMPG' : (PCOMPG, add_methods._add_property_object), 'PSHELL' : (PSHELL, add_methods._add_property_object), 'PTRSHL' : (PTRSHL, add_methods._add_property_object), 'PQUAD1' : (PQUAD1, add_methods._add_property_object), 'PLPLANE' : (PLPLANE, add_methods._add_property_object), 'CPLSTN3' : (CPLSTN3, add_methods._add_element_object), 'CPLSTN4' : (CPLSTN4, add_methods._add_element_object), 'CPLSTN6' : (CPLSTN6, add_methods._add_element_object), 'CPLSTN8' : (CPLSTN8, add_methods._add_element_object), 'CPLSTS3' : (CPLSTS3, add_methods._add_element_object), 'CPLSTS4' : (CPLSTS4, add_methods._add_element_object), 'CPLSTS6' : (CPLSTS6, add_methods._add_element_object), 'CPLSTS8' : (CPLSTS8, add_methods._add_element_object), 'PPLANE' : (PPLANE, add_methods._add_property_object), 'CSHEAR' : (CSHEAR, add_methods._add_element_object), 'PSHEAR' : (PSHEAR, add_methods._add_property_object), # nastran95 'CIHEX1' : (CIHEX1, add_methods._add_element_object), 'CIHEX2' : (CIHEX2, add_methods._add_element_object), 'CHEXA1' : (CHEXA1, add_methods._add_element_object), 'CHEXA2' : (CHEXA2, add_methods._add_element_object), 'PIHEX' : (PIHEX, add_methods._add_property_object), # msc/nx 'PSOLID' : (PSOLID, add_methods._add_property_object), 'PLSOLID' : (PLSOLID, add_methods._add_property_object), 'PCOMPS' : (PCOMPS, add_methods._add_property_object), 'PCOMPLS' : (PCOMPLS, add_methods._add_property_object), 'CELAS1' : (CELAS1, add_methods._add_element_object), 'CELAS2' : (CELAS2, add_methods._add_element_object), 'CELAS3' : (CELAS3, add_methods._add_element_object), 'CELAS4' : (CELAS4, add_methods._add_element_object), 'CVISC' : (CVISC, add_methods._add_element_object), 'PELAST' : (PELAST, add_methods._add_pelast_object), 'CDAMP1' : (CDAMP1, add_methods._add_damper_object), 'CDAMP2' : (CDAMP2, add_methods._add_damper_object), 'CDAMP3' : (CDAMP3, add_methods._add_damper_object), # CDAMP4 added later because the documentation is wrong 'CDAMP5' : (CDAMP5, add_methods._add_damper_object), 'PDAMP5' : (PDAMP5, add_methods._add_property_object), 'CFAST' : (CFAST, add_methods._add_damper_object), 'PFAST' : (PFAST, add_methods._add_property_object), 'CGAP' : (CGAP, add_methods._add_element_object), 'PGAP' : (PGAP, add_methods._add_property_object), 'CBUSH' : (CBUSH, add_methods._add_damper_object), 'CBUSH1D' : (CBUSH1D, add_methods._add_damper_object), 'CBUSH2D' : (CBUSH2D, add_methods._add_damper_object), 'PBUSH' : (PBUSH, add_methods._add_property_object), 'PBUSH1D' : (PBUSH1D, add_methods._add_property_object), 'CRAC2D' : (CRAC2D, add_methods._add_element_object), 'PRAC2D' : (PRAC2D, add_methods._add_property_object), 'CRAC3D' : (CRAC3D, add_methods._add_element_object), 'PRAC3D' : (PRAC3D, add_methods._add_property_object), 'GENEL' : (GENEL, add_methods._add_element_object), 'PDAMPT' : (PDAMPT, add_methods._add_pdampt_object), 'PBUSHT' : (PBUSHT, add_methods._add_pbusht_object), 'CCONEAX' : (CCONEAX, add_methods._add_element_object), 'PCONEAX' : (PCONEAX, add_methods._add_property_object), 'AXIC' : (AXIC, add_methods._add_axic_object), 'AXIF' : (AXIF, add_methods._add_axif_object), 'CYAX' : (CYAX, add_methods._add_cyax_object), 'RBAR' : (RBAR, add_methods._add_rigid_element_object), 'RBAR1' : (RBAR1, add_methods._add_rigid_element_object), 'RBE1' : (RBE1, add_methods._add_rigid_element_object), 'RBE2' : (RBE2, add_methods._add_rigid_element_object), 'RBE3' : (RBE3, add_methods._add_rigid_element_object), 'RROD' : (RROD, add_methods._add_rigid_element_object), 'RSPLINE' : (RSPLINE, add_methods._add_rigid_element_object), 'RSSCON' : (RSSCON, add_methods._add_rigid_element_object), ## there is no MAT6 or MAT7 'MAT1' : (MAT1, add_methods._add_structural_material_object), 'MAT2' : (MAT2, add_methods._add_structural_material_object), 'MAT3' : (MAT3, add_methods._add_structural_material_object), 'MAT8' : (MAT8, add_methods._add_structural_material_object), 'MAT9' : (MAT9, add_methods._add_structural_material_object), 'MAT10' : (MAT10, add_methods._add_structural_material_object), 'MAT11' : (MAT11, add_methods._add_structural_material_object), 'MAT3D' : (MAT3D, add_methods._add_structural_material_object), 'MATEV' : (MATEV, add_methods._add_structural_material_object), 'EQUIV' : (EQUIV, add_methods._add_structural_material_object), 'MATG' : (MATG, add_methods._add_structural_material_object), 'MATHE' : (MATHE, add_methods._add_hyperelastic_material_object), 'MATHP' : (MATHP, add_methods._add_hyperelastic_material_object), 'MATEV' : (MATEV, add_methods._add_structural_material_object), 'MAT4' : (MAT4, add_methods._add_thermal_material_object), 'MAT5' : (MAT5, add_methods._add_thermal_material_object), 'MATS1' : (MATS1, add_methods._add_material_dependence_object), #'MATS3' : (MATS3, add_methods._add_material_dependence_object), #'MATS8' : (MATS8, add_methods._add_material_dependence_object), 'MATT1' : (MATT1, add_methods._add_material_dependence_object), 'MATT2' : (MATT2, add_methods._add_material_dependence_object), 'MATT3' : (MATT3, add_methods._add_material_dependence_object), 'MATT4' : (MATT4, add_methods._add_material_dependence_object), 'MATT5' : (MATT5, add_methods._add_material_dependence_object), 'MATT8' : (MATT8, add_methods._add_material_dependence_object), 'MATT9' : (MATT9, add_methods._add_material_dependence_object), 'MATDMG': (MATDMG, add_methods._add_material_dependence_object), 'NXSTRAT' : (NXSTRAT, add_methods._add_nxstrat_object), # hasn't been verified, links up to MAT1, MAT2, MAT9 w/ same MID 'CREEP' : (CREEP, add_methods._add_creep_material_object), 'NSMADD' : (NSMADD, add_methods._add_nsmadd_object), 'NSM1' : (NSM1, add_methods._add_nsm_object), 'NSML1' : (NSML1, add_methods._add_nsm_object), 'CONM1' : (CONM1, add_methods._add_mass_object), 'CONM2' : (CONM2, add_methods._add_mass_object), 'CMASS1' : (CMASS1, add_methods._add_mass_object), 'CMASS2' : (CMASS2, add_methods._add_mass_object), 'CMASS3' : (CMASS3, add_methods._add_mass_object), # CMASS4 - added later because documentation is wrong 'MPC' : (MPC, add_methods._add_constraint_mpc_object), 'MPCADD' : (MPCADD, add_methods._add_constraint_mpcadd_object), 'SPC' : (SPC, add_methods._add_constraint_spc_object), 'SPC1' : (SPC1, add_methods._add_constraint_spc_object), 'SPCOFF' : (SPCOFF, add_methods._add_constraint_spcoff_object), 'SPCOFF1' : (SPCOFF1, add_methods._add_constraint_spcoff_object), 'SPCAX' : (SPCAX, add_methods._add_constraint_spc_object), 'SPCADD' : (SPCADD, add_methods._add_constraint_spcadd_object), ## parametric 'GMSPC' : (GMSPC, add_methods._add_constraint_spc_object), 'SESUP' : (SESUP, add_methods._add_sesuport_object), # pseudo-constraint 'SUPORT' : (SUPORT, add_methods._add_suport_object), # pseudo-constraint 'SUPORT1' : (SUPORT1, add_methods._add_suport1_object), # pseudo-constraint 'FORCE' : (FORCE, add_methods._add_load_object), 'FORCE1' : (FORCE1, add_methods._add_load_object), 'FORCE2' : (FORCE2, add_methods._add_load_object), 'MOMENT' : (MOMENT, add_methods._add_load_object), 'MOMENT1' : (MOMENT1, add_methods._add_load_object), 'MOMENT2' : (MOMENT2, add_methods._add_load_object), 'LSEQ' : (LSEQ, add_methods._add_lseq_object), 'LOAD' : (LOAD, add_methods._add_load_combination_object), 'CLOAD' : (CLOAD, add_methods._add_load_combination_object), 'LOADCYN' : (LOADCYN, add_methods._add_load_object), 'LOADCYH' : (LOADCYH, add_methods._add_load_object), # basic static loads 'GRAV' : (GRAV, add_methods._add_load_object), 'ACCEL' : (ACCEL, add_methods._add_load_object), 'ACCEL1' : (ACCEL1, add_methods._add_load_object), 'PLOAD' : (PLOAD, add_methods._add_load_object), 'PLOAD1' : (PLOAD1, add_methods._add_load_object), 'PLOAD2' : (PLOAD2, add_methods._add_load_object), 'PLOAD4' : (PLOAD4, add_methods._add_load_object), 'RFORCE' : (RFORCE, add_methods._add_load_object), 'RFORCE1' : (RFORCE1, add_methods._add_load_object), 'SLOAD' : (SLOAD, add_methods._add_load_object), 'SPCD' : (SPCD, add_methods._add_load_object), # enforced displacement 'QVOL' : (QVOL, add_methods._add_load_object), # thermal # axisymmetric loads 'FORCEAX' : (FORCEAX, add_methods._add_load_object), 'PLOADX1' : (PLOADX1, add_methods._add_load_object), 'PRESAX' : (PRESAX, add_methods._add_load_object), # axisymmetric 'DLOAD' : (DLOAD, add_methods._add_dload_object), 'ACSRCE' : (ACSRCE, add_methods._add_dload_entry), 'TLOAD1' : (TLOAD1, add_methods._add_dload_entry), 'TLOAD2' : (TLOAD2, add_methods._add_dload_entry), 'RLOAD1' : (RLOAD1, add_methods._add_dload_entry), 'RLOAD2' : (RLOAD2, add_methods._add_dload_entry), 'RANDPS' : (RANDPS, add_methods._add_dload_entry), # random 'RANDT1' : (RANDT1, add_methods._add_dload_entry), # random 'QVECT' : (QVECT, add_methods._add_dload_entry), 'FREQ' : (FREQ, add_methods._add_freq_object), 'FREQ1' : (FREQ1, add_methods._add_freq_object), 'FREQ2' : (FREQ2, add_methods._add_freq_object), 'FREQ3' : (FREQ3, add_methods._add_freq_object), 'FREQ4' : (FREQ4, add_methods._add_freq_object), 'FREQ5' : (FREQ5, add_methods._add_freq_object), 'DOPTPRM' : (DOPTPRM, add_methods._add_doptprm_object), 'DESVAR' : (DESVAR, add_methods._add_desvar_object), 'TOPVAR' : (TOPVAR, add_methods._add_topvar_object), # BCTSET 'TEMPRB' : (TEMPRB, add_methods._add_thermal_load_object), 'TEMP' : (TEMP, add_methods._add_thermal_load_object), 'TEMPB3' : (TEMPB3, add_methods._add_thermal_load_object), 'QBDY1' : (QBDY1, add_methods._add_thermal_load_object), 'QBDY2' : (QBDY2, add_methods._add_thermal_load_object), 'QBDY3' : (QBDY3, add_methods._add_thermal_load_object), 'QHBDY' : (QHBDY, add_methods._add_thermal_load_object), 'PHBDY' : (PHBDY, add_methods._add_phbdy_object), 'CHBDYE' : (CHBDYE, add_methods._add_thermal_element_object), 'CHBDYG' : (CHBDYG, add_methods._add_thermal_element_object), 'CHBDYP' : (CHBDYP, add_methods._add_thermal_element_object), 'PCONV' : (PCONV, add_methods._add_convection_property_object), 'PCONVM' : (PCONVM, add_methods._add_convection_property_object), 'VIEW' : (VIEW, add_methods._add_view_object), 'VIEW3D' : (VIEW3D, add_methods._add_view3d_object), # aero 'AECOMP' : (AECOMP, add_methods._add_aecomp_object), 'AECOMPL' : (AECOMPL, add_methods._add_aecomp_object), 'AEFACT' : (AEFACT, add_methods._add_aefact_object), 'AELINK' : (AELINK, add_methods._add_aelink_object), 'AELIST' : (AELIST, add_methods._add_aelist_object), 'AEPARM' : (AEPARM, add_methods._add_aeparm_object), 'AESTAT' : (AESTAT, add_methods._add_aestat_object), 'AESURF' : (AESURF, add_methods._add_aesurf_object), 'AESURFS' : (AESURFS, add_methods._add_aesurfs_object), 'CAERO1' : (CAERO1, add_methods._add_caero_object), 'CAERO2' : (CAERO2, add_methods._add_caero_object), 'CAERO3' : (CAERO3, add_methods._add_caero_object), 'CAERO4' : (CAERO4, add_methods._add_caero_object), 'CAERO5' : (CAERO5, add_methods._add_caero_object), 'PAERO1' : (PAERO1, add_methods._add_paero_object), 'PAERO2' : (PAERO2, add_methods._add_paero_object), 'PAERO3' : (PAERO3, add_methods._add_paero_object), 'PAERO4' : (PAERO4, add_methods._add_paero_object), 'PAERO5' : (PAERO5, add_methods._add_paero_object), 'SPLINE1' : (SPLINE1, add_methods._add_spline_object), 'SPLINE2' : (SPLINE2, add_methods._add_spline_object), 'SPLINE3' : (SPLINE3, add_methods._add_spline_object), 'SPLINE4' : (SPLINE4, add_methods._add_spline_object), 'SPLINE5' : (SPLINE5, add_methods._add_spline_object), # SOL 144 'AEROS' : (AEROS, add_methods._add_aeros_object), 'TRIM' : (TRIM, add_methods._add_trim_object), 'TRIM2' : (TRIM2, add_methods._add_trim_object), 'DIVERG' : (DIVERG, add_methods._add_diverg_object), # SOL 145 'AERO' : (AERO, add_methods._add_aero_object), 'FLUTTER' : (FLUTTER, add_methods._add_flutter_object), 'FLFACT' : (FLFACT, add_methods._add_flfact_object), 'MKAERO1' : (MKAERO1, add_methods._add_mkaero_object), 'MKAERO2' : (MKAERO2, add_methods._add_mkaero_object), 'GUST' : (GUST, add_methods._add_gust_object), 'CSSCHD' : (CSSCHD, add_methods._add_csschd_object), 'MONPNT1' : (MONPNT1, add_methods._add_monpnt_object), 'MONPNT2' : (MONPNT2, add_methods._add_monpnt_object), 'MONPNT3' : (MONPNT3, add_methods._add_monpnt_object), 'MONDSP1' : (MONDSP1, add_methods._add_monpnt_object), 'NLPARM' : (NLPARM, add_methods._add_nlparm_object), 'NLPCI' : (NLPCI, add_methods._add_nlpci_object), 'TSTEP' : (TSTEP, add_methods._add_tstep_object), 'TSTEP1' : (TSTEP1, add_methods._add_tstepnl_object), 'TSTEPNL' : (TSTEPNL, add_methods._add_tstepnl_object), 'TF' : (TF, add_methods._add_tf_object), 'TIC' : (TIC, add_methods._add_tic_object), 'DCONADD' : (DCONADD, add_methods._add_dconstr_object), 'DCONSTR' : (DCONSTR, add_methods._add_dconstr_object), 'DDVAL' : (DDVAL, add_methods._add_ddval_object), 'DLINK' : (DLINK, add_methods._add_dlink_object), 'DSCREEN' : (DSCREEN, add_methods._add_dscreen_object), 'DTABLE' : (DTABLE, add_methods._add_dtable_object), 'DRESP1' : (DRESP1, add_methods._add_dresp_object), # dresps 'DRESP2' : (DRESP2, add_methods._add_dresp_object), 'DRESP3' : (DRESP3, add_methods._add_dresp_object), 'DVCREL1' : (DVCREL1, add_methods._add_dvcrel_object), # dvcrels 'DVCREL2' : (DVCREL2, add_methods._add_dvcrel_object), 'DVPREL1' : (DVPREL1, add_methods._add_dvprel_object), # dvprels 'DVPREL2' : (DVPREL2, add_methods._add_dvprel_object), 'DVMREL1' : (DVMREL1, add_methods._add_dvmrel_object), # ddvmrels 'DVMREL2' : (DVMREL2, add_methods._add_dvmrel_object), 'DVGRID' : (DVGRID, add_methods._add_dvgrid_object), # dvgrids # nx_opt 'DVTREL1' : (DVTREL1, add_methods._add_dvtrel_object), # dvtrels 'GROUP' : (GROUP, add_methods._add_group_object), # group 'DMNCON' : (DMNCON, add_methods._add_dmncon_object), # dmncon # tables 'TABLES1' : (TABLES1, add_methods._add_table_object), 'TABLEST' : (TABLEST, add_methods._add_table_object), 'TABLEHT' : (TABLEHT, add_methods._add_table_object), 'TABLEH1' : (TABLEH1, add_methods._add_table_object), # dynamic tables 'TABLED1' : (TABLED1, add_methods._add_tabled_object), 'TABLED2' : (TABLED2, add_methods._add_tabled_object), 'TABLED3' : (TABLED3, add_methods._add_tabled_object), 'TABLED4' : (TABLED4, add_methods._add_tabled_object), # material tables 'TABLEM1' : (TABLEM1, add_methods._add_tablem_object), 'TABLEM2' : (TABLEM2, add_methods._add_tablem_object), 'TABLEM3' : (TABLEM3, add_methods._add_tablem_object), 'TABLEM4' : (TABLEM4, add_methods._add_tablem_object), # other tables 'TABDMP1' : (TABDMP1, add_methods._add_table_sdamping_object), 'TABRND1' : (TABRND1, add_methods._add_random_table_object), 'TABRNDG' : (TABRNDG, add_methods._add_random_table_object), 'EIGB' : (EIGB, add_methods._add_method_object), 'EIGR' : (EIGR, add_methods._add_method_object), 'EIGRL' : (EIGRL, add_methods._add_method_object), 'EIGC' : (EIGC, add_methods._add_cmethod_object), 'EIGP' : (EIGP, add_methods._add_cmethod_object), 'BCRPARA' : (BCRPARA, add_methods._add_bcrpara_object), 'BCTADD' : (BCTADD, add_methods._add_bctadd_object), 'BCTPARA' : (BCTPARA, add_methods._add_bctpara_object), 'BSURF' : (BSURF, add_methods._add_bsurf_object), 'BSURFS' : (BSURFS, add_methods._add_bsurfs_object), 'RADCAV' : (RADCAV, add_methods._add_radcav_object), # #'RADLST' : (RADLST, add_methods._add_radcav_object), # TestOP2.test_bdf_op2_thermal_02 #'RADMTX' : (RADMTX, add_methods._add_radmtx_object), # TestOP2.test_bdf_op2_thermal_02 #'RADMT' : (Crash, None), 'ASET' : (ASET, add_methods._add_aset_object), 'ASET1' : (ASET1, add_methods._add_aset_object), 'BSET' : (BSET, add_methods._add_bset_object), 'BSET1' : (BSET1, add_methods._add_bset_object), 'CSET' : (CSET, add_methods._add_cset_object), 'CSET1' : (CSET1, add_methods._add_cset_object), 'QSET' : (QSET, add_methods._add_qset_object), 'QSET1' : (QSET1, add_methods._add_qset_object), 'USET' : (USET, add_methods._add_uset_object), 'USET1' : (USET1, add_methods._add_uset_object), 'OMIT' : (OMIT, add_methods._add_omit_object), 'OMIT1' : (OMIT1, add_methods._add_omit_object), 'SET1' : (SET1, add_methods._add_set_object), 'SET2' : (SET2, add_methods._add_set_object), 'SET3' : (SET3, add_methods._add_set_object), # radset 'RADSET' : (RADSET, add_methods._add_radset_object), # superelement sets 'SESET' : (SESET, add_methods._add_seset_object), 'SEBSET' : (SEBSET, add_methods._add_sebset_object), 'SEBSET1' : (SEBSET1, add_methods._add_sebset_object), 'SECSET' : (SECSET, add_methods._add_secset_object), 'SECSET1' : (SECSET1, add_methods._add_secset_object), 'SEQSET' : (SEQSET, add_methods._add_seqset_object), 'SEQSET1' : (SEQSET1, add_methods._add_seqset_object), #'SESUP' : (SESUP, add_methods._add_sesup_object), # pseudo-constraint #'SEUSET' : (SEUSET, add_methods._add_seuset_object), #'SEUSET1' : (SEUSET1, add_methods._add_seuset_object), # BCTSET 'ROTORG' : (ROTORG, add_methods._add_rotor_object), 'ROTORD' : (ROTORD, add_methods._add_rotor_object), 'DAREA' : (DAREA, add_methods._add_darea_object), 'DPHASE' : (DPHASE, add_methods._add_dphase_object), 'DELAY' : (DELAY, add_methods._add_delay_object), 'CYJOIN' : (CYJOIN, add_methods._add_cyjoin_object), } self._card_parser_prepare = { 'BOLT': self._prepare_bolt, 'PLOTEL': self._prepare_plotel, 'CBAR' : self._prepare_cbar, 'CBEAM' : self._prepare_cbeam, 'CTETRA' : self._prepare_ctetra, 'CPENTA' : self._prepare_cpenta, 'CHEXA' : self._prepare_chexa, 'CPYRAM' : self._prepare_cpyram, 'CORD1R' : self._prepare_cord1r, 'CORD1C' : self._prepare_cord1c, 'CORD1S' : self._prepare_cord1s, #'CORD3G' : self._prepare_CORD3G, 'PMASS' : self._prepare_pmass, 'CMASS4' : self._prepare_cmass4, 'CDAMP4' : self._prepare_cdamp4, 'DEFORM' : self._prepare_deform, # enforced displacement 'DTI' : self._prepare_dti, 'DMIG' : self._prepare_dmig, 'DMIAX' : self._prepare_dmiax, 'DMI' : self._prepare_dmi, 'DMIJ' : self._prepare_dmij, 'DMIK' : self._prepare_dmik, 'DMIJI' : self._prepare_dmiji, 'RINGFL' : self._prepare_ringfl, 'DEQATN' : self._prepare_dequatn, 'NSM' : self._prepare_nsm, 'NSML' : self._prepare_nsml, 'PVISC' : self._prepare_pvisc, 'PELAS' : self._prepare_pelas, 'PDAMP' : self._prepare_pdamp, 'TEMPAX' : self._prepare_tempax, 'TEMPD' : self._prepare_tempd, 'TEMPBC' : self._prepare_tempbc, 'CONVM' : self._prepare_convm, 'CONV' : self._prepare_conv, 'RADM' : self._prepare_radm, 'RADBC' : self._prepare_radbc, # GRDSET-will be last card to update from _card_parser_prepare 'GRDSET' : self._prepare_grdset, 'BCTSET' : self._prepare_bctset, 'ACMODL' : self._prepare_acmodl, } new_reject_method = False if new_reject_method: self.cards_to_read.remove('CONM2') # bwb_saero: 6.37-6.57 before using all_cards = set(self._card_parser.keys() + self._card_parser_prepare.keys()) reject_cards = all_cards.difference(self.cards_to_read) #print('reject_cards =', reject_cards) for card_name in reject_cards: if card_name in self._card_parser: del self._card_parser[card_name] self._card_parser_prepare[card_name] = self._reject_card_obj2 def _reject_card_obj2(self, unused_card_name, card_obj): """rejects a card object""" self.reject_cards.append(card_obj) def reject_card_lines(self, card_name: str, card_lines: list[str], show_log: bool=True, comment: str='') -> None: """rejects a card""" if card_name.isdigit(): # TODO: this should technically work (I think), but it's a problem # for the code # # prevents: # spc1,100,456,10013832,10013833,10013830,10013831,10013836,10013837, # 10013834,10013835,10013838,10013839,10014508,10008937,10008936,10008935, msg = 'card_name=%r was misparsed...\ncard_lines=%s' % ( card_name, card_lines) raise RuntimeError(msg) #elif card_name in SOL_700 or card_name in MISSING_CARDS: #self.log.warning(f' rejecting card_name = {card_name!r}') #return if card_name not in self.card_count: _check_for_spaces(card_name, card_lines, comment, self.log) #raise RuntimeError(card_name) if card_name == '\ufeff': self.log.warning(f' rejecting card_name = {card_name!r}') self.log.warning(' comment:\n') print(comment) self.log.warning(' lines:\n') for line in card_lines: print(line) elif show_log: self.log.info(f' rejecting card_name = {card_name!r}') assert isinstance(show_log, bool), show_log self.increase_card_count(card_name) self.reject_lines.append([_format_comment(comment)] + card_lines) def _write_reject_message(self, card_name, unused_card_obj, comment=''): """common method to not write duplicate reject card names""" if card_name not in self.card_count: #if ' ' in card_name: #_check_for_spaces(card_name, card_lines, comment, self.log) self.log.info(' rejecting card_name = %s' % card_name) def _prepare_bolt(self, card: list[str], card_obj: BDFCard, comment: str='') -> list[BOLT | BOLT_MSC]: """adds a BOLT""" card_obj.card = [value.upper() if isinstance(value, str) else value for value in card_obj.card] if 'TOP' in card_obj.card or 'BOTTOM' in card_obj.card: self.set_as_msc() else: self.set_as_nx() if self.is_nx: bolt = BOLT.add_card(card_obj, comment=comment) else: bolt = BOLT_MSC.add_card(card_obj, comment=comment) self._add_methods._add_bolt_object(bolt) return bolt def _prepare_plotel(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[PLOTEL]: """adds a PLOTEL""" #['PLOTEL', '3101', '3101', '3102', None, '3102', '3102', '3103'] plotels = [PLOTEL.add_card(card_obj, 0, comment=comment)] if card_obj.field(5): # eid plotels.append(PLOTEL.add_card(card_obj, 1, comment='')) for plotel in plotels: self._add_methods._add_plotel_object(plotel) return plotels def _prepare_cbar(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CBAR: """adds a CBAR""" elem = CBAR.add_card(card_obj, baror=self.baror, comment=comment) self._add_methods._add_element_object(elem) return elem def _prepare_cbeam(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CBEAM: """adds a CBEAM""" elem = CBEAM.add_card(card_obj, beamor=self.beamor, comment=comment) self._add_methods._add_element_object(elem) return elem def _prepare_ctetra(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CTETRA4: """adds a CTETRA4/CTETRA10""" if len(card_obj) == 7: elem = CTETRA4.add_card(card_obj, comment=comment) else: elem = CTETRA10.add_card(card_obj, comment=comment) self._add_methods._add_element_object(elem) return elem def _prepare_cpyram(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CPYRAM5: """adds a CPYRAM5/CPYRAM13""" if len(card_obj) == 8: elem = CPYRAM5.add_card(card_obj, comment=comment) else: elem = CPYRAM13.add_card(card_obj, comment=comment) self._add_methods._add_element_object(elem) return elem def _prepare_cpenta(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CPENTA6: """adds a CPENTA6/CPENTA15""" if len(card_obj) == 9: elem = CPENTA6.add_card(card_obj, comment=comment) else: elem = CPENTA15.add_card(card_obj, comment=comment) self._add_methods._add_element_object(elem) return elem def _prepare_chexa(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CHEXA8: """adds a CHEXA8/CHEXA20""" if len(card_obj) == 11: elem = CHEXA8.add_card(card_obj, comment=comment) else: elem = CHEXA20.add_card(card_obj, comment=comment) self._add_methods._add_element_object(elem) return elem def _prepare_bctset(self, unused_card: list[str], card_obj: BDFCard, comment='') -> BCTSET: """adds a BCTSET""" bctset = BCTSET.add_card(card_obj, comment=comment, sol=self.sol) self._add_methods._add_bctset_object(bctset) return bctset def _prepare_grdset(self, unused_card: list[str], card_obj: BDFCard, comment='') -> GRDSET: """adds a GRDSET""" self.grdset = GRDSET.add_card(card_obj, comment=comment) return self.grdset def _prepare_cdamp4(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CDAMP4: """adds a CDAMP4""" dampers = [CDAMP4.add_card(card_obj, comment=comment)] if card_obj.field(5): dampers.append(CDAMP4.add_card(card_obj, 1, comment='')) for damper in dampers: self._add_methods._add_damper_object(damper) return dampers def _prepare_deform(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DEFORM: """adds a DEFORM""" loads = [DEFORM.add_card(card_obj, comment=comment)] if card_obj.field(4): loads.append(DEFORM.add_card(card_obj, 1, comment=comment)) if card_obj.field(6): loads.append(DEFORM.add_card(card_obj, 2, comment=comment)) for loadi in loads: self._add_methods._add_load_object(loadi) return loads def _prepare_tempbc(self, unused_card: list[str], card_obj: BDFCard, comment='') -> TEMPBC: """adds a TEMPBC""" boundary_condition = TEMPBC.add_card(card_obj, comment=comment) self._add_methods._add_thermal_bc_object(boundary_condition, boundary_condition.eid) return boundary_condition def _prepare_convm(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CONVM: """adds a CONVM""" boundary_condition = CONVM.add_card(card_obj, comment=comment) self._add_methods._add_thermal_bc_object(boundary_condition, boundary_condition.eid) return boundary_condition def _prepare_conv(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CONV: """adds a CONV""" boundary_condition = CONV.add_card(card_obj, comment=comment) self._add_methods._add_thermal_bc_object(boundary_condition, boundary_condition.eid) return boundary_condition def _prepare_radm(self, unused_card: list[str], card_obj: BDFCard, comment='') -> RADM: """adds a RADM""" boundary_condition = RADM.add_card(card_obj, comment=comment) self._add_methods._add_thermal_bc_object(boundary_condition, boundary_condition.radmid) return boundary_condition def _prepare_radbc(self, unused_card: list[str], card_obj: BDFCard, comment='') -> RADBC: """adds a RADBC""" boundary_condition = RADBC.add_card(card_obj, comment=comment) self._add_methods._add_thermal_bc_object(boundary_condition, boundary_condition.nodamb) return boundary_condition def _prepare_tempd(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[TEMPD]: """adds a TEMPD""" tempds = [TEMPD.add_card(card_obj, 0, comment=comment)] if card_obj.field(3): tempds.append(TEMPD.add_card(card_obj, 1, comment='')) if card_obj.field(5): tempds.append(TEMPD.add_card(card_obj, 2, comment='')) if card_obj.field(7): tempds.append(TEMPD.add_card(card_obj, 3, comment='')) for tempd in tempds: self._add_methods._add_tempd_object(tempd) return tempds def _prepare_tempax(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[TEMPAX]: """adds a TEMPAX""" tempaxs = [TEMPAX.add_card(card_obj, 0, comment=comment)] if card_obj.field(5): tempaxs.append(TEMPAX.add_card(card_obj, 1, comment='')) for tempax in tempaxs: self._add_methods._add_load_object(tempax) return tempaxs def _prepare_dequatn(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DEQATN: """adds a DEQATN""" deqatn = DEQATN.add_card(card_obj, comment=comment) self._add_methods._add_deqatn_object(deqatn) return deqatn def _prepare_dti(self, unused_card_name, card_obj, comment='') -> DTI: """adds a DTI""" name = string(card_obj, 1, 'name') if name == 'UNITS': dti = DTI_UNITS.add_card(card_obj, comment=comment) else: dti = DTI.add_card(card_obj, comment=comment) self._add_methods._add_dti_object(dti) return dti def _prepare_dmig(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DMIG: """adds a DMIG""" name = string(card_obj, 1, 'name') field2 = integer_or_string(card_obj, 2, 'flag') if name == 'UACCEL': # special DMIG card if field2 == 0: dmig = DMIG_UACCEL.add_card(card_obj, comment=comment) self._add_methods._add_dmig_object(dmig) else: dmig = -1 self._dmig_temp[name].append((card_obj, comment)) else: field2 = integer_or_string(card_obj, 2, 'flag') if field2 == 0: dmig = DMIG.add_card(card_obj, comment=comment) self._add_methods._add_dmig_object(dmig) else: dmig = -1 self._dmig_temp[name].append((card_obj, comment)) return dmig def _prepare_dmix(self, class_obj, add_method, card_obj, comment='') -> Union[DMI, DMIJ, DMIJI, DMIK]: """adds a DMI, DMIJ, DMIJI, or DMIK""" field2 = integer(card_obj, 2, 'flag') if field2 == 0: dmix = class_obj.add_card(card_obj, comment=comment) add_method(dmix) else: dmix = -1 name = string(card_obj, 1, 'name') self._dmig_temp[name].append((card_obj, comment)) return dmix def _prepare_dmiax(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DMIAX: """adds a DMIAX""" return self._prepare_dmix(DMIAX, self._add_methods._add_dmiax_object, card_obj, comment=comment) def _prepare_dmi(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DMI: """adds a DMI""" return self._prepare_dmix(DMI, self._add_methods._add_dmi_object, card_obj, comment=comment) def _prepare_dmij(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DMIJ: """adds a DMIJ""" return self._prepare_dmix(DMIJ, self._add_methods._add_dmij_object, card_obj, comment=comment) def _prepare_dmik(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DMIK: """adds a DMIK""" return self._prepare_dmix(DMIK, self._add_methods._add_dmik_object, card_obj, comment=comment) def _prepare_dmiji(self, unused_card: list[str], card_obj: BDFCard, comment='') -> DMIJI: """adds a DMIJI""" return self._prepare_dmix(DMIJI, self._add_methods._add_dmiji_object, card_obj, comment=comment) def _prepare_cmass4(self, unused_card: list[str], card_obj: BDFCard, comment='') -> CMASS4: """adds a CMASS4""" elements = [CMASS4.add_card(card_obj, icard=0, comment=comment)] if card_obj.field(5): elements.append(CMASS4.add_card(card_obj, icard=1, comment=comment)) for elem in elements: self._add_methods._add_mass_object(elem) return elements def _prepare_pelas(self, unused_card: list[str], card_obj: BDFCard, comment='') -> PELAS: """adds a PELAS""" properties = [PELAS.add_card(card_obj, icard=0, comment=comment)] if card_obj.field(5): properties.append(PELAS.add_card(card_obj, icard=1, comment=comment)) for prop in properties: self._add_methods._add_property_object(prop) return properties def _prepare_nsm(self, unused_card: list[str], card_obj: BDFCard, comment='') -> NSM: """adds an NSM""" nfields = len(card_obj) ncards = (nfields - 3) // 2 nextra = (nfields - 3) % 2 assert nextra == 0, 'NSM error; nfields=%s must have an odd number of fields\ncard=%s' % ( nfields, card_obj) nsms = [] for icard in range(ncards): nsms.append(NSM.add_card(card_obj, icard, comment=comment)) for nsm in nsms: self._add_methods._add_nsm_object(nsm) return nsms def _prepare_nsml(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[NSML]: """adds an NSML""" nfields = len(card_obj) ncards = (nfields - 3) // 2 nextra = (nfields - 3) % 2 assert nextra == 0, 'NSML error; nfields=%s must have an odd number of fields\ncard=%s' % ( nfields, card_obj) nsms = [] for icard in range(ncards): nsms.append(NSML.add_card(card_obj, icard, comment=comment)) for nsm in nsms: self._add_methods._add_nsm_object(nsm) return nsms def _prepare_pvisc(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[PVISC]: """adds a PVISC""" properties = [PVISC.add_card(card_obj, icard=0, comment=comment)] if card_obj.field(5): properties.append(PVISC.add_card(card_obj, icard=1, comment=comment)) for prop in properties: self._add_methods._add_property_object(prop) return properties def _prepare_ringfl(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[RINGFL]: """adds a RINGFL""" rings = [RINGFL.add_card(card_obj, icard=0, comment=comment)] if card_obj.field(3): rings.append(RINGFL.add_card(card_obj, icard=1, comment=comment)) if card_obj.field(5): rings.append(RINGFL.add_card(card_obj, icard=2, comment=comment)) for ring in rings: self._add_methods._add_ringfl_object(ring) return rings def _prepare_pdamp(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[PDAMP]: """adds a PDAMP""" properties = [PDAMP.add_card(card_obj, icard=0, comment=comment)] if card_obj.field(3): properties.append(PDAMP.add_card(card_obj, icard=1, comment=comment)) if card_obj.field(5): properties.append(PDAMP.add_card(card_obj, icard=2, comment=comment)) if card_obj.field(7): properties.append(PDAMP.add_card(card_obj, icard=3, comment=comment)) for prop in properties: self._add_methods._add_property_object(prop) return properties def _prepare_pmass(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[PMASS]: """adds a PMASS""" properties = [PMASS.add_card(card_obj, icard=0, comment=comment)] for (i, j) in enumerate([3, 5, 7]): if card_obj.field(j): properties.append(PMASS.add_card(card_obj, icard=i+1, comment=comment)) for prop in properties: self._add_methods._add_property_mass_object(prop) return properties def _prepare_cord1r(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[CORD1R]: """adds a CORD1R""" coords = [CORD1R.add_card(card_obj, comment=comment)] if card_obj.field(5): coords.append(CORD1R.add_card(card_obj, icard=1, comment=comment)) for coord in coords: self._add_methods._add_coord_object(coord) return coords def _prepare_cord1c(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[CORD1C]: """adds a CORD1C""" coords = [CORD1C.add_card(card_obj, comment=comment)] if card_obj.field(5): coords.append(CORD1C.add_card(card_obj, icard=1, comment=comment)) for coord in coords: self._add_methods._add_coord_object(coord) return coords def _prepare_cord1s(self, unused_card: list[str], card_obj: BDFCard, comment='') -> list[CORD1S]: """adds a CORD1S""" coords = [CORD1S.add_card(card_obj, comment=comment)] if card_obj.field(5): coords.append(CORD1S.add_card(card_obj, icard=1, comment=comment)) for coord in coords: self._add_methods._add_coord_object(coord) return coords def _prepare_acmodl(self, unused_card: list[str], card_obj: BDFCard, comment='') -> ACMODL: acmodl = ACMODL.add_card(card_obj, self._nastran_format, comment=comment) self._add_methods._add_acmodl_object(acmodl) def add_card_ifile(self, ifile: int, card_lines: list[str], card_name: str, comment: str='', is_list: bool=True, has_none: bool=True) -> Any: """Same as ``add_card`` except it has an ifile parameter""" assert isinstance(ifile, (int, np.int32)), 'ifile=%s type=%s' % (ifile, type(ifile)) card_name = card_name.upper() card_obj, unused_card = self.create_card_object( card_lines, card_name, is_list=is_list, has_none=has_none) self._add_card_helper_ifile(ifile, card_obj, card_name, card_name, comment) return card_obj def add_card(self, card_lines: list[str], card_name: str, comment: str='', ifile=None, is_list: bool=True, has_none: bool=True) -> Any: """ Adds a card object to the BDF object. Parameters ---------- card_lines: list[str] the list of the card fields card_name : str the card_name -> 'GRID' comment : str an optional the comment for the card is_list : bool, optional False : input is a list of card fields -> ['GRID', 1, None, 3.0, 4.0, 5.0] True : input is list of card_lines -> ['GRID, 1,, 3.0, 4.0, 5.0'] has_none : bool; default=True can there be trailing Nones in the card data (e.g. ['GRID', 1, 2, 3.0, 4.0, 5.0, None]) can there be trailing Nones in the card data (e.g. ['GRID', 1, 2, 3.0, 4.0, 5.0, ']) Returns ------- card_object : BDFCard() the card object representation of card .. code-block:: python >>> model = BDF() # is_list is a somewhat misleading name; is it a list of card_lines # where a card_line is an unparsed string >>> card_lines = ['GRID,1,2'] >>> comment = 'this is a comment' >>> model.add_card(card_lines, 'GRID', comment, is_list=True) # here is_list=False because it's been parsed >>> card = ['GRID', 1, 2,] >>> model.add_card(card_lines, 'GRID', comment, is_list=False) # here is_list=False because it's been parsed # Note the None at the end of the 1st line, which is there # because the CONM2 card has a blank field. # It must be there. # We also set i32 on the 2nd line, so it will default to 0.0 >>> card = [ 'CONM2', eid, nid, cid, mass, x1, x2, x3, None, i11, i21, i22, i31, None, i33, ] >>> model.add_card(card_lines, 'CONM2', comment, is_list=False) # here's an alternate approach for the CONM2 # we use Nastran's CSV format # There are many blank fields, but it's parsed exactly like a # standard CONM2. >>> card = [ 'CONM2,1,2,3,10.0', ',1.0,,5.0' ] >>> model.add_card(card_lines, 'CONM2', comment, is_list=True) .. note:: this is a very useful method for interfacing with the code .. note:: the card_object is not a card-type object...so not a GRID card or CQUAD4 object. It's a BDFCard Object. However, you know the type (assuming a GRID), so just call the *mesh.Node(nid)* to get the Node object that was just created. """ card_name = card_name.upper() card_obj, unused_card = self.create_card_object( card_lines, card_name, is_list=is_list, has_none=has_none) self._add_card_helper(card_obj, card_name, card_name, ifile, comment=comment) return card_obj def add_card_lax(self, card_lines: list[str], card_name: str, comment: str='', ifile=None, is_list: bool=True, has_none: bool=True) -> Any: """see ``add_card``""" card_name = card_name.upper() #if card_name not in self.card_count: #print(card_name) card_obj, unused_card = self.create_card_object( card_lines, card_name, is_list=is_list, has_none=has_none) self._add_card_helper_lax(card_obj, card_name, card_name, ifile, comment=comment) return card_obj def add_card_fields(self, card_lines, card_name, comment='', has_none=True): """ Adds a card object to the BDF object. Parameters ---------- card_lines: list[str] the list of the card fields input is a list of card fields -> ['GRID', 1, 2, 3.0, 4.0, 5.0] card_name : str the card_name -> 'GRID' comment : str an optional the comment for the card has_none : bool; default=True can there be trailing Nones in the card data (e.g. ['GRID', 1, 2, 3.0, 4.0, 5.0, None]) Returns ------- card_object : BDFCard() the card object representation of card """ card_name = card_name.upper() card_obj, card = self.create_card_object(card_lines, card_name, is_list=True, has_none=has_none) self._add_card_helper(card_obj, card, card_name, comment) def add_card_lines(self, card_lines, card_name, comment='', has_none=True): """ Adds a card object to the BDF object. Parameters ---------- card_lines: list[str] the list of the card fields input is list of card_lines -> ['GRID, 1, 2, 3.0, 4.0, 5.0'] card_name : str the card_name -> 'GRID' comment : str; default='' an optional the comment for the card has_none : bool; default=True can there be trailing Nones in the card data (e.g. ['GRID, 1, 2, 3.0, 4.0, 5.0, ']) """ card_name = card_name.upper() card_obj, card = self.create_card_object(card_lines, card_name, is_list=False, has_none=has_none) self._add_card_helper(card_obj, card, card_name, comment) def get_xyz_in_coord_no_xref(self, cid=0, fdtype='float64', sort_ids=True): """see get_xyz_in_coord""" npoints, nids, all_nodes = self._get_npoints_nids_allnids() xyz_cid0 = np.zeros((npoints, 3), dtype=fdtype) if cid == 0: for i, nid in enumerate(nids): node = self.nodes[nid] xyz = node.get_position_no_xref(self) xyz_cid0[i, :] = xyz else: for i, nid in enumerate(nids): node = self.nodes[nid] xyz = node.get_position_wrt_no_xref(self, cid) xyz_cid0[i, :] = xyz if sort_ids: isort = np.argsort(all_nodes) xyz_cid0 = xyz_cid0[isort, :] return all_nodes, xyz_cid0 def _get_npoints_nids_allnids(self): """helper method for get_xyz_in_coord""" if self.is_bdf_vectorized: return self.nodes.nids nnodes = len(self.nodes) nspoints = 0 nepoints = 0 nids = list(self.node_ids) all_nodes = list(self.node_ids) if self.spoints: spoints = list(self.spoints) nspoints = len(spoints) all_nodes += spoints if self.epoints: epoints = list(self.epoints) nepoints = len(epoints) all_nodes += epoints #self.log.debug('all_nodes = %s' % all_nodes) npoints = nnodes + nspoints + nepoints if len(all_nodes) != npoints: msg = 'len(unique(all_nodes))=%s npoints=%s\n' % (len(np.unique(all_nodes)), npoints) msg += 'npoints = nnodes+nspoints+nepoints = %s + %s + %s\n' % ( nnodes, nspoints, nepoints) msg += 'all_nodes=%s' % (all_nodes) raise RuntimeError(msg) if npoints == 0: msg = 'nnodes=%s nspoints=%s nepoints=%s' % (nnodes, nspoints, nepoints) raise ValueError(msg) return npoints, nids, all_nodes def get_xyz_in_coord(self, cid: int=0, fdtype: str='float64', sort_ids: bool=True): """ Gets the xyz points (including SPOINTS) in the desired coordinate frame Parameters ---------- cid : int; default=0 the desired coordinate system fdtype : str; default='float64' the data type of the xyz coordinates sort_ids : bool; default=True sort the ids Returns ------- xyz : (n, 3) ndarray the xyz points in the cid coordinate frame """ #return self.get_displacement_index_xyz_cp_cd(cid=cid, fdtype=dtype)[2] npoints, nids, all_nodes = self._get_npoints_nids_allnids() xyz_cid0 = np.zeros((npoints, 3), dtype=fdtype) if cid == 0: for i, nid in enumerate(nids): node = self.nodes[nid] xyz = node.get_position() xyz_cid0[i, :] = xyz else: for i, nid in enumerate(nids): node = self.nodes[nid] xyz = node.get_position_wrt(self, cid) xyz_cid0[i, :] = xyz if sort_ids: isort = np.argsort(all_nodes) xyz_cid0 = xyz_cid0[isort, :] return xyz_cid0 def _add_card_helper_ifile(self, ifile, card_obj, card, card_name, comment=''): """See ``_add_card_helper``""" if card_name == 'ECHOON': self.echo = True return elif card_name == 'ECHOOFF': self.echo = False return if self.echo and not self.force_echo_off: _echo_card(card, card_obj) if card_name in self._card_parser: card_class, add_card_function = self._card_parser[card_name] try: class_instance = card_class.add_card(card_obj, comment=comment) class_instance.ifile = ifile add_card_function(class_instance) except TypeError: # this should never be turned on, but is useful for testing msg = f'problem adding {card_obj}' print(msg) raise except (SyntaxError, AssertionError, KeyError, ValueError) as exception: self._iparse_errors += 1 self.log.error(card_obj) var = traceback.format_exception_only(type(exception), exception) self._stored_parse_errors.append((card, var)) if self._iparse_errors > self._nparse_errors: self.pop_parse_errors() elif card_name in self._card_parser_prepare: add_card_function = self._card_parser_prepare[card_name] try: obj = add_card_function(card, card_obj, comment=comment) except (SyntaxError, AssertionError, KeyError, ValueError) as exception: #raise self._iparse_errors += 1 self.log.error(card_obj) var = traceback.format_exception_only(type(exception), exception) self._stored_parse_errors.append((card, var)) if self._iparse_errors > self._nparse_errors: self.pop_parse_errors() if obj is None: print(add_card_function) print(card) print(card_obj) raise RuntimeError(f'_prepare_{card_name.lower()} needs to implement obj') elif isinstance(obj, list): for obji in obj: obji.ifile = ifile elif obj == -1: pass else: obj.ifile = ifile else: self.reject_cards.append(card_obj) def _add_card_helper_lax(self, card_obj: BDFCard, card: list[str], card_name: str, ifile: int, comment: str='') -> None: #if card_name not in ['GRID', 'CQUAD4', 'CTRIA3']: #print(card_obj) if card_name == 'ECHOON': self.echo = True return elif card_name == 'ECHOOFF': self.echo = False return if self.echo and not self.force_echo_off: _echo_card(card, card_obj) if card_name in self._card_parser: card_class, add_card_function = self._card_parser[card_name] if hasattr(card_class, 'add_card_lax'): class_instance = card_class.add_card_lax(card_obj, comment=comment) else: class_instance = card_class.add_card(card_obj, comment=comment) add_card_function(class_instance) elif card_name in self._card_parser_prepare: add_card_function = self._card_parser_prepare[card_name] add_card_function(card, card_obj, comment=comment) else: #raise RuntimeError(card_obj) self.reject_cards.append(card_obj) def _add_card_helper(self, card_obj: BDFCard, card: list[str], card_name: str, ifile: int, comment: str='') -> None: """ Adds a card object to the BDF object. Parameters ---------- card_object : BDFCard() the card object representation of card card : list[str] the fields of the card object; used for rejection and special cards card_name : str the card_name -> 'GRID' ifile : int the file number comment : str an optional the comment for the card """ if card_name == 'ECHOON': self.echo = True return elif card_name == 'ECHOOFF': self.echo = False return if self.echo and not self.force_echo_off: _echo_card(card, card_obj) if card_name in self._card_parser: card_class, add_card_function = self._card_parser[card_name] try: class_instance = card_class.add_card(card_obj, comment=comment) add_card_function(class_instance) except TypeError: # this should never be turned on, but is useful for testing print('problem adding %s' % card_obj) raise #raise TypeError(msg) except (SyntaxError, AssertionError, KeyError, ValueError) as exception: print('problem adding %s' % card_obj) #msg = 'problem adding card from %s' % self.active_filenames[ifile] #raise # WARNING: Don't catch RuntimeErrors or a massive memory leak can occur #tpl/cc451.bdf #raise # NameErrors should be caught self._iparse_errors += 1 #self.log.error(str(card_obj)) var = traceback.format_exception_only(type(exception), exception) self._stored_parse_errors.append((card, var)) if self._iparse_errors > self._nparse_errors: self.pop_parse_errors() #raise #except AssertionError as exception: #self.log.error(str(card_obj)) elif card_name in self._card_parser_prepare: add_card_function = self._card_parser_prepare[card_name] try: add_card_function(card, card_obj, comment=comment) except (SyntaxError, AssertionError, KeyError, ValueError) as exception: #raise # WARNING: Don't catch RuntimeErrors or a massive memory leak can occur #tpl/cc451.bdf #raise # NameErrors should be caught self._iparse_errors += 1 self.log.error(str(card_obj)) var = traceback.format_exception_only(type(exception), exception) self._stored_parse_errors.append((card, var)) if self._iparse_errors > self._nparse_errors: self.pop_parse_errors() #except AssertionError as exception: #self.log.error(str(card_obj)) #raise else: #raise RuntimeError(card_obj) self.reject_cards.append(card_obj) def is_acoustic(self) -> bool: card_names = ['ACPLNW', 'AMLREG', 'CAABSF', 'PMIC', 'MATPOR', 'MICPNT'] nacoustics = [self.card_count.get(card_name, 0) for card_name in card_names] nacoustic = sum(nacoustics) is_acoustic = nacoustic > 0 return is_acoustic def get_bdf_stats(self, return_type: str='string') -> Union[str, list[str]]: """ Print statistics for the BDF Parameters ---------- return_type : str (default='string') the output type ('list', 'string') 'list' : list of strings 'string' : single, joined string Returns ------- return_data : str, optional the output data .. note:: if a card is not supported and not added to the proper lists, this method will fail .. todo:: RBE3s from OP2s can show up as ???s """ return get_bdf_stats(self, return_type=return_type) def get_displacement_index_xyz_cp_cd(self, fdtype: str='float64', idtype: str='int32', sort_ids: bool=True) -> Any: """ Get index and transformation matricies for nodes with their output in coordinate systems other than the global. Used in combination with ``OP2.transform_displacements_to_global`` Parameters ---------- fdtype : str; default='float64' the type of xyz_cp idtype : str; default='int32' the type of nid_cp_cd sort_ids : bool; default=True sort the ids Returns ------- icd_transform : dict{int cd : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their output (`CD`) in that coordinate system. icp_transform : dict{int cp : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their input (`CP`) in that coordinate system. xyz_cp : (n, 3) float ndarray points in the CP coordinate system nid_cp_cd : (n, 3) int ndarray node id, CP, CD for each node Examples -------- # assume GRID 1 has a CD=10, CP=0 # assume GRID 2 has a CD=10, CP=0 # assume GRID 5 has a CD=50, CP=0 >>> model.point_ids [1, 2, 5] >>> out = model.get_displacement_index_xyz_cp_cd() >>> icd_transform, icp_transform, xyz_cp, nid_cp_cd = out >>> nid_cp_cd [ [1, 0, 10], [2, 0, 10], [5, 0, 50], ] >>> icd_transform[10] [0, 1] >>> icd_transform[50] [2] """ nnodes = len(self.nodes) nspoints = 0 nepoints = 0 spoints = None epoints = None nrings = len(self.ringaxs) if self.spoints: spoints = list(self.spoints) nspoints = len(spoints) if self.epoints: epoints = list(self.epoints) nepoints = len(epoints) ngridb = len(self.gridb) if nnodes + nspoints + nepoints + ngridb + nrings == 0: msg = 'nnodes=%s nspoints=%s nepoints=%s nrings=%s' % ( nnodes, nspoints, nepoints, nrings) raise ValueError(msg) if idtype == 'int32': try: out = _set_nodes(self, spoints, epoints, nnodes, nspoints, nepoints, ngridb, idtype, fdtype) except OverflowError: out = _set_nodes(self, spoints, epoints, nnodes, nspoints, nepoints, ngridb, 'int64', fdtype) else: out = _set_nodes(self, spoints, epoints, nnodes, nspoints, nepoints, ngridb, idtype, fdtype) nid_cp_cd, xyz_cp, nids_cd_transform, nids_cp_transform = out if sort_ids: nids = nid_cp_cd[:, 0] isort = nids.argsort() nid_cp_cd = nid_cp_cd[isort, :] xyz_cp = xyz_cp[isort, :] icp_transform = {} icd_transform = {} nids_all = nid_cp_cd[:, 0] # get the indicies of the xyz array where the nodes that # need to be transformed are for cd, nids in sorted(nids_cd_transform.items()): if cd in [0, -1]: continue nids = np.array(nids) icd_transform[cd] = np.where(in1d(nids_all, nids))[0] for cp, nids in sorted(nids_cp_transform.items()): if cp in [-1]: continue nids = np.array(nids) icp_transform[cp] = np.where(in1d(nids_all, nids))[0] return icd_transform, icp_transform, xyz_cp, nid_cp_cd def get_xyz_in_coord_array(self, cid: int=0, fdtype: str='float64', idtype: str='int32') -> tuple[np.ndarray, np.ndarray, np.ndarray, dict[int, np.ndarray], dict[int, np.ndarray]]: """ Gets the xyzs as an array in an arbitrary coordinate system Parameters ---------- fdtype : str; default='float64' the type of xyz_cp idtype : str; default='int32' the type of nid_cp_cd cid : int; default=0 the coordinate system to get xyz in Returns ------- nid_cp_cd : (n, 3) int ndarray node id, CP, CD for each node xyz_cid : (n, 3) float ndarray points in the CID coordinate system xyz_cp : (n, 3) float ndarray points in the CP coordinate system icd_transform : dict{int cd : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their output (`CD`) in that coordinate system. icp_transform : dict{int cp : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their input (`CP`) in that coordinate system. .. todo:: how are SPOINTs/EPOINTs identified? Examples -------- >>> out = model.get_xyz_in_coord_array(cid=0, fdtype='float64', idtype='int32') >>> nid_cp_cd, xyz_cid, xyz_cp, icd_transform, icp_transform = out """ icd_transform, icp_transform, xyz_cp, nid_cp_cd = self.get_displacement_index_xyz_cp_cd( fdtype=fdtype, idtype=idtype, sort_ids=True) nids = nid_cp_cd[:, 0] xyz_cid = self.transform_xyzcp_to_xyz_cid(xyz_cp, nids, icp_transform, cid=cid, in_place=False, atol=1e-6) return nid_cp_cd, xyz_cid, xyz_cp, icd_transform, icp_transform def transform_xyzcp_to_xyz_cid(self, xyz_cp: np.ndarray, nids: np.ndarray, icp_transform: dict[int, np.ndarray], cid: int=0, in_place: bool=False, atol: float=1e-6) -> np.ndarray: """ Vectorized method for calculating node locations in an arbitrary coordinate system. Parameters ---------- xyz_cp : (n, 3) float ndarray points in the CP coordinate system nids : (n, ) int ndarray the GRID/SPOINT/EPOINT ids corresponding to xyz_cp icp_transform : dict{int cp : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their input (`CP`) in that coordinate system. cid : int; default=0 the coordinate system to get xyz in in_place : bool, default=False If true the original xyz_cp is modified, otherwise a new one is created. Returns ------- xyz_cid : (n, 3) float ndarray points in the CID coordinate system Examples -------- # assume GRID 1 has a CD=10, CP=0 # assume GRID 2 has a CD=10, CP=0 # assume GRID 5 has a CD=50, CP=1 >>> model.point_ids [1, 2, 5] >>> out = model.get_displacement_index_xyz_cp_cd() >>> icd_transform, icp_transform, xyz_cp, nid_cp_cd = out >>> nids = nid_cp_cd[:, 0] >>> xyz_cid0 = model.transform_xyzcp_to_xyz_cid( xyz_cp, nids, icp_transform, cid=0) >>> xyz_cid1 = model.transform_xyzcp_to_xyz_cid( xyz_cp, nids, icp_transform, cid=1) """ #F:\work\pyNastran\examples\femap_examples\Support\nast\tpl\heli112em7.dat if self.is_bdf_vectorized: # this is used when xref=False (only for vectorized=True) # we now require nids, where the other approach # (the one with xref=True) does not in_place = False cps_to_check = list(self.coords.keys()) else: # this requires xref #cps_to_check = list(icp_transform.keys()) # xref allows in_place=True # this is more general and slightly slower # requires in_place=False??? cps_to_check = list(self.coords.keys()) cps_to_check.sort() assert 0 in cps_to_check, cps_to_check nodes = self.nodes coord2 = self.coords[cid] #assert in_place is False, 'in_place=%s' % in_place if in_place: xyz_cid0 = xyz_cp else: xyz_cid0 = np.copy(xyz_cp) do_checks = False xyz_cid0_correct = None if do_checks: # transform the grids to the global coordinate system xyz_cid0_correct = self.get_xyz_in_coord(fdtype=xyz_cid0.dtype, cid=0) #cps_to_check = list(icp_transform.keys()) #ncoords_to_setup = len(icp_transform) ncoords_to_setup = len(cps_to_check) nids_checked = [] while ncoords_to_setup > 0: #print('--------------------------------------------------------------------------') #print('ncoords_to_setup = ', ncoords_to_setup) ncoords_to_setup = 0 nids_checkedi, cps_checked, cps_to_check = self._transform( cps_to_check, icp_transform, nids, xyz_cp, xyz_cid0, xyz_cid0_correct, in_place, do_checks) if cps_to_check: nids_checked.append(nids_checkedi) #print("nids_checkedi =", nids_checkedi) out = _get_coords_to_update( self.coords, cps_to_check, cps_checked, nids_checked) unused_ncoords_to_setup, cord1s_to_update, cord2s_to_update, nids_checked = out #print('CPs not handled=%s\n cord1s_to_update=%s\n cord2s_to_update=%s' % ( #cps_to_check, cord1s_to_update, cord2s_to_update)) for cp in cord2s_to_update: coord = self.coords[cp] coord.rid_ref = self.coords[coord.rid] coord.setup_no_xref(self) for cp in cord1s_to_update: coord = self.coords[cp] nid1, nid2, nid3 = coord.node_ids if self.is_bdf_vectorized or 1: i1, i2, i3 = np.searchsorted(nids, coord.node_ids) assert nids[i1] == nid1 assert nids[i2] == nid2 assert nids[i3] == nid3 coord.e1 = xyz_cid0[i1, :] #: the origin in the local frame coord.e2 = xyz_cid0[i2, :] #: a point on the z-axis coord.e3 = xyz_cid0[i3, :] #: a point on the xz-plane else: g1_ref = nodes[nid1] g2_ref = nodes[nid2] g3_ref = nodes[nid3] coord.e1 = g1_ref.get_position() #: the origin in the local frame coord.e2 = g2_ref.get_position() #: a point on the z-axis coord.e3 = g3_ref.get_position() #: a point on the xz-plane coord.setup_no_xref(self) #coord.rid_ref = self.coords[coord.rid] #coord.setup_no_xref(self) ncoords_to_setup = len(cord1s_to_update) + len(cord2s_to_update) #print('ncoords_next = ', ncoords_to_setup) #print('--------------------------------------------------------------------------') #print('ncoords_to_setup = ', ncoords_to_setup) #if ncoords == 0: if cps_to_check: msg = 'CPs not handled=%s cord1s_to_update=%s cord2s_to_update=%s\n' % ( cps_to_check, cord1s_to_update, cord2s_to_update) for cp in cps_to_check: coord = self.coords[cp] msg += coord.rstrip() + '\n' if coord.type in ['CORD2R', 'CORD2C', 'CORD2S']: rid_ref = self.coords[coord.rid] msg += rid_ref.rstrip() + '\n' msg += f' rid={coord.rid!r} origin={rid_ref.origin}\n\n' else: nid1, nid2, nid3 = coord.node_ids #coord.e1 = xyz_cid0[i1, :] #: the origin in the local frame #coord.e2 = xyz_cid0[i2, :] #: a point on the z-axis #coord.e3 = xyz_cid0[i3, :] #: a point on the xz-plane if self.is_bdf_vectorized: i1, i2, i3 = np.searchsorted(nids, coord.node_ids) cp1 = nodes.cp[i1] cp2 = nodes.cp[i2] cp3 = nodes.cp[i3] else: cp1 = nodes[nid1].cp cp2 = nodes[nid2].cp cp3 = nodes[nid3].cp msg += f' g1={nid1} xyz={coord.e1} cp={cp1}\n' msg += f' g2={nid2} xyz={coord.e2} cp={cp2}\n' msg += f' g3={nid3} xyz={coord.e3} cp={cp3}\n' #break raise RuntimeError(msg) if do_checks and not np.allclose(xyz_cid0, xyz_cid0_correct, atol=atol): #np.array_equal(xyz_cid, xyz_cid_alt): out = self.get_displacement_index_xyz_cp_cd(fdtype=xyz_cid0.dtype, sort_ids=True) unused_icd_transform, icp_transform, xyz_cp, nid_cp_cd = out msg = ('xyz_cid0:\n%s\n' 'xyz_cid0_correct:\n%s\n' 'nid_cp_cd:\n%s\n' 'xyz_cp:\n%s'% (xyz_cid0, xyz_cid0_correct, nid_cp_cd, xyz_cp)) raise ValueError(msg) if cid == 0: return xyz_cid0 # transform the grids to the local coordinate system #is_beta = np.diagonal(beta2).min() != 1. #is_origin = np.abs(coord2.origin).max() != 0. #if is_beta and is_origin: xyz_cid = coord2.transform_node_to_local_array(xyz_cid0) #xyz_cid = coord2.xyz_to_coord_array(np.dot(xyz_cid0 - coord2.origin, beta2.T)) #elif is_beta: #xyz_cid = coord2.xyz_to_coord_array(xyz_cid0 @ beta2.T) #else: #xyz_cid = coord2.xyz_to_coord_array(xyz_cid0 - coord2.origin) if atol is not None: xyz_cid_correct = self.get_xyz_in_coord(cid=cid) if not np.allclose(xyz_cid, xyz_cid_correct, atol=atol): #np.array_equal(xyz_cid, xyz_cid_correct): msg = ('xyz_cid:\n%s\n' 'xyz_cid_correct:\n%s'% (xyz_cid, xyz_cid_correct)) raise ValueError(msg) return xyz_cid def _transform(self, cps_to_check0, icp_transform, nids, xyz_cp, xyz_cid0, xyz_cid0_correct, unused_in_place, do_checks): """ Transforms coordinates in a vectorized way Helper method for ``transform_xyzcp_to_xyz_cid`` Parameters ---------- cps_to_check0 : list[int] the Cps to check icp_transform : dict{int cp : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their input (`CP`) in that coordinate system. nids : (n, ) int ndarray the GRID/SPOINT/EPOINT ids corresponding to xyz_cp xyz_cp : (n, 3) float ndarray points in the CP coordinate system xyz_cid : (n, 3) float ndarray points in the CID coordinate system xyz_cid_correct : (n, 3) float ndarray points in the CID coordinate system unused_in_place : bool, default=False If true the original xyz_cp is modified, otherwise a new one is created. do_checks : bool; default=False internal value for testing True : makes use of xyz_cid_correct False : xyz_cid_correct is unused Returns ------- nids_checked : (nnodes_checked,) int ndarray the node ids that were checked cps_checked : list[int] the Cps that were checked cps_to_check : list[int] the Cps that are unreferenceable given the current information """ nids_checked, cps_checked, cps_to_check = transform_coords_vectorized( cps_to_check0, icp_transform, nids, xyz_cp, xyz_cid0, xyz_cid0_correct, self.coords, do_checks) return nids_checked, cps_checked, cps_to_check @property def is_bdf_vectorized(self): """Returns False for the ``BDF`` class""" return hasattr(self, 'grid') def get_displacement_index(self) -> tuple[Any, Any, dict[int, Any]]: """ Get index and transformation matricies for nodes with their output in coordinate systems other than the global. Used in combination with ``OP2.transform_displacements_to_global`` Returns ------- nids_all : (nnodes,) int ndarray the GRID/SPOINT/EPOINT ids nids_transform : dict[cd] : (nnodesi,) int ndarray the indicies in nids_all that correspond to cd > 0 cd : int the CD coordinate system nnodesi : int nnodesi <= nnodes icd_transform : dict{int cid : (n,) int ndarray} Dictionary from coordinate id to index of the nodes in ``self.point_ids`` that their output (`CD`) in that coordinate system. Examples -------- # assume GRID 1 has a CD=10 # assume GRID 2 has a CD=10 # assume GRID 5 has a CD=50 >>> model.point_ids [1, 2, 5] >>> icd_transform = model.get_displacement_index() >>> icd_transform[10] [0, 1] >>> icd_transform[50] [2] """ nids_transform = defaultdict(list) icd_transform = {} if len(self.coords) == 1: # was ncoords > 2; changed b/c seems dangerous return icd_transform for nid, node in sorted(self.nodes.items()): cid_d = node.Cd() if cid_d: nids_transform[cid_d].append(nid) nids_all = np.array(sorted(self.point_ids)) for cid in sorted(nids_transform.keys()): nids = np.array(nids_transform[cid]) icd_transform[cid] = np.where(in1d(nids_all, nids))[0] return nids_all, nids_transform, icd_transform def increase_card_count(self, card_name: str, count_num: int=1) -> None: """ Used for testing to check that the number of cards going in is the same as each time the model is read verifies proper writing of cards Parameters ---------- card_name : str the card_name -> 'GRID' count_num : int, optional the amount to increment by (default=1) >>> bdf.read_bdf(bdf_filename) >>> bdf.card_count['GRID'] 50 """ assert '=' not in card_name, card_name if card_name in self.card_count: self.card_count[card_name] += count_num else: self.card_count[card_name] = count_num def _old_card_fields(self, card_lines: list[str], card_name: str, log: SimpleLogger, is_list: bool=False, has_none: bool=True, is_dynamic_syntax: bool=False) -> BDFCard: """replication helper""" if is_list: fields = card_lines else: fields = to_fields(card_lines, card_name) # apply OPENMDAO syntax if is_dynamic_syntax: fields = [print_field_16(_parse_dynamic_syntax(field, self.dict_of_vars, log)) if '%' in field.strip()[0:1] else print_field_16(field) for field in fields] has_none = False if has_none: card = wipe_empty_fields([print_field_16(field) for field in fields]) else: card = wipe_empty_fields(fields) card_obj = BDFCard(card, has_none=False) return card_obj def _expand_replication(self, card_name: str, icard: int, cards_list, card_lines_new, dig: bool=True): """replication helper""" #dig_str = ' ' if dig is False else '' #print(dig_str, '-----------************---------') #print(dig_str, '--dig=%s--' % dig) #print(dig_str, 'card_lines_new=%s' % card_lines_new) card = [] cards = [] card_lines_old = cards_list[icard-1][2] is_star_lines = any('*' in line for line in card_lines_old) if is_star_lines: #new_fields = to_fields_replication(card_lines_old) old_card = to_fields_replication(card_lines_old) else: #old_card, unused_card = self.create_card_object( #card_lines_old, card_name, #is_list=False, has_none=True) #print(card_lines_old) old_card = self._old_card_fields(card_lines_old, card_name, self.log, is_list=False, has_none=True, is_dynamic_syntax=self._is_dynamic_syntax) #print(old_card) #assert '=' not in card_name nlines = len(card_lines_new) #print(dig_str, "card_lines_new =", card_lines_new) new_card = to_fields_replication(card_lines_new) assert len(card_lines_new) == nlines, card_lines_new #print(dig_str, 'old_card =', old_card) #print(dig_str, 'card_name = %r' % card_name) old_card_real = None if old_card[0] == '=': #print(dig_str, 'A!!!') if dig is False: raise ReplicationError(f'dig=False...old_card=\n{old_card}') cards2 = self._expand_replication( card_name, icard-1, cards_list, card_lines_old, dig=False) assert len(cards2) == 1, f'cards2={cards2}; ncards={len(cards2)}' #print(dig_str, 'cards_equal =', cards2) old_card_fields = cards2[0] old_card_real = old_card #print(dig_str, 'old_card_fields =', old_card_fields) #print(dig_str, 'old_card_real =', old_card_real) #print(dig_str, 'card_lines_old =', card_lines_old) old_card = self._old_card_fields(old_card_fields, card_name, self.log, is_list=True, has_none=True, is_dynamic_syntax=self._is_dynamic_syntax) elif '=' in card_name: #print(dig_str, 'B!!!') #print(dig_str, 'old_card =', old_card) #print(dig_str, 'card_lines_new =', card_lines_new) #print(dig_str, 'card_name = %r' % card_name) # good #new_card = [u'=3'] #old_card = [u'CQUAD4', u'64', u'1', u'88', u'89', u'101', u'100'] #old_card_real = [u'=', u'*1', u'=', u'*1', u'*1', u'*1', u'*1'] # bad #card_name = u'=(7)' #new_card = [u'=(7)', u'*(10)', u'=', u'=', u'=', u'*(1.0)'] #old_card = [u'grid', u'1001', None, u'0.', u'0.', u'0.'] #old_card_real = [u'grid', u'1001', None, u'0.', u'0.', u'0.'] old_card_real = new_card #else: #print('old_card[0] %r' % old_card[0]) #print(dig_str, "old_card =", old_card) #print(dig_str, "new_card =", new_card) for ifield, field in enumerate(new_card): if field is None: field2 = old_card.field(ifield) #print(' %i: %r -> %r' % (ifield, field, field2)) #assert field2 is None, 'field=%s field2=%s' % (field, field2) card.append(field2) continue #if field == '': #pass field = field.strip() if field == '=': field2 = old_card[ifield] #field2 = old_card.field(ifield) elif field == '==': # just append the remaining fields card.extend(old_card[ifield:]) #print(dig_str, ' %i : extending %s' % (ifield, old_card[ifield:])) #print(dig_str, ' break _expand_replication...') break elif '=' in field: # =4 assert ifield == 0, f'ifield={ifield} field={field!r} new_card={new_card}' nrepeats = get_nrepeats(field, old_card, new_card) if old_card_real is None: #old_card_real = old_card msg = ( 'Invalid Replication Syntax (continuations arent supported)\n' 'old:\n%s\n' 'new:\n%s' % (old_card, new_card)) raise RuntimeError(msg) #new_card = [u'=3'] #old_card = [u'CQUAD4', u'64', u'1', u'88', u'89', u'101', u'100'] #old_card_real = [u'=', u'*1', u'=', u'*1', u'*1', u'*1', u'*1'] #print('---') #print(dig_str, "nrepeats =", nrepeats) new_card[0] = '=' #print(dig_str, "new_card =", new_card) #print(dig_str, "old_card =", old_card) #print(dig_str, "old_card_real =", old_card_real) for unused_irepeat in range(nrepeats): repeated_cards = repeat_cards(old_card, old_card_real) if len(repeated_cards) != 1: for repeated_card in repeated_cards: print(" repeated_card =", repeated_card) raise RuntimeError('too many repeated cards') #for repeated_card in repeated_cards: #print(" repeated_card =", repeated_card) repeated_card = repeated_cards[0] cards.append(repeated_card) old_card = repeated_card #print(dig_str, " repeated_card =", repeated_card) #print(dig_str, 'breaking...') return cards elif '*' in field: # this is an increment, not multiplication... old_field = _field(old_card, ifield) assert old_field is not None, f'old_card:{old_card}\nnew_card:\n{new_card}' try: if '.' in field: field2 = float_replication(field, old_field) else: field2 = int_replication(field, old_field) except Exception: self.log.error(f'old_card:{old_card}\nnew_card:\n{new_card}') raise else: assert '(' not in field, f'field={field!r}' assert '*' not in field, f'field={field!r}' assert '=' not in field, f'field={field!r}' field2 = field #print(dig_str, ' %i: %r -> %r' % (ifield, field, field2)) card.append(field2) if card: cards.append(card) #print(dig_str, 'card_expanded = %s' % card) else: # pragma: no cover raise RuntimeError(card) return cards def _parse_cards(self, cards_list: list[list[str]], cards_dict: dict[str, list[str]], card_count: dict[str, int], strict: bool=True) -> None: """creates card objects and adds the parsed cards to the deck""" # we don't want replication markers in the card_count card_names_to_remove = (card_name for card_name in list(card_count.keys()) if '=' in card_name) for card_name in card_names_to_remove: del card_count[card_name] self.echo = False if cards_dict: # self._is_cards_dict = True self._parse_cards_dict(cards_dict) if cards_list: # this is the block that actually runs self._parse_cards_list(cards_list, strict=strict) def _parse_cards_dict(self, cards_dict: dict[str, list[str]]) -> None: """parses the cards that are in dictionary format""" if self.save_file_structure: raise NotImplementedError('save_file_structure=True is not supported\n%s' % ( list(cards_dict.keys()))) for card_name, cards in sorted(cards_dict.items()): try: is_reject = self.is_reject(card_name) except ReplicationError as error: card_strs = [f'{icard}: {str(card)}' for icard, (comment, card, ifile) in enumerate(cards)] msg = '\n'.join(card_strs) raise ReplicationError('Unparsable Replication:\n' + msg) from error if is_reject: self.log.info(f' rejecting card_name = {card_name}') for comment, card_lines, unused_ifile_iline in cards: self.increase_card_count(card_name) self.reject_lines.append([_format_comment(comment)] + card_lines) else: for comment, card_lines, (ifile, unused_iline) in cards: self.add_card(card_lines, card_name, comment=comment, ifile=ifile, is_list=False, has_none=False) def _parse_cards_list(self, cards_list: list[str], strict: bool=True): """parses the cards that are in list format""" add_card = self.add_card if strict else self.add_card_lax del strict save_file_structure = self.save_file_structure if save_file_structure: for icard, card in enumerate(cards_list): card_name, comment, card_lines, (ifile, unused_iline) = card card_name = cast(str, card_name) comment = cast(str, comment) card_lines = cast(list[str],card_lines) if card_name is None: msg = f'card_name = {card_name!r}\n' msg += f'card_lines = {card_lines}' raise RuntimeError(msg) if '=' in card_name: #print(card) try: replicated_cards = self._expand_replication( card_name, icard, cards_list, card_lines) except ReplicationError: self.log.error('failed to expand %s\n%s' % (card_name, ''.join(card_lines))) raise _check_replicated_cards(replicated_cards) for replicated_card in replicated_cards: self.add_card_ifile(ifile, replicated_card, replicated_card[0], comment=comment, is_list=True, has_none=True) continue if self.is_reject(card_name): # pragma: no cover msg = f"save_file_structure=True doesn't support {card_name}" raise NotImplementedError(msg) #self.reject_card_lines(card_name, card_lines, comment) else: self.add_card_ifile(ifile, card_lines, card_name, comment=comment, is_list=False, has_none=False) else: for icard, card in enumerate(cards_list): card_name, comment, card_lines, (ifile, unused_iline) = card #print(unused_iline, card_lines[0]) if card_name is None: msg = f'card_name = {card_name!r}\n' msg += f'card_lines = {card_lines}' raise RuntimeError(msg) if '=' in card_name: #print(card) try: replicated_cards = self._expand_replication( card_name, icard, cards_list, card_lines) except ReplicationError: self.log.error('failed to expand %s\n%s' % (card_name, ''.join(card_lines))) raise _check_replicated_cards(replicated_cards) for replicated_card in replicated_cards: add_card(replicated_card, replicated_card[0], comment=comment, is_list=True, has_none=True) continue if self.is_reject(card_name): try: self.reject_card_lines(card_name, card_lines, comment=comment) except: card_old = cards_list[icard-1] old_card_name, old_comment, old_card_lines, unused_ifile_iline = card_old self.log.error('Last card was:\n%s' % '\n'.join(old_card_lines)) print('Last card was:\n%s' % '\n'.join(old_card_lines)) raise else: add_card(card_lines, card_name, comment=comment, ifile=ifile, is_list=False, has_none=False) #def _is_case_control_deck(self, line): #line_upper = line.upper().strip() #if 'CEND' in line.upper(): #raise SyntaxError('invalid Case Control Deck card...CEND...') #if '=' in line_upper or ' ' in line_upper: #return True #for card in self.uniqueBulkDataCards: #lenCard = len(card) #if card in line_upper[:lenCard]: #return False #return True def _parse_primary_file_header(self, bdf_filename: Union[str, StringIO]) -> None: """ Extract encoding, nastran_format, and punch from the primary BDF. Parameters ---------- bdf_filename : str the input filename ..code-block :: python $ pyNastran: version=NX $ pyNastran: encoding=latin-1 $ pyNastran: punch=True $ pyNastran: dumplines=True $ pyNastran: nnodes=10 $ pyNastran: nelements=100 $ pyNastran: skip_cards=PBEAM,CBEAM $ pyNastran: units=in,lb,s ..warning :: pyNastran lines must be at the top of the file """ if isinstance(bdf_filename, (str, PurePath)): try: with open(bdf_filename, 'r') as bdf_file: lines = bdf_file.readlines() except UnicodeDecodeError: with open(bdf_filename, 'r', errors='replace') as bdf_file: line = 'temp' lines = [] while line: line = bdf_file.readline() lines.append(line) try: # try to force a crash unused_bytes_line = line.encode('ascii') # TODO: use the encoding except UnicodeEncodeError: break n = 20 i = 0 i0 = len(lines) - n for i, line in enumerate(lines[-n:-1]): self.log.debug(f'Line {i0+i}: {line.strip()!r}') self.log.error(f'Line {i0+i+1}: {lines[-1].strip()!r}') raise else: # StringIO if hasattr(bdf_filename, 'read') and hasattr(bdf_filename, 'write'): lines = bdf_filename.readlines() bdf_filename.seek(0) # need to rewind the buffer! self._check_pynastran_header(lines, check_header=True) map_update(self, self.nastran_format) def _update_for_nastran(self): """updates for msc/nx/optistruct""" # TODO: undo the changes for zona card_parser = self._card_parser CARD_MAP['PARAM'] = PARAM card_parser['PARAM'] = (PARAM, self._add_methods._add_param_object) self.add_param = self._add_param_nastran def _update_for_optistruct(self): """updates for mystran""" self._update_for_nastran() # copies this... card_parser = self._card_parser CARD_MAP['PBUSH_OPTISTRUCT'] = PBUSH_OPTISTRUCT card_parser['PBUSH'] = (PBUSH_OPTISTRUCT, self._add_methods._add_property_object) def _update_for_mystran(self): """updates for mystran""" card_parser = self._card_parser CARD_MAP['PARAM'] = PARAM_MYSTRAN card_parser['PARAM'] = (PARAM_MYSTRAN, self._add_methods._add_param_object) self.add_param = self._add_param_mystran def _update_for_nasa95(self): """updates for nasa95""" CARD_MAP['PARAM'] = PARAM_NASA95 card_parser = self._card_parser card_parser['PARAM'] = (PARAM_NASA95, self._add_methods._add_param_object) self.add_param = self._add_param_nasa95 def _check_pynastran_header(self, lines: list[str], check_header: bool=True) -> None: """updates the $pyNastran: key=value variables""" if not check_header: return for line in lines: if not line.startswith('$'): break key, value = _parse_pynastran_header(line) if not key: break # key/value are lowercase if key == 'version': assert value.lower() in ['msc', 'nx', 'optistruct', 'zona', 'nasa95', 'mystran'], f'version={value!r} is not supported' self.nastran_format = value elif key == 'encoding': self._encoding = value elif key == 'punch': self.punch = _bool(value) elif key in ['nnodes', 'nelements']: pass elif key == 'dumplines': self.dumplines = _bool(value) elif key == 'is_superelements': self.is_superelements = _bool(value) elif key == 'skip_cards': cards = {value.strip() for value in value.upper().split(',')} self.cards_to_read = self.cards_to_read - cards elif 'skip ' in key: type_to_skip = key[5:].strip() #values = [int(value) for value in value.upper().split(',')] values = parse_patran_syntax(value) if type_to_skip not in self.object_attributes(): raise RuntimeError(f'{type_to_skip!r} is an invalid key') if type_to_skip not in self.values_to_skip: self.values_to_skip[type_to_skip] = values else: self.values_to_skip[type_to_skip] = np.hstack([ self.values_to_skip[type_to_skip], values ]) #elif key == 'skip_elements' #elif key == 'skip_properties' elif key == 'units': self.units = [value.strip() for value in value.upper().split(',')] elif key in ['code-block', 'code_block']: value = line.split('=', 1)[1] if not hasattr(self, 'code_block'): self.code_block = '' char0 = value.lstrip()[0] indent = value.index(char0) self.code_block += value[indent:] else: raise NotImplementedError(key) if hasattr(self, 'code_block'): exec(self.code_block) #--------------------------------------------------------------------------------------------------- # HDF5 def _read_bdf_cards(self, bdf_filename: Optional[str]=None, punch: bool=False, read_includes: bool=True, encoding: Optional[str]=None) -> dict[str, list[Any]]: """ Read method for the bdf files Parameters ---------- bdf_filename : str / None the input bdf (default=None; popup a dialog) punch : bool; default=False indicates whether the file is a punch file read_includes : bool; default=True indicates whether INCLUDE files should be read encoding : str; default=None -> system default the unicode encoding .. code-block:: python >>> bdf = BDF() >>> bdf.read_bdf(bdf_filename, xref=True) >>> g1 = bdf.Node(1) >>> print(g1.get_position()) [10.0, 12.0, 42.0] >>> bdf.write_card(bdf_filename2) >>> print(bdf.card_stats()) ---BDF Statistics--- SOL 101 bdf.nodes = 20 bdf.elements = 10 etc. """ self._is_cards_dict = True self._read_bdf_helper(bdf_filename, encoding, punch, read_includes) self.log.debug(f'---starting BDF.read_bdf of {self.bdf_filename}---') self._parse_primary_file_header(bdf_filename) obj = BDFInputPy(self.read_includes, self.dumplines, self._encoding, nastran_format=self.nastran_format, log=self.log, debug=self.debug) obj.use_new_parser = self.use_new_deck_parser out = obj.get_lines(bdf_filename, punch=self.punch, make_ilines=True) (system_lines, executive_control_lines, case_control_lines, bulk_data_lines, bulk_data_ilines, additional_deck_lines) = out self._set_pybdf_attributes(obj, save_file_structure=False) self.system_command_lines = system_lines self.executive_control_lines = executive_control_lines self.case_control_lines = case_control_lines sol, method, sol_iline, app = parse_executive_control_deck(executive_control_lines) self.app = app self.update_solution(sol, method, sol_iline) #self._is_cards_dict = True if self._is_cards_dict: cards, card_count = self.get_bdf_cards_dict(bulk_data_lines, bulk_data_ilines) cards_out = self._parse_cards_hdf5(cards, card_count) assert isinstance(cards_out, dict), cards_out self.case_control_deck = CaseControlDeck(case_control_lines, log=self.log) self.case_control_deck.solmap_to_value = self._solmap_to_value self.case_control_deck.rsolmap_to_str = self.rsolmap_to_str return cards_out def create_subcases(self, subcase_ids: Union[int, list[int], None]=None) -> dict[int, Subcase]: """creates a series of subcases""" if subcase_ids is None: subcase_ids = [] elif isinstance(subcase_ids, int): subcase_ids = [subcase_ids] self.punch = False if self.case_control_deck is None: self.case_control_deck = CaseControlDeck([], log=self.log) subcases = {} for subcase_id in subcase_ids: subcase = self.case_control_deck.create_new_subcase(subcase_id) subcases[subcase_id] = subcase return subcases def _parse_cards_hdf5(self, cards: dict[str, Any], unused_card_count) -> dict[str, list[Any]]: """creates card objects and adds the parsed cards to the deck""" self.echo = False cards_out = {} for card_name, card in sorted(cards.items()): cards_list = [] cards_out[card_name] = cards_list if self.is_reject(card_name): self.log.info(f' rejecting card_name = {card_name}') for comment, card_lines, unused_ifile_iline in card: self.increase_card_count(card_name) self.reject_lines.append([_format_comment(comment)] + card_lines) else: for comment, card_lines, unused_ifile_iline in card: class_instance = self._add_card_hdf5(card_lines, card_name, comment=comment, is_list=False, has_none=False) cards_list.append(class_instance) return cards_out def _add_card_hdf5(self, card_lines: list[str], card_name: str, comment='', is_list: bool=True, has_none: bool=True) -> Any: """ Creates a BaseCard object that will be used to simplify HDF5 adding. Parameters ---------- card_lines: list[str] the list of the card fields card_name : str the card_name -> 'GRID' comment : str an optional the comment for the card is_list : bool, optional False : input is a list of card fields -> ['GRID', 1, None, 3.0, 4.0, 5.0] True : input is list of card_lines -> ['GRID, 1,, 3.0, 4.0, 5.0'] has_none : bool; default=True can there be trailing Nones in the card data (e.g. ['GRID', 1, 2, 3.0, 4.0, 5.0, None]) can there be trailing Nones in the card data (e.g. ['GRID, 1, 2, 3.0, 4.0, 5.0, ']) Returns ------- class_instance : BaseCard() the card object representation of card .. code-block:: python >>> model = BDF() # is_list is a somewhat misleading name; is it a list of card_lines # where a card_line is an unparsed string >>> card_lines = ['GRID,1,2'] >>> comment = 'this is a comment' >>> model.add_card(card_lines, 'GRID', comment, is_list=True) # here is_list=False because it's been parsed >>> card = ['GRID', 1, 2,] >>> model.add_card(card_lines, 'GRID', comment, is_list=False) # here is_list=False because it's been parsed # Note the None at the end of the 1st line, which is there # because the CONM2 card has a blank field. # It must be there. # We also set i32 on the 2nd line, so it will default to 0.0 >>> card = [ 'CONM2', eid, nid, cid, mass, x1, x2, x3, None, i11, i21, i22, i31, None, i33, ] >>> model.add_card(card_lines, 'CONM2', comment, is_list=False) # here's an alternate approach for the CONM2 # we use Nastran's CSV format # There are many blank fields, but it's parsed exactly like a # standard CONM2. >>> card = [ 'CONM2,1,2,3,10.0', ',1.0,,5.0' ] >>> model.add_card(card_lines, 'CONM2', comment, is_list=True) Notes ----- This is a very useful method for interfacing with the code. The card_object is not a card-type object...so not a GRID card or CQUAD4 object. It's a BDFCard Object. However, you know the type (assuming a GRID), so just call the *mesh.Node(nid)* to get the Node object that was just created. """ card_name = card_name.upper() card_obj, unused_card = self.create_card_object( card_lines, card_name, is_list=is_list, has_none=has_none) class_instance = self._add_card_helper_hdf5(card_obj, card_name, card_name, comment) return class_instance def _add_card_helper_hdf5(self, card_obj: BDFCard, card: list[str], card_name: str, comment: str='') -> None: """ Adds a card object to the BDF object. Parameters ---------- card_object : BDFCard() the card object representation of card card : list[str] the fields of the card object; used for rejection and special cards card_name : str the card_name -> 'GRID' comment : str an optional the comment for the card Returns ------- class_instance : obj or None obj : GRID, CQUAD4, ... None : ECHOON, ECHOOFF, reject """ if card_name == 'ECHOON': self.echo = True return None elif card_name == 'ECHOOFF': self.echo = False return None if self.echo and not self.force_echo_off: try: print(print_card_8(card_obj).rstrip()) except Exception: if card in ['DEQATN']: print(str(card_obj).rstrip()) else: print(print_card_16(card_obj).rstrip()) if card_name in self._card_parser: card_class, add_card_function = self._card_parser[card_name] # simplified, so no error catching class_instance = card_class.add_card(card_obj, comment=comment) #add_card_function(class_instance) elif card_name in self._card_parser_prepare: add_card_function = self._card_parser_prepare[card_name] # simplified, so no error catching class_instance = add_card_function(card, card_obj, comment=comment) else: self.reject_cards.append(card_obj) class_instance = None return class_instance
[docs] class BDF(BDF_): """NASTRAN BDF Reader/Writer/Editor class.""" _properties = ['is_bdf_vectorized', 'nid_map', 'wtmass', 'type_slot_str'] + [ 'nastran_format', 'is_long_ids', 'sol', 'subcases', 'nnodes', 'node_ids', 'point_ids', 'npoints', 'nelements', 'element_ids', 'nproperties', 'property_ids', 'nmaterials', 'material_ids', 'ncoords', 'coord_ids', 'ncaeros', 'caero_ids', 'wtmass', 'is_bdf_vectorized', 'nid_map', #'dmigs', 'dmijs', 'dmiks', 'dmijis', 'dtis', 'dmis', ] def __init__(self, debug: Optional[bool]=True, log: Any=None, mode: str='msc') -> None: """ Initializes the BDF object Parameters ---------- debug : bool/None; default=True used to set the logger if no logger is passed in True: logs debug/info/warning/error messages False: logs info/warning/error messages None: logs warning/error messages log : logging module object / None if log is set, debug is ignored and uses the settings the logging object has mode : str; default='msc' the type of Nastran valid_modes = {'msc', 'nx', 'nasa95', 'mystran', 'zona'} """ BDF_.__init__(self, debug=debug, log=log, mode=mode) #: stores SPOINT, GRID cards self.nodes = {} # type: dict[int, Any] # loads #: stores LOAD, FORCE, FORCE1, FORCE2, MOMENT, MOMENT1, MOMENT2, #: PLOAD, PLOAD2, PLOAD4, SLOAD #: GMLOAD, SPCD, DEFORM, #: QVOL self.loads = {} # type: dict[int, list[Any]] self.load_combinations = {} # type: dict[int, list[Any]] def __deepcopy__(self, memo: dict[str, Any]): """performs a deepcopy""" #newone = type(self)() #newone.__dict__.update(self.__dict__) #return newone cls = self.__class__ result = cls.__new__(cls) memo[id(self)] = result for key, value in self.__dict__.items(): setattr(result, key, deepcopy(value, memo)) if result._xref: result.cross_reference( xref=True, xref_nodes=True, xref_elements=True, xref_nodes_with_elements=False, xref_properties=True, xref_masses=True, xref_materials=True, xref_loads=True, xref_constraints=True, xref_aero=True, xref_sets=True, xref_optimization=True, word='') return result def __copy__(self): """performs a copy""" result = type(self)() result.__dict__.update(self.__dict__) #if result._xref: # result.cross_reference( # xref=True, xref_nodes=True, xref_elements=True, xref_nodes_with_elements=False, # xref_properties=True, xref_masses=True, xref_materials=True, xref_loads=True, # xref_constraints=True, xref_aero=True, xref_sets=True, xref_optimization=True, # word='') return result def _add_disabled_cards(self): self._remove_disabled_cards = False self.cards_to_read.update(REMOVED_CARDS) # add
def _echo_card(card, card_obj): """echos a card""" try: print(print_card_8(card_obj).rstrip()) except Exception: if card in ['DEQATN']: print(str(card_obj).rstrip()) else: print(print_card_16(card_obj).rstrip())
[docs] def read_bdf(bdf_filename: Optional[str]=None, validate: bool=True, xref: bool=True, punch: bool=False, save_file_structure: bool=False, skip_cards: Optional[list[str]]=None, read_cards: Optional[list[str]]=None, encoding: Optional[str]=None, log: Optional[SimpleLogger]=None, debug: bool=True, mode: str='msc') -> BDF: """ Creates the BDF object Parameters ---------- bdf_filename : str (default=None -> popup) the bdf filename debug : bool/None used to set the logger if no logger is passed in True: logs debug/info/error messages False: logs info/error messages None: logs error messages log : logging module object / None if log is set, debug is ignored and uses the settings the logging object has validate : bool; default=True runs various checks on the BDF xref : bool; default=True should the bdf be cross referenced punch : bool; default=False indicates whether the file is a punch file save_file_structure : bool; default=False enables the ``write_bdfs`` method skip_cards : list[str]; default=None None : include all cards list of cards to skip read_cards : list[str]; default=None None : include all cards list of cards to read (all the cards) encoding : str; default=None -> system default the unicode encoding mode : str; default='msc' the type of Nastran valid_modes = {'msc', 'nx'} Returns ------- model : BDF() an BDF object .. code-block:: python >>> bdf = BDF() >>> bdf.read_bdf(bdf_filename, xref=True) >>> g1 = bdf.Node(1) >>> print(g1.get_position()) [10.0, 12.0, 42.0] >>> bdf.write_card(bdf_filename2) >>> print(bdf.card_stats()) ---BDF Statistics--- SOL 101 bdf.nodes = 20 bdf.elements = 10 etc. .. note :: this method will change in order to return an object that does not have so many methods .. todo:: finish this """ model = BDF(log=log, debug=debug, mode=mode) if read_cards and skip_cards: msg = 'read_cards=%s skip_cards=%s cannot be used at the same time' raise NotImplementedError(msg) if skip_cards: model.disable_cards(skip_cards) elif read_cards: model.enable_cards(read_cards) if bdf_filename and not isinstance(bdf_filename, StringIO): check_path(bdf_filename, 'bdf_filename') model.read_bdf(bdf_filename=bdf_filename, validate=validate, xref=xref, punch=punch, read_includes=True, save_file_structure=save_file_structure, encoding=encoding) #if 0: ### TODO: remove all the extra methods #keys_to_suppress = [] #method_names = model.object_methods(keys_to_skip=keys_to_suppress) #methods_to_remove = [ #'_process_card', 'read_bdf', 'disable_cards', 'set_dynamic_syntax', #'create_card_object', 'create_card_object_fields', 'create_card_object_list', #'add_AECOMP', 'add_AEFACT', 'add_AELINK', 'add_AELIST', 'add_AEPARM', 'add_AERO', #'add_AEROS', 'add_AESTAT', 'add_AESURF', 'add_ASET', 'add_BCRPARA', 'add_BCTADD', #'add_BCTPARA', 'add_BCTSET', 'add_BSET', 'add_BSURF', 'add_BSURFS', 'add_CAERO', #'add_DIVERG', #'add_CSET', 'add_CSSCHD', 'add_DAREA', 'add_DCONADD', 'add_DCONSTR', 'add_DDVAL', #'add_DELAY', 'add_DEQATN', 'add_DESVAR', 'add_DLINK', 'add_DMI', 'add_DMIG', #'add_DMIJ', 'add_DMIJI', 'add_DMIK', 'add_DPHASE', 'add_DRESP', 'add_DTABLE', #'add_DVMREL', 'add_DVPREL', 'add_EPOINT', 'add_FLFACT', 'add_FLUTTER', 'add_FREQ', #'add_GUST', 'add_LSEQ', 'add_MKAERO', 'add_MONPNT', 'add_NLPARM', 'add_NLPCI', #'add_PAERO', 'add_PARAM', 'add_PBUSHT', 'add_PDAMPT', 'add_PELAST', 'add_PHBDY', #'add_QSET', 'add_SEBSET', 'add_SECSET', 'add_SEQSET', 'add_SESET', 'add_SET', #'add_SEUSET', 'add_SPLINE', 'add_spoint', 'add_tempd', 'add_TF', 'add_TRIM', #'add_TSTEP', 'add_TSTEPNL', 'add_USET', #'add_card', 'add_card_fields', 'add_card_lines', 'add_cmethod', 'add_constraint', #'add_constraint_MPC', 'add_constraint_MPCADD', #'add_constraint_SPC', 'add_constraint_SPCADD', #'add_convection_property', 'add_coord', 'add_creep_material', 'add_damper', #'add_dload', '_add_dload_entry', 'add_element', 'add_hyperelastic_material', #'add_load', 'add_mass', 'add_material_dependence', 'add_method', 'add_node', #'add_plotel', 'add_property', 'add_property_mass', 'add_random_table', #'add_rigid_element', 'add_structural_material', 'add_suport', 'add_suport1', #'add_table', 'add_table_sdamping', 'add_thermal_BC', 'add_thermal_element', #'add_thermal_load', 'add_thermal_material', #'set_as_msc', #'set_as_nx', #'pop_parse_errors', ##'pop_xref_errors', #'set_error_storage', #'is_reject', #] #for method_name in method_names: #if method_name not in methods_to_remove + keys_to_suppress: ##print(method_name) #pass #else: ### TODO: doesn't work... ##delattr(model, method_name) #pass #model.get_bdf_stats() return model
def _prep_comment(comment): return comment.rstrip() #print('comment = %r' % comment) #comment = ' this\n is\n a comment\n' #print(comment.rstrip('\n').split('\n')) #sline = [comment[1:] if len(comment) and comment[0] == ' ' else comment #for comment in comment.rstrip().split('\n')] #print('sline = ', sline) def _check_replicated_cards(replicated_cards): """helper method for ``parse_cards_list``""" replicated_card_old = [] try: for replicated_card in replicated_cards: assert replicated_card != replicated_card_old replicated_card_old = replicated_card except AssertionError: #print('card_list = %s' % card_list) #print('card_lines = %s' % card_lines) replicated_card_old = [] for replicated_card in replicated_cards: #print('adding ', replicated_card) assert replicated_card != replicated_card_old replicated_card_old = replicated_card raise def _set_nodes(model: BDF, spoints, epoints, nnodes: int, nspoints: int, nepoints: int, ngridb: int, idtype, fdtype): """helper method for ``get_displacement_index_xyz_cp_cd``""" i = 0 nids_cd_transform = defaultdict(list) # type: dict[int, np.ndarray] nids_cp_transform = defaultdict(list) # type: dict[int, np.ndarray] nxyz = nnodes + nspoints + nepoints + ngridb xyz_cp = np.zeros((nxyz, 3), dtype=fdtype) nid_cp_cd = np.zeros((nxyz, 3), dtype=idtype) for nid, node in sorted(model.nodes.items()): cd = node.Cd() cp = node.Cp() nids_cp_transform[cp].append(nid) nids_cd_transform[cd].append(nid) nid_cp_cd[i, :] = [nid, cp, cd] xyz_cp[i, :] = node.xyz i += 1 if nspoints: for nid in sorted(spoints): nid_cp_cd[i, 0] = nid i += 1 if nepoints: for nid in sorted(epoints): nid_cp_cd[i, 0] = nid i += 1 if ngridb: for nid, node in sorted(model.gridb.items()): phi = node.phi cd = node.cd ringfl_id = node.ringfl ringfl = model.ringfl[ringfl_id] x = ringfl.xa ## TODO: what about xb? y = phi ## TODO: is this really phi? z = 0. axif = model.axif cp = axif.cid nid_cp_cd[i, :] = [nid, cp, cd] xyz_cp[i, :] = [x, y, z] i += 1 return nid_cp_cd, xyz_cp, nids_cd_transform, nids_cp_transform def _bool(value): """casts a lower string to a booean""" return True if value == 'true' else False def _get_coords_to_update(coords: dict[int, Union[CORD1R, CORD1C, CORD1S, CORD2R, CORD2C, CORD2S]], cps_to_check: list[int], cps_checked: list[int], nids_checked: list[int]) -> tuple[int, list[int], list[int], list[int]]: """helper method for ``transform_xyzcp_to_xyz_cid``""" cord1s_to_update_temp = [] cord2s_to_update_list = [] for cp in sorted(cps_to_check): coord = coords[cp] if coord.type in ['CORD2R', 'CORD2C', 'CORD2S']: if coord.rid in cps_checked: cord2s_to_update_list.append(cp) elif coord.type in ['CORD1R', 'CORD1C', 'CORD1S']: cord1s_to_update_temp.append(cp) else: raise NotImplementedError(coord.rstrip()) cord1s_to_update_list = [] if cord1s_to_update_temp: cord1s_to_update = set() if len(nids_checked) == 0: raise RuntimeError('len(nids_checked)=0...this should not happen.') elif len(nids_checked) == 1: pass else: nids_checked = [np.hstack(nids_checked)] nids_checkedi = nids_checked[0] if len(nids_checkedi) != 0: # check the CORD1x cards # #print('nids_checked = ', nids_checkedi) for cp in cord1s_to_update_temp: coord = coords[cp] nids = coord.node_ids #print('cp=%s nids=%s' % (cp, nids)) for nid in nids: if nid not in nids_checkedi: #print(' nid=%s break...' % nid) break else: #print(' passed') # all nids passed cord1s_to_update.add(cp) cord1s_to_update_list = list(cord1s_to_update) cord1s_to_update_list.sort() ncoords = len(cord1s_to_update_list) + len(cord2s_to_update_list) #if ncoords == 0: #msg = 'CPs not handled=%s cord1s_to_update=%s cord2s_to_update=%s\n' % ( #cps_to_check, cord1s_to_update, cord2s_to_update) #for cp in (cord1s_to_update + cord2s_to_update): #msg += str(cp) #raise RuntimeError(msg) return ncoords, cord1s_to_update_list, cord2s_to_update_list, nids_checked
[docs] def map_version(fem: BDF, version: str): version_map = { 'msc': fem.set_as_msc, 'nx': fem.set_as_nx, 'optistruct': fem.set_as_optistruct, 'mystran': fem.set_as_mystran, 'nasa95': fem.set_as_nasa95, 'zona': fem.set_as_zona, } try: func = version_map[version] except KeyError: # msc, nx, zona, nasa95, mystran fmts = ', '.join(version_map) msg = f'mode={version!r} is not supported; modes=[{fmts}]' raise RuntimeError(msg) func()
[docs] def map_update(fem: BDF, version: str): #if self.nastran_format == 'zona': #self.zona.update_for_zona() #elif self.nastran_format == 'mystran': #self._update_for_mystran() #elif self.nastran_format == 'nasa95': #self._update_for_nasa95() #else: # msc / nx / optistruct #self._update_for_nastran() version_map = { 'msc': fem._update_for_nastran, 'nx': fem._update_for_nastran, 'optistruct': fem._update_for_optistruct, 'mystran': fem._update_for_mystran, 'nasa95': fem._update_for_nasa95, 'zona': fem.zona.update_for_zona, } try: func = version_map[version] except KeyError: msg = f'mode={version!r} is not supported; modes=[msc, nx, optistruct, zona, nasa95, mystran]' raise RuntimeError(msg) func()
#if mode == 'msc': #self.set_as_msc() #elif mode == 'nx': #self.set_as_nx() #elif mode == 'nasa95': #self.set_as_nasa95() #elif mode == 'mystran': #self.set_as_mystran() #elif mode == 'zona': #self.set_as_zona() #else: # pragma: no cover #msg = f'mode={self._nastran_format!r} is not supported; modes=[msc, nx, zona, nasa95, mystran]' #raise NotImplementedError(msg)
[docs] def main(): # pragma: no cover """shows off how unicode works because it's overly complicated""" import pyNastran pkg_path = pyNastran.__path__[0] bdf_filename = os.path.abspath(os.path.join( pkg_path, '..', 'models', 'solid_bending', 'solid_bending.bdf')) model = BDF() model.read_bdf(bdf_filename, encoding='latin-1') node1 = model.nodes[1] # decode when we receive, encode on send note = 'helló wörld from two' # must be same encoding as the header (utf-8) #note = b'helló wörld from two\n'.decode('utf-8') print(note) # this will be wrong because it's inconsistent with the header (utf-8) #note = b'á'.decode('latin-1') # this will work note = 'á' # so will this #note = b'á'.decode('utf-8') # The encoding that goes into the comment must be consistent with the local # file, so if your print doesn't work right, your comment will be bad too. # # If the print is correct, assuming all the characters are supported in your # desired encoding, it *should* work. print(note) # Comments are unmodified, so you can inadvertently add cards/bugs. # A comment is a single string where all lines start with $ and end # with an endline character. node1.comment = '$ ' + note + '\n' # in other words, msg is a bad comment: msg = '$ line 1\n' msg += 'line 2\n' msg += '$ line 3\n' print(msg) model.write_bdf('test.bdf')
#if __name__ == '__main__': # pragma: no cover #from pyNastran.bdf.test.test_bdf import main #main()