Source code for viiapackage.connections.auto_hinges

### ===================================================================================================================
###   Auto hinge creation
### ===================================================================================================================
# Copyright ©VIIA 2024

### ===================================================================================================================
###   1. Import modules
### ===================================================================================================================

# General imports
from __future__ import annotations
from itertools import combinations
from typing import TYPE_CHECKING, Optional, List, Union

# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.shape_geometries import Node
from rhdhv_fem.shapes import Lines, Surfaces, Beam, Column
from rhdhv_fem.connections import Unite, NoConnection, BoundarySpring
from rhdhv_fem.fem_tools import fem_find_object

# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
    from viiapackage.viiaStatus import ViiaProject
from viiapackage.connections.helper_functions import _viia_check_input, viia_get_existing_connections, _connect_node


### ===================================================================================================================
###   2. Function to auto generate all hinges in the model
### ===================================================================================================================

[docs]def viia_auto_hinges( project: ViiaProject, override_hinges: Optional[List[List[str]]] = None, disable_nodes: Optional[List[Union[Node, List[float, int]]]] = None, disable_hinges: Optional[List[List[str]]] = None, collection: Optional[List[Union[Beam, Column]]] = None): """ This function generates hinges for all columns and beams in project.collections. It checks if the members are continuous, if true it will create a fixed connection. This function only works on columns and beams. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - override_hinges (list of list of str): List of exceptions for the automatic algorithm. Default value is None, no overrides applied. e.g. [['source_shape_name', 'target_shape_name', 'hingetype'], ['N2-BALKEN-STAAL-S235-HEA140-5','N2-BALKEN-STAAL-S235-SHS80x6-1', 'H_RRF']] - disable_nodes (list of (nodes or lists of floats/ints)): List of nodes to disable auto hinge generation. The input for this argument can also be provided as a list of coordinates. - disable_hinges (list of list of str): List of list of source-target pairs for which the hinges need to be omitted. The order in which the elements are specified is not important. eg. [['N2-BALKEN-STAAL-S235-HEA140-5','N2-BALKEN-STAAL-S235-SHS80x6-1']] - collection (list of shapes): A list of object references of Column or Beam class which are checked to apply hinges on. Default value is None, checking all shapes in the project. Output: - Returns list of created hinges. """ # Get node_shapes objects if the nodes are specified as coordinates if disable_nodes is not None: nodes_not_found = [] for i, _node in enumerate(disable_nodes): if isinstance(_node, Node): continue elif all(isinstance(coordinate, (int, float)) for coordinate in _node): _node_object = project.find_node(point=_node) if not _node_object: project.write_log( f"WARNING: No node found at {_node}. This location cannot be disabled for hinge generation.") nodes_not_found.append(i) else: disable_nodes[i] = _node_object else: raise ValueError( "ERROR: The input of disable_nodes should be a list of shape_nodes objects or a list of " "coordinates.") # Remove the coordinates from the disable_nodes list where no node was found for _index in reversed(nodes_not_found): disable_nodes.pop(_index) if disable_nodes is None: disable_nodes = [] if override_hinges is None: override_hinges = [] # Get set of connections as combinations of source and target shape names connected = viia_get_existing_connections(project) # Check input for override_hinges input (list or dict) _viia_check_input(item=override_hinges, name='override_hinges') # Collect list of nodes from hinges that do not have a target connecting-shape # These are excluded for generating hinges auto_hinge_nodes = [ hinge.connecting_shapes['source_shape_geometry'] for hinge in project.collections.hinges if not hinge.connecting_shapes.get('target_connecting_shape', False)] # process exceptions exceptions = {} for exception in override_hinges: exceptions[frozenset([exception[0], exception[1]])] = { 'connection': exception[2], 'source': exception[0], 'target': exception[1]} # list of source-target pairs for which the user has disabled hinges or where a point connection is already present exempt_hinges_node = {} exempt_hinges_line = {} # User defined exemptions if disable_hinges: for disable_pair in disable_hinges: source_shape = project.viia_get(name=disable_pair[0]) target_shape = project.viia_get(name=disable_pair[1]) common_node = set(source_shape.get_connecting_nodes(target_shape)) exempt_hinges_node[frozenset([source_shape.name, target_shape.name])] = common_node if len(common_node) > 1: common_lines = set(source_shape.get_connecting_lines(target_shape)) exempt_hinges_line[frozenset([source_shape.name, target_shape.name])] = common_lines if len(common_node) < 1: project.write_log( f"ERROR: {disable_pair[0]} and {disable_pair[1]} do not have a coincident node and are excluded from disable_nodes") # Point connection exemptions for connection in project.collections.connections: if connection.connection_type == 'point-point': if connection.connecting_shapes['source_shape_geometry'] != connection.connecting_shapes[ 'target_shape_geometry']: continue else: source_shape = connection.connecting_shapes['source_connecting_shape'].name target_shape = connection.connecting_shapes['target_connecting_shape'].name common_node = connection.connecting_shapes['source_shape_geometry'] if frozenset([source_shape, target_shape]) in exempt_hinges_node: exempt_hinges_node[frozenset([source_shape, target_shape])].add(common_node) else: exempt_hinges_node[frozenset([source_shape, target_shape])] = set() exempt_hinges_node[frozenset([source_shape, target_shape])].add(common_node) node_connections = {} # Select the collection of shapes to apply hinges on if collection: members = [shape for shape in collection if isinstance(shape, (Column, Beam))] else: members = project.collections.columns + project.collections.beams # Check if piles modelled as columns are present, this should not be the case (not according UPR) if len([member for member in members if 'PAAL' in member.name]) > 0: raise ValueError( "ERROR: Some column or beam has the name 'PAAL' in the name, please fix model first to model piles.") # Start loop through members to apply hinges on for member in members: for node in member.get_nodes(): to_unite_node = set() to_unite_line = set() slave_shapes = [] disregarded = [] # Check if hinge should be created if not (node.name not in node_connections and node not in disable_nodes and node not in auto_hinge_nodes): # Check if this node is already described if node in disable_nodes: project.write_log(f"WARNING: Hinge generation for node {node} is disabled by the user.") if node in auto_hinge_nodes: project.write_log( f"WARNING: Hinge generation for node {node} is disabled due to presence of auto-hinge.") # No hinge created continue # Define connection connecting_shapes = node.get_shapes() # Filter connecting shapes for the collection if collection: connecting_shapes = [shape for shape in connecting_shapes if shape in collection] # Examine each pair connecting to the node if not connecting_shapes: continue for pair in combinations(connecting_shapes, 2): connecting_nodes = pair[0].get_connecting_nodes(shape=pair[1], include_openings=False) if connecting_nodes: if node in connecting_nodes: # Determine if there are any parallel beams if isinstance(pair[0], Lines) and isinstance(pair[1], Lines) and pair[0].is_parallel(pair[1]): # A unite object is created, first one is selected as slave if 'LIJNMASSA' not in pair[0].name: if pair[0] not in slave_shapes and pair[0] not in disregarded: slave_shapes.append(pair[0]) else: # Line-masses are disregarded disregarded.append(pair[0]) disregarded.append(pair[1]) # Add the pair to be joined later on if len(pair[0].get_connecting_lines(shape=pair[1], include_openings=False)) > 0: to_unite_line.add(frozenset([pair[0].name, pair[1].name])) elif len(pair[0].get_connecting_nodes(shape=pair[1], include_openings=False)) > 0: to_unite_node.add(frozenset([pair[0].name, pair[1].name])) # Determine if there are any 2d pairs. If there are check if interfaces between them are # generated if not unite them elif isinstance(pair[0], Surfaces) and isinstance(pair[1], Surfaces): if not frozenset((pair[0].name, pair[1].name)) in connected: if len(pair[0].get_connecting_lines(shape=pair[1], include_openings=False)) > 0: to_unite_line.add(frozenset([pair[0].name, pair[1].name])) # Determine if any pairs are beam to shape if yes then create a hinge if no other # connections present elif isinstance(pair[0], Lines) and isinstance(pair[1], Surfaces) or \ isinstance(pair[0], Surfaces) and isinstance(pair[1], Lines): something_is_connected = False if 'LIJN' in pair[0].name or 'LIJN' in pair[1].name: continue # If the two shapes are disabled by the user if frozenset((pair[0].name, pair[1].name)) in exempt_hinges_line: continue # If the two shapes already have connections, then the hinge is skipped if frozenset((pair[0].name, pair[1].name)) in connected: continue else: # Addition of algorithm to check how to add the hinges. If the shapes # (line and surface pair) within the pair being considered is the target # in one of the connections at the node, then the hinge is not formed # between those shapes, else it is created. connections_node = node.get_connections() if connections_node: for connection in connections_node: if not isinstance(connection, (Unite, NoConnection, BoundarySpring)): if connection.connecting_shapes['target_connecting_shape'].name == pair[ 0].name \ or connection.connecting_shapes['target_connecting_shape'].name \ == pair[1].name: connecting_shapes_shorter = [ shape for shape in connecting_shapes if pair[0].name != shape.name and pair[1].name != shape.name] for shape_source in connecting_shapes_shorter: if connection.connecting_shapes['source_connecting_shape'].name \ == shape_source.name: something_is_connected = True # check if beam forms a line with the shape lines = pair[0].get_connecting_lines(shape=pair[1], include_openings=False) if not something_is_connected and not lines: if pair[0] not in slave_shapes and pair[0] not in disregarded: slave_shapes.append(pair[0]) if pair[1] not in slave_shapes and pair[1] not in disregarded: slave_shapes.append(pair[1]) # Check shapes one by one if they are eligible to be connected for shape in connecting_shapes: if isinstance(shape, Lines): if 'LIJNMASSA' not in shape.name: if shape not in slave_shapes and shape not in disregarded: # Add shapes to slave shapes slave_shapes.append(shape) # Determine master shape master_shape = None for i, shape in enumerate(slave_shapes): if shape in project.collections.fstrips: if not master_shape: master_shape = slave_shapes.pop(i) else: project.write_log( f"WARNING: {shape.name} cannot be connected with a hinge! In this connection only " f"one source shape is possible and there are more than 1 supported shapes.") if not master_shape: # Determine if any shapes in the connection are provided as exceptions for pair in combinations(slave_shapes, 2): p = frozenset((pair[0].name, pair[1].name)) if p in exceptions: if not master_shape: master_shape = fem_find_object(exceptions[p]["source"], project.collections.shapes) else: if master_shape.name != exceptions[p]["source"]: project.write_log( f"ERROR: At node ({str(node)}) conflicting hinge overrides are specified. " f"Only one source per node is possible!") if not master_shape: master_shape = slave_shapes.pop(-1) else: slave_shapes.remove(master_shape) node_connections[node.name] = {} node_connections[node.name]['master'] = master_shape node_connections[node.name]['slaves'] = slave_shapes if master_shape.parent: parents = [master_shape.parent] else: parents = [] # connect the shapes with hinges for slave_shape in slave_shapes: p = frozenset((master_shape.name, slave_shape.name)) # Remove slave shapes with duplicate parents parent = slave_shape.parent if parent: if parent in parents: continue else: parents.append(parent) # disable hinges for user defined pairs and point interfaces if p in exempt_hinges_node: if node in exempt_hinges_node[p]: continue if p in exceptions: project.write_log( f"WARNING: A hinge between {master_shape.name} and {slave_shape.name} is being overwritten " f"with hinge {exceptions[p]}.") _connect_node(project, node, master_shape, slave_shape, exceptions[p]['connection']) else: _connect_node(project, node, master_shape, slave_shape, 'H_RRR') # Unite shapes for pair in to_unite_line: if pair not in connected: connected.add(pair) pair_l = list(pair) project.viia_create_connection(pair_l[0], pair_l[1], 'UNITE-L') for pair in to_unite_node: if pair not in connected: connected.add(pair) pair_l = list(pair) project.viia_create_connection(pair_l[0], pair_l[1], 'UNITE-P')
### =================================================================================================================== ### 3. End of script ### ===================================================================================================================