"""
Represents netral geometry objects - these represent objects in space and are
independant of any type of design
"""
from dataclasses import dataclass, field
from .support import Support, SupportTypes2D
from .. units import ConverterLength
import numpy as np
__all__ = ['Node', 'getLengthNodes',
'Line', 'getLineFromNodes', 'getLineFromLength',
'Member', 'initSimplySupportedMember']
[docs]@dataclass()
class Node:
"""
Represents a node in 2D or 3D space.
Nodes in 3D are currently not supported.
Nodes have degrees of freedom [dx, dy, rz], or [dx, dy, dz, rx, ry, rz]
where di is a translation, and ri is a rotation.
Parameters
----------
p1 : list or numpy ndarray
A list or array of coordinate values in the form [x,y] or [x,y,z].
units : str
The units the node is stored in.
label : str
A label for the node.
support : Support
A support object, either one that is custom defined or defined using
SupportTypes2D.
"""
p1:np.ndarray | list
units:str = 'm'
label:str = None
support:Support = field(default_factory=lambda: SupportTypes2D.FREE.value)
def __post_init__(self):
if not isinstance(self.p1, np.ndarray):
self.p1 = np.array(self.p1)
[docs] def getx(self):
"""
Returns the x component of a nodes position.
"""
return self.p1[0]
[docs] def gety(self):
"""
Returns the y component of a nodes position.
"""
return self.p1[1]
[docs] def getz(self):
"""
Returns the z component of a nodes position, if it exists.
"""
if len(self.p1) <=2:
raise Exception('Node is 2D, no z value exists.')
else:
return self.p1[2]
[docs] def setSupportType(self, newType:Support):
"""
Sets a new support condition for the node.
"""
self.support = newType
[docs] def getDimension(self):
"""
Returns the dimension of a node, i.e. 2D or 3D
"""
return len(self.p1)
def _checkUnitsMatch(obj1, obj2):
if obj1.units == obj2.units:
return True
else:
raise Exception('Units must match - obj1 has units f{obj1.units}'\
'obj2 has units f{obj2.units}')
def getLengthNodes(n1:Node, n2:Node):
"""
Gets the length between two input nodes.
Assumes that both points have the same units and dimensionality.
Parameters
----------
n1 : Node
The first node.
n2 : Node
The second node.
Returns
-------
float
The length between two nodes.
"""
_checkUnitsMatch(n1, n2)
return np.sum((n1.p1 - n2.p1)**2)**0.5
class Curve:
"""
An arbitary curve connecting two points.
Currently only lines are used.
"""
pass
[docs]@dataclass()
class Line(Curve):
"""
A represents straight line in space between two nodes.
Parameters
----------
n1 : Node
The first Node.
n2 : Node
The Second Node.
units : str
The length units the line uses. Will match the node units
label : str
A label for the line.
"""
n1:Node
n2:Node
units:str = 'm'
label:str = None
def __post_init__(self):
self.L = getLengthNodes(self.n1, self.n2)
[docs]def getLineFromNodes(n1:Node, n2:Node, label = None) -> Line:
"""
Returns a new line that connects two input nodes.
Parameters
----------
n1 : Node
The first Node.
n2 : Node
The Second Node.
label : str
A label for the line.
Returns
-------
Line
The output line.
"""
_checkUnitsMatch(n1, n2)
return Line(n1, n2, n1.units, label)
[docs]def getLineFromLength(L:float, units = 'm') -> Line:
"""
Makes a new line of length L that starts at the origin. Two nodes will be
defined, one at the origin, and one at poition L in the x axis.
Parameters
----------
L : float
The length of the line to be defined.
units : TYPE, optional
The units to use for the line. The default is 'm'.
Returns
-------
Line
The output line of length "L".
"""
n1 = Node(np.array([0.,0.,0.]),units)
n2 = Node(np.array([L, 0.,0.]),units)
line = Line(n1, n2)
line.L = L
return line
# TODO: have a long hard thought about if this needs to be a dataclass
[docs]@dataclass()
class Member:
"""
Members represent a multi-portion curve, and fully define where a
structural element goes in space.
Members are made of curve segments, which are split by supports.
Supports are assigned to nodes.
They also contain data about the analysis, such as bending moment diagrams.
Parameters
----------
nodes : list[Node]
The nodes of the member.
curves : list[Line]
The curves connecting each node.
units : str, optional
The units used in the member. Should match the Node and Line units.
The default is 'm'.
label : str
A label for the Member.
loadData : str
A dictionary containing loading information about the Member.
CURRENTLY UNUSED AND MAY BE REPLACED
analysisData : str
A dictionary containing output information about analysis of the
Member, e.g. the bending moment diagram, the shear force diagram, etc.
"""
nodes:list[Node] = None
curves:list[Line] = None
lUnit:str = 'm'
label:str = None
loadData:dict = None
analysisData:dict = None
def __post_init__(self):
self._initUnits(self.lUnit)
self.L = sum([curve.L for curve in self.curves])
Nspan = len(self.curves)
self.Nspan = Nspan
if Nspan != 1:
self.isMultiSpan = True
else:
self.isMultiSpan = False
self._classifySpans()
def _initUnits(self, lUnit:str='m'):
"""
Inititiates the unit of the section.
"""
self.lUnit = lUnit
self.lConverter = ConverterLength()
[docs] def lConvert(self, outputUnit:str):
"""
Get the conversion factor from the current unit to the output unit
for length units
"""
return self.lConverter.getConversionFactor(self.lUnit, outputUnit)
def _classifySpans(self):
isCantilever:list[bool] = [False]*self.Nspan
nStart = self.curves[0].n1
nEnd = self.curves[-1].n2
if (nStart.getDimension() ==2) and (nStart.support.isFree()):
isCantilever[0] = True
if (nEnd.getDimension() ==2) and (nEnd.support.isFree()):
isCantilever[-1] = True
self.isCantilever = isCantilever
def __repr__(self):
return f'<limitstates {self.Nspan} span member>'
[docs] def setNodeSupprt(self, ind:int, support: Support):
"""
Updates the support conditon for a single node.
The spans are clasiffied after results are set.
Parameters
----------
ind : int
The node index to update.
support : Support
The new support type for that node.
Returns
-------
None.
"""
self.nodes[ind].support = support
self._classifySpans()
[docs] def setNodeSupprts(self, inds:list[int], supports: list[Support]):
"""
Updates the support conditon for a multiple nodes.
This is more efficeint than calling "setNodeSupprt" multiple times,
as the the spans are classfied only once at the end.
Parameters
----------
ind : list[int]
The node index to update.
support : list[Support]
The new support type for that node.
Returns
-------
None.
"""
for support, ind in zip(supports, inds):
self.nodes[ind].support = support
self._classifySpans()
[docs]def initSimplySupportedMember(L:float, lUnit:str) -> Member:
"""
A function that can intialize a simply supported member of length L between
two points.
Parameters
----------
L : float
The member length.
lUnit : str
The units to be used by the member and line.
Returns
-------
Member
The output member.
"""
line = getLineFromLength(L, lUnit)
line.n1.setSupportType(SupportTypes2D.PINNED.value)
line.n2.setSupportType(SupportTypes2D.ROLLER.value)
nodes = [line.n1, line.n2]
return Member(nodes, [line], lUnit)
# =============================================================================
# Experimental classes,
# =============================================================================
@dataclass()
class Surface:
"""
!!!EXPERIMENTAL!!!
This class is experimental and may not be supported in the full release
Surfaces are flat elements that are defined between a set of nodes.
They do not have width/length.
"""
nodes:list[Node] = None
lUnit:str = 'm'
label:str = None
loadData:dict = None
analysisData:dict = None
lConverter:ConverterLength = None
def __post_init__(self):
self._initUnits(self.lUnit)
def _initUnits(self, lUnit:str='m'):
"""
Inititiates the unit of the section.
"""
self.lUnit = lUnit
self.lConverter = ConverterLength()
def lConvert(self, outputUnit:str):
"""
Get the conversion factor from the current unit to the output unit
for length units
"""
return self.lConverter.getConversionFactor(self.lUnit, outputUnit)
#TODO: add this!
def convertLunit(self):
pass
class SurfaceRectangular(Surface):
"""
!!!EXPERIMENTAL!!!
This class is experimental and may not be supported in the full release
A panel is an element that is a rectangular plane, with width and length.
"""
def __init__(self, nodes:list[Node]):
super().__init__()