Source code for viiapackage.strengthening.l4.l4r

### ===================================================================================================================
###   L4-R strengthening measure
### ===================================================================================================================
# Copyright ©VIIA 2024

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

# General imports
from __future__ import annotations
from typing import TYPE_CHECKING, Union
from copy import deepcopy
from re import findall
import math

# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.fem_math import fem_cross_product_vector, fem_unit_vector, fem_distance_coordinates, \
    fem_unit_vector_2_points, fem_greater, fem_smaller
from rhdhv_fem.shapes import Wall, Beam

# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
    from viiapackage.viiaStatus import ViiaProject
from viiapackage.strengthening.helper_functions import viia_check_shape_argument, viia_find_wall_openings, \
    viia_find_min_max_z, viia_sort_openings, viia_add_strengthening_shape, viia_merge_openings, \
    viia_check_measure_in_gmc


### ===================================================================================================================
###   2. Function to create L4-R strengthening measures
### ===================================================================================================================

[docs]def viia_l4r( project: ViiaProject, variant: int, wall: Union[Wall, str], offset_left: float = 0.2, offset_right: float = 0.2): """ This function creates an L4-R-measure for a selected wall. It adds vertical steel columns with horizontal wooden beams (generally 100x200, 400 ctc), including eccentricity. It will model the columns and beams around wall openings with the offset specified in the project. The wall should be vertical, but angled in horizontal plane is okay. Also the top and bottom do not have to be straight. offsetLeft and offsetRight define the offsets of the first and last column from the edge of the wall. .. warning:: Function is currently not available, it requires updates, please send request to the VIIA automating team. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - variant (int): The variant number of the measure that is in the GMC. - wall (obj): The object reference of the wall that has to be strengthened. Alternative (str): Name of the wall to be strengthened. - offset_left (float): Not obligatory, default value is 0.2m. This is the offset of the first steel column to the left edge of the wall in [m]. - offset_right (float): Not obligatory, default value is 0.2m. This is the offset of the last steel column to the right edge of the wall in [m]. Output: - The strengthening measure is added to class of columns and beams and modelled in DIANA or SCIA. For example: >>> project.viia_l4r(wall, 0.3, 0.2) This example will apply the L4-R measure with vertical steel columns and horizontal wooden beams, for out-of-plane strengthening, to teh wall with an offset at the left of 0.3m and 0.2m at the right. """ raise NotImplementedError( "ERROR: Function to apply L4-R measure is currently not available, script and UPR needs updates. Please " "contact the VIIA automation team.") # Argument handling wall = viia_check_shape_argument(project, wall, 'viia_l4r') # Check if measure is in GMC measure_type = 'L4-R' measure_sub_type = f"{measure_type}-{int(variant)}" viia_check_measure_in_gmc(project=project, measure_sub_type=measure_sub_type) # Find geometry properties of wall for geometry in project.collections.geometries: if wall.geometry == geometry.name: break # Compute normal vector that points outward, which is used to compute the eccentricities normal_vector_outward = wall.outward_vector() # Find the length of the wall wallxmin = wall.points[0][0][0] wallxmax = wall.points[0][0][0] wallymin = wall.points[0][0][1] wallymax = wall.points[0][0][1] wallzmin = wall.points[0][0][2] wallzmax = wall.points[0][0][2] for i in range(1, len(wall.points[0])): if fem_greater(wallxmin, wall.points[0][i][0]): wallxmin = wall.points[0][i][0] if fem_greater(wall.points[0][i][0], wallxmax): wallxmax = wall.points[0][i][0] if fem_greater(wallymin, wall.points[0][i][1]): wallymin = wall.points[0][i][1] if fem_greater(wall.points[0][i][1], wallymax): wallymax = wall.points[0][i][1] if fem_greater(wallzmin, wall.points[0][i][2]): wallzmin = wall.points[0][i][2] if fem_greater(wall.points[0][i][2], wallzmax): wallzmax = wall.points[0][i][2] wallDeltaX = wallxmax - wallxmin wallDeltaY = wallymax - wallymin wallLength = math.sqrt(wallDeltaX * wallDeltaX + wallDeltaY * wallDeltaY) # Determine the centre point of the projected line of the wall centerPointX = wallxmin + wallDeltaX / 2 centerPointY = wallymin + wallDeltaY / 2 # Compute localZAxis with localAxis of geometryObject and outward normal vector localZAxisWall = fem_cross_product_vector(geometry.localAxis, normal_vector_outward) # When the localAxis is defined from left to right seen from the inside, the ZAxis points up # If this is not the case, use reversed localAxis if localZAxisWall[2] == 1: localXAxis = geometry.localAxis elif localZAxisWall[2] == -1: localXAxis = fem_unit_vector([-geometry.localAxis[0], -geometry.localAxis[1], -geometry.localAxis[2]]) # Compute points of the first and last column of the wall, computed by means of the localXAxis startpointX = centerPointX - (wallDeltaX / 2 - (offset_left * wallDeltaX / wallLength)) * localXAxis[0] startpointY = centerPointY - (wallDeltaY / 2 - (offset_left * wallDeltaY / wallLength)) * localXAxis[1] startpoint = [startpointX, startpointY, 0.0] endpointX = centerPointX + (wallDeltaX / 2 - (offset_right * wallDeltaX / wallLength)) * localXAxis[0] endpointY = centerPointY + (wallDeltaY / 2 - (offset_right * wallDeltaY / wallLength)) * localXAxis[1] endpoint = [endpointX, endpointY, 0.0] # Name, material and geometry to be used for strengthening measure # Eccentricity is half the thickness of the wall and half of the column materialNameColumn = 'LIN-STAAL' heightColumn = \ float(findall(r'\d+', project.project_specific['strengthening_measures']['L4-O']['column'])[0]) materialNameBeam = 'LIN-HOUT' heightBeam = float(project.project_specific['strengthening_measures']['L4-O']['beam'].split('x')[0]) widthBeam = float(project.project_specific['strengthening_measures']['L4-O']['beam'].split('x')[1]) # Compute localZAxisColumn localXAxisColumn = [0.0, 0.0, 1.0] localZAxisColumn = fem_cross_product_vector(localXAxisColumn, normal_vector_outward) localZAxisColumnstring = '(' + str(localZAxisColumn[0]) + ', ' + str(localZAxisColumn[1]) + ', ' + \ str(localZAxisColumn[2]) + ')' # Compute eccentricity eccentricityColumn = '(0, -' + str(round(geometry.thickness / 2 + heightColumn / 2000, 3)) + ', 0)' # Compute localZAxisBeam localXAxisBeam = localXAxis localZAxisBeam = fem_cross_product_vector(localXAxisBeam, normal_vector_outward) localZAxisBeamstring = '(' + str(localZAxisBeam[0]) + ', ' + str(localZAxisBeam[1]) + ', ' + \ str(localZAxisBeam[2]) + ')' # Compute eccentricity eccentricityBeam = '(0, -' + str(round(geometry.thickness / 2 + heightBeam / 2000, 3)) + ', 0)' # Set geometry names geometryNameColumn =\ 'KOLOM-' + str(project.project_specific['strengthening_measures']['L4-O']['column']) + '-' + \ localZAxisColumnstring + '-' + eccentricityColumn geometryNameBeam = \ 'BALK-' + project.project_specific['strengthening_measures']['L4-O']['beam'] + '-' +\ localZAxisBeamstring + '-' + eccentricityBeam # Find starting number for column numbers maxColumnNr = 0 for column in project.collections.columns: columnNr = int(column.name.split('-')[-1]) if columnNr > maxColumnNr: maxColumnNr = columnNr # Find starting number for beam numbers maxBeamNr = 0 for beam in project.collections.beams: beamNr = int(beam.name.split('-')[-1]) if beamNr > maxBeamNr: maxBeamNr = beamNr # Initiate container for columnPoints, which holds the x- and y-coordinates of column positions columnPoints = [] # Check for openings if len(wall.points) > 1: # Find sortedOpenings, that are arranged in direction of the localXAxis openings = viia_find_wall_openings(wall, localXAxis) # Sort openings with respect to localXAxis sortedOpenings = viia_sort_openings(openings, localXAxis) # Add first point, which is the point left to the first wall opening p1 = [0.0, 0.0, 0.0] for j in range(0, 3): p1[j] = sortedOpenings[0][0][j] - localXAxis[j] *\ project.project_specific['strengthening_measures']['L4-R']['minimum offset'] # Check if p1 lies within wall boundaries, then append if ((wallxmax >= p1[0] >= wallxmin) or (wallxmax <= p1[0] <= wallxmin)) \ and ((wallymax >= p1[1] >= wallymin) or (wallymax <= p1[1] <= wallymin)): columnPoints.append(p1) # This part could maybe be replaced with the functions for handling openings, but it is unnecessary if len(sortedOpenings) > 1: mergecounter = 0 # Iterate through sortedOpenings # During each iteration, the distance between consecutive openings is considered, to determine whether and # where columns should be positioned for i in range(len(sortedOpenings) - 1): # Compute distance and unit vector between end point of current opening and start point of next opening distanceOpenings = fem_distance_coordinates(sortedOpenings[i - mergecounter][1], sortedOpenings[i + 1 - mergecounter][0]) uvOpenings = fem_unit_vector_2_points(sortedOpenings[i - mergecounter][1], sortedOpenings[i + 1 - mergecounter][0]) # If distanceOpenings smaller than 2 times minOffset # or if uvOpenings not equal to localXAxis, i.e. consecutive sortedOpenings overlap, # Don't add column between consecutive sortedOpenings and merge sortedOpenings if distanceOpenings < 2 * project.project_specific['strengthening_measures']['L4-R']['minimum offset'] \ or uvOpenings != localXAxis: sortedOpenings[i - mergecounter] = [sortedOpenings[i - mergecounter][0], sortedOpenings[i + 1 - mergecounter][1]] del sortedOpenings[i + 1 - mergecounter] mergecounter += 1 continue # Else if, distanceOpenings smaller than offsets + minDistance # Use only one column instead of two, for sortedOpenings that are close to each other # Compute some extra space next to the actual sortedOpenings elif (distanceOpenings < 2 * project.project_specific['strengthening_measures']['L4-R']['minimum offset'] + project.project_specific['strengthening_measures']['L4-R']['minimum distance']): # Compute p2, which is thus (approximately) in the middle of the two openings restDistance = distanceOpenings - 2 *\ project.project_specific['strengthening_measures']['L4-R']['minimum offset'] p2 = [0.0, 0.0, 0.0] for j in range(0, 3): p2[j] = sortedOpenings[i - mergecounter][1][j] + \ (project.project_specific['strengthening_measures']['L4-R']['minimum offset'] + restDistance / 2) * localXAxis[j] columnPoints.append(p2) # Else, sufficient space between consecutive sortedOpenings else: # Compute p2 and p3, simply by using the specified offsets # p2 is the point right to the current opening p2 = [0.0, 0.0, 0.0] for j in range(0, 3): p2[j] = sortedOpenings[i - mergecounter][1][j] +\ project.project_specific['strengthening_measures']['L4-R']['minimum offset'] * \ localXAxis[j] # p3 is the point left to the next opening p3 = [0.0, 0.0, 0.0] for j in range(0, 3): p3[j] = sortedOpenings[i + 1 - mergecounter][0][j] - \ project.project_specific['strengthening_measures']['L4-R']['minimum offset'] * \ localXAxis[j] # Compute distance between points p2 and p3, which will become columns distanceColumns = fem_distance_coordinates(p2, p3) # Calculate if there are additional columns required between p2 and p3 nrColumns = int( distanceColumns / project.project_specific['strengthening_measures']['L4-R']['ctc columns']) + 1 restdistanceColumns = ( distanceColumns - (nrColumns - 1) * project.project_specific['strengthening_measures']['L4-R']['ctc columns']) / 2 if fem_smaller(restdistanceColumns, project.elementsize / 2): nrColumns -= 1 # If nrColumns smaller or equal to 2, only add p2 and p3 if nrColumns <= 2: columnPoints.append(p2) columnPoints.append(p3) # Else, start loop to create columns iteratively with computed ctcColumns else: ctcColumns = distanceColumns / (nrColumns - 1) for j in range(nrColumns): p = [0.0, 0.0, 0.0] for k in range(0, 3): p[k] = p2[k] + j * ctcColumns * localXAxis[k] columnPoints.append(p) # Add last point, which is the point right to the last wall opening p4 = [0.0, 0.0, 0.0] for j in range(0, 3): p4[j] = sortedOpenings[len(sortedOpenings) - 1][1][j] + \ project.project_specific['strengthening_measures']['L4-R']['minimum offset'] * localXAxis[j] # Check if p4 lies within wall boundaries, then append if ((wallxmax >= p4[0] >= wallxmin) or (wallxmax <= p4[0] <= wallxmin)) \ and ((wallymax >= p4[1] >= wallymin) or (wallymax <= p4[1] <= wallymin)): columnPoints.append(p4) # Now add start and end point if they are closer to the wall edge than the already added columnPoints # If not, the already added columnPoints are closer to the wall edge, do not add start and/or endpoint # NOTE: for wall openings close to the wall edge it is possible that a columnPoint lies before the startpoint # or behind the endpoint, thereby the variables offsetLeft and offsetRight are effectively overruled, meaning # that the first and last column become closer to the wall edges # Otherwise, columns would stand in front of the wall opening # If openings are so close to the wall edge, that there is no place left for the column, # the column can be placed in front of the opening if fem_unit_vector_2_points(startpoint, columnPoints[0]) == localXAxis: columnPoints.insert(0, startpoint) if fem_unit_vector_2_points(columnPoints[-1], endpoint) == localXAxis: columnPoints.append(endpoint) else: # Compute distance between start- and end point, which will become columns distanceColumns = fem_distance_coordinates(startpoint, endpoint) # Calculate if there are additional columns required between start- and end point nrColumns = int(distanceColumns / project.project_specific['strengthening_measures']['L4-R']['ctc columns']) + 1 restdistanceColumns = (distanceColumns - (nrColumns - 1) * project.project_specific['strengthening_measures']['L4-R']['ctc columns']) / 2 if fem_smaller(restdistanceColumns, project.elementsize / 2): nrColumns -= 1 # If nrColumns smaller or equal to 2, only add start- and end point if nrColumns <= 2: columnPoints.append(startpoint) columnPoints.append(endpoint) # Else, start loop to create columns iteratively with computed ctcColumns else: ctcColumns = distanceColumns / (nrColumns - 1) for j in range(nrColumns): p = [0.0, 0.0, 0.0] for k in range(0, 3): p[k] = startpoint[k] + j * ctcColumns * localXAxis[k] columnPoints.append(p) # Initiate counters columncounter = 1 beamcounter = 1 # Iterate trough columnPoints for i in range(len(columnPoints)): columnPoint = columnPoints[i] level = wall.name.split('-')[0] intersections = sorted(viia_find_min_max_z(wall.points[0], [columnPoint[0], columnPoint[1], 0])) bottomLevel = intersections[0] topLevel = intersections[-1] nr = maxColumnNr + columncounter name = level + '-KOLOMMEN-L4R-' + materialNameColumn + '-' + \ str(project.project_specific['strengthening_measures']['L4-O']['column']) + '-' + str(nr) bottomPoint = [columnPoint[0], columnPoint[1], bottomLevel] topPoint = [columnPoint[0], columnPoint[1], topLevel] # Deepcopy points, bottom- and topPoint are used for making the column # The Z-coordinate of newbottom- and newtopPoint can be adjusted after which they are used to create beams newbottomPoint = deepcopy(bottomPoint) newtopPoint = deepcopy(topPoint) if fem_greater((topLevel - bottomLevel), project.project_specific['strengthening_measures']['L4-O']['minimum height']): column = project.create_column(name, project.create_line([bottomPoint, topPoint]), materialNameColumn, geometryNameColumn) columncounter += 1 viia_add_strengthening_shape(shape=wall, strengthening_shape=column) # If more than one column created up until now and column created in this iteration # Place wooden beams between consecutive steel columns if columncounter > 2: # Get X- and Y-coordinates of previous and current column x1Column = oldbottomPoint[0] y1Column = oldbottomPoint[1] x2Column = newbottomPoint[0] y2Column = newbottomPoint[1] # Find extreme z-coordinates that both columns reach, over that height beams are placed minZ = None maxZ = None if fem_greater(round(oldbottomPoint[2], 3), round(newbottomPoint[2], 3)): minZ = oldbottomPoint[2] elif fem_smaller(round(oldbottomPoint[2], 3), round(newbottomPoint[2], 3)): minZ = newbottomPoint[2] if fem_smaller(round(oldtopPoint[2], 3), round(newtopPoint[2], 3)): maxZ = oldtopPoint[2] elif fem_greater(round(oldtopPoint[2], 3), round(newtopPoint[2], 3)): maxZ = newtopPoint[2] # Initiate container that will contain wall openings that lie between the consecutive columns openings = [] # Iterate through definitions of the wall openings, to compute local minima and # maxima of Z of wall openings between two consecutive columns for j in range(1, len(wall.points)): # Find coordinates of first point of opening x0 = wall.points[j][0][0] y0 = wall.points[j][0][1] z0 = wall.points[j][0][2] # Initiate local minimum and maximum zmin = z0 zmax = z0 # If X- and Y-coordinates between those of the columns, the entire wall opening must be also # x1Column, x2Column etc. are global coordinates, while a localXAxis is used # When the localXAxis is a negative vector, x1Column can be higher than x2Column # Here it is checked if x0 lies between xmin and xmax if ((x0 >= x1Column and x0 <= x2Column) or (x0 <= x1Column and x0 >= x2Column)) and \ ((y0 >= y1Column and y0 <= y2Column) or (y0 <= y1Column and y0 >= y2Column)): # Compute local minima and maxima for k in range(1, len(wall.points[j])): if fem_greater(zmin, wall.points[j][k][2]): zmin = wall.points[j][k][2] if fem_greater(wall.points[j][k][2], zmax): zmax = wall.points[j][k][2] openings.append([[x1Column, y1Column, zmin], [x1Column, y1Column, zmax]]) # Sort and merge openings between columns in positive vertical direction sortedOpenings = viia_sort_openings(openings, [0.0, 0.0, 1.0]) sortedOpenings = viia_merge_openings(sortedOpenings, [0.0, 0.0, 1.0]) # Initiate container that will contain z-ccordinates at which the wall bottom, openings and top are # found. Beams will be placed over the height between the first and second coordinate, third and fourth # coordinate, etc. zCoordinates = [] if len(sortedOpenings) > 0: # If minZ (lowest Z-coordinate that both columns reach) below lowest sortedOpening if fem_smaller(minZ, sortedOpenings[0][0][2]): # Add minZ and all coordinates of the sortedOpenings zCoordinates.append(minZ) for sortedOpening in sortedOpenings: zCoordinates.append(sortedOpening[0][2]) zCoordinates.append(sortedOpening[1][2]) else: # Add only top Z-coordinate of lowest sortedOpening and all coordinates of further # sortedOpenings zCoordinates.append(sortedOpenings[0][1][2]) for sortedOpening in sortedOpenings[1:len(sortedOpenings)]: zCoordinates.append(sortedOpening[0][2]) zCoordinates.append(sortedOpening[1][2]) # If maxZ (highest Z-coordinate that both columns reach) above highest sortedOpening, add maxZ if fem_greater(maxZ, zCoordinates[-1]): zCoordinates.append(maxZ) # Else, delete last Z-coordinate, which is the top Z-coordinate of the highest sortedOpening else: del zCoordinates[-1] else: # If no sortedOpenings, only add minZ and maxZ, spread beams over entire height zCoordinates = [minZ, maxZ] # Iterate through zCoordinates for j in range(int(len(zCoordinates) / 2)): # In each step, the beams spread over the height spanning from minZtemp to maxZtemp minZtemp = zCoordinates[2 * j] maxZtemp = zCoordinates[1 + 2 * j] # Distribute beams of measure L4-R tempHeight = maxZtemp - minZtemp nrBeams = int(tempHeight / project.project_specific['strengthening_measures']['L4-O']['ctc beams']) + 1 restWallLength = (tempHeight - nrBeams * project.project_specific['strengthening_measures']['L4-O']['ctc beams']) / 2 # Reduce number of beams by one if necessary # Don't if only one beam with enough tempHeight if nrBeams == 1 and tempHeight >= widthBeam/1000 + 2 * \ project.project_specific['strengthening_measures']['L4-O']['minimum height']: pass elif fem_smaller(restWallLength, project.ElementSize / 2): nrBeams -= 1 # Starting z-coordinate of the first beam of the wall pointZ = (maxZtemp + minZtemp) / 2 - \ ((nrBeams - 1) / 2 * project.project_specific['strengthening_measures']['L4-O']['ctc beams']) for k in range(nrBeams): nrBeam = maxBeamNr + beamcounter nameBeam = level + '-BALKEN-L4R-' + materialNameBeam + '-' + \ project.project_specific['strengthening_measures']['L4-O']['beam'] + '-' + \ str(nrBeam) leftPoint = [oldbottomPoint[0], oldbottomPoint[1], pointZ] rightPoint = [newbottomPoint[0], newbottomPoint[1], pointZ] beam = Beam( nameBeam, project.create_line([leftPoint, rightPoint]), materialNameBeam, geometryNameBeam) beamcounter += 1 viia_add_strengthening_shape(shape=wall, strengthening_shape=beam) pointZ = pointZ + project.project_specific['strengthening_measures']['L4-O']['ctc beams'] oldbottomPoint = deepcopy(newbottomPoint) oldtopPoint = deepcopy(newtopPoint) # Update wall attribute with strengthening measure if columncounter > 1: wall.add_meta_data({'strengthening': measure_sub_type}) project.write_log( f"L4-R measure applied on wall {wall.name}, number of applied columns: {str(columncounter - 1)}, " f"number of applied beams: {str(beamcounter - 1)}.")
### =================================================================================================================== ### 3. End of script ### ===================================================================================================================