# pylint: disable=R0902,R0904,R0914,W0231,R0201
"""
All static loads are defined in this file. This includes:
* LSEQ
* DAREA
* SLOAD
* RFORCE
* RANDPS
"""
from __future__ import annotations
from typing import Any, TYPE_CHECKING
import numpy as np
from pyNastran.bdf import MAX_INT
#from pyNastran.bdf.errors import CrossReferenceError
from pyNastran.bdf.field_writer_8 import set_blank_if_default
from pyNastran.bdf.cards.base_card import BaseCard, _node_ids, write_card
from pyNastran.bdf.bdf_interface.assign_type import (
integer, integer_or_blank, double, double_or_blank, components_or_blank,
string_or_blank)
from pyNastran.bdf.field_writer_8 import print_card_8
from pyNastran.bdf.field_writer_16 import print_card_16
from pyNastran.bdf.field_writer_double import print_card_double
from pyNastran.utils.numpy_utils import integer_types, float_types
if TYPE_CHECKING: # pragma: no cover
from pyNastran.bdf.bdf import BDF
[docs]
class Load(BaseCard):
"""defines the DefaultLoad class"""
type = 'DefLoad'
def __init__(self):
self.cid = None
self.nodes = None
@property
def node_ids(self):
"""get the node ids"""
try:
return self._node_ids()
except Exception:
#raise
raise RuntimeError('error processing nodes for \n%s' % str(self))
def _node_ids(self, nodes=None):
"""returns node ids for repr functions"""
if not nodes:
nodes = self.nodes
if isinstance(nodes[0], integer_types):
return [node for node in nodes]
else:
return [node.nid for node in nodes]
[docs]
class LoadCombination(BaseCard):
"""Common method for LOAD, DLOAD"""
def __init__(self, sid, scale, scale_factors, load_ids, comment=''):
"""
Common method for LOAD, DLOAD
Parameters
----------
sid : int
load id
scale : float
overall scale factor
scale_factors : list[float]
individual scale factors (corresponds to load_ids)
load_ids : list[int]
individual load_ids (corresponds to scale_factors)
comment : str; default=''
a comment for the card
"""
BaseCard.__init__(self)
if comment:
self.comment = comment
#: load ID
self.sid = sid
#: overall scale factor
self.scale = scale
#: individual scale factors (corresponds to load_ids)
if isinstance(scale_factors, float):
scale_factors = [scale_factors]
self.scale_factors = scale_factors
#: individual load_ids (corresponds to scale_factors)
if isinstance(load_ids, int):
load_ids = [load_ids]
self.load_ids = load_ids
assert 0 not in load_ids, self
self.load_ids_ref = None
[docs]
def validate(self):
msg = ''
if not isinstance(self.scale, float_types):
msg += 'scale=%s must be a float; type=%s\n' % (self.scale, type(self.scale))
assert isinstance(self.scale_factors, list), self.scale_factors
assert isinstance(self.load_ids, list), self.load_ids
if len(self.scale_factors) != len(self.load_ids):
msg += 'scale_factors=%s load_ids=%s\n' % (self.scale_factors, self.load_ids)
if msg:
raise IndexError(msg)
for scalei, load_id in zip(self.scale_factors, self.get_load_ids()):
assert isinstance(scalei, float_types), scalei
assert isinstance(load_id, integer_types), load_id
[docs]
@classmethod
def add_card(cls, card, comment=''):
sid = integer(card, 1, 'sid')
scale = double(card, 2, 'scale')
scale_factors = []
load_ids = []
# alternating of scale factor & load set ID
nloads = len(card) - 3
assert nloads % 2 == 0, 'card=%s' % card
for i in range(nloads // 2):
n = 2 * i + 3
scale_factors.append(double(card, n, 'scale_factor'))
load_ids.append(integer(card, n + 1, 'load_id'))
assert len(card) > 3, 'len(%s card) = %i\ncard=%s' % (cls.__name__, len(card), card)
return cls(sid, scale, scale_factors, load_ids, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
sid = data[0]
scale = data[1]
scale_factors = data[2]
load_ids = data[3]
assert len(data) == 4, '%s data=%s' % (cls.type, data)
return cls(sid, scale, scale_factors, load_ids, comment=comment)
[docs]
def LoadID(self, lid):
if isinstance(lid, integer_types):
return lid
elif isinstance(lid, list):
return lid[0].sid
else:
raise NotImplementedError(lid)
[docs]
def get_load_ids(self):
"""
xref/non-xref way to get the load ids
"""
if self.load_ids_ref is None:
return self.load_ids
load_ids = []
supported_loads = [
'FORCE', 'FORCE1', 'FORCE2', 'MOMENT', 'MOMENT1', 'MOMENT2',
'PLOAD', 'PLOAD1', 'PLOAD2', 'PLOAD4', 'GRAV', 'SPCD',
# 'GMLOAD',
'RLOAD1', 'RLOAD2', 'TLOAD1', 'TLOAD2', 'PLOADX1', 'LOAD',
'RFORCE', 'RFORCE1', #'RFORCE2'
'ACCEL', 'ACCEL1', 'SLOAD', 'ACSRCE',
'QBDY3',
]
for loads in self.load_ids_ref:
load_idsi = []
for load in loads:
if isinstance(load, integer_types):
load_ids.append(load)
#elif load.type == 'LOAD':
#load_ids.append(load.sid)
elif load.type in supported_loads:
load_idsi.append(load.sid)
else:
msg = ('The get_load_ids method doesnt support %s cards.\n'
'%s' % (load.__class__.__name__, str(load)))
raise NotImplementedError(msg)
load_idi = list(set(load_idsi))
assert len(load_idi) == 1, load_idsi
load_ids.append(load_idi[0])
return load_ids
[docs]
def get_loads(self):
"""
.. note:: requires a cross referenced load
"""
loads = []
for all_loads in self.load_ids_ref:
assert not isinstance(all_loads, int), 'all_loads=%s\n%s' % (str(all_loads), str(self))
for load in all_loads:
try:
loads += load.get_loads()
except RuntimeError:
print('recursion error on load=\n%s' % str(load))
raise
#loads += self.ID #: :: todo: what does this mean, was uncommented
return loads
[docs]
class LSEQ(BaseCard): # Requires LOADSET in case control deck
"""
Defines a sequence of static load sets
.. todo:: how does this work...
+------+-----+----------+-----+-----+
| 1 | 2 | 3 | 4 | 5 |
+======+=====+==========+=====+=====+
| LSEQ | SID | EXCITEID | LID | TID |
+------+-----+----------+-----+-----+
ACSRCE : If there is no LOADSET Case Control command, then EXCITEID
may reference DAREA and SLOAD entries. If there is a LOADSET
Case Control command, then EXCITEID may reference DAREA
entries as well as SLOAD entries specified by the LID field
in the selected LSEQ entry corresponding to EXCITEID.
DAREA : Refer to RLOAD1, RLOAD2, TLOAD1, TLOAD2, or ACSRCE entries
for the formulas that define the scale factor Ai in dynamic
analysis.
DPHASE :
SLOAD : In the static solution sequences, the load set ID (SID) is
selected by the Case Control command LOAD. In the dynamic
solution sequences, SID must be referenced in the LID field
of an LSEQ entry, which in turn must be selected by the Case
Control command LOADSET.
LSEQ LID : Load set identification number of a set of static load
entries such as those referenced by the LOAD Case Control
command.
LSEQ, SID, EXCITEID, LID, TID
#--------------------------------------------------------------
# F:\\Program Files\\Siemens\\NXNastran\\nxn10p1\\nxn10p1\\nast\\tpl\\cube_iter.dat
DLOAD 1001 1.0 1.0 55212
sid = 1001
load_id = [55212] -> RLOAD2.SID
RLOAD2, SID, EXCITEID, DELAYID, DPHASEID, TB, TP, TYPE
RLOAD2 55212 55120 55122 55123 55124
EXCITEID = 55120 -> DAREA.SID
DPHASEID = 55122 -> DPHASE.SID
DARA SID NID COMP SCALE
DAREA 55120 913 3 9.9E+9
SID = 55120 -> RLOAD2.SID
DPHASE SID POINTID C1 TH1
DPHASE 55122 913 3 -90.0
SID = 55122
POINTID = 913 -> GRID.NID
GRID NID X Y Z
GRID 913 50. 0.19 -39.9
"""
type = 'LSEQ'
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
excite_id = 2
lid = 3
return LSEQ(sid, excite_id, lid, tid=None, comment='')
def __init__(self, sid, excite_id, lid, tid=None, comment=''):
"""
Creates a LSEQ card
Parameters
----------
sid : int
loadset id; LOADSET points to this
excite_id : int
set id assigned to this static load vector
lid : int
load set id of a set of static load entries;
LOAD in the Case Control
tid : int; default=None
temperature set id of a set of thermal load entries;
TEMP(LOAD) in the Case Control
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
self.sid = sid
self.excite_id = excite_id
self.lid = lid
self.tid = tid
self.lid_ref = None
self.tid_ref = None
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a LSEQ card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
excite_id = integer(card, 2, 'excite_id')
load_id = integer_or_blank(card, 3, 'lid')
temp_id = integer_or_blank(card, 4, 'tid')
if load_id is None and temp_id is None:
msg = 'LSEQ load_id/temp_id must not be None; load_id=%s temp_id=%s' % (load_id, temp_id)
raise RuntimeError(msg)
assert len(card) <= 5, f'len(LSEQ card) = {len(card):d}\ncard={card}'
return LSEQ(sid, excite_id, load_id, tid=temp_id, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
"""
Adds an LSEQ card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
comment : str; default=''
a comment for the card
"""
sid = data[0]
excite_id = data[1]
lid = data[2]
if lid == 0:
lid = None
tid = data[3]
if tid == 0:
tid = None
return LSEQ(sid, excite_id, lid, tid, comment=comment)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
msg = ', which is required by LSEQ=%s' % (self.sid)
if self.lid is not None:
self.lid_ref = model.Load(self.lid, consider_load_combinations=True, msg=msg)
#self.excite_id = model.Node(self.excite_id, msg=msg)
if self.tid:
# TODO: temperature set, not a table?
self.tid_ref = model.Load(self.tid, consider_load_combinations=True, msg=msg)
#self.tid_ref = model.Table(self.tid, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
return self.cross_reference(model)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.lid = self.Lid()
self.tid = self.Tid()
self.lid_ref = None
self.tid_ref = None
[docs]
def LoadID(self, lid: int) -> int:
if isinstance(lid, list):
sid = self.LoadID(lid[0])
elif isinstance(lid, integer_types):
sid = lid
else:
sid = lid.sid
return sid
[docs]
def get_loads(self) -> Any:
return self.lid_ref
[docs]
def Lid(self) -> int:
if self.lid_ref is not None:
return self.LoadID(self.lid_ref)
return self.lid
#@property
#def node_id(self):
#print('self.excite_id =', self.excite_id)
#if isinstance(self.excite_id, integer_types):
#return self.excite_id
#return self.excite_id.nid
#def Tid(self):
#if self.tid_ref is not None:
#return self.tid_ref.tid
#return self.tid
[docs]
def Tid(self):
if self.tid_ref is not None:
return self.LoadID(self.tid_ref)
return self.tid
[docs]
def raw_fields(self):
list_fields = ['LSEQ', self.sid, self.excite_id, self.Lid(), self.Tid()]
return list_fields
[docs]
def repr_fields(self):
return self.raw_fields()
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
return write_card(self.comment, card, size, is_double)
[docs]
class LOADCYN(Load):
type = 'LOADCYN'
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
scale = 1.
segment_id = 2
scales = [1., 2.]
load_ids = [10, 20]
return LOADCYN(sid, scale, segment_id, scales, load_ids, segment_type=None, comment='')
def __init__(self, sid, scale, segment_id, scales, load_ids, segment_type=None, comment=''):
if comment:
self.comment = comment
self.sid = sid
self.scale = scale
self.segment_id = segment_id
self.scales = scales
self.load_ids = load_ids
self.segment_type = segment_type
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a LOADCYN card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
scale = double(card, 2, 'scale')
segment_id = integer(card, 3, 'segment_id')
segment_type = string_or_blank(card, 4, 'segment_type')
scalei = double(card, 5, 'scale1')
loadi = integer(card, 6, 'load1')
scales = [scalei]
load_ids = [loadi]
scalei = double_or_blank(card, 7, 'scale2')
if scalei is not None:
loadi = double_or_blank(card, 8, 'load2')
scales.append(scalei)
load_ids.append(loadi)
return LOADCYN(sid, scale, segment_id, scales, load_ids,
segment_type=segment_type, comment=comment)
[docs]
def get_loads(self):
return [self]
[docs]
def cross_reference(self, model: BDF) -> None:
pass
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
pass
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
return self.cross_reference(model)
[docs]
def raw_fields(self):
end = []
for scale, load in zip(self.scales, self.load_ids):
end += [scale, load]
list_fields = ['LOADCYN', self.sid, self.scale, self.segment_id, self.segment_type
] + end
return list_fields
[docs]
def repr_fields(self):
return self.raw_fields()
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
return write_card(self.comment, card, size, is_double)
[docs]
class LOADCYH(BaseCard):
"""
Harmonic Load Input for Cyclic Symmetry
Defines the harmonic coefficients of a static or dynamic load for
use in cyclic symmetry analysis.
+---------+-----+----------+-----+-------+-----+------+------+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+=========+=====+==========+=====+=======+=====+======+======+======+
| LOADCYH | SID | S | HID | HTYPE | S1 | L1 | S2 | L2 |
+---------+-----+----------+-----+-------+-----+------+------+------+
"""
type = 'LOADCYH'
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
scale = 1.
hid = 0
htype = 'C'
scales = [1.]
load_ids = [2]
return LOADCYH(sid, scale, hid, htype, scales, load_ids, comment='')
def __init__(self, sid, scale, hid, htype, scales, load_ids, comment=''):
"""
Creates a LOADCYH card
Parameters
----------
sid : int
loadset id; LOADSET points to this
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
self.sid = sid
self.scale = scale
self.hid = hid
self.htype = htype
self.scales = scales
self.load_ids = load_ids
assert htype in {'C', 'S', 'CSTAR', 'SSTAR', 'GRAV', 'RFORCE', None}, htype
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a LOADCYH card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
scale = double(card, 2, 's')
hid = integer(card, 3, 'hid')
htype = string_or_blank(card, 4, 'htype')
scale1 = double(card, 5, 'scale1')
load1 = integer_or_blank(card, 6, 'load1')
scale2 = double_or_blank(card, 7, 'scale2')
load2 = integer_or_blank(card, 8, 'load2')
scales = []
load_ids = []
if load1 != 0:
load_ids.append(load1)
scales.append(scale1)
if load2 != 0:
load_ids.append(load2)
scales.append(scale2)
assert len(card) <= 7, f'len(LOADCYH card) = {len(card):d}\ncard={card}'
return LOADCYH(sid, scale, hid, htype, scales, load_ids, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
"""
Adds an LSEQ card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
comment : str; default=''
a comment for the card
"""
raise NotImplementedError()
#sid = data[0]
#excite_id = data[1]
#lid = data[2]
#tid = data[3]
#return LSEQ(sid, excite_id, lid, tid, comment=comment)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
pass
#msg = ', which is required by LOADCYH=%s' % (self.sid)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
pass
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
pass
[docs]
def get_loads(self):
return [self]
[docs]
def raw_fields(self):
list_fields = ['LOADCYH', self.sid, self.scale, self.hid, self.htype]
for scale, load_id in zip(self.scales, self.load_ids):
list_fields += [scale, load_id]
return list_fields
[docs]
def repr_fields(self):
return self.raw_fields()
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
return write_card(self.comment, card, size, is_double)
[docs]
class DAREA(BaseCard):
"""
Defines scale (area) factors for static and dynamic loads. In dynamic
analysis, DAREA is used in conjunction with ACSRCE, RLOADi and TLOADi
entries.
RLOAD1 -> DAREA by SID
+-------+-----+----+----+-----+----+----+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+=======+=====+====+====+=====+====+====+======+
| DAREA | SID | P1 | C1 | A1 | P2 | C2 | A2 |
+-------+-----+----+----+-----+----+----+------+
| DAREA | 3 | 6 | 2 | 8.2 | 15 | 1 | 10.1 |
+-------+-----+----+----+-----+----+----+------+
"""
type = 'DAREA'
_properties = ['node_ids']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
nodes = [1]
components = [1]
scales = [1.]
return DAREA(sid, nodes, components, scales, comment='')
def __init__(self, sid, nodes, components, scales, comment=''):
"""
Creates a DAREA card
Parameters
----------
sid : int
darea id
nodes : list[int]
GRID, EPOINT, SPOINT id
components : list[int]
Component number. (0-6; 0-EPOINT/SPOINT; 1-6 GRID)
scales : list[float]
Scale (area) factor
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
self.sid = sid
if isinstance(nodes, integer_types):
nodes = [nodes]
if isinstance(components, integer_types):
components = [components]
if isinstance(scales, float):
scales = [scales]
self.nodes = nodes
self.components = components
assert isinstance(components, list), 'components=%r' % components
for component in components:
assert 0 <= component <= 6, 'component=%r' % component
self.scales = scales
self.nodes_ref = None
[docs]
@classmethod
def add_card(cls, card, icard=0, comment=''):
noffset = 3 * icard
sid = integer(card, 1, 'sid')
nid = integer(card, 2 + noffset, 'p')
component = int(components_or_blank(card, 3 + noffset, 'c', '0'))
scale = double(card, 4 + noffset, 'scale')
return DAREA(sid, nid, component, scale, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
"""
Adds a DAREA card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
comment : str; default=''
a comment for the card
"""
sid = data[0]
p = data[1]
c = data[2]
scale = data[3]
assert len(data) == 4, 'data = %s' % data
return DAREA(sid, p, c, scale, comment=comment)
[docs]
def add(self, darea):
assert self.sid == darea.sid, 'sid=%s darea.sid=%s' % (self.sid, darea.sid)
if darea.comment:
if hasattr(self, '_comment'):
self._comment += darea.comment
else:
self._comment = darea.comment
self.nodes += darea.nodes
self.components += darea.components
self.scales += darea.scales
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
msg = ', which is required by DAREA=%s' % (self.sid)
self.nodes_ref = model.Nodes(self.node_ids, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors, debug=True):
nids2 = []
msg = ', which is required by DAREA=%s' % (self.sid)
for nid in self.node_ids:
try:
nid2 = model.Node(nid, msg=msg)
except KeyError:
if debug:
msg = 'Couldnt find nid=%i, which is required by DAREA=%s' % (
nid, self.sid)
print(msg)
continue
nids2.append(nid2)
self.nodes_ref = nids2
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.nodes_ref = None
@property
def node_ids(self):
if self.nodes_ref is None:
return self.nodes
msg = ', which is required by DAREA=%s' % (self.sid)
return _node_ids(self, nodes=self.nodes_ref, allow_empty_nodes=False, msg=msg)
[docs]
def raw_fields(self):
for nid, comp, scale in zip(self.node_ids, self.components, self.scales):
list_fields = ['DAREA', self.sid, nid, comp, scale]
return list_fields
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
msg = self.comment
node_ids = self.node_ids
_print_card = print_card_8
if max(node_ids) > MAX_INT:
_print_card = print_card_16
for nid, comp, scale in zip(node_ids, self.components, self.scales):
msg += _print_card(['DAREA', self.sid, nid, comp, scale])
return msg
[docs]
class DynamicLoad(BaseCard):
def __init__(self):
pass
[docs]
class SPCD(Load):
"""
Defines an enforced displacement value for static analysis and an
enforced motion value (displacement, velocity or acceleration) in
dynamic analysis.
+------+-----+-----+-----+------+----+----+----+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+======+=====+=====+=====+======+====+====+====+
| SPCD | SID | G1 | C1 | D1 | G2 | C2 | D2 |
+------+-----+-----+-----+------+----+----+----+
| SPCD | 100 | 32 | 436 | -2.6 | 5 | 2 | .9 |
+------+-----+-----+-----+------+----+----+----+
"""
type = 'SPCD'
_properties = ['node_ids']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
nodes = [1]
components = ['1']
enforced = 1.
return SPCD(sid, nodes, components, enforced, comment='')
def __init__(self, sid, nodes, components, enforced, comment=''):
"""
Creates an SPCD card, which defines the degree of freedoms to be
set during enforced motion
Parameters
----------
conid : int
constraint id
nodes : list[int]
GRID/SPOINT ids
components : list[str]
the degree of freedoms to constrain (e.g., '1', '123')
enforced : list[float]
the constrained value for the given node (typically 0.0)
comment : str; default=''
a comment for the card
.. note:: len(nodes) == len(components) == len(enforced)
.. warning:: Non-zero enforced deflection requires an SPC/SPC1 as well.
Yes, you really want to constrain the deflection to 0.0
with an SPC1 card and then reset the deflection using an
SPCD card.
"""
if comment:
self.comment = comment
self.sid = sid
if isinstance(nodes, int):
nodes = [nodes]
if isinstance(components, str):
components = [components]
elif isinstance(components, int):
components = [str(components)]
if isinstance(enforced, float):
enforced = [enforced]
self.nodes = nodes
self.components = components
self.enforced = enforced
self.nodes_ref = None
assert isinstance(self.nodes, list), self.nodes
assert isinstance(self.components, list), self.components
assert isinstance(self.enforced, list), self.enforced
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a SPCD card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
if card.field(5) in [None, '']:
nodes = [integer(card, 2, 'G1'),]
components = [components_or_blank(card, 3, 'C1', '0')]
enforced = [double_or_blank(card, 4, 'D1', 0.0)]
else:
nodes = [
integer(card, 2, 'G1'),
integer(card, 5, 'G2'),
]
# :0 if scalar point 1-6 if grid
components = [components_or_blank(card, 3, 'C1', '0'),
components_or_blank(card, 6, 'C2', '0')]
enforced = [double_or_blank(card, 4, 'D1', 0.0),
double_or_blank(card, 7, 'D2', 0.0)]
return SPCD(sid, nodes, components, enforced, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
"""
Adds an SPCD card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
comment : str; default=''
a comment for the card
"""
sid = data[0]
nodes = [data[1]]
components = [data[2]]
enforced = [data[3]]
return SPCD(sid, nodes, components, enforced, comment=comment)
@property
def node_ids(self):
if self.nodes_ref is None:
return self.nodes
msg = ', which is required by SPCD=%s' % (self.sid)
return _node_ids(self, nodes=self.nodes_ref, allow_empty_nodes=True, msg=msg)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
msg = ', which is required by SPCD=%s' % (self.sid)
self.nodes_ref = model.EmptyNodes(self.nodes, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors, debug=True):
msg = ', which is required by SPCD=%s' % (self.sid)
self.nodes_ref = model.EmptyNodes(self.nodes, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.nodes = self.node_ids
self.nodes_ref = None
[docs]
def get_loads(self):
return [self]
[docs]
def raw_fields(self):
fields = ['SPCD', self.sid]
for (nid, component, enforced) in zip(self.node_ids, self.components,
self.enforced):
fields += [nid, component, enforced]
return fields
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
return write_card(self.comment, card, size, is_double)
[docs]
class SLOAD(Load):
"""
Static Scalar Load
Defines concentrated static loads on scalar or grid points.
+-------+-----+----+-----+----+------+----+-------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+=======+=====+====+=====+====+======+====+=======+
| SLOAD | SID | S1 | F1 | S2 | F2 | S3 | F3 |
+-------+-----+----+-----+----+------+----+-------+
| SLOAD | 16 | 2 | 5.9 | 17 | -6.3 | 14 | -2.93 |
+-------+-----+----+-----+----+------+----+-------+
.. note:: Can be used in statics OR dynamics.
If Si refers to a grid point, the load is applied to component T1 of the
displacement coordinate system (see the CD field on the GRID entry).
"""
type = 'SLOAD'
_properties = ['node_ids']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
nodes = [1]
mags = [1.]
return SLOAD(sid, nodes, mags, comment='')
def __init__(self, sid, nodes, mags, comment=''):
"""
Creates an SLOAD (GRID/SPOINT load)
Parameters
----------
sid : int
load id
nodes : int; list[int]
the GRID/SPOINT ids
mags : float; list[float]
the load magnitude
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
if isinstance(nodes, integer_types):
nodes = [nodes]
if isinstance(mags, float):
mags = [mags]
#: load ID
self.sid = sid
self.nodes = nodes
self.mags = mags
self.nodes_ref = None
[docs]
def validate(self):
assert len(self.nodes) == len(self.mags), 'len(nodes)=%s len(mags)=%s' % (len(self.nodes), len(self.mags))
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a SLOAD card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
nfields = len(card) - 2
ngroups = nfields // 2
if nfields % 2 == 1:
ngroups += 1
msg = 'Missing last magnitude on SLOAD card=%s' % card.fields()
raise RuntimeError(msg)
nodes = []
mags = []
for i in range(ngroups):
j = 2 * i + 2
nodes.append(integer(card, j, 'nid' + str(i)))
mags.append(double(card, j + 1, 'mag' + str(i)))
return SLOAD(sid, nodes, mags, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
"""
Adds an SLOAD card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
comment : str; default=''
a comment for the card
"""
(sid, nid, scale_factor) = data
return SLOAD(sid, [nid], [scale_factor], comment=comment)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
msg = ', which is required by SLOAD=%s' % (self.sid)
self.nodes_ref = []
for nid in self.nodes:
self.nodes_ref.append(model.Node(nid, msg=msg))
#self.nodes_ref = model.EmptyNodes(self.nodes, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
return self.cross_reference(model)
#msg = ', which is required by SLOAD=%s' % (self.sid)
#self.nodes_ref = model.safe_empty_nodes(self.nodes, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.nodes = self.node_ids
self.nodes_ref = None
[docs]
def Nid(self, node):
if isinstance(node, integer_types):
return node
return node.nid
[docs]
def get_loads(self):
"""
.. todo:: not done
"""
return []
@property
def node_ids(self):
if self.nodes_ref is None:
return self.nodes
return [self.Nid(nid) for nid in self.nodes_ref]
[docs]
def raw_fields(self):
list_fields = ['SLOAD', self.sid]
for nid, mag in zip(self.node_ids, self.mags):
list_fields += [nid, mag]
return list_fields
[docs]
def repr_fields(self):
return self.raw_fields()
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
return write_card(self.comment, card, size, is_double)
[docs]
def write_card_16(self, is_double: bool=False) -> str:
card = self.raw_fields()
if is_double:
return self.comment + print_card_double(card)
return self.comment + print_card_16(card)
[docs]
class RFORCE(Load):
type = 'RFORCE'
_properties = ['node_id']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
nid = 1
scale = 1.
r123 = [1., 0., 1.]
return RFORCE(sid, nid, scale, r123,
cid=0, method=1, racc=0., mb=0, idrf=0, comment='')
def __init__(self, sid, nid, scale, r123, cid=0, method=1, racc=0.,
mb=0, idrf=0, comment=''):
"""
idrf doesn't exist in MSC 2005r2; exists in MSC 2016
Parameters
----------
sid : int
load set id
nid : int
grid point through which the rotation vector acts
scale : float
scale factor of the angular velocity in revolutions/time
r123 : list[float, float, float] / (3, ) float ndarray
rectangular components of the rotation vector R that passes
through point G (R1**2+R2**2+R3**2 > 0 unless A and RACC are
both zero).
cid : int; default=0
Coordinate system defining the components of the rotation vector.
method : int; default=1
Method used to compute centrifugal forces due to angular velocity.
racc : int; default=0.0
Scale factor of the angular acceleration in revolutions per
unit time squared.
mb : int; default=0
Indicates whether the CID coordinate system is defined in the main
Bulk Data Section (MB = -1) or the partitioned superelement Bulk
Data Section (MB = 0). Coordinate systems referenced in the main
Bulk Data Section are considered stationary with respect to the
assembly basic coordinate system.
idrf : int; default=0
ID indicating to which portion of the structure this particular
RFORCE entry applies. It is possible to have multiple RFORCE
entries in the same subcase for SOL 600 to represent different
portions of the structure with different rotational accelerations.
IDRF corresponds to a SET3 entry specifying the elements with this
acceleration. A BRKSQL entry may also be specified with a matching
IDRF entry.
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
self.sid = sid
self.nid = nid
self.cid = cid
self.scale = scale
self.r123 = r123
self.method = method
self.racc = racc
self.mb = mb
self.idrf = idrf
self.nid_ref = None
self.cid_ref = None
self.validate()
[docs]
def validate(self):
assert self.method in [1, 2], self.method
assert isinstance(self.r123, list), self.r123
assert isinstance(self.scale, float), self.scale
assert isinstance(self.cid, int), self.cid
assert isinstance(self.mb, int), self.mb
assert isinstance(self.idrf, int), self.idrf
assert isinstance(self.racc, float), self.racc
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a RFORCE card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
nid = integer_or_blank(card, 2, 'nid', default=0)
cid = integer_or_blank(card, 3, 'cid', default=0)
scale = double_or_blank(card, 4, 'scale', default=1.)
r1 = double_or_blank(card, 5, 'r1', default=0.)
r2 = double_or_blank(card, 6, 'r2', default=0.)
r3 = double_or_blank(card, 7, 'r3', default=0.)
method = integer_or_blank(card, 8, 'method', default=1)
racc = double_or_blank(card, 9, 'racc', default=0.)
mb = integer_or_blank(card, 10, 'mb', default=0)
idrf = integer_or_blank(card, 11, 'idrf', default=0)
assert len(card) <= 12, f'len(RFORCE card) = {len(card):d}\ncard={card}'
return RFORCE(sid, nid, scale, [r1, r2, r3],
cid=cid, method=method, racc=racc, mb=mb, idrf=idrf, comment=comment)
@classmethod
def add_op2_data(cls, data, comment=''):
"""
Adds a RFORCE card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
comment : str; default=''
a comment for the card
"""
sid, nid, cid, a, r1, r2, r3, method, racc, mb = data
scale = 1.0
return RFORCE(sid, nid, scale, [r1, r2, r3], cid=cid, method=method, racc=racc, mb=mb,
idrf=0, comment=comment)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
msg = ', which is required by RFORCE sid=%s' % self.sid
if self.nid > 0:
self.nid_ref = model.Node(self.nid, msg=msg)
self.cid_ref = model.Coord(self.cid, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
msg = ', which is required by RFORCE sid=%s' % self.sid
if self.nid > 0:
self.nid_ref = model.Node(self.nid, msg=msg)
self.cid_ref = model.safe_coord(self.cid, self.sid, xref_errors, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.nid = self.Nid()
self.cid = self.Cid()
self.nid_ref = None
self.cid_ref = None
@property
def node_id(self):
if self.nid_ref is not None:
return self.nid_ref.nid
return self.nid
[docs]
def Nid(self):
return self.node_id
[docs]
def Cid(self):
if self.cid_ref is not None:
return self.cid_ref.cid
return self.cid
[docs]
def get_loads(self):
return [self]
[docs]
def raw_fields(self):
list_fields = (['RFORCE', self.sid, self.node_id, self.Cid(), self.scale] +
list(self.r123) + [self.method, self.racc, self.mb, self.idrf])
return list_fields
[docs]
def repr_fields(self):
#method = set_blank_if_default(self.method,1)
racc = set_blank_if_default(self.racc, 0.)
mb = set_blank_if_default(self.mb, 0)
idrf = set_blank_if_default(self.idrf, 0)
list_fields = (['RFORCE', self.sid, self.node_id, self.Cid(), self.scale] +
list(self.r123) + [self.method, racc, mb, idrf])
return list_fields
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.repr_fields()
return write_card(self.comment, card, size, is_double)
[docs]
class RFORCE1(Load):
"""
NX Nastran specific card
+---------+------+----+---------+---+----+----+----+--------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+=========+======+====+=========+===+====+====+====+========+
| RFORCE1 | SID | G | CID | A | R1 | R2 | R3 | METHOD |
+---------+------+----+---------+---+----+----+----+--------+
| | RACC | MB | GROUPID | | | | | |
+---------+------+----+---------+---+----+----+----+--------+
"""
type = 'RFORCE1'
_properties = ['node_id']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
nid = 1
scale = 1.
group_id = 1
return RFORCE1(sid, nid, scale, group_id,
cid=0, r123=None, racc=0., mb=0, method=2, comment='')
def __init__(self, sid, nid, scale, group_id,
cid=0, r123=None, racc=0., mb=0, method=2, comment=''):
"""
Creates an RFORCE1 card
Parameters
----------
sid : int
load set id
nid : int
grid point through which the rotation vector acts
scale : float
scale factor of the angular velocity in revolutions/time
r123 : list[float, float, float] / (3, ) float ndarray
rectangular components of the rotation vector R that passes
through point G (R1**2+R2**2+R3**2 > 0 unless A and RACC are
both zero).
racc : int; default=0.0
Scale factor of the angular acceleration in revolutions per
unit time squared.
mb : int; default=0
Indicates whether the CID coordinate system is defined in the main
Bulk Data Section (MB = -1) or the partitioned superelement Bulk
Data Section (MB = 0). Coordinate systems referenced in the main
Bulk Data Section are considered stationary with respect to the
assembly basic coordinate system.
group_id : int
Group identification number. The GROUP entry referenced in the
GROUPID field selects the grid points to which the load is applied.
cid : int; default=0
Coordinate system defining the components of the rotation vector.
method : int; default=2
Method used to compute centrifugal forces due to angular velocity.
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
self.sid = sid
self.nid = nid
self.cid = cid
self.scale = scale
if r123 is None:
self.r123 = np.array([1., 0., 0.])
else:
self.r123 = np.asarray(r123)
self.method = method
self.racc = racc
self.mb = mb
self.group_id = group_id
self.nid_ref = None
self.cid_ref = None
[docs]
def validate(self):
if not np.linalg.norm(self.r123) > 0.:
msg = 'r123=%s norm=%s' % (self.r123, np.linalg.norm(self.r123))
raise RuntimeError(msg)
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a RFORCE1 card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
nid = integer_or_blank(card, 2, 'nid', default=0)
cid = integer_or_blank(card, 3, 'cid', default=0)
scale = double_or_blank(card, 4, 'scale', default=1.)
r123 = [
double_or_blank(card, 5, 'r1', default=1.),
double_or_blank(card, 6, 'r2', default=0.),
double_or_blank(card, 7, 'r3', default=0.),
]
method = integer_or_blank(card, 8, 'method', default=1)
racc = double_or_blank(card, 9, 'racc', default=0.)
mb = integer_or_blank(card, 10, 'mb', default=0)
group_id = integer_or_blank(card, 11, 'group_id', default=0)
assert len(card) <= 12, f'len(RFORCE1 card) = {len(card):d}\ncard={card}'
return RFORCE1(sid, nid, scale, cid=cid, r123=r123, racc=racc,
mb=mb, group_id=group_id, method=method, comment=comment)
[docs]
def get_loads(self):
return [self]
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
msg = ', which is required by RFORCE1 sid=%s' % self.sid
#if self.nid > 0: # TODO: why was this every here?
self.nid_ref = model.Node(self.nid, msg=msg)
self.cid_ref = model.Coord(self.cid, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
msg = ', which is required by RFORCE1 sid=%s' % self.sid
#if self.nid > 0: # TODO: why was this every here?
self.nid_ref = model.Node(self.nid, msg=msg)
self.cid_ref = model.safe_coord(self.cid, self.sid, xref_errors, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.nid = self.node_id
self.cid = self.Cid()
self.nid_ref = None
self.cid_ref = None
@property
def node_id(self):
if self.nid_ref is not None:
return self.nid_ref.nid
return self.nid
[docs]
def Nid(self):
return self.node_id
[docs]
def Cid(self):
if self.cid_ref is not None:
return self.cid_ref.cid
return self.cid
[docs]
def raw_fields(self):
list_fields = (['RFORCE1', self.sid, self.node_id, self.Cid(), self.scale]
+ list(self.r123) + [self.method, self.racc,
self.mb, self.group_id])
return list_fields
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.repr_fields()
return write_card(self.comment, card, size, is_double)