"""
defines:
- SolidSection
- ShellSection
- Material
- Assembly
- Part
"""
from __future__ import annotations
from typing import Union, TextIO, Optional, Any, TYPE_CHECKING
import numpy as np
from pyNastran.converters.abaqus.elements import Elements
if TYPE_CHECKING: # pragma: no cover
from cpylog import SimpleLogger
[docs]
class Frequency:
def __init__(self, solver: str, nmodes: int):
self.solver = solver
self.nmodes = nmodes
def __repr__(self) -> str:
msg = f'Frequency(solver={self.solver} nmodes={self.nmodes})'
return msg
[docs]
class Boundary:
def __init__(self, nid_dof_to_value: dict[tuple[int, int], float]):
"""
*BOUNDARY
nid, dof1, dof2, displacement
1,1,,0
1,2,,0
1,3,,0
20,1,,0
"""
self.type = 'displacement'
self.nid_dof_to_value = nid_dof_to_value
[docs]
def write(self):
if len(self.nid_dof_to_value) == 0:
return ''
msg = '*BOUNDARY\n'
for (nid, dof), value in self.nid_dof_to_value.items():
msg += f'{nid}, {dof}, {value}\n'
return msg
[docs]
@classmethod
def from_data(cls, slines: list[list[str]]):
"""
1) node or node set, first degree of freedom, last degree of freedom
2) node or node set, first degree of freedom, last degree of freedom, value
"""
nid_dof_to_value = {}
for sline in slines:
sline = [val.strip() for val in sline]
nsline = len(sline)
nid_name = sline[0]
try:
nid = int(nid_name)
except ValueError:
if nid_name.isnumeric():
raise RuntimeError(f'Boundary field 1 must be an integer or string without a space; nid_name={nid_name!r}')
if ' ' in nid_name:
raise RuntimeError(f'Boundary field 1 must be an integer or string without a space; nid_name={nid_name!r}')
nid = nid_name
dof1 = int(sline[1])
if nsline == 2:
nid_dof_to_value[(nid, dof1)] = 0.
continue
dofs = [dof1]
if sline[2]:
dof2 = int(sline[2])
if dof1 != dof2:
assert dof1 <= dof2, (dof1, dof2)
dofs = range(dof1, dof2+1)
value = 0.0
if nsline > 3 and sline[3]:
value = float(sline[3])
for dof in dofs:
nid_dof_to_value[(nid, dof)] = value
return Boundary(nid_dof_to_value)
def __repr__(self) -> str:
msg = f'Boundary(nid_dof_to_value={str(self.nid_dof_to_value)})'
return msg
[docs]
class Mass:
def __init__(self, elset: str,
value: float):
self.elset = elset
self.value = value
def __repr__(self):
"""prints a summary for the solid section"""
msg = 'BeamSection(\n'
#msg += ' param_map = %r,\n' % self.param_map
msg += f' elset = {self.elset},\n'
msg += f' value = {self.value},\n'
msg += ')\n'
return msg
[docs]
class BeamSection:
section_name_to_npoints = {
'RECT': 2, # consistent with PBARL
'PIPE': 2, # r (outside radius), t (wall thickness)
}
def __init__(self, elset: str,
material_name: str,
section: str,
dimensions: np.ndarray,
x_vector: np.ndarray):
self.elset = elset
self.material_name = material_name
self.section = section
self.dimensions = dimensions
self.x_vector = x_vector
npoints = self.section_name_to_npoints[section]
assert len(dimensions) == npoints, dimensions
def __repr__(self):
"""prints a summary for the solid section"""
msg = 'BeamSection(\n'
#msg += ' param_map = %r,\n' % self.param_map
msg += f' elset = {self.elset},\n'
msg += f' material_name = {self.material_name},\n'
msg += f' section = {self.section},\n'
msg += f' dimensions = {self.dimensions},\n'
msg += f' x_vector = {self.x_vector},\n'
msg += ')\n'
return msg
[docs]
class ShellSection:
"""
A ShellSection defines thickness and a material for a PSHELL/PCOMP
*SHELL SECTION, ELSET=PLATE, MATERIAL=A, ORIENTATION=GLOBAL, OFFSET=0.0
0.005
*SHELL SECTION, ELSET=CARBON_FIBER, ORIENTATION=GLOBAL, OFFSET=0.0
0.005,,CF
0.005,,CF
0.005,,CF
"""
def __init__(self, log: SimpleLogger,
material_name: str, elset: str,
thickness: list[float],
orientation: int=-1,
offset: float=0.0):
#self.data_lines = data_lines
#self.material = param_map['material']
self.material_name = material_name
self.elset = elset
self.thickness = thickness
self.orientation = orientation
self.offset = offset
self.log = log
[docs]
@classmethod
def add_from_data_lines(cls, param_map: dict[str, str],
data_lines: list[str],
log: SimpleLogger):
elset = param_map['elset']
orientation = param_map.get('orientation', -1)
offset = param_map.get('offset', 0.0)
if param_map['is_composite']:
material_name = []
thickness = []
orientation_name = []
for line in data_lines:
#thickness (required)
#not used
#name of the material to be used for this layer (required)
#name of the orientation to be used for this layer (optional)
sline = line.split(',')
if len(sline) == 3:
thickness_stri, junk, material_namei = sline
orientation_namei = None
else:
thickness_stri, junk, material_namei, orientation_namei = sline
## TODO: how does orientation work (from the flags)
## with the orientation name in this table?
raise RuntimeError(sline)
material_namei = material_namei.strip().lower()
thicknessi = float(thickness_stri)
thickness.append(thicknessi)
material_name.append(material_namei)
orientation_name.append(orientation_namei)
else:
orientation_name = None
material_name = param_map['material']
log.debug(f'material_name = {material_name}')
#if len(data_lines) == 0:
#pass
thicknessi = 0.0
if len(data_lines) == 1:
assert len(data_lines) == 1, data_lines
line0 = data_lines[0].split()
assert len(line0) == 1, data_lines
thicknessi = float(line0[0])
else: # pragma: no cover
raise RuntimeError(data_lines)
thickness = [thicknessi]
for line in data_lines:
log.debug(f'shell - {line!r}')
return ShellSection(log,
material_name, elset, thickness,
orientation=orientation,
offset=offset)
def __repr__(self):
"""prints a summary for the solid section"""
msg = 'ShellSection(\n'
#msg += ' param_map = %r,\n' % self.param_map
msg += f' material_name = {self.material_name},\n'
msg += f' thickness = {self.thickness},\n'
msg += f' orientation = {self.orientation},\n'
msg += f' offset = {self.offset},\n'
msg += ')\n'
return msg
[docs]
class SolidSection:
"""a SolidSection defines depth and a material"""
def __init__(self, material_name: str,
elset: str,
thickness: float,
log: SimpleLogger):
self.material_name = material_name
self.elset = elset
self.thickness = thickness
self.log = log
[docs]
@classmethod
def add_from_data_lines(cls, param_map: dict[str, str],
data_lines: list[str],
log: SimpleLogger):
material_name = param_map['material']
#print('param_map =', param_map)
elset = param_map.get('elset', None)
log.debug(f'material_name = {material_name}')
param_map = param_map
data_lines = data_lines
thickness = 0.
#print('param_map =', param_map)
if len(data_lines) == 0:
pass
elif len(data_lines) == 1:
assert len(data_lines) == 1, data_lines
line0 = data_lines[0].split()
assert len(line0) == 1, data_lines
try:
thickness = float(line0[0])
except ValueError:
pass
for line in data_lines:
log.info('solid - %r' % line)
return SolidSection(material_name, elset, thickness, log)
def __repr__(self):
"""prints a summary for the solid section"""
msg = 'SolidSection(\n'
msg += f' material_name = {self.material_name},\n'
msg += f' elset = {self.elset},\n'
#msg += ' param_map = %r,\n' % self.param_map
msg += ' thickness = %s,\n' % self.thickness
msg += ')\n'
return msg
[docs]
class Material:
"""a Material object is a series of nodes & elements (of various types)"""
def __init__(self, name: str,
sections: dict[str, float],
density: float=0.0,
ndepvars: Optional[int]=None,
ndelete: Optional[int]=None):
assert isinstance(density, float), density
self.name = name
self.density = density
#self.is_elastic = is_elastic
#self.depvar = None
self.ndelete = ndelete
self.ndepvars = ndepvars
self.user_material = None
#print(sections)
#if 'density' in sections:
#self.density = sections['density']
#if 'depvar' in sections:
#self.depvar = sections['depvar']
#if 'user_material' in sections:
#self.user_material = sections['user_material']
self.sections = sections
def __repr__(self) -> str:
"""prints a summary for the material"""
msg = 'Material(\n'
msg += ' name=%r,\n' % self.name
for key, value in self.sections.items():
msg += ' %r : %r,\n' % (key, value)
msg += ')\n'
return msg
[docs]
def write(self, abq_file) -> None:
"""
*Material, name=Glassy828DEA
*Density
1180.,
*Elastic
2.14078e+09, 0.42718
*Material, name=MAT1_828DEA_Dam
*Density
1180.,
*Depvar, delete=4
20,
*User Material, constants=16
** K CTELIN C10 C01 DAM_FORM FUNC_FORM EVOLF EVMF
3.2e+09, 5.667e-05, 3.75e+08, 0., 2., 1., 50000., 0.05
**EVM0ISO EVM0VOL EVM0VM DAM_METHOD ALPHA A B C
0., 0.5, 0.5, 1., 0., 0., 0.5, 0.6
*Material, name=Steel
*Density
7800.,
*Elastic
2e+11, 0.3
"""
name = write_name(self.name)
abq_file.write(f'*Material, name={name}\n')
if 'elastic' in self.sections:
e, g = self.sections['elastic']
abq_file.write(f'*Elastic\n')
abq_file.write(f'{e},{g}\n')
if 'engineering constants' in self.sections:
#*Shell section, Elset=Internal_Selection-1_Shell_section-1, COMPOSITE
#0.25,,Steel
#0.25,,Steel
#*Material, Name=CF
#*ELASTIC,TYPE=ENGINEERING CONSTANTS
#135000.,10000.,10000.,0.3,0.3,,5000.,5000.,
#5000,273
eng_consts = self.sections['engineering constants']
eng_const_strs = ['%g' % val for val in eng_consts]
args1 = eng_const_strs[:8]
args2 = eng_const_strs[8:]
assert len(args1) == 8, args1
assert len(args2) == 2, args2
abq_file.write(f'*Elastic,type=Engineering Constants\n')
abq_file.write.write(','.join(args1))
abq_file.write.write(','.join(args2))
if self.density > 0.:
abq_file.write(f'*Density\n {self.density},\n')
if self.ndepvars:
ndelete = '' if self.ndelete is None else f', delete={self.ndelete}'
abq_file.write(f'*Depvar{ndelete}\n {self.ndepvars},\n')
if self.user_material:
nconstants = ''
abq_file.write(f'*User Material{nconstants}\n {self.user_material},\n')
#abq_file.write('** skipping Material %s\n' % self.name)
[docs]
class Assembly:
def __init__(self, element_types, node_sets, element_sets):
self.element_types = element_types
self.node_sets = node_sets
self.element_sets = element_sets
[docs]
def write(self, abq_file):
abq_file.write('** skipping Assembly\n')
def __repr__(self):
"""summary for the Assembly"""
etypes = list(self.element_types.keys())
nsets = list(self.node_sets.keys())
esets = list(self.element_sets.keys())
msg = (
'Assembly:\n'
f' element_types = {etypes}\n'
f' node_sets = {nsets}\n'
f' element_sets = {esets}\n'
)
return msg
[docs]
class Part:
"""a Part object is a series of nodes & elements (of various types)"""
def __init__(self, name: str,
nids: np.ndarray,
nodes: np.ndarray,
element_types: dict[str, np.ndarray],
node_sets: dict[str, np.ndarray],
element_sets: dict[str, tuple[np.ndarray, str]],
beam_sections: list[BeamSection],
solid_sections: list[SolidSection],
shell_sections: list[ShellSection],
log: SimpleLogger):
"""
creates a Part object
Parameters
----------
name : str
the name
element_types : dict[element_type] : (node_ids, set_name)
element_type : str
the element type
bars:
r2d2 : (nelements, 2) int ndarray
b31 : (nelements, 2) int ndarray
b31h : (nelements, 2) int ndarray
shells:
cpe3 : (nelements, 3) int ndarray
cpe4 : (nelements, 4) int ndarray
cpe4r : (nelements, 4) int ndarray
cps3 : (nelements, 3) int ndarray
cps4 : (nelements, 4) int ndarray
cps4r : (nelements, 4) int ndarray
coh2d4 : (nelements, 4) int ndarray
cohax4 : (nelements, 4) int ndarray
cax3 : (nelements, 3) int ndarray
cax4r : (nelements, 4) int ndarray
solids:
c3d10h : (nelements, 10) int ndarray
"""
self.name = name
self.log = log
self.node_sets = node_sets
self.element_sets = element_sets
self.elements = Elements(element_types, self.log)
if beam_sections is None:
beam_sections = []
if solid_sections is None:
solid_sections = []
if shell_sections is None:
shell_sections = []
self.beam_sections = beam_sections
self.solid_sections = solid_sections
self.shell_sections = shell_sections
for set_name, node_set in self.node_sets.items():
assert isinstance(node_set, np.ndarray), set_name
for set_name, element_set in self.element_sets.items():
assert isinstance(element_set, np.ndarray), set_name
self.nids, self.nodes = cast_nodes(nids, nodes, self.log, require=True)
[docs]
def check_materials(self, materials):
"""validates the materials"""
for section in self.solid_sections:
key = section.material_name
if key in materials:
self.log.debug('material=%r for part=%r exists' % (key, self.name))
else:
self.log.warning('key=%r is an invalid material' % key)
def __repr__(self):
"""prints a summary for the part"""
nnodes = self.nodes.shape[0]
repr(self.elements)
neids = self.elements.nelements
msg = (
f'Part(name={self.name}, nnodes={nnodes:d}, neids={neids:d})\n'
)
nsets = list(self.node_sets.keys())
esets = list(self.element_sets.keys())
msg += f' Node Sets: {nsets}\n'
msg += f' Element Sets: {esets}\n'
for section in self.solid_sections:
msg += str(section) + '\n'
return msg
[docs]
def write(self, abq_file, is_2d=False):
"""writes a Part"""
#name, nids, nodes, element_types, node_sets, element_sets,
# solid_sections, log
abq_file.write('*Part,name=%s\n' % write_name(self.name))
abq_file.write('*Node\n')
if is_2d:
for nid, node in zip(self.nids, self.nodes):
abq_file.write('%i,\t%s,\t%s,\t%s\n' % (nid, node[0], node[1], node[2]))
else:
for nid, node in zip(self.nids, self.nodes):
abq_file.write('%i,\t%s,\t%s\n' % (nid, node[0], node[1]))
#for mat in self.materials:
for shell_section in self.shell_sections:
#print(shell_section)
shell_section.write(abq_file)
#for solid_section in self.solid_sections:
#solid_section.write(abq_file)
for set_name, values in sorted(self.node_sets.items()):
write_node_set_to_file(abq_file, set_name, values)
self.elements.write(abq_file)
for set_name, values in sorted(self.element_sets.items()):
write_element_set_to_file(abq_file, set_name, values)
abq_file.write('*end part\n')
[docs]
class Step:
def __init__(self, name: str,
boundaries: list[Any],
node_output: list[str],
element_output: list[str],
cloads: dict[str, Any],
dloads: dict[str, Any],
surfaces: list[Any],
frequencies: list[Frequency],
is_nlgeom: bool=False):
"""
*Step, name=Stretch, nlgeom=YES
*Static
0.1, 1.0, 0.1, 0.1
*Boundary, op=MOD
Block-1.Top, 1, 1, 0.0
Block-1.Top, 2, 2, 1.0
Block-1.Top, 3, 3, 0.0
NewBlock-1.Top, 1, 1, 0.0
NewBlock-1.Top, 2, 2, 1.0
NewBlock-1.Top, 3, 3, 0.0
*Output, field, variable=ALL
*Output, history, variable=PRESELECT
*End Step
"""
self.name = name
self.is_nlgeom = is_nlgeom
self.boundaries: list[Boundary] = boundaries
self.node_output = node_output
self.element_output = element_output
self.cloads: list[tuple[Union[int, str], int, float]] = cloads
self.dloads = dloads
self.frequencies = frequencies
assert isinstance(cloads, list), cloads
assert isinstance(dloads, list), dloads
def __repr__(self) -> str:
msg = (
'Step:\n'
f' name={self.name!r}\n'
f' is_nlgeom={self.is_nlgeom}\n'
f' boundaries={self.boundaries}\n'
f' node_output={self.node_output}\n'
f' element_output={self.element_output}\n'
f' cloads={self.cloads}\n'
f' frequencies={self.frequencies}\n'
)
return msg
[docs]
def write(self, abq_file: TextIO) -> None:
"""writes a Step"""
name = write_name(self.name)
nlgeom = ', nlgeom=YES' if self.is_nlgeom else ''
abq_file.write(f'*Step, name={name}{nlgeom}\n')
abq_file.write('*Static\n')
abq_file.write('0.1, 1.0, 0.1, 0.1\n')
for boundary in self.boundaries:
abq_file.write(boundary.write())
for cload in self.cloads:
abq_file.write('*CLOAD\n')
for (nid, dof, mag) in cload:
#[36, 1, 100.0]
abq_file.write(f'{nid}, {dof}, {mag}\n')
#for name, cload in self.cloads.items():
#abq_file.write('*CLOAD\n')
#abq_file.write(f'**name={name}\n')
#for (nid, dof, mag) in cload:
##[36, 1, 100.0]
#abq_file.write(f'{nid}, {dof}, {mag}\n')
for output in self.node_output + self.element_output:
abq_file.write(output + '\n')
abq_file.write(f'*End Step\n')
[docs]
def cast_nodes(nids_list: list[Any],
nodes_list: list[Any],
log: SimpleLogger,
require: bool=True) -> tuple[np.ndarray, np.ndarray]:
if len(nids_list) == 0 and require is False:
assert len(nodes_list) == 0, len(nodes_list)
return None, None
try:
nids = np.array(nids_list, dtype='int32')
except ValueError:
msg = f'nids={nids} are not integers'
raise ValueError(msg)
nnodes = len(nids)
node0 = nodes_list[0]
node_shape = len(node0)
if node_shape == 3:
nodes = np.array(nodes_list, dtype='float32')
log.info(f'3d model found; nodes.shape={nodes.shape}')
elif node_shape == 2:
# abaqus can have only x/y coordinates, so we fake the z coordinate
nodes = np.zeros((nnodes, 3), dtype='float32')
nodes2 = np.array(nodes_list, dtype='float32')
#print(nodes2.shape, self.nodes.shape)
nodes[:, :2] = nodes2
log.info(f'2d model found; nodes.shape={nodes.shape}')
else:
raise NotImplementedError(node0)
assert nodes.shape[0] == nnodes, f'nodes.shape={nodes.shape} nnodes={nnodes}'
return nids, nodes
[docs]
def write_name(name):
"""Abaqus has odd rules for writing words without spaces vs. with spaces"""
return '%r' % name if ' ' in name else '%s' % name
[docs]
def write_element_set_to_file(abq_file, set_name, values_array):
"""writes an element set"""
abq_file.write('*Elset, elset=%s\n' % write_name(set_name))
write_set_to_file(abq_file, values_array)
[docs]
def write_node_set_to_file(abq_file, set_name, values_array):
"""writes a node set"""
abq_file.write('*Nset, nset=%s\n' % write_name(set_name))
write_set_to_file(abq_file, values_array)
[docs]
def write_set_to_file(abq_file, values_array):
"""writes 16 integer values per line to a set card"""
assert isinstance(values_array, np.ndarray), type(values_array)
nvalues = len(values_array)
nrows = nvalues // 16
nleftover = nvalues % 16
if nrows:
values_array_square = values_array[:nrows*16].reshape(nrows, 16)
fmt = '%i,\t' * 16 + '\n'
fmt2 = '%i,\t' * 15 + '%i\n'
for row in values_array_square[:-1, :]:
abq_file.write(fmt % tuple(row))
abq_file.write(fmt2 % tuple(values_array_square[-1, :]))
if nleftover:
fmt = '%i,\t' * (nleftover - 1) + '%i\n'
leftover = values_array[nrows*16:]
abq_file.write(fmt % tuple(leftover))
[docs]
class Surface:
def __init__(self, name: str, surface_type: str,
set_names: list[str], faces: list[str]):
self.name = name
self.surface_type = surface_type
self.set_names = set_names
self.faces = faces
str(self)
def __repr__(self) -> str:
msg = (f'Surface(name={self.name!r}, surface_type={self.surface_type!r}, '
f'set_names={self.set_names}, faces={self.faces})')
return msg
[docs]
class Tie:
def __init__(self, name: str, master: str, slave: str,
position_tolerance: float):
self.name = name
self.master = master
self.slave = slave
self.position_tolerance = position_tolerance
str(self)
def __repr__(self) -> str:
msg = (
f'Tie(name={self.name!r}, '
f'master={self.master!r}, slave={self.slave!r}, '
'position_tolerance={self.position_tolerance})')
return msg
[docs]
class Orientation:
def __init__(self, name: str, system: str,
origin: np.ndarray,
x_axis=None, xy_plane=None,
axis=None, alpha=None):
self.name = name.lower()
self.system = system
self.origin = origin
self.x_axis = x_axis
self.xy_plane = xy_plane
self.axis = axis
self.alpha = alpha