# pylint: disable=C0103
"""
defines:
- CBEAM
- BEAMOR
"""
from __future__ import annotations
from typing import Union, Any, TYPE_CHECKING
import numpy as np
from numpy.linalg import norm # type: ignore
from pyNastran.utils.numpy_utils import integer_types
from pyNastran.bdf import MAX_INT
from pyNastran.bdf.cards.base_card import BaseCard
from pyNastran.bdf.cards.elements.bars import (
LineElement, init_x_g0, rotate_v_wa_wb, check_offt)
from pyNastran.bdf.bdf_interface.assign_type import (
integer, integer_or_blank, double_or_blank, integer_double_string_or_blank,
integer_double_or_blank, integer_string_or_blank,
)
from pyNastran.bdf.field_writer_8 import set_blank_if_default
from pyNastran.bdf.field_writer_8 import print_card_8
from pyNastran.bdf.field_writer_16 import print_card_16
from pyNastran.utils.mathematics import integrate_positive_unit_line
if TYPE_CHECKING: # pragma: no cover
from pyNastran.bdf.bdf import BDF, GRID
[docs]
class CBEAM(LineElement):
"""
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+=======+=====+=====+=====+=====+=====+=====+=====+==========+
| CBEAM | EID | PID | GA | GB | X1 | X2 | X3 | OFFT/BIT |
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
| | PA | PB | W1A | W2A | W3A | W1B | W2B | W3B |
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
| | SA | SB | | | | | | |
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
or
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+=======+=====+=====+=====+=====+=====+=====+=====+==========+
| CBEAM | EID | PID | GA | GB | G0 | | | OFFT/BIT |
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
| | PA | PB | W1A | W2A | W3A | W1B | W2B | W3B |
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
| | SA | SB | | | | | | |
+-------+-----+-----+-----+-----+-----+-----+-----+----------+
bit is an MSC specific field
NX 2020 added offt
"""
type = 'CBEAM'
_field_map = {
1: 'eid', 2:'pid', 3:'ga', 4:'gb', #5:'x_g0', 6:'g1', 7:'g2',
#8:'offt',
9:'pa', 10:'pb',
17:'sa', 18:'sb',
}
def _get_field_helper(self, n: int) -> Union[int, float]:
if n == 11:
value = self.wa[0]
elif n == 12:
value = self.wa[1]
elif n == 13:
value = self.wa[2]
elif n == 14:
value = self.wb[0]
elif n == 15:
value = self.wb[1]
elif n == 16:
value = self.wb[2]
elif n in {5, 6, 7}:
if self.g0 is not None:
if n == 5:
value = self.g0
else: # offt
msg = f'Field {n!r} is an invalid {self.type} entry or is unsupported.'
raise KeyError(msg)
else:
if n == 5:
value = self.x[0]
elif n == 6:
value = self.x[1]
elif n == 7:
value = self.x[2]
else:
msg = f'Field {n!r} is an invalid {self.type} entry or is unsupported.'
raise KeyError(msg)
else:
msg = f'Field {n!r} is an invalid {self.type} entry or is unsupported.'
raise KeyError(msg)
return value
def _update_field_helper(self, n: int, value):
if n == 11:
self.wa[0] = value
elif n == 12:
self.wa[1] = value
elif n == 13:
self.wa[2] = value
elif n == 14:
self.wb[0] = value
elif n == 15:
self.wb[1] = value
elif n == 16:
self.wb[2] = value
else:
if self.g0 is not None:
if n == 5:
self.g0 = value
else: # offt
msg = 'Field %r=%r is an invalid %s entry or is unsupported.' % (
n, value, self.type)
raise KeyError(msg)
else:
if n == 5:
self.x[0] = value
elif n == 6:
self.x[1] = value
elif n == 7:
self.x[2] = value
else:
msg = 'Field %r=%r is an invalid %s entry or is unsupported.' % (
n, value, self.type)
raise KeyError(msg)
[docs]
@classmethod
def export_to_hdf5(cls, h5_file, model, eids):
"""exports the elements in a vectorized way"""
encoding = model._encoding
#comments = []
pids = []
nodes = []
x = []
g0 = []
offt = []
bit = []
pa = []
pb = []
wa = []
wb = []
sa = []
sb = []
nan = np.full(3, np.nan)
for eid in eids:
element = model.elements[eid]
#comments.append(element.comment)
pids.append(element.pid)
nodes.append(element.nodes)
if element.g0 is None:
x.append(element.x)
g0.append(-1)
else:
x.append(nan)
g0.append(element.g0)
if element.bit is not None:
bit.append(element.bit)
offt.append(b'')
else:
bit.append(np.nan)
offti = element.offt
if isinstance(offti, integer_types):
offti = str(offti)
offt.append(offti.encode(encoding))
pa.append(element.pa)
pb.append(element.pb)
sa.append(element.sa)
sb.append(element.sb)
wa.append(element.wa)
wb.append(element.wb)
#h5_file.create_dataset('_comment', data=comments)
h5_file.create_dataset('eid', data=eids)
h5_file.create_dataset('nodes', data=nodes)
h5_file.create_dataset('pid', data=pids)
#print('x =', x)
#print('g0 =', g0)
h5_file.create_dataset('x', data=x)
h5_file.create_dataset('g0', data=g0)
h5_file.create_dataset('offt', data=offt)
h5_file.create_dataset('bit', data=bit)
h5_file.create_dataset('pa', data=pa)
h5_file.create_dataset('pb', data=pb)
h5_file.create_dataset('sa', data=sa)
h5_file.create_dataset('sb', data=sb)
h5_file.create_dataset('wa', data=wa)
h5_file.create_dataset('wb', data=wb)
def __init__(self, eid, pid, nids,
x: Optional[list[float]], g0: Optional[int],
offt='GGG', bit=None,
pa=0, pb=0, wa=None, wb=None,
sa=0, sb=0, comment=''):
"""
Adds a CBEAM card
Parameters
----------
pid : int
property id
mid : int
material id
nids : list[int, int]
node ids; connected grid points at ends A and B
x : list[float, float, float]
Components of orientation vector, from GA, in the displacement
coordinate system at GA (default), or in the basic coordinate system
g0 : int
Alternate method to supply the orientation vector using grid
point G0. Direction of is from GA to G0. is then transferred
to End A
offt : str; default='GGG'
Offset vector interpretation flag
None : bit is active
bit : float; default=None
Built-in twist of the cross-sectional axes about the beam axis
at end B relative to end A.
For beam p-elements ONLY!
None : offt is active
pa / pb : int; default=0
Pin Flag at End A/B. Releases the specified DOFs
wa / wb : list[float, float, float]
Components of offset vectors from the grid points to the end
points of the axis of the shear center
sa / sb : int; default=0
Scalar or grid point identification numbers for the ends A and B,
respectively. The degrees-of-freedom at these points are the
warping variables . SA and SB cannot be specified for
beam p-elements
comment : str; default=''
a comment for the card
offt/bit are MSC specific fields
"""
LineElement.__init__(self)
if comment:
self.comment = comment
if wa is None:
wa = np.zeros(3, dtype='float64')
else:
wa = np.asarray(wa)
if wb is None:
wb = np.zeros(3, dtype='float64')
else:
wb = np.asarray(wb)
if isinstance(offt, str):
offt = offt.replace('E', 'O')
offt = int(offt) if offt.isdigit() else offt
self.eid = eid
self.pid = pid
self.nodes = nids
self.x = x
self.g0 = g0
self.offt = offt
self.bit = bit
self.pa = pa
self.pb = pb
self.wa = wa
self.wb = wb
self.sa = sa
self.sb = sb
self.nodes_ref = None
self.pid_ref = None
self.g0_ref = None
self.g0_vector = None
@property
def ga(self) -> int:
return self.nodes[0]
@property
def gb(self) -> int:
return self.nodes[1]
@property
def ga_ref(self) -> GRID:
return self.nodes_ref[0]
@property
def gb_ref(self) -> GRID:
return self.nodes_ref[1]
@ga.setter
def ga(self, ga: int) -> None:
self.nodes[0] = ga
@gb.setter
def gb(self, gb: int) -> None:
self.nodes[1] = gb
@ga_ref.setter
def ga_ref(self, ga_ref: GRID) -> None:
self.nodes_ref[0] = ga_ref
@gb_ref.setter
def gb_ref(self, gb_ref: GRID) -> None:
self.nodes_ref[1] = gb_ref
[docs]
def validate(self):
msg = ''
#assert self.g0 is None or self.x is None, (self.g0, self.x)
#assert self.g0 is not None or self.x is not None, (self.g0, self.x)
if self.x is None:
if not isinstance(self.g0, integer_types):
msg += f'CBEAM eid={self.eid}: x is None, so g0={self.g0} must be an integer'
else:
if not isinstance(self.x, (list, np.ndarray)):
msg += 'CBEAM eid=%s: x=%s and g0=%s, so x must be a list; type(x)=%s' % (
self.eid, self.x, self.g0, type(self.x))
if msg:
raise ValueError(msg)
if self.g0 is not None:
assert isinstance(self.g0, integer_types), 'g0=%s must be an integer' % self.g0
if self.g0 in [self.ga, self.gb]:
msg = f'G0={self.g0} cannot be GA={self.ga} or GB={self.gb}'
raise RuntimeError(msg)
if self.x is not None:
xlen = np.linalg.norm(self.x)
if xlen == 0.0:
msg = f'Eid={self.eid:d}; X={self.x} has no length'
raise RuntimeError(msg)
#print(f'CBEAM validate: eid={self.eid} g0={self.g0} x={self.x}')
if self.bit is None and self.offt is None:
msg = f'OFFT/BIT must not be None; offt={self.offt!r} bit={self.bit}'
raise RuntimeError(msg)
if self.offt is not None:
if isinstance(self.offt, integer_types):
assert self.offt in [1, 2, 21, 22, 41, 42], 'invalid offt; offt=%i' % self.offt
#raise NotImplementedError('invalid offt; offt=%i' % self.offt)
elif isinstance(self.offt, str):
check_offt(self)
else:
raise TypeError('invalid offt expected a string of length 3 '
'offt=%r; Type=%s' % (self.offt, type(self.offt)))
[docs]
@classmethod
def add_card(cls, card, beamor=None, comment=''):
"""
Adds a CBEAM card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
beamor : BEAMOR() or None
defines the defaults
comment : str; default=''
a comment for the card
"""
eid = integer(card, 1, 'eid')
pid_default = eid
x1_default, x2_default, x3_default = 0., 0., 0.
offt_default = 'GGG'
if beamor is not None:
if beamor.pid is not None:
pid_default = beamor.pid
if beamor.x is None:
x1_default = beamor.g0
x2_default = None
x3_default = None
else:
x1_default, x2_default, x3_default = beamor.x
offt_default = beamor.offt
pid = integer_or_blank(card, 2, 'pid', pid_default)
ga = integer(card, 3, 'ga')
gb = integer(card, 4, 'gb')
x, g0 = init_x_g0(card, eid, x1_default, x2_default, x3_default)
offt, bit = _init_offt_bit(card, eid, offt_default)# offt doesn't exist in NX nastran
pa = integer_or_blank(card, 9, 'pa', 0)
pb = integer_or_blank(card, 10, 'pb', 0)
wa = np.array([double_or_blank(card, 11, 'w1a', 0.0),
double_or_blank(card, 12, 'w2a', 0.0),
double_or_blank(card, 13, 'w3a', 0.0)], 'float64')
wb = np.array([double_or_blank(card, 14, 'w1b', 0.0),
double_or_blank(card, 15, 'w2b', 0.0),
double_or_blank(card, 16, 'w3b', 0.0)], 'float64')
sa = integer_or_blank(card, 17, 'sa', 0)
sb = integer_or_blank(card, 18, 'sb', 0)
assert len(card) <= 19, f'len(CBEAM card) = {len(card):d}\ncard={card}'
return CBEAM(eid, pid, [ga, gb], x, g0, offt, bit,
pa=pa, pb=pb, wa=wa, wb=wb, sa=sa, sb=sb, comment=comment)
@classmethod
def add_op2_data(cls, data, f, comment=''):
"""
Adds a CBEAM card from the OP2
Parameters
----------
data : list[varies]
a list of fields defined in OP2 format
f : int
beam flag
0 : basic
[x1, x2, x3] is used
1 : cid
[x1, x2, x3] is used
2 : grid
g0 is used instead of [x1, x2, x3]
comment : str; default=''
a comment for the card
"""
#: .. todo:: verify
assert len(data) == 2, 'data=%s len(data)=%s' % (data, len(data))
#data = [[eid,pid,ga,gb,sa,sb, pa,pb,w1a,w2a,w3a,w1b,w2b,w3b],
# [f,g0]]
#data = [[eid,pid,ga,gb,sa,sb, pa,pb,w1a,w2a,w3a,w1b,w2b,w3b],
# [f,x1,x2,x3]]
main, aft = data
flag = aft[0]
assert f == flag, 'f=%s flag=%s' % (f, flag)
if flag == 0:
# basic cid
#data_in = [[eid, pid, ga, gb, sa, sb, pa, pb, w1a, w2a, w3a, w1b, w2b, w3b],
#[f, x1, x2, x3]]
assert len(aft) == 4, 'f=%s aft=%s len(aft)=%s' % (f, aft, len(aft))
x1, x2, x3 = aft[1:]
g0 = None
x = np.array([x1, x2, x3], dtype='float64')
elif flag == 1:
# global cid
#data_in = [[eid, pid, ga, gb, sa, sb, pa, pb, w1a, w2a, w3a, w1b, w2b, w3b],
#[f, x1, x2, x3]]
assert len(aft) == 4, 'f=%s aft=%s len(aft)=%s' % (f, aft, len(aft))
g0 = None
x1, x2, x3 = aft[1:]
x = np.array([x1, x2, x3], dtype='float64')
elif flag == 2:
# grid option
#data_in = [[eid, pid, ga, gb, sa, sb, pa, pb, w1a, w2a, w3a, w1b, w2b, w3b],
#[f, g0]]
assert len(aft) == 2, 'f=%s aft=%s len(aft)=%s' % (f, aft, len(aft))
g0 = data[1][1]
x = None
else:
raise NotImplementedError()
eid = main[0]
pid = main[1]
ga = main[2]
gb = main[3]
sa = main[4]
sb = main[5]
#offt = str(data[6]) # GGG
bit = None # ???
offt = 'GGG' #: .. todo:: is this correct???
pa = main[6]
pb = main[7]
wa = np.array([main[8], main[9], main[10]], 'float64')
wb = np.array([main[11], main[12], main[13]], 'float64')
return CBEAM(eid, pid, [ga, gb], x, g0, offt, bit,
pa=pa, pb=pb, wa=wa, wb=wb, sa=sa, sb=sb, comment=comment)
[docs]
def Nodes(self):
return [self.ga, self.gb]
[docs]
def Centroid(self):
""""""
node1, node2 = self.nodes_ref
xyz1 = node1.get_position()
xyz2 = node2.get_position()
centroid = (xyz1 + xyz2) / 2.
return centroid
[docs]
def center_of_mass(self):
"""the centroid formuala is way more complicated if you consider the nonstructural mass axis"""
elem = self
prop = self.pid_ref
node1, node2 = self.nodes_ref
xyz1 = node1.get_position()
xyz2 = node2.get_position()
#centroid = ( + self.gb_ref.get_position()) / 2.
centroid = (xyz1 + xyz2) / 2.
#length = norm(xyz2 - xyz1)
#cda = model.nodes[n1].cid_ref
#cdb = model.nodes[n2].cid_ref
model = None
log = None
is_failed, out = elem.get_axes_by_nodes(model, node1, node2, xyz1, xyz2, log)
if is_failed:
#model.log.error(out)
raise RuntimeError(out)
_v, _ihat, jhat, khat, wa, wb = out
p1 = xyz1 + wa
p2 = xyz2 + wb
if prop.type == 'PBEAM':
rho = prop.Rho()
# we don't call the MassPerLength method so we can put the NSM centroid
# on a different axis (the PBEAM is weird)
mass_per_lengths = []
nsm_per_lengths = []
for (area, nsm) in zip(prop.A, prop.nsm):
mass_per_lengths.append(area * rho)
nsm_per_lengths.append(nsm)
mass_per_length = integrate_positive_unit_line(prop.xxb, mass_per_lengths)
nsm_per_length = integrate_positive_unit_line(prop.xxb, nsm_per_lengths)
nsm_n1 = (p1 + jhat * prop.m1a + khat * prop.m2a)
nsm_n2 = (p2 + jhat * prop.m1b + khat * prop.m2b)
#print("nsm_per_length=%s" % nsm_per_length)
#print("nsm_n1=%s" % nsm_n1)
#print("nsm_n2=%s" % nsm_n2)
nsm_centroid = (nsm_n1 + nsm_n2) / 2.
#if nsm != 0.:
#p1_nsm = p1 + prop.ma
#p2_nsm = p2 + prop.mb
elif prop.type == 'PBEAML':
mass_per_lengths = prop.get_mass_per_lengths()
#mass_per_length = prop.MassPerLength() # includes simplified nsm
# m1a, m1b, m2a, m2b=0.
nsm_centroid = (p1 + p2) / 2.
# mass_per_length already includes nsm
mass_per_length = integrate_positive_unit_line(prop.xxb, mass_per_lengths)
nsm_per_length = 0.
#print('mass_per_lengths=%s nsm_per_lengths=%s' % (
#mass_per_lengths, nsm_per_lengths))
#print('mass_per_length=%s nsm_per_length=%s' % (
#mass_per_length, nsm_per_length))
#nsm_centroid = np.zeros(3) # TODO: what is this...
#nsm = prop.nsm[0] * length # TODO: simplified
elif prop.type == 'PBCOMP':
mass_per_length = prop.MassPerLength()
nsm_per_length = prop.nsm
nsm_n1 = (p1 + jhat * prop.m1 + khat * prop.m2)
nsm_n2 = (p2 + jhat * prop.m1 + khat * prop.m2)
nsm_centroid = (nsm_n1 + nsm_n2) / 2.
#elif prop.type == 'PBMSECT':
#continue
#mass_per_length = prop.MassPerLength()
#m = mass_per_length * length
#nsm = prop.nsm
elif prop.type == 'PBMSECT':
mass_per_length = 0. ## TODO: fix me
nsm_per_length = prop.nsm
nsm_centroid = (p1 + p2) / 2.
else:
raise NotImplementedError(prop.type)
total_mass = mass_per_length + nsm_per_length
if total_mass == 0.0:
return centroid
centroid2 = (centroid * mass_per_length + nsm_centroid * nsm_per_length) / total_mass
return centroid2
[docs]
def get_axes(self, model: BDF) -> tuple[bool, tuple[Any, Any, Any, Any, Any, Any]]:
"""
Gets the axes of a CBAR/CBEAM, while respecting the OFFT flag.
Notes
-----
:func:`pyNastran.bdf.cards.elements.bars.rotate_v_wa_wb` for a
description of the OFFT flag.
Returns
-------
is_passed: bool
flag
out: (v, ihat, jhat, khat, wa, wb)
data
"""
is_failed = True
#TODO: not integrated with CBAR yet...
if self.bit is not None:
print(self.get_stats())
return is_failed, None
check_offt(self)
is_failed = True
ihat = None
yhat = None
zhat = None
#eid = self.eid
(nid1, nid2) = self.node_ids
node1 = model.nodes[nid1]
node2 = model.nodes[nid2]
xyz1 = node1.get_position()
xyz2 = node2.get_position()
#elem = model.elements[eid]
is_failed, (v, ihat, yhat, zhat, wa, wb) = self.get_axes_by_nodes(
model, node1, node2, xyz1, xyz2, model.log)
return is_failed, (v, ihat, yhat, zhat, wa, wb)
[docs]
def get_orientation_vector(self, model: BDF):
"""
Gets the axes of a CBAR/CBEAM, while respecting the OFFT flag.
Notes
-----
:func:`pyNastran.bdf.cards.elements.bars.rotate_v_wa_wb` for a
description of the OFFT flag.
"""
#TODO: not integrated with CBAR yet...
eid = self.eid
elem = self
node1 = self.nodes_ref[0]
node2 = self.nodes_ref[1]
xyz1 = node1.get_position()
xyz2 = node2.get_position()
# wa/wb are not considered in i_offset
# they are considered in ihat
i = xyz2 - xyz1
ihat_norm = norm(i)
if ihat_norm== 0.:
msg = 'xyz1=%s xyz2=%s\n%s' % (xyz1, xyz2, self)
raise ValueError(msg)
i_offset = i / ihat_norm
v, unused_wa, unused_wb, unused_xform = rotate_v_wa_wb(
model, elem,
xyz1, xyz2, node1, node2,
i_offset, i, eid, ihat_norm, model.log)
return v
[docs]
def get_axes_by_nodes(self, model: BDF,
node1: GRID, node2: GRID,
xyz1: np.ndarray, xyz2: np.ndarray,
log: SimpleLogger) -> tuple[bool, Any]:
"""
Gets the axes of a CBAR/CBEAM, while respecting the OFFT flag.
Notes
-----
:func:`pyNastran.bdf.cards.elements.bars.rotate_v_wa_wb` for a
description of the OFFT flag.
"""
#TODO: not integrated with CBAR yet...
is_failed = True
eid = self.eid
#centroid = (n1 + n2) / 2.
#i = n2 - n1
#Li = norm(i)
#ihat = i / Li
elem = self
#(nid1, nid2) = elem.node_ids
#node1 = model.nodes[nid1]
#node2 = model.nodes[nid2]
#xyz1 = node1.get_position()
#xyz2 = node2.get_position()
# wa/wb are not considered in i_offset
# they are considered in ihat
i = xyz2 - xyz1
ihat_norm = norm(i)
if ihat_norm== 0.:
msg = 'xyz1=%s xyz2=%s\n%s' % (xyz1, xyz2, self)
raise ValueError(msg)
i_offset = i / ihat_norm
v, wa, wb, xform = rotate_v_wa_wb(
model, elem,
xyz1, xyz2, node1, node2,
i_offset, i, eid, ihat_norm, log)
if wb is None:
# one or more of v, wa, wb are bad
#
# xform is xform_offset...assuming None
ihat = None
yhat = None
zhat = None
return is_failed, (v, ihat, yhat, zhat, wa, wb)
ihat = xform[0, :]
yhat = xform[1, :]
zhat = xform[2, :]
is_failed = False
return is_failed, (v, ihat, yhat, zhat, wa, wb)
@property
def node_ids(self):
return [self.Ga(), self.Gb()]
[docs]
def get_edge_ids(self):
return [tuple(sorted(self.node_ids))]
[docs]
def Mid(self):
if self.pid_ref is None:
raise RuntimeError('Element eid=%i has not been '
'cross referenced.\n%s' % (self.eid, str(self)))
return self.pid_ref.Mid()
[docs]
def Area(self):
if self.pid_ref is None:
raise RuntimeError('Element eid=%i has not been '
'cross referenced.\n%s' % (self.eid, str(self)))
return self.pid_ref.Area()
[docs]
def Nsm(self):
if self.pid_ref is None:
raise RuntimeError('Element eid=%i has not been '
'cross referenced.\n%s' % (self.eid, str(self)))
return self.pid_ref.Nsm()
@property
def is_offt(self):
"""is the offt flag active?"""
if self.bit is not None:
assert isinstance(self.bit, float), 'bit=%r type=%s' % (self.bit, type(self.bit))
return False
#assert isinstance(self.offt, str), 'offt=%r' % self.offt
return True
@property
def is_bit(self):
"""is the bit flag active?"""
return not self.is_offt
[docs]
def get_offt_bit_defaults(self):
"""
offt doesn't exist in NX nastran
"""
if self.is_offt:
field8 = set_blank_if_default(self.offt, 'GGG')
else:
field8 = set_blank_if_default(self.bit, 0.0)
return field8
[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 CBEAM eid=%s' % (self.eid)
self.nodes_ref = [
model.Node(self.ga, msg=msg),
model.Node(self.gb, msg=msg),
]
self.pid_ref = model.Property(self.pid, msg=msg)
if self.g0:
self.g0_ref = model.nodes[self.g0]
ga_ref = self.nodes_ref[0]
self.g0_vector = self.g0_ref.get_position() - ga_ref.get_position()
else:
self.g0_vector = self.x
if model.is_nx:
assert self.offt == 'GGG', 'NX only support offt=GGG; offt=%r' % self.offt
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
msg = ', which is required by CBEAM eid=%s' % (self.eid)
self.nodes_ref = [
model.Node(self.ga, msg=msg),
model.Node(self.gb, msg=msg),
]
self.nodes_ref = [self.ga_ref, self.gb_ref]
self.pid_ref = model.safe_property(self.pid, self.eid, xref_errors, msg=msg)
if self.g0:
try:
self.g0_ref = model.nodes[self.g0]
ga_ref = self.nodes_ref[0]
self.g0_vector = self.g0_ref.get_position() - ga_ref.get_position()
except KeyError:
model.log.warning('Node=%s%s' % (self.g0, msg))
else:
self.g0_vector = self.x
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.pid = self.Pid()
self.ga = self.Ga()
self.gb = self.Gb()
self.g0 = self.G0()
self.nodes_ref = None
self.g0_ref = None
self.pid_ref = None
def _verify(self, xref):
eid = self.eid
unused_pid = self.Pid()
unused_edges = self.get_edge_ids()
if xref: # True
prop = self.pid_ref
assert prop.type in ['PBEAM', 'PBEAML', 'PBCOMP', 'PBMSECT'], prop
mid = self.Mid()
nsm = self.Nsm()
assert isinstance(mid, int), 'mid=%r' % mid
assert isinstance(nsm, float), 'nsm=%r' % nsm
assert self.pid_ref.type in ['PBEAM', 'PBEAML', 'PBCOMP', 'PBMSECT'], '%s%s' % (self, self.pid_ref)
A = self.Area()
mpl = self.MassPerLength()
L = self.Length()
mass = self.Mass()
assert isinstance(A, float), 'eid=%s A=%r' % (eid, A)
assert isinstance(L, float), 'eid=%s L=%r' % (eid, L)
assert isinstance(mpl, float), 'eid=%s mass_per_length=%r' % (eid, mpl)
assert isinstance(mass, float), 'eid=%s mass=%r' % (eid, mass)
assert L > 0.0, 'eid=%s L=%s' % (eid, L)
[docs]
def Ga(self) -> int:
"""gets Ga/G1"""
if self.nodes_ref is None:
return self.ga
return self.ga_ref.nid
[docs]
def Gb(self) -> int:
"""gets Gb/G2"""
if self.nodes_ref is None:
return self.gb
return self.gb_ref.nid
[docs]
def G0(self) -> int:
"""gets G0"""
if self.g0_ref is None:
return self.g0
return self.g0_ref.nid
[docs]
def get_x_g0_defaults(self):
"""
X and G0 compete for the same fields, so the method exists to
make it easier to write the card
Returns
-------
x_g0 : varies
g0 : list[int, None, None]
x : list[float, float, float]
Notes
-----
Used by CBAR and CBEAM
"""
if self.g0 is not None:
return (self.G0(), None, None)
else:
return list(self.x)
[docs]
def raw_fields(self):
(x1, x2, x3) = self.get_x_g0_defaults()
offt = self.get_offt_bit_defaults()
ga, gb = self.node_ids
list_fields = ['CBEAM', self.eid, self.Pid(), ga, gb, x1, x2, x3, offt,
self.pa, self.pb] + list(self.wa) + list(self.wb) + [self.sa, self.sb]
return list_fields
[docs]
def repr_fields(self):
w1a = set_blank_if_default(self.wa[0], 0.0)
w2a = set_blank_if_default(self.wa[1], 0.0)
w3a = set_blank_if_default(self.wa[2], 0.0)
w1b = set_blank_if_default(self.wb[0], 0.0)
w2b = set_blank_if_default(self.wb[1], 0.0)
w3b = set_blank_if_default(self.wb[2], 0.0)
pa = set_blank_if_default(self.pa, 0)
pb = set_blank_if_default(self.pb, 0)
sa = set_blank_if_default(self.sa, 0)
sb = set_blank_if_default(self.sb, 0)
(x1, x2, x3) = self.get_x_g0_defaults()
offt = self.get_offt_bit_defaults()
ga, gb = self.node_ids
list_fields = ['CBEAM', self.eid, self.Pid(), ga, gb, x1, x2, x3, offt,
pa, pb, w1a, w2a, w3a,
w1b, w2b, w3b, sa, sb]
return list_fields
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.repr_fields()
if size == 8:
if max(self.eid, max(self.node_ids)) > MAX_INT:
return self.comment + print_card_16(card)
return self.comment + print_card_8(card)
return self.comment + print_card_16(card)
[docs]
def write_card_16(self, is_double=False):
card = self.repr_fields()
return self.comment + print_card_16(card)
[docs]
def _init_offt_bit(card, unused_eid: int, offt_default):
"""
offt doesn't exist in NX nastran
"""
field8 = integer_double_string_or_blank(card, 8, 'field8', offt_default)
if isinstance(field8, float):
offt = None
bit = field8
elif field8 is None:
offt = 'GGG' # default
bit = None
elif isinstance(field8, integer_types):
bit = None
offt = field8
elif isinstance(field8, str):
bit = None
offt = field8
msg = 'invalid offt parameter of CBEAM...offt=%s' % offt
assert offt[0] in ['G', 'B', 'O', 'E'], msg
assert offt[1] in ['G', 'B', 'O', 'E'], msg
assert offt[2] in ['G', 'B', 'O', 'E'], msg
else:
msg = ('field8 on %s card is not a string(offt) or bit '
'(float)...field8=%s\n' % (card.field(0), field8))
raise SyntaxError("Card Instantiation: %s" % msg)
return offt, bit
[docs]
class BEAMOR(BaseCard):
"""
+--------+-----+---+---+---+-------+-----+-------+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+========+=====+===+===+===+=======+=====+=======+======+
| BEAMOR | PID | | | | G0/X1 | X2 | X3 | OFFT |
+--------+-----+---+---+---+-------+-----+-------+------+
| BEAMOR | 39 | | | | 0.6 | 2.9 | -5.87 | GOG |
+--------+-----+---+---+---+-------+-----+-------+------+
"""
type = 'BEAMOR'
def __init__(self, pid, is_g0, g0, x, offt='GGG', comment=''):
BaseCard.__init__(self)
if comment:
self.comment = comment
self.pid = pid
self.g0 = g0
self.x = x
self.offt = offt
[docs]
@classmethod
def _init_from_empty(cls):
pid = 1
is_g0 = True
g0 = 1
x = None
return BEAMOR(pid, is_g0, g0, x, offt='GGG', comment='')
[docs]
@classmethod
def add_card(cls, card, comment=''):
pid = integer_or_blank(card, 2, 'pid')
# x / g0
field5 = integer_double_or_blank(card, 5, 'g0_x1', 0.0)
if isinstance(field5, integer_types):
is_g0 = True
g0 = field5
x = [0., 0., 0.]
elif isinstance(field5, float):
is_g0 = False
g0 = None
x = np.array([field5,
double_or_blank(card, 6, 'x2', 0.0),
double_or_blank(card, 7, 'x3', 0.0)],
dtype='float64')
else:
raise NotImplementedError('BEAMOR field5 = %r' % field5)
offt = integer_string_or_blank(card, 8, 'offt', 'GGG')
assert len(card) <= 9, f'len(BEAMOR card) = {len(card):d}\ncard={card}'
return BEAMOR(pid, is_g0, g0, x, offt=offt, comment=comment)
[docs]
def raw_fields(self):
return ['BEAMOR', None, self.pid, None, None] + list(self.x) + [self.offt]
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.repr_fields()
if size == 8:
return self.comment + print_card_8(card)
return self.comment + print_card_16(card)