### ===================================================================================================================
### FUNCTION: Find center of mass according NLPO requirements VIIA
### ===================================================================================================================
# Copyright ©VIIA 2024
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from __future__ import annotations
from typing import TYPE_CHECKING, Dict
# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
from viiapackage.viiaStatus import ViiaProject
### ===================================================================================================================
### 2. Function viia_center_of_mass_nlpo
### ===================================================================================================================
[docs]def viia_center_of_mass_nlpo(project: ViiaProject) -> Dict[str, Dict[str, float]]:
"""
Function to calculate the center of mass of all floors of a building, necessary for NLPO analysis. Based on the NPR,
a maximum of 3 floors can be analysed with NLPO. Therefore, the center of mass is calculated for at most 3 floors.
Function is based on the naming convention for storeys.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
Output:
- For each of the floors in the building, up to the 3rd floor (N3), the coordinates of the center of mass will
be generated. The maximum number of floors is equal to 3 since this is the maximum number of floors allowed
for NLPO analysis. The coordinates represent the evaluation node for every floor. This evaluation node can be
used to generate capacity curves for NLPO analysis. The point is returned as dictionary with lists with the
x-, y- and z-coordinate as floats (keys are the floor levels, for example 'N1').
- Optional output (see input) returns the mass associated with the storeys as dictionary (keys are the floor
levels, for example 'N1').
"""
# Calculation of center of mass of floors per floor level. In case there are walls on the attic floor and there is
# an inclined roof, the coordinates of the ridge center are returned
building = {}
for floor in project.collections.floors:
if floor not in project.collections.roofs:
level = floor.name.split('-')[0]
if ('N' in level or 'F' in level) and len(level) < 3:
coordinates, mass = floor.get_center_of_mass()
if mass:
if level not in building:
building[level] = {'x': 0, 'y': 0, 'z': 0, 'mass': 0}
for direction in ['x', 'y', 'z']:
building[level][direction] = \
(building[level][direction] * building[level]['mass'] +
mass * coordinates[direction]) / (building[level]['mass'] + mass)
building[level]['mass'] += mass
for roof in project.collections.roofs:
level = roof.name.split('-')[0]
if 'N' in level and len(level) < 3:
coordinates, mass = roof.get_center_of_mass()
if mass:
if level not in building:
# Assumed is that all roofs on this level are at same level
building[level] = {'x': 0, 'y': 0, 'z': 0, 'mass': 0}
for direction in ['x', 'y', 'z']:
building[level][direction] = \
(building[level][direction] * building[level]['mass'] +
mass * coordinates[direction]) / (building[level]['mass'] + mass)
building[level]['mass'] += mass
else:
level_above = 'N' + str(int(level.split('N')[-1]) + 1)
if level_above not in building:
# If wall is on the highest floor, the wall will completely be added to the floor it is on
level_above = level
if level == level_above:
for direction in ['x', 'y']:
building[level][direction] = \
(building[level][direction] * building[level]['mass'] +
mass * coordinates[direction]) / (building[level]['mass'] + mass)
building[level]['mass'] += mass
else:
for direction in ['x', 'y']:
building[level][direction] = \
(building[level][direction] * building[level]['mass'] +
0.5 * mass * coordinates[direction]) / (building[level]['mass'] + 0.5 * mass)
building[level]['mass'] += 0.5 * mass
for direction in ['x', 'y']:
building[level_above][direction] = \
(building[level_above][direction] * building[level_above]['mass'] +
0.5 * mass * coordinates[direction]) / (building[level_above]['mass'] + 0.5 * mass)
building[level_above]['mass'] += 0.5 * mass
project.write_log("WARNING: The outer leaves are not modelled and are currently added as line masses at the fstrip"
"level. Hence their masses are not considered in the calculation of seismic mass and hence the"
"center of mass per storey level")
# Calculation of center of mass of walls and add half of it to the floor above and half to the floor beneath it
for wall in project.collections.walls:
level = wall.name.split('-')[0]
if 'N' in level and len(level) < 3:
level_above = 'N' + str(int(level.split('N')[-1]) + 1)
if level_above not in building:
# If wall is on the highest floor, the wall will completely be added to the floor it is on
level_above = level
if level not in building:
building[level] = {'x': 0, 'y': 0, 'z': 0, 'mass': 0}
coordinates, mass = wall.get_center_of_mass()
if level == level_above:
for direction in ['x', 'y']:
building[level][direction] = \
(building[level][direction] * building[level]['mass'] +
mass * coordinates[direction]) / (building[level]['mass'] + mass)
building[level]['mass'] += mass
else:
for direction in ['x', 'y']:
building[level][direction] = \
(building[level][direction] * building[level]['mass'] +
0.5 * mass * coordinates[direction]) / (building[level]['mass'] + 0.5 * mass)
building[level]['mass'] += 0.5 * mass
for direction in ['x', 'y']:
building[level_above][direction] = \
(building[level_above][direction] * building[level_above]['mass'] +
0.5 * mass * coordinates[direction]) / (building[level_above]['mass'] + 0.5 * mass)
building[level_above]['mass'] += 0.5 * mass
if 'F' in level and len(level) < 3:
level_above = 'N0'
if level_above not in building:
level_above = level
coordinates, mass = wall.get_center_of_mass()
for direction in ['x', 'y']:
building[level_above][direction] = \
(building[level_above][direction] * building[level_above]['mass'] +
0.5 * mass * coordinates[direction]) / (building[level_above]['mass'] + 0.5 * mass)
building[level_above]['mass'] += 0.5 * mass
# NLPO check for more then 3 floors
if 'N4' in building or 'N5' in building:
project.write_log("WARNING: More then 3 floors detected, building not suitable for NLPO.")
# NLPO check to add the mass of the roof to the floor below if there is no wall present at that level
wall_attic_check = True
levels = sorted(list(set([wall.name.split('-')[0] for wall in project.collections.walls])))
for floor in project.collections.floors:
if 'N' + str(int(levels[-1].split('N')[1]) + 1) in floor.name:
if 'DAKEN' not in floor.name:
levels.append('N' + str(int(levels[-1].split('N')[1]) + 1))
for key in list(building.keys()):
if key not in levels:
if 'FUNDERING' not in key:
wall_attic_check = False
for direction in ['x', 'y']:
building[levels[-1]][direction] = \
(building[key][direction] * building[key]['mass'] +
building[levels[-1]]['mass'] * building[levels[-1]][direction]) / (
building[levels[-1]]['mass'] + building[key]['mass'])
building[levels[-1]]['mass'] += building[key]['mass']
building.pop(key)
# NLPO check for inclined roof coordinate (ridge) if walls are present in that floor
if wall_attic_check:
z_coor_collection = []
for roof in project.collections.roofs:
coordinates = roof.get_points()
z_coor = list(set([coordinate[2] for coordinate in coordinates]))
for z_co in z_coor:
z_coor_collection.append(z_co)
z_coor_collection = sorted(list(set(z_coor_collection)))
if len(z_coor_collection) > 1:
ridge = []
z_top = z_coor_collection[-1]
for roof in project.collections.roofs:
coordinates = roof.get_points()
for coordinate in coordinates:
if coordinate[2] == z_top:
ridge.append(coordinate)
x_ridge = 0
y_ridge = 0
for coor in ridge:
x_ridge += coor[0]
y_ridge += coor[1]
x_ridge = x_ridge / len(ridge)
y_ridge = y_ridge / len(ridge)
building[list(building.keys())[-1]]['x'] = x_ridge
building[list(building.keys())[-1]]['y'] = y_ridge
building[list(building.keys())[-1]]['z'] = z_top
project.write_log(
"WARNING: A coordinates for the top of the ridge is assumed for the top storey level. Please check if "
"the selected coordinates are correct.")
# NLPO check if basement is present
basement_present = False
basement_height = None
for wall in project.collections.walls:
level = wall.name.split('-')[0]
if 'F1' in level and len(level) < 3:
basement_height = wall.contour.get_height()
basement_present = True
break
if basement_present and 'N0' in building and building['N0']['z'] < 0.5 * basement_height:
project.write_log(
"WARNING: The center of mass for NLPO detected a basement that is more than half the height above the top "
"of soil. Therefore mass of level 'N0' is taken into account.")
else:
if 'N0' in building:
del building['N0']
# Return the required output
return building
### ===================================================================================================================
### 3. End of script
### ===================================================================================================================