Source code for simdesign.rcmrf.bnsm.cp03.joint

"""This module provides the joint class implementations for the ``CP03`` model
in the BNSM layer.
"""
# Imports from installed packages
import openseespy.opensees as ops
from typing import Dict, List, Literal

# Imports from bnsm base library
from ..baselib.joint import StairsJointBase, FloorJointBase, JointDesign
from ..baselib.node import Node
from ..baselib.constants import (
    RIGID_SEC, LINEAR_TRANSF_X, LINEAR_TRANSF_Y, LINEAR_TRANSF_Z
)


[docs] class StairsJoint(StairsJointBase): """Stairs joint implementation for the ``CP03`` model. Represents a beam-column joint located at an intermediate (mid-storey) level associated with staircase framing. This class extends ``StairsJointBase`` by creating rigid offset nodes around the joint center and connecting them to the center joint node using rigid-like ``elasticBeamColumn`` elements. Offset directions: - left/right nodes: along global X (beam offsets) - bottom/top nodes: along global Z (column offsets) Attributes ---------- left_node : Node | None The left offset joint node along global X (created only if the corresponding left beam exists). right_node : Node | None The right offset joint node along global X (created only if the corresponding right beam exists). bottom_node : Node | None The bottom offset joint node along global Z (created only if the corresponding vottom beam exists). top_node : Node | None The top offset joint node along global Z (created only if the corresponding top beam exists). rigid_ele : list[int] Element tags of the rigid-like offset links. See Also -------- :class:`~StairsJointBase` Base stairs joint definition extended by this class. """ left_node: Node | None right_node: Node | None bottom_node: Node | None top_node: Node | None rigid_ele: List[int] def __init__(self, design: JointDesign, mass: float) -> None: """Initialize StairsJoint object. Parameters ---------- design : JointDesign Reference design information of joint. mass : float Total mass assigned to joint. """ # Store rigid-like element tags self.rigid_ele = [] # Save reference design information of joint self.design = design # Set reference node properties ref_point = self.design.elastic_node ref_tag = ref_point.tag ref_coords = ref_point.coordinates # Initialize center node masses = [mass, mass, mass, 0.0, 0.0, 0.0] self.center_node = Node(ref_tag, ref_coords, masses) # Initialize rigid offsets nodes if self.design.bottom_column: # bottom coords = ref_coords.copy() coords[2] -= self.h / 2 self.bottom_node = Node(ref_tag + 20000, coords) else: self.bottom_node = None if self.design.right_beam: # right coords = ref_coords.copy() coords[0] += self.bx / 2 self.right_node = Node(ref_tag + 30000, coords) else: self.right_node = None if self.design.left_beam: # left coords = ref_coords.copy() coords[0] -= self.bx / 2 self.left_node = Node(ref_tag + 50000, coords) else: self.left_node = None if self.design.top_column: # top coords = ref_coords.copy() coords[2] += self.h / 2 self.top_node = Node(ref_tag + 70000, coords) else: self.top_node = None
[docs] def add_to_ops(self) -> None: """Adds stairs joint model objects to the OpenSees domain (i.e, rigid joint offsets elements and nodes). """ # Central joint node self.center_node.add_to_ops() # Rigid-joint offset elements if self.left_node: self.left_node.add_to_ops() ele_nodes = [self.left_node.tag, self.center_node.tag] ele_tag = self.left_node.tag ops.element('elasticBeamColumn', ele_tag, *ele_nodes, RIGID_SEC, LINEAR_TRANSF_X) self.rigid_ele.append(ele_tag) if self.right_node: self.right_node.add_to_ops() ele_nodes = [self.center_node.tag, self.right_node.tag] ele_tag = self.right_node.tag ops.element('elasticBeamColumn', ele_tag, *ele_nodes, RIGID_SEC, LINEAR_TRANSF_X) self.rigid_ele.append(ele_tag) if self.bottom_node: self.bottom_node.add_to_ops() ele_nodes = [self.bottom_node.tag, self.center_node.tag] ele_tag = self.bottom_node.tag ops.element('elasticBeamColumn', ele_tag, *ele_nodes, RIGID_SEC, LINEAR_TRANSF_Z) self.rigid_ele.append(ele_tag) if self.top_node: self.top_node.add_to_ops() ele_nodes = [self.center_node.tag, self.top_node.tag] ele_tag = self.top_node.tag ops.element('elasticBeamColumn', ele_tag, *ele_nodes, RIGID_SEC, LINEAR_TRANSF_Z) self.rigid_ele.append(ele_tag)
[docs] def to_py(self) -> List[str]: """Gets the Python commands to define stairs joint model objects in the OpenSees domain (i.e, rigid joint offsets elements and nodes). Returns ------- list[str] List of Python commands for constructing the components of stairs joint in OpenSees. """ grids = ', '.join([f"{i}" for i in self.design.elastic_node.grid_ids]) content = [f'# Joint grid ids (x, y, z): ({grids})'] content.append("# Central joint node") content.append(self.center_node.to_py()) content.append("# Rigid-joint offset elements") if self.left_node: content.append(self.left_node.to_py()) ele_nodes = f"{self.left_node.tag}, {self.center_node.tag}" ele_tag = self.left_node.tag content.append( f"ops.element('elasticBeamColumn', {ele_tag}, {ele_nodes}, " f"{RIGID_SEC}, {LINEAR_TRANSF_X})" ) if self.right_node: content.append(self.right_node.to_py()) ele_nodes = f"{self.center_node.tag}, {self.right_node.tag}" ele_tag = self.right_node.tag content.append( f"ops.element('elasticBeamColumn', {ele_tag}, {ele_nodes}, " f"{RIGID_SEC}, {LINEAR_TRANSF_X})" ) if self.bottom_node: content.append(self.bottom_node.to_py()) ele_nodes = f"{self.bottom_node.tag}, {self.center_node.tag}" ele_tag = self.bottom_node.tag content.append( f"ops.element('elasticBeamColumn', {ele_tag}, {ele_nodes}, " f"{RIGID_SEC}, {LINEAR_TRANSF_Z})" ) if self.top_node: content.append(self.top_node.to_py()) ele_nodes = f"{self.center_node.tag}, {self.top_node.tag}" ele_tag = self.top_node.tag content.append( f"ops.element('elasticBeamColumn', {ele_tag}, {ele_nodes}, " f"{RIGID_SEC}, {LINEAR_TRANSF_Z})" ) return content
[docs] def to_tcl(self) -> List[str]: """Gets the Tcl commands to define stairs joint model objects in the OpenSees domain (i.e, rigid joint offsets elements and nodes). Returns ------- list[str] List of Tcl commands for constructing the components of stairs joint in OpenSees. """ grids = ', '.join([f"{i}" for i in self.design.elastic_node.grid_ids]) content = [f'# Joint grid ids (x, y, z): ({grids})'] content.append("# Central joint node") content.append(self.center_node.to_tcl()) content.append("# Rigid-joint offset elements") if self.left_node: content.append(self.left_node.to_tcl()) ele_nodes = f"{self.left_node.tag} {self.center_node.tag}" ele_tag = self.left_node.tag content.append( f"element elasticBeamColumn {ele_tag} {ele_nodes} " f"{RIGID_SEC} {LINEAR_TRANSF_X}" ) if self.right_node: content.append(self.right_node.to_tcl()) ele_nodes = f"{self.center_node.tag} {self.right_node.tag}" ele_tag = self.right_node.tag content.append( f"element elasticBeamColumn {ele_tag} {ele_nodes} " f"{RIGID_SEC} {LINEAR_TRANSF_X}" ) if self.bottom_node: content.append(self.bottom_node.to_tcl()) ele_nodes = f"{self.bottom_node.tag} {self.center_node.tag}" ele_tag = self.bottom_node.tag content.append( f"element elasticBeamColumn {ele_tag} {ele_nodes} " f"{RIGID_SEC} {LINEAR_TRANSF_Z}" ) if self.top_node: content.append(self.top_node.to_tcl()) ele_nodes = f"{self.center_node.tag} {self.top_node.tag}" ele_tag = self.top_node.tag content.append( f"element elasticBeamColumn {ele_tag} {ele_nodes} " f"{RIGID_SEC} {LINEAR_TRANSF_Z}" ) return content
[docs] class FloorJoint(FloorJointBase, StairsJoint): """Floor joint implementation for the ``CP03`` model. Represents a beam-column joint located at a floor level. This class combines: - rigid offset modelling (center node + rigid-like offset links), and - an optional joint-flexibility element (rigid/elastic/inelastic) between the joint center node and the floor diaphragm-constrained node. In addition to the X- and Z-direction offsets, this class also creates front/rear offset nodes along global Y (if the corresponding beams exist) and connects them to the joint center node using rigid-like ``elasticBeamColumn`` elements. Attributes ---------- rear_node : Node | None The rear offset joint node along global Y-axis (created only if the corresponding rear beam exists). front_node : Node | None The front offset joint node along global Y-axis (created only if the corresponding front beam exists). See Also -------- :class:`~FloorJointBase` Base floor joint class from which beam-column joint model is inherited. :class:`~StairsJoint` Stairs joint class extended by this class to define rigid offsets. """ rear_node: Node | None front_node: Node | None def __init__(self, design: JointDesign, mass: float, model: Literal["inelastic", "elastic", "rigid"], load_factors: Dict[Literal['G', 'Q'], float]) -> None: """Initialize FloorJoint object. Parameters ---------- design : JointDesign Reference design information of joint. mass : float Total mass assigned to joint. model : {"inelastic", "elastic", "rigid"} Joint flexibility model. load_factors : dict[{'G', 'Q'}, float] Load factors used to compute axial load on joint. """ # Save joint flexibility model option self.flexibility_model = model # Initialize the nodes in stairs joint super(FloorJointBase, self).__init__(design, mass) # Initialize the floor node to account for joint flexibility ref_point = self.design.elastic_node # Set the joint node constrained by the floor diaphragm if model == 'rigid': # There is no need to create additional joint node self.floor_node = self.center_node else: # Initialize the new node to account for joint flexibility self.floor_node = Node(ref_point.tag + 10000, ref_point.coordinates) # Initialize rigid offsets (in Y) nodes if self.design.front_beam: # front coords = ref_point.coordinates.copy() coords[1] += self.by / 2 self.front_node = Node(ref_point.tag + 40000, coords) else: self.front_node = None if self.design.rear_beam: # rear coords = ref_point.coordinates.copy() coords[1] -= self.by / 2 self.rear_node = Node(ref_point.tag + 60000, coords) else: self.rear_node = None # Axial force on the joint if self.design.bottom_column: # forces = ( # load_factors['G'] * # self.design.bottom_column.forces['G/seismic'] + # load_factors['Q'] * # self.design.bottom_column.forces['Q/seismic'] # ) # self.axial_load = -forces.N9 self.axial_force = ( load_factors['G'] * self.design.bottom_column.hinge_Ng + load_factors['Q'] * self.design.bottom_column.hinge_Nq ) else: raise ValueError( "Bottom column is missing, joint model won't work here.")
[docs] def add_to_ops(self) -> None: """Adds floor joint model objects to the OpenSees domain (i.e, rigid joint offsets elements, nodes and joint flexibility element). """ super(FloorJointBase, self).add_to_ops() # Rigid-joint offset elements along Y if self.rear_node: self.rear_node.add_to_ops() ele_nodes = [self.rear_node.tag, self.center_node.tag] ele_tag = self.rear_node.tag ops.element('elasticBeamColumn', ele_tag, *ele_nodes, RIGID_SEC, LINEAR_TRANSF_Y) self.rigid_ele.append(ele_tag) if self.front_node: self.front_node.add_to_ops() ele_nodes = [self.center_node.tag, self.front_node.tag] ele_tag = self.front_node.tag ops.element('elasticBeamColumn', ele_tag, *ele_nodes, RIGID_SEC, LINEAR_TRANSF_Y) self.rigid_ele.append(ele_tag) # Add joint flexibility element for no-rigid joint cases if self.flexibility_model != 'rigid': # Add floor joint node constrained by the floor diaphragm self.floor_node.add_to_ops() # Materials defining flexible rotation behaviour ops.uniaxialMaterial(*self._get_mat_inputs('X')) ops.uniaxialMaterial(*self._get_mat_inputs('Y')) # Create the new section with flexible rotation behaviour ops.section(*self._get_agg_sec_inputs()) # Create the joint flexibility element ops.element(*self._get_ele_inputs())
[docs] def to_py(self) -> List[str]: """Gets the Python commands to define floor joint model objects in the OpenSees domain (i.e, rigid joint offsets elements, nodes and joint flexibility element). Returns ------- list[str] List of Python commands for constructing the components of floor joint in OpenSees. """ content = super(FloorJointBase, self).to_py() # Rigid-joint offset elements along Y if self.rear_node: content.append(self.rear_node.to_py()) ele_nodes = f"{self.rear_node.tag}, {self.center_node.tag}" ele_tag = self.rear_node.tag content.append( f"ops.element('elasticBeamColumn', {ele_tag}, {ele_nodes}, " f"{RIGID_SEC}, {LINEAR_TRANSF_Y})" ) if self.front_node: content.append(self.front_node.to_py()) ele_nodes = f"{self.center_node.tag}, {self.front_node.tag}" ele_tag = self.front_node.tag content.append( f"ops.element('elasticBeamColumn', {ele_tag}, {ele_nodes}, " f"{RIGID_SEC}, {LINEAR_TRANSF_Y})" ) content.append(f"# Joint flexibility model: {self.flexibility_model}") # Add joint flexibility element for no-rigid joint cases if self.flexibility_model != 'rigid': # Add floor joint node constrained by the floor diaphragm note = ' # Constrained floor node' content.append(self.floor_node.to_py() + note) # Materials defining flexible rotation behaviour mz_mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_mat_inputs('X') ) my_mat_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_mat_inputs('Y') ) content.append(f"ops.uniaxialMaterial({mz_mat_inputs})") content.append(f"ops.uniaxialMaterial({my_mat_inputs})") # Create the new section with flexible rotation behaviour sec_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_agg_sec_inputs() ) content.append(f"ops.section({sec_inputs})") # Create the joint flexibility element ele_inputs = ', '.join( repr(item) if isinstance(item, str) else str(item) for item in self._get_ele_inputs() ) content.append(f"ops.element({ele_inputs})") return content
[docs] def to_tcl(self) -> List[str]: """Gets the Tcl commands to define floor joint model objects in the OpenSees domain (i.e, rigid joint offsets elements, nodes and joint flexibility element). Returns ------- list[str] List of Tcl commands for constructing the components of floor joint in OpenSees. """ content = super(FloorJointBase, self).to_tcl() # Rigid-joint offset elements along Y if self.rear_node: content.append(self.rear_node.to_tcl()) ele_nodes = f"{self.rear_node.tag} {self.center_node.tag}" ele_tag = self.rear_node.tag content.append( f"element elasticBeamColumn {ele_tag} {ele_nodes} " f"{RIGID_SEC} {LINEAR_TRANSF_Y}" ) if self.front_node: content.append(self.front_node.to_tcl()) ele_nodes = f"{self.center_node.tag} {self.front_node.tag}" ele_tag = self.front_node.tag content.append( f"element elasticBeamColumn {ele_tag} {ele_nodes} " f"{RIGID_SEC} {LINEAR_TRANSF_Y}" ) content.append(f"# Joint flexibility model: {self.flexibility_model}") # Add joint flexibility element for no-rigid joint cases if self.flexibility_model != 'rigid': # Add floor joint node constrained by the floor diaphragm note = ' # Constrained floor node' content.append(self.floor_node.to_tcl() + note) # Materials defining flexible rotation behaviour mz_mat_inputs = ' '.join( f'{item}' for item in self._get_mat_inputs('X') ) my_mat_inputs = ' '.join( f'{item}' for item in self._get_mat_inputs('Y') ) content.append(f"uniaxialMaterial {mz_mat_inputs}") content.append(f"uniaxialMaterial {my_mat_inputs}") # Create the new section with flexible rotation behaviour sec_inputs = ' '.join( f'{item}' for item in self._get_agg_sec_inputs() ) content.append(f"section {sec_inputs}") # Create the joint flexibility element ele_inputs = ' '.join( f'{item}' for item in self._get_ele_inputs() ) content.append(f"element {ele_inputs} ") return content