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