### ===================================================================================================================
### 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
### ===================================================================================================================