Source code for viiapackage.viiaLoads

### =============================================================================================================== ###
###                                                                                                                 ###
###                                                  viiaLoads.py                                                   ###
###                                                                                                                 ###
### =============================================================================================================== ###
# This module ``viiaLoads`` enables to apply different types of loads and masses, apply signals, change signals and
# create load-combinations.

# Module is based on:
# VIIA_QE_R376 Basis of Design Retrofit Advice NLTH, v11.0, d.d. 29 August 2024
# VIIA_QE_R376 Basis of Design Step-by-Step MRS, v1.0, d.d. 21 January 2022
# VIIA_QE_R1674 Uitgangspuntenrapport engineering NLPO, v1.0, d.d. 1 August 2019

# This script was based upon the Constitution For Python At VIIA
# For use by VIIA
# Copyright RHDHV

### ===================================================================================================================
###    Contents script 
### ===================================================================================================================

#   1. Import modules

#   2. Overall and helper functions

#   3. Mass on lines

#   4. Mass on surfaces

#   5. Creating general loads

#   6. Changing the time-signal

#   7. Load-combinations

#   8. Creating the response spectrum

#   9. End of script

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

# General imports
from __future__ import annotations
import yaml
from copy import deepcopy
from pathlib import Path
from warnings import warn
from typing import TYPE_CHECKING, Union, List, Dict, Optional, Tuple

# References for functions and classes in the datafusr_py_base package
from datafusr_py_base.deprecation import rename_argument

# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.loads import LoadCase, LoadCombination
from rhdhv_fem.general import Direction
from rhdhv_fem.fem_tools import fem_write_log, fem_axis_to_string, fem_compare_objects, fem_find_object
from rhdhv_fem.fem_math import fem_compare_coordinates, fem_distance_coordinates, fem_normal_vector, \
    fem_cross_product_vector, fem_point_to_plane, fem_unit_vector, fem_compare_values, fem_smaller, \
    fem_purge_surface_points
from rhdhv_fem.materials import Steel, Concrete, Masonry, Timber, ReinforcementSteel, DiscreteMaterial
from rhdhv_fem.shapes import Shapes, Roof, Surfaces, Floor, Wall, PointMass, LineMass
from rhdhv_fem.shape_geometries.points.node import Node
from rhdhv_fem.geometries import UserDefinedProfile
from rhdhv_fem.tools.NPR9998_webtool import fem_response_spectrum_parameters_npr
from rhdhv_fem.groups import Layer
from rhdhv_fem.general import TimeseriesCombinationSet
from rhdhv_fem.analyses import NonlinearAnalysis, TimeSteps

# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
    from viiapackage.viiaStatus import ViiaProject
from viiapackage.VIIAClassDefinition import _name_check
from viiapackage.viiaSettings import ViiaSettings
from viiapackage import normalised_signals as _sig_matched
from viiapackage import normalised_signals_unmatched as _sig_unmatched
from viiapackage.analyses.helper_functions import viia_get_time_steps_nlth


### ===================================================================================================================
###    2. Overall and helper functions
### ===================================================================================================================

[docs]def viia_create_mass_all_openings(project: ViiaProject, unsupported_mass_on_opposite: bool = False) -> List[LineMass]: """ Function to apply regular truss elements to account for mass on top- and bottom edge of all openings in the project. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - unsupported_mass_on_opposite (bool): States whether to double the mass density of supported line-masses opposite to unsupported line-masses. The default value is False. Output: - Beam shape is added to the model on top and bottom of opening. Material, geometry and elementclass for LineMass are attached (mass is applied by self weight in material). """ line_masses = [] # Add mass to wall openings line_masses += project.viia_create_mass_all_wall_openings(unsupported_mass_on_opposite=unsupported_mass_on_opposite) # Add mass to roof openings line_masses += project.viia_create_mass_all_roof_openings(unsupported_mass_on_opposite=unsupported_mass_on_opposite) return line_masses
def _get_viia_load_constants(project: ViiaProject) -> Optional[Dict]: """ Helper function to get the values for the loads that are set in the UPR for VIIA.""" with open(project.viia_settings.project_specific_package_location / 'viia_constants.yaml') as f: return yaml.load(f, Loader=yaml.FullLoader)['Loads'] def _get_viia_importance_factor(project: ViiaProject, consequence_class: str = None) -> float: """ Helper function to get the importance factor based on the consequence_class. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - consequence_class (str): The consequence class. If None the consequence_class from the project_information will be used. Default is None. Output: - The importance_factor as a float """ if consequence_class is not None and consequence_class.upper() in ViiaSettings.IMPORTANCE_FACTORS: return ViiaSettings.IMPORTANCE_FACTORS[consequence_class.upper()] if 'consequence_class' in project.project_information and \ project.project_information['consequence_class'].upper() in ViiaSettings.IMPORTANCE_FACTORS: return ViiaSettings.IMPORTANCE_FACTORS[project.project_information['consequence_class'].upper()] importance_factor = 1.1 project.write_log( f"WARNING: The importance factor is set to {importance_factor}. Freq. parameter and spec. acceleration " f"will be calculated using this value of importance factor.") return importance_factor ### =================================================================================================================== ### 3. Mass on lines ### =================================================================================================================== def viia_create_mass_horizontal_surface_openings( project: ViiaProject, shape: Union[Floor, Roof, str]) -> List[LineMass]: """ Function to apply regular truss elements on four edges of opening to apply mass. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - shape (obj): Object reference of the horizontal surface, i.e., floors or flat roofs, on which openings mass will be applied. Alternative (str): Name of the floor or flat roof. Output: - Line masses are added to the model on all edges of all openings in the shape. The LineMass objects are only added to the project if the LineMass objects can be fully supported by (a set of) host shape(s). Linemasses that do not meet this requirement are not created. The missing masses are subsequently spread out over the other LineMasses that are fully supported by (a set of) host shape(s). """ # Argument handling if isinstance(shape, str): check_bool = False for floor_object in project.collections.floors: if shape == floor_object.name: shape = floor_object check_bool = True break for roof_object in project.collections.roofs: if shape == roof_object.name: shape = roof_object check_bool = True break if not check_bool: project.write_log( f"ERROR: {shape} is not recognised, check for typing error. " f"Function viia_create_mass_horizontal_surface_openings aborted.") return [] # Check if wall contains openings if not shape.openings: project.write_log(f"WARNING: Shape {shape.name} has no openings. Function " f"viia_create_mass_horizontal_surface_openings aborted.") return [] # Get the value for the to be applied load window_load = _get_viia_load_constants(project=project)['dead loads']['window']['uniform'] # Calculate the height of the opening all_line_masses_all_openings = [] for i in range(len(shape.openings)): opening = shape.openings[i] opening_area = opening.get_area() total_window_load = window_load * opening_area total_line_length = 0 for line in opening.lines: total_line_length += line.get_length() # Line mass in kg/m is rounded to 1 decimal line_mass_mass = '%.1f' % (total_window_load / project.gravitational_acceleration / total_line_length) line_mass_material = project.viia_create_material('LIN-LIJNMASSA-' + str(line_mass_mass)) line_mass_geometry = project.viia_create_geometry('LIJNMASSA', line_mass_material, None) unsupported_length = 0 supported_length = 0 line_mass_count = 0 line_mass_list = [] for line in opening.lines: try: line_mass_name = shape.name + '-LIJNMASSA-' + str(i + 1) + '-' + str(line_mass_count + 1) line_mass_object = \ project.create_line_mass( name=line_mass_name, contour=line, material=line_mass_material, geometry=line_mass_geometry) # if no line-mass is made, skip the rest of the code for this line of the opening if line_mass_object is None: warn(f"WARNING: Line-mass cannot be created on the opening {shape.openings[i]} " f"of shape {shape}.") unsupported_length += line.get_length() # Recreate line-mass material and geometry if removed by project.create_line_mass() if line_mass_material.project is None: line_mass_material = project.viia_create_material('LIN-LIJNMASSA-' + str(line_mass_mass)) if line_mass_geometry.project is None: line_mass_geometry = project.viia_create_geometry('LIJNMASSA', line_mass_material, None) continue # Make sure a list object is made opening_line_masses = line_mass_object if isinstance(line_mass_object, list) else [line_mass_object] # Loop through list of line-masses for line_mass in opening_line_masses: if line_mass.is_fully_supported(shape): line_mass.host = shape if project.rhdhvDIANA.model_created: line_mass.assign_material() line_mass.assign_geometry() # Add line mass to layer layer_name = line_mass_name.split('-')[0] layer = project.find(layer_name, 'layers') layer.add_shape(line_mass) supported_length += line_mass.contour.get_length() line_mass_list.append(line_mass) line_mass_count += 1 except ValueError: warn(f"WARNING: Line-mass cannot be created on the opening {shape.openings[i]} of shape {shape}.") unsupported_length += line.get_length() # If a line-mass is unsupported, it will be redistributed to other edges if fem_smaller(supported_length, 0): warn(f"WARNING: All line-masses of opening {shape.openings[i]} of shape {shape} are unsupported. " f"No line-masses were added to this opening") elif not fem_smaller(unsupported_length, 0): warn(f"WARNING: Part of line-masses of opening {shape.openings[i]} of shape {shape} are unsupported. The " f"unsupported line-masses are distributed along supported edges") m_parts = line_mass_material.name.split('-') m_name = f"{'-'.join(m_parts[:-1])}-{float(m_parts[-1]) * total_line_length / supported_length:.1f}" new_material = project.viia_create_material(m_name) for line_mass in line_mass_list: line_mass.material = new_material project.write_log(f"Mass applied on opening {i + 1} of shape {shape.name}.") all_line_masses_all_openings += line_mass_list return all_line_masses_all_openings def viia_create_mass_all_horizontal_surface_openings(project: ViiaProject) -> List[LineMass]: """ This function creates a line mass in all horizontal surfaces openings in the model, all four edges. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Returns regular truss-elements are added to all the openings in horizontal surfaces the model on four edges of the opening. Material, geometry and elementclass for LineMass are attached (mass is applied by self weight in material). """ all_line_masses_all_horizontal_surfaces = [] [all_line_masses_all_horizontal_surfaces.extend( viia_create_mass_horizontal_surface_openings(project=project, shape=shape)) for shape in project.collections.floors + project.collections.roofs if shape.openings and shape.is_horizontal()] return all_line_masses_all_horizontal_surfaces
[docs]def viia_create_mass_wall_openings( project: ViiaProject, wall: Union[Wall, str], unsupported_mass_on_opposite: bool = False) -> List[LineMass]: """ Function to apply regular truss elements on top- and bottom edge of opening to apply mass. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object reference of the wall on which openings mass will be applied. Alternative (str): Name of the wall i.e. 'N0-WANDEN-MW-KLEI>1945-110-1'. - unsupported_mass_on_opposite (bool): States whether to double the mass density of supported line-masses opposite to unsupported line-masses. The default value is False. Output: - Beam shape is added to the model on top and bottom of opening. Material, geometry and elementclass for LineMass are attached (mass is applied by self weight in material). """ # Argument handling if isinstance(wall, str): check_bool = False for wall_object in project.collections.walls: if wall == wall_object.name: wall = wall_object check_bool = True break if not check_bool: project.write_log( f"WARNING: {wall} is not recognised, check for typing error. Function " f"viia_create_mass_wall_openings aborted.") return [] # Check if wall contains openings if not wall.openings: project.write_log( f"WARNING: Wall {wall.name} has no openings. Function viia_create_mass_wall_openings aborted.") return [] # Get the value for the to be applied load window_load = _get_viia_load_constants(project=project)['dead loads']['window']['uniform'] all_line_masses_all_openings = [] # Calculate the height of the opening for i in range(len(wall.openings)): opening_coordinates = fem_purge_surface_points(wall.openings[i].get_points()) z_coordinate = sorted([opening_coordinate[2] for opening_coordinate in opening_coordinates]) z_coordinate_round = [] for k in range(len(z_coordinate)): if k == 0: z_coordinate_round.append(z_coordinate[k]) continue if not fem_compare_values( value1=z_coordinate[k], value2=z_coordinate[k-1], precision=project.check_precision): z_coordinate_round.append(z_coordinate[k]) # Calculate the mass to be added to the model if len(z_coordinate_round) == 2: delta_z = z_coordinate_round[0] - z_coordinate_round[1] if fem_smaller(delta_z, 0): delta_z = -delta_z # Line mass in kg/m is rounded to 1 decimal line_mass_mass = '%.1f' % (window_load * delta_z / project.gravitational_acceleration / 2) line_mass_material = project.viia_create_material('LIN-LIJNMASSA-' + str(line_mass_mass)) line_mass_geometry = project.viia_create_geometry('LIJNMASSA', line_mass_material, None) bott_unsupported, top_unsupported = False, False bot_line_masses = [] top_line_masses = [] for j in range(len(opening_coordinates)): if j == len(opening_coordinates) - 1: nr = 0 else: nr = j + 1 # Opening bottom side if fem_compare_coordinates( [z_coordinate_round[0]], [opening_coordinates[j][2]], project.check_precision): if fem_compare_coordinates([z_coordinate_round[0]], [opening_coordinates[nr][2]], project.check_precision): try: line_mass_name = wall.name + '-LIJNMASSA-ONDER-' + str(i + 1) line_mass_object = \ project.create_line_mass( name=line_mass_name, contour=project.create_line([opening_coordinates[j], opening_coordinates[nr]]), material=line_mass_material, geometry=line_mass_geometry) # can be None, an object or a list # if no line-mass is made, skip the rest of the code for this line if line_mass_object is None: warn(f"WARNING: Line-mass cannot be created on the bottom of opening " f"{wall.openings[i]} of wall {wall}.") bott_unsupported = True # Recreate line-mass material and geometry if removed by project.create_line_mass() if line_mass_material.project is None: line_mass_material = project.viia_create_material( 'LIN-LIJNMASSA-' + str(line_mass_mass)) if line_mass_geometry.project is None: line_mass_geometry = project.viia_create_geometry( 'LIJNMASSA', line_mass_material, None) continue # Make sure a list object is made bot_line_masses = \ line_mass_object if isinstance(line_mass_object, list) else [line_mass_object] # loop through list of line-masses for line_mass in bot_line_masses: if line_mass.is_fully_supported(wall): line_mass.host = wall if project.rhdhvDIANA.model_created: line_mass.assign_material() line_mass.assign_geometry() # Add line mass to layer layer_name = line_mass_name.split('-')[0] layer = project.find(layer_name, 'layers') layer.add_shape(line_mass) except ValueError: warn(f"WARNING: Line-mass cannot be created on the bottom of opening {wall.openings[i]} " f"of wall {wall}.") bott_unsupported = True # Opening top side if fem_compare_coordinates([z_coordinate_round[1]], [opening_coordinates[j][2]], 7): if fem_compare_coordinates([z_coordinate_round[1]], [opening_coordinates[nr][2]], 7): try: line_mass_name = wall.name + '-LIJNMASSA-BOVEN-' + str(i + 1) line_mass_object = \ project.create_line_mass( name=line_mass_name, contour=project.create_line([opening_coordinates[j], opening_coordinates[nr]]), material=line_mass_material, geometry=line_mass_geometry) # can be None, an object or a list # if no line-mass is made, skip the rest of the code for this line if line_mass_object is None: warn( f"WARNING: Line-mass cannot be created on the top of opening {wall.openings[i]} " f"of wall {wall}.") top_unsupported = True # Recreate line-mass material and geometry if removed by project.create_line_mass() if line_mass_material.project is None: line_mass_material = project.viia_create_material( 'LIN-LIJNMASSA-' + str(line_mass_mass)) if line_mass_geometry.project is None: line_mass_geometry = project.viia_create_geometry( 'LIJNMASSA', line_mass_material, None) continue # make sure a list object is made top_line_masses = \ line_mass_object if isinstance(line_mass_object, list) else [line_mass_object] # loop through list of line-masses for line_mass in top_line_masses: if line_mass.is_fully_supported(wall): line_mass.host = wall if project.rhdhvDIANA.model_created: line_mass.assign_material() line_mass.assign_geometry() # Add line mass to layer layer_name = line_mass_name.split('-')[0] layer = project.find(layer_name, 'layers') layer.add_shape(line_mass) except ValueError: warn(f"WARNING: Line-mass cannot be created on the top of opening {wall.openings[i]} " f"of wall {wall}.") top_unsupported = True all_line_masses = bot_line_masses + top_line_masses all_line_masses_all_openings += all_line_masses # If a line-mass is unsupported, double the mass of the opposite (supported) line-mass, if desired if bott_unsupported and top_unsupported: warn(f"WARNING: Both line-masses of opening {wall.openings[i]} of wall {wall} are unsupported. " f"No line-masses were added to this opening") elif unsupported_mass_on_opposite and (bott_unsupported or top_unsupported): m_parts = line_mass_material.name.split('-') m_name = f"{'-'.join(m_parts[:-1])}-{float(m_parts[-1]) * 2:.1f}" new_line_mass_material = project.viia_create_material(m_name) for line_mass in all_line_masses: line_mass.material = new_line_mass_material # Notifications else: project.write_log( f"WARNING: Opening {i} in wall {wall.name} is not square. No mass applied. Points of the opening are " f"{wall.openings[i].get_points()}") project.write_log(f"Mass applied on opening {i + 1} of wall {wall.name}.") return all_line_masses_all_openings
[docs]def viia_create_mass_all_wall_openings( project: ViiaProject, unsupported_mass_on_opposite: bool = False) -> List[LineMass]: """ This function creates a line mass in all wall openings in the model, top and bottom edge. The outer leaf of cavity walls is excluded in the function. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - unsupported_mass_on_opposite (bool): States whether to double the mass density of supported line-masses opposite to unsupported line-masses. The default value is False. Output: - regular truss-elements are added to all the openings in walls the model on top and bottom of opening. Material, geometry and element-class for LineMass are attached (mass is applied by self weight in material) """ line_masses = [] [line_masses.extend(viia_create_mass_wall_openings( project=project, wall=wall, unsupported_mass_on_opposite=unsupported_mass_on_opposite)) for wall in project.collections.walls if wall.openings and wall.id < 9000] return line_masses
[docs]def viia_create_mass_roof_openings( project: ViiaProject, roof: Union[Roof, str], unsupported_mass_on_opposite: bool = False) -> List[LineMass]: """ Function to apply regular truss elements on top- and bottom edge of opening to apply mass. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - roof (obj): Object reference of the roof (or inclined floor) on which openings mass will be applied. Alternative (str): Name of the roof. - unsupported_mass_on_opposite (bool): States whether to double the mass density of supported line-masses opposite to unsupported line-masses. The default value is False. Output: - Line mass is added to the model on top and bottom of opening. Material, geometry and elementclass for LineMass are attached (mass is applied by self weight in material). """ # Argument handling roof_input = roof if isinstance(roof, str): roof = project.find(description=roof, collection='floors') if not isinstance(roof, Floor): raise ValueError(f"ERROR: The roof '{roof_input}' could not be was found, please check your input.") # Check if roof contains openings if not roof.openings: project.write_log( f"WARNING: Roof {roof.name} has no openings. Function viia_create_mass_roof_openings aborted.") return [] # Compute normal vector and upward vertical vector roof_normal_vector = fem_normal_vector(roof.contour.get_points()) z_vector = [0, 0, 1] # Check if roof is not horizontal if roof_normal_vector[2] == 1 or roof_normal_vector[2] == -1: project.write_log(f"ERROR: Roof {roof.name} is horizontal. Function viia_create_mass_roof_openings aborted.") return [] # Determine roof direction if it is not yet determined if roof.element_x_axis.vector == [1, 0, 0]: horizontal_roof_direction = fem_cross_product_vector(roof_normal_vector, z_vector) roof_direction = fem_unit_vector(fem_cross_product_vector(horizontal_roof_direction, roof_normal_vector)) else: roof_direction = roof.element_x_axis.vector # Set up vertical plane in roof direction helper_plane = [roof.contour.get_points()[0]] helper_plane.append([helper_plane[0][i] + roof_direction[i] for i in range(3)]) helper_plane.append([helper_plane[0][i] + z_vector[i] for i in range(3)]) # Calculate the height of the opening all_line_masses_all_openings = [] for i in range(len(roof.openings)): projected_opening_points = [] projected_opening_points_z = [] opening_points = roof.openings[i].get_points() for point in opening_points: projected_opening_points.append(fem_point_to_plane(point, helper_plane)) for j in range(len(projected_opening_points)): if j == len(projected_opening_points) - 1: k = 0 else: k = j + 1 if fem_compare_coordinates([projected_opening_points[j][2]], [projected_opening_points[k][2]], project.check_precision): projected_opening_points_z.append(projected_opening_points[j]) projected_opening_points_z.sort(key=lambda temp_point: temp_point[2]) # Calculate the mass to be added to the model if len(projected_opening_points_z) == 2: delta = fem_distance_coordinates(projected_opening_points_z[0], projected_opening_points_z[1]) # Get the VIIA setting for the value of the to be applied load window_load = _get_viia_load_constants(project=project)['dead loads']['window']['uniform'] # Line mass in kg/m is rounded to 1 decimal linemass_mass = '%.1f' % (window_load * delta / project.gravitational_acceleration / 2) line_mass_material = project.viia_create_material('LIN-LIJNMASSA-' + str(linemass_mass)) line_mass_geometry = project.viia_create_geometry('LIJNMASSA', line_mass_material, None) bott_unsupported, top_unsupported = False, False bot_line_masses = [] top_line_masses = [] for j in range(len(opening_points)): if j == len(opening_points) - 1: k = 0 else: k = j + 1 # Opening bottom side if fem_compare_coordinates( [projected_opening_points_z[0][2]], [opening_points[j][2]], project.check_precision): if fem_compare_coordinates( [projected_opening_points_z[0][2]], [opening_points[k][2]], project.check_precision): try: line_mass_name = roof.name + '-LIJNMASSA-ONDER-' + str(i + 1) line_mass_object = \ project.create_line_mass( name=line_mass_name, contour=project.create_line([opening_points[j], opening_points[k]]), material=line_mass_material, geometry=line_mass_geometry) # if no line-mass is made, skip the rest of the code for this line if line_mass_object is None: warn( f"WARNING: Line-mass cannot be created on the bottom of opening {roof.openings[i]} " f"of roof {roof}.") bott_unsupported = True # Recreate line-mass material and geometry if removed by project.create_line_mass() if line_mass_material.project is None: line_mass_material = project.viia_create_material( 'LIN-LIJNMASSA-' + str(linemass_mass)) if line_mass_geometry.project is None: line_mass_geometry = project.viia_create_geometry( 'LIJNMASSA', line_mass_material, None) continue # make sure a list object is made bot_line_masses = \ line_mass_object if isinstance(line_mass_object, list) else [line_mass_object] # loop through list of line-masses for line_mass in bot_line_masses: if line_mass.is_fully_supported(roof): line_mass.host = roof if project.rhdhvDIANA.model_created: line_mass.assign_material() line_mass.assign_geometry() # Add line mass to layer layer_name = line_mass_name.split('-')[0] layer = project.find(layer_name, 'layers') layer.add_shape(line_mass) except ValueError: warn(f"WARNING: Line-mass cannot be created on the bottom of opening {roof.openings[i]} " f"of roof {roof}.") bott_unsupported = True # Opening top side if fem_compare_coordinates([projected_opening_points_z[1][2]], [opening_points[j][2]], 7): if fem_compare_coordinates([projected_opening_points_z[1][2]], [opening_points[k][2]], 7): try: line_mass_name = roof.name + '-LIJNMASSA-BOVEN-' + str(i + 1) line_mass_object = \ project.create_line_mass( name=line_mass_name, contour=project.create_line([opening_points[j], opening_points[k]]), material=line_mass_material, geometry=line_mass_geometry) # Can be None, object or list # if no line-mass is made, skip the rest of the code for this line if line_mass_object is None: warn( f"WARNING: Line-mass cannot be created on the top of opening {roof.openings[i]} " f"of roof {roof}.") top_unsupported = True # Recreate line-mass material and geometry if removed by project.create_line_mass() if line_mass_material.project is None: line_mass_material = project.viia_create_material( 'LIN-LIJNMASSA-' + str(linemass_mass)) if line_mass_geometry.project is None: line_mass_geometry = project.viia_create_geometry( 'LIJNMASSA', line_mass_material, None) continue # make sure a list object is made top_line_masses = \ line_mass_object if isinstance(line_mass_object, list) else [line_mass_object] # loop through list of line-masses for line_mass in top_line_masses: if line_mass.is_fully_supported(roof): line_mass.host = roof if project.rhdhvDIANA.model_created: line_mass.assign_material() line_mass.assign_geometry() # Add line mass to layer layer_name = line_mass_name.split('-')[0] layer = project.find(layer_name, 'layers') layer.add_shape(line_mass) except ValueError: warn(f"WARNING: Line-mass cannot be created on the top of opening {roof.openings[i]} " f"of woof {roof}.") top_unsupported = True all_line_masses = bot_line_masses + top_line_masses all_line_masses_all_openings += all_line_masses # If a line-mass is unsupported, double the mass of the opposite (supported) line-mass, if desired if bott_unsupported and top_unsupported: warn(f"WARNING: Both line-masses of opening {roof.openings[i]} of roof {roof} are unsupported. " f"No line-masses were added to this opening") elif unsupported_mass_on_opposite and (bott_unsupported or top_unsupported): m_parts = line_mass_material.name.split('-') m_name = f"{'-'.join(m_parts[:-1])}-{float(m_parts[-1]) * 2:.1f}" new_line_mass_material = project.viia_create_material(m_name) for line_mass in all_line_masses: line_mass.material = new_line_mass_material # Notifications else: project.write_log( f"ERROR: Opening {str(i)} in roof {roof.name} is not square. No mass applied.", False) project.write_log(f"Mass applied on opening {str(i + 1)} of roof {roof.name}.") return all_line_masses_all_openings
[docs]def viia_create_mass_all_roof_openings( project: ViiaProject, excluded_roofs: Optional[List[Union[Roof, str, int]]] = None, unsupported_mass_on_opposite: bool = False) -> List[LineMass]: """ Function to add mass to all roof openings in the project. The user can exclude roofs if desired. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - excluded_roofs (list): List of roofs to exclude. Can either be an object reference, or simple name like 'roof_4' or ID. - unsupported_mass_on_opposite (bool): States whether to double the mass density of supported line-masses opposite to unsupported line-masses. The default value is False. This input only effects inclined roofs Output: - Adds mass for all openings in the roofs provided. """ # Convert excluded roofs input to list of ID's excluded_roof_ids = [] if excluded_roofs: for excluded_roof in excluded_roofs: if isinstance(excluded_roof, Roof): excluded_roof_ids.append(excluded_roof.id) elif isinstance(excluded_roof, str): excluded_roof_ids.append(int(excluded_roof.split('-')[1])) elif isinstance(excluded_roof, int): excluded_roof_ids.append(excluded_roof) # Loop over all the roofs in the project and add masses all_line_masses_all_roofs = [] for roof in project.collections.roofs: if not roof.is_horizontal(): if roof.openings: if roof.id not in excluded_roof_ids: all_line_masses_all_roofs += viia_create_mass_roof_openings( project=project, roof=roof, unsupported_mass_on_opposite=unsupported_mass_on_opposite) elif roof.is_horizontal(): if roof.openings: if roof.id not in excluded_roof_ids: all_line_masses_all_roofs += viia_create_mass_horizontal_surface_openings( project=project, shape=roof) return all_line_masses_all_roofs
[docs]def viia_create_linemass( project: ViiaProject, layer: str, points: List[List[float]], value: float, linemass_type='density', direction: List[Union[int, float]] = None, host: 'Shapes' = None): """ Function to apply beam elements to apply mass (as self weight of beam). .. warning:: Currently line-mass type 'mass-elements' is not used within VIIA. If this is needed please contact the automation team. .. note:: When line-mass type is set to 'mass-elements' the load will be added for static calculations. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - layer (str): Layer of the line-mass, determines the order for phasing. - points (list with two lists of three floats): Coordinates of begin and end point of LineMass. - value (float): Mass per meter [kg/m]. - linemass_type (str): Type describes the way the mass is modelled. Default value is 'density', can be switched to 'mass-elements' to apply mass-elements instead of beam elements. - direction (list of 3 floats): The direction is used to apply the mass in x, y- or z-direction ([0, 0, 1] means that the mass acts only in x-direction). Value is only taken into account if type is set to 'MassElements'. Default direction is set to None, in which case a vector of [1, 1, 1] is applied, the mass is applied in all directions. - host (obj): Shape object where the line-mass will be embedded to. Required if exporting to ABAQUS is needed. Output: - Line-mass shape is created at the requested input coordinates. - Material, geometry and elementclass for LineMass are assigned (mass is applied by self weight in material). If software is DIANA and model created. """ def _modify_sub_line_masses(sub_line_masses_lst: List[LineMass], layer_sub: Layer, sub_id: int): """ Function to modify the names and ids of sub-line-masses created when the single line-mass is not fully supported""" for sub_lm in sub_line_masses_lst: sub_lm.name = f'{layer_sub.name}-LIJNMASSA-{sub_id}' sub_lm.id = sub_id sub_id += 1 project.write_log( f"Because the line mass to be created is not fully supported by a single shape, line masses " f"{','.join([item.name for item in sub_line_masses_lst])} are created with fully supported shapes.") # Argument handling if not direction: direction = [1, 1, 1] # Check the input for layer layer, new_id = _name_check(project=project, name=layer, collection_name='linemasses') if not new_id: new_id = project.find_max_id(project.collections.line_masses) + 1 # Determine the type of modelling, with beam-elements: line_mass_object = None if linemass_type.lower() == 'density': # LineMass material - line mass in kg/m is rounded to 1 decimal line_mass_mass = '%.1f' % float(value) line_mass_material = project.viia_create_material('LIN-LIJNMASSA-' + str(line_mass_mass)) line_mass_geometry = project.viia_create_geometry('LIJNMASSA', line_mass_material, None) # Create the object in the class LineMass contour = project.create_line(points) line_mass_object = project.create_line_mass( name=f'{layer.name}-LIJNMASSA-{new_id}', material=line_mass_material, geometry=line_mass_geometry, contour=contour, host=host) if isinstance(line_mass_object, list): _modify_sub_line_masses(line_mass_object, layer_sub=layer, sub_id=new_id) else: line_mass_object.id = new_id project.write_log(f"Line-mass '{line_mass_object.name}' is created with beam-elements.") # Determine the type of modelling, with mass-elements: elif linemass_type.lower() in ['mass-elements', 'mass_elements', 'mass']: warn( f"WARNING: Currently {linemass_type=} is not in the current workflow (UPR). Please check again if the use " f"of {linemass_type=} is required. If so, please report this to the responsible person of the adopted " f"rekenmethodiek (for NLTH this is Reinier Ringers).") linemass_type = 'mass-elements' # LineMass material - line mass in kg/m is rounded to 1 decimal line_mass_mass = '%.1f' % float(value) line_mass_material = project.viia_create_material( f"LIN-LIJNMASSA-{line_mass_mass}-{fem_axis_to_string(project, direction)}") line_mass_geometry = project.viia_create_geometry('LIJNMASSA', line_mass_material, None) # Create the object in the class LineMass line_mass_object = project.create_line_mass( name=f'{layer.name}-LIJNMASSA-DISCREET-{new_id}', contour=project.create_line(points), material=line_mass_material, geometry=line_mass_geometry, mass_type=linemass_type, host=host) if isinstance(line_mass_object, list): _modify_sub_line_masses(line_mass_object, layer_sub=layer, sub_id=new_id) for lm in line_mass_object: # Create a line-load project.create_line_load( name=f"{layer.name}-LIJNMASSA-DISCREET-{lm.name.split('-')[-1]}", load_type='force', value=value * project.gravitational_acceleration, direction=project.create_direction(name='-Z'), connecting_shapes=[{'connecting_shape': lm}], load_case=project.create_load_case(name='Dead load')) else: line_mass_object.id = new_id project.write_log(f"LineMass {line_mass_object.name} is created with mass-elements.") # Create a line-load project.create_line_load( name=f'{layer.name}-LIJNMASSA-DISCREET-{new_id}', load_type='force', value=value * project.gravitational_acceleration, direction=project.create_direction(name='-Z'), connecting_shapes=[{'connecting_shape': line_mass_object}], load_case=project.create_load_case(name='Dead load')) # Add to layer and return newly created line-mass layer.add_shape(line_mass_object) return line_mass_object
[docs]def viia_surface_mass(project: ViiaProject, surface: Surfaces, new_material_name: str, added_mass: float): """ Sub-function for adding mass to a surface. It will check if already a material exists (only VIIA naming convention), it will check if it can just update the current material object, or that it needs to create a new material. """ # Get old material name old_material_name = deepcopy(surface.material.name) # Check if new material needs to be created and if created, assign new material to the surface new_material_object = None if new_material_name in [material.name for material in project.collections.materials]: for material in project.collections.materials: if material.name == new_material_name: other_material_object = material surface.material = other_material_object break elif len(surface.material.get_shapes() + surface.material.get_connections()) == 1: surface.material.name = new_material_name surface.material.added_mass = added_mass else: # Copy the material to create a specific material for this load new_material_object = surface.material.copy_material(new_material_name) if project.rhdhvDIANA.model_created: project.rhdhvDIANA.model_created = False new_material_object.added_mass = added_mass surface.material = new_material_object project.rhdhvDIANA.model_created = True else: new_material_object.added_mass = added_mass surface.material = new_material_object # Change name with updated density old_material_name = old_material_name.replace(f'-{surface.elementsize}x{surface.elementsize}', '') new_material_name = new_material_name.replace(f'-{surface.elementsize}x{surface.elementsize}', '') surface.name = surface.name.replace(old_material_name, new_material_name) # Assign the new material if model is created if project.rhdhvDIANA.model_created: if new_material_object: new_material_object.to_diana() surface.assign_material()
[docs]def viia_create_linemass_beam(project: ViiaProject, beam, extra_load: float): """ Function to apply extra loads on beams. Geometry with different loads and/or functions should be separated into more geometries. Load is added by adjusting self weight of the line shape. Function is available for beams, columns and lintels. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - beam (obj): Object reference to the beam of which the density will be changed. Alternative (str): Name of the beam of which the density will be changed. - extra_load (float): The additional load in [N/m]. Output: - The material of the beam is updated to a new material with increased density to take into account the extra load. """ # Argument handling if isinstance(beam, str): beam = fem_find_object( reference=beam, collection=project.collections.beams + project.collections.columns + project.collections.lintels) if beam is None: project.write_log( f"WARNING: {beam} is not recognised, or is no beam, column or lintel. Check for typing error. " f"Line-mass on beam cannot be created.") return # Calculate extra mass on the beam # Line mass in kg/m rounded to 1 decimal extra_linemass_mass = "%.1f" % float(extra_load / project.gravitational_acceleration) # Find the area of cross-section of the beam (in geometry object) if not isinstance(beam.geometry.geometry_model, UserDefinedProfile): raise NotImplementedError( f"ERROR: This function is only available for beams with user-defined profile geometry models. Aborted " f"application on {beam.geometry.name}.") area = beam.geometry.geometry_model.area if area is None: raise NotImplementedError(f"ERROR: The area for {beam.geometry.name} is not available.") # Find the (current) density of the beam (in material object) if not beam.material.mass_density: project.write_log(f"ERROR: '{beam.material.name}' is not recognised as beam material.") return original_density = beam.material.mass_density # Calculate the additional mass in [kg/m] extra_beam_mass = float(extra_linemass_mass) # Check if there was already an adjusted density, this mass is added extra_mass_present = beam.material.added_mass project.write_log(f"Total extra mass of {beam.name}: {str(round(extra_beam_mass, 1))} kg/m.") # Calculate the new density, the extra mass will be integrated in the material if extra_mass_present: increased_density = round(extra_beam_mass / area + extra_mass_present, 0) else: increased_density = round(extra_beam_mass / area, 0) # Create a new material with the adjusted density if '-DENSIT' in beam.material.name: new_material_name = beam.material.name.split('-DENSIT')[0] + '-DENSIT' + str(int(round(increased_density, 0))) else: new_material_name = beam.material.name + '-DENSIT' + str(int(round(increased_density, 0))) # Create new material object with new name and adjusted density if new_material_name in [material.name for material in project.collections.materials]: new_material_object = project.viia_get('materials', name=new_material_name) else: new_material_object = None if isinstance(beam.material, Steel): new_material_object = project.create_user_defined_steel( name=new_material_name, material_model=beam.material.material_model, mass_density=original_density, added_mass=increased_density) elif isinstance(beam.material, Concrete): new_material_object = project.create_user_defined_concrete( name=new_material_name, material_model=beam.material.material_model, mass_density=original_density, added_mass=increased_density) elif isinstance(beam.material, Masonry): new_material_object = project.create_user_defined_masonry( name=new_material_name, material_model=beam.material.material_model, mass_density=original_density, added_mass=increased_density) elif isinstance(beam.material, Timber): new_material_object = project.create_user_defined_timber( name=new_material_name, material_model=beam.material.material_model, mass_density=original_density, added_mass=increased_density) elif isinstance(beam.material, (ReinforcementSteel, DiscreteMaterial)): project.write_log( "ERROR: viia_create_linemass_beam only handles Steel, Concrete, Masonry and Timber materials.") return # Compare with existing materials for material in project.collections.materials: # If such material already exists, remove newly created material and assign existing material # NOTE: The attribute shapes is excluded from the comparison. if fem_compare_objects(material, new_material_object, ['shapes']) and material != new_material_object: new_material_object.remove_material() new_material_object = material break beam.material = new_material_object # Assign the new material if model is created if project.rhdhvDIANA.model_created: beam.assign_material() # Notifications project.write_log(f"{beam.name} has an extra resting load of {extra_load / 1000} kN/m.") project.write_log(f'Increased density of {beam.name}: {str("%.1f" % increased_density)} kg/m3.')
[docs]def viia_create_pointmass( project: ViiaProject, point: Union[List[float], Node], mass: float, layer: Union[str, Layer]) -> PointMass: """ Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - point (list of 3 floats): Point to apply the point-mass shape. Alternative (obj): Object reference of the node to apply the point-mass shape. - mass (float): Mass of the point-mass in [kg]. Mass is applied in x-, y- and z-direction. - layer (str or Layer): Name of the layer as a string, or the Layer object itself, to which the newly created point-mass should be added. Full name of shape is also accepted, not applying auto naming function, this will be removed in future releases. Output: - Object for point-mass is created and attributes are set. - Object-reference is added to list of point_masses, points and shapes (in collections of project). - Project settings for shapes are applied (if any given). - Point-mass is created in DIANA (if software is DIANA and model created). - Material, data, elementclass_type, colour and mesh properties are assigned in DIANA (if software is DIANA and model created). """ # Check the input for name layer, new_id = _name_check(project=project, name=layer, collection_name='point_masses') # Assemble name new_id = project.find_max_id(project.collections.points) + 1 name = f'{layer.name}-PUNTMASSA-{round(mass, 2)}-{new_id}' # Point mass material - point mass in kg is rounded to 1 decimal point_mass_value = '%.1f' % float(mass) point_mass_material = project.viia_create_material(f'LIN-PUNTMASSA-{point_mass_value}') # PointMass geometry geometry = project.viia_create_geometry( geometry_name='PUNTMASSA', material_object=point_mass_material, class_type='PointMass') # Create the object in the class LineMass if not isinstance(point, Node): point = project.create_node(point) point_mass = project.create_point_mass( name=name, material=point_mass_material, geometry=geometry, contour=point) if point_mass.id != new_id: raise ValueError(f"ERROR: Assigned id is not consistent. {point_mass.id} vs {new_id}") # Add shape to layer layer.add_shape(point_mass) return point_mass
[docs]@rename_argument(old='loadcase', new='load_case', since='71.1.0', until='72.0.0', package='viiapackage') def viia_add_outer_leaf_wall_line_load( project: ViiaProject, load_case: LoadCase, walls: List[Wall], is_foundation_cavity: bool, outer_leaf_volume: float = None, outer_leaf_mass_density: float = None): """ This function adds line load of the outer leaf to the foundation wall. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - load_case (obj): Object reference of LoadCase to the load-case the load to be applied. - walls (list): List with object references of wall objects. The walls should be given based on the facade the foundation walls should be included. - is_foundation_cavity (bool): Consider the foundation walls cavity (True) or solid (False) - outer_leaf_volume (float): Total wall volume of the outer leaf walls. Use this parameter in case the outer walls have different shape than the inner leaf walls. - outer_leaf_mass_density (float): Mass density of the outer leaf walls. Use this parameter in case different material is used for the outer leaf. Output: - Line loads on the foundation walls representing the weight of outer leaf. """ if not all(isinstance(wall, Wall) for wall in walls): project.write_log( f"ERROR: In the function viia_add_outer_leaf_wall_line_load, the walls are not Wall objects. " f"Check your walls input.") return if not any('FUNDERING' in wall.name for wall in walls): project.write_log( f"ERROR: In the function viia_add_outer_leaf_wall_line_load, no foundation walls are given. " f"Check your walls input.") return if not isinstance(load_case, LoadCase): project.write_log( f"ERROR: In the function viia_add_outer_leaf_wall_line_load, the load-case could not be found. " f"Check your load-case input.") return # Calculate the total wall volume of the walls [m^3] and the outer leaf unit force [N] outer_leaf_unit_force = 0 if outer_leaf_volume is None: for wall in walls: if 'FUNDERING' in wall.name and not is_foundation_cavity: continue outer_leaf_volume = wall.get_area() * wall.geometry.geometry_model.thickness if outer_leaf_mass_density is None: outer_leaf_mass_density = wall.material.mass_density outer_leaf_unit_force += outer_leaf_volume * outer_leaf_mass_density * project.gravitational_acceleration # Find the lowest lines of the foundation walls foundation_wall_bottom_edges = dict() total_bottom_length = 0 for wall in walls: if 'FUNDERING' not in wall.name: continue bottom_edges = wall.get_bottom_edges() foundation_wall_bottom_edges[wall.name] = bottom_edges for bottom_edge in bottom_edges: total_bottom_length += bottom_edge.get_length() # Calculate the outer leaf distributed force [N/m] outer_leaf_dis_force = outer_leaf_unit_force / total_bottom_length # Create the line loads for foundation wall bottom edges for wall_name, bottom_edges in foundation_wall_bottom_edges.items(): wall = fem_find_object(wall_name, project.collections.walls) for edge_idx, bottom_edge in enumerate(bottom_edges): project.create_line_load( name=f'outer_leaf_{wall.id}_{edge_idx + 1}', load_type='force', value=outer_leaf_dis_force, direction=project.create_direction(name='-Z'), load_case=load_case, connecting_shapes=[{'connecting_shape': wall, 'shape_geometry': bottom_edge}]) # Notifications wall_names = [wall.name for wall in walls] project.write_log(f'Outer leaf line loads are added for walls: {wall_names}.')
### =================================================================================================================== ### 4. Mass on surfaces ### ===================================================================================================================
[docs]def viia_adjust_mass_of_surface(project: ViiaProject, load, surface): """ This function calculates the mass for the provided load, it calculates the increased or decreased self weight, based on the thickness of the surface. For this adjusted self weight, a new material is created, an existing (already updated) material is used or the material itself is updated. .. note:: Negative loads are valid, resulting in a decrease of the self weight. This can be used to remove any previously applied loads. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - load (float): Value of the load to be added as mass, in [N/m2]. - surface (obj): Object reference of surface shape to which the load is applied. Output: - Updated material is applied to the surface. """ # Calculate extra mass on the surface, mass in kg/m2 rounded to 1 decimal extra_surface_mass = '%.1f' % float(load / project.gravitational_acceleration) # Check if there was already an adjusted density, this mass is added extra_mass_present = 0 if surface.material.added_mass: extra_mass_present = surface.material.added_mass * surface.geometry.geometry_model.thickness project.write_log( f"Total extra mass of {surface.name}: {round(extra_mass_present, 1)} kg/m2 + {extra_surface_mass} kg/m2.") else: project.write_log(f"Total extra mass of {surface.name}: {extra_surface_mass} kg/m2.") # Calculate the new density, the extra mass will be integrated in the material added_mass = (float(extra_surface_mass) + extra_mass_present) / surface.geometry.geometry_model.thickness # Create a new material with the adjusted density if '-DENSIT' in surface.material.name: if fem_compare_values(added_mass, 0.0, project.rounding_precision): new_material_name = surface.material.name.split('-DENSIT')[0] else: new_material_name = surface.material.name.split('-DENSIT')[0] + '-DENSIT' + str(int(round(added_mass, 0))) elif not fem_compare_values(added_mass, 0.0, project.rounding_precision): new_material_name = surface.material.name + '-DENSIT' + str(int(round(added_mass, 0))) else: new_material_name = surface.material.name # Update or create new material viia_surface_mass(project=project, surface=surface, new_material_name=new_material_name, added_mass=added_mass)
[docs]def viia_create_surface_mass_floor( project: ViiaProject, floor: Floor, resting_load: Optional[List[str]] = None, category: str = None, extra_load: float = 0.0): """ This function applies floor loads. The load is added by adjusting self weight of the floor. Geometry with different loads and/or functions should be separated into more geometries Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - floor (obj): Object reference to the floor on which the load is applied. Alternative(str): Name of the floor. - resting_load (list with strings): List with the resting loads to be applied. Default value is None, in which case no resting loads are applied. - category (str): name of the category of variable load to be applied - extra_load (float): Optional input to apply a user defined load. Default value is 0.0, value of the additional (non-programmed) loads in the seismic situation, in [N/m2]. Category can be (only one category can be chosen): 'AFloor', 'AStairs', ABalc', 'BOffice', 'CSchool', 'C1_2', 'C3_5', 'DShop', 'E', 'F', 'HRoof'. Rest can contain: 'WindowLoad', 'FinishLoad', 'CeilingLoad', 'WallLoad', 'WallLoadLight', 'WallLoadMedium', 'WallLoadHeavy', 'InstalLoad', 'FlatRoofLoad','FlatRoofGravelLoad', 'SlopeRoofLoad', 'SlopeRoofLoadConcrete', 'SlopeRoofLoadClay','SunPanelLoad'. Output: - Material of the floor is changed to a new material with increased density to take into account the extra load. """ # Argument handling if isinstance(floor, str): check_bool = False for floor_object in project.collections.surfaces: if floor == floor_object.name and isinstance(floor_object, Floor): floor = floor_object check_bool = True break if not check_bool: project.write_log(f"ERROR: {floor} is not recognised as floor, please check.") return if resting_load is None: resting_load = [] elif isinstance(resting_load, str): resting_load = [resting_load] # Get the VIIA load constants viia_loads = _get_viia_load_constants(project=project) # Calculate total resting load dead_load = 0 if 'WindowLoad' in resting_load: dead_load += viia_loads['dead loads']['window']['uniform'] if 'FinishLoad' in resting_load: dead_load += viia_loads['dead loads']['finishing']['uniform'] if 'CeilingLoad' in resting_load: dead_load += viia_loads['dead loads']['ceiling']['uniform'] if 'WallLoad' in resting_load: dead_load += viia_loads['dead loads']['lightweight walls']['uniform'] if 'WallLoadLight' in resting_load: dead_load += viia_loads['dead loads']['lightweight walls']['uniform'] if 'WallLoadMedium' in resting_load: dead_load += viia_loads['dead loads']['mediumweight walls']['uniform'] if 'WallLoadHeavy' in resting_load: dead_load += viia_loads['dead loads']['heavyweight walls']['uniform'] if 'InstalLoad' in resting_load: dead_load += viia_loads['dead loads']['installations']['uniform'] if 'FlatRoofLoad' in resting_load: dead_load += viia_loads['dead loads']['flat roof']['uniform'] if 'FlatRoofGravelLoad' in resting_load: dead_load += viia_loads['dead loads']['flat roof gravel']['uniform'] if 'SlopeRoofLoad' in resting_load: dead_load += viia_loads['dead loads']['sloping roof']['uniform'] if 'SlopeRoofLoadConcrete' in resting_load: dead_load += viia_loads['dead loads']['sloping roof concrete']['uniform'] if 'SlopeRoofLoadClay' in resting_load: dead_load += viia_loads['dead loads']['sloping roof clay']['uniform'] if 'SunPanelLoad' in resting_load: dead_load += viia_loads['dead loads']['sunpanels']['uniform'] if extra_load: dead_load += float(extra_load) # Calculate variable loads for the correct category key = None if category == 'AFloor': key = 'A-floor' elif category == 'AStairs': key = 'A-stair' elif category == 'ABalc': key = 'A-balcony' elif category == 'BOffice': key = 'B-office' elif category == 'CSchool': key = 'C-school' elif category == 'C1_2': key = 'C2' elif category == 'C3_5': key = 'C5' elif category == 'DShop': key = 'D' elif category == 'E': project.write_log("WARNING: category E has default (extreme) value of 5kN/m2. Adjust manually if necessary.") key = 'E' elif category == 'F': key = 'F' elif category == 'HRoof': key = 'H' if key: imposed_load = viia_loads['imposed loads'][key]['uniform'] * \ viia_loads['imposed loads'][key]['psi2'] * \ viia_loads['imposed loads'][key]['phi'] else: imposed_load = 0 # Calculate extra mass on the surface, mass in kg/m2 rounded to 1 decimal extra_surface_mass = '%.1f' % float((dead_load + imposed_load) / project.gravitational_acceleration) # Check if there was already an adjusted density, this mass is added extra_mass_present = 0 if floor.material.added_mass: extra_mass_present = floor.material.added_mass * floor.geometry.geometry_model.thickness project.write_log( f"Total extra mass of {floor.name}: {round(extra_mass_present, 1)} kg/m2 + {extra_surface_mass} kg/m2.") else: project.write_log( f"Total extra mass of {floor.name}: {extra_surface_mass} kg/m2.") # Calculate the new density, the extra mass will be integrated in the material added_mass = (float(extra_surface_mass) + extra_mass_present) / floor.geometry.geometry_model.thickness # Create a new material with the adjusted density if '-DENSIT' in floor.material.name: new_material_name = floor.material.name.split('-DENSIT')[0] + '-DENSIT' + str(int(round(added_mass, 0))) else: new_material_name = floor.material.name + '-DENSIT' + str(int(round(added_mass, 0))) # Update or create new material project.viia_surface_mass(surface=floor, new_material_name=new_material_name, added_mass=added_mass) # Store data of applied loads in meta-data if floor.meta_data is None or 'resting loads' not in floor.meta_data: floor.add_meta_data({'resting loads': resting_load}) else: for item in resting_load: floor.meta_data['resting loads'].append(item) # Notifications project.write_log( f"{floor.name} with added {resting_load} has an extra resting load of {dead_load / 1000} kN/m2. And with " f"category {category} has a variable load of {imposed_load / 1000} kN/m2.") project.write_log( f"Increased density of {floor.name}: {'%.1f' % added_mass} kg/m3.")
[docs]def viia_create_surface_mass_wall(project: ViiaProject, wall, extra_load): """ Function to apply wall loads (e.g. cladding or cavity walls). Geometry with different loads and/or functions should be seperated into more geometries. Load is added by adjusting self weight of wall. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object reference to the wall on which the load is applied. Alternative(str): Name of the wall. - extra_load (float): Added weight, in [N/m2]. Output: - Material of the wall is changed to a new material with increased density to take into account the extra load. """ # Print warning that surface masses are currently not automatically shown in the appendix C1 warn("WARNING: Surface masses on walls are currently not included in the automatic generation of the Appendix C1." "Contact the VIIA automation team to request this to be implemented.") # Argument handling if isinstance(wall, str): check_bool = False for wall_object in project.collections.walls: if wall == wall_object.name: wall = wall_object check_bool = True break if not check_bool: project.write_log(f"ERROR: {wall} is not recognised as wall, please check.") return None # Calculate extra mass on the surface, mass in kg/m2 rounded to 1 decimal extra_surface_mass = '%.1f' % float(extra_load / project.gravitational_acceleration) # Check if there was already an adjusted density, this mass is added extra_mass_present = 0 if wall.material.added_mass: extra_mass_present = wall.material.added_mass * wall.geometry.geometry_model.thickness project.write_log( f"Total extra mass of {wall.name}: {round(extra_mass_present, 1)} kg/m2 + {extra_surface_mass} kg/m2.") else: project.write_log(f"Total extra mass of {wall.name}: {extra_surface_mass} kg/m2.") # Calculate the new density, the extra mass will be integrated in the material added_mass = (float(extra_surface_mass) + extra_mass_present) / wall.geometry.geometry_model.thickness # Create a new material with the adjusted density if '-DENSIT' in wall.material.name: new_material_name = wall.material.name.split('-DENSIT')[0] + '-DENSIT' + str(int(round(added_mass, 0))) elif 'MW' in wall.material.name: new_material_name = wall.material.name + '-DENSIT' + str(int(round(wall.material.mass_density + added_mass, 0))) else: new_material_name = wall.material.name + '-DENSIT' + str(int(round(added_mass, 0))) # Update or create new material project.viia_surface_mass(surface=wall, new_material_name=new_material_name, added_mass=added_mass) # Notifications project.write_log(f"{wall.name} has an extra resting load of {extra_load / 1000} kN/m2.") project.write_log(f"Increased density of {wall.name}: {'%.1f' % added_mass} kg/m3.")
[docs]def viia_include_outerleaf_in_innerleaf_density( project: ViiaProject, wall, thickness_outerleaf, material_outerleaf=None): """ Function to take into account the mass of the outer leaf of the cavity wall by adding it to the self weight of the inner leaf of the cavity wall. This function is based on viiaCreateInnerleafDensity(WallName, ExtraLoad) and determines the ExtraLoad out of the outer leaf properties. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object reference of the wall (inner leaf) of which the density will be increased. Alternative (str): Name of the wall (inner leaf) of which the density will be increased. - thickness_outerleaf (float): Thickness of the outer wall in [m], i.e.: 0.1 (without quotation marks). - material_outerleaf (str): Material of the outer leaf, not required: if left empty the same material as the inner wall will be applied, i.e.: 'MW-KLEI>1945'. Output: - Material of the inner wall is changed to a new material with increased density to take into account the outer leaf. """ # Argument handling if isinstance(wall, str): check_bool = False for wall_object in project.collections.walls: if wall == wall_object.name: check_bool = True wall = wall_object break if not check_bool: project.write_log( f"ERROR: {wall} is not recognized, check for typing error. " "Function viia_include_outerleaf_in_innerleaf_density aborted.") return # Find the density of the inner leaf of the cavity wall density_inner_leaf = wall.material.total_mass() # Density of outer leaf of the cavity wall material from PY memory material_dict = dict() density_outer_leaf = 0 if material_outerleaf: dictionaries = [ 'LinRebar', 'LinearInterface', 'Linear', 'LinearSteel', 'LinearOrthotropic', 'LinearOrthotropicSteel', 'NonLinBondSlipRebar', 'NonLinConcrete', 'NonLinElasticInterface', 'NonLinInterfaceFriction', 'NonLinMasonry', 'NonLinRebar', 'NonLinSteel', 'SpringMaterial', 'TimberFrame'] for i, material_group in enumerate(dictionaries): material_dict = project.viia_get_material(material_outerleaf, material_group) if material_dict: break if 'mass density' in material_dict: density_outer_leaf = material_dict['mass density'] else: project.write_log( "ERROR: Density of outer leaf is unknown. " "Function viia_include_outerleaf_in_innerleaf_density aborted.") else: density_outer_leaf = density_inner_leaf # Additional load from outer leaf of cavity wall extra_load = density_outer_leaf * thickness_outerleaf * project.gravitational_acceleration # Function viia_create_surface_massWall() to adapt self-weight inner leaf with mass outer leaf viia_create_surface_mass_wall(project, wall=wall, extra_load=extra_load)
### =================================================================================================================== ### 5. Creating general loads ### ===================================================================================================================
[docs]def viia_create_dead_loads( project: ViiaProject, shapes_list: List[Shapes], load_value: float, force_direction: Optional[Union[Direction, str]] = None): """ This function applies a vertical dead load on a shape (can be point, line, or surface). The load is added as load and does not have a mass which is needed in dynamic calculations. .. note:: Shapes can be from different classes, be aware that the unity of the value changes. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - shapes_list (list of objects): List with the object references on which the load will be applied. Alternative (list of strings): List of the names of the shapes on which the load is applied. - load_value (float): Value of the load acting in negative z-direction if shape is a surface in [N/m2], if shape is a line in [N/m] or if shape is a point in [N]. A vertical load that acts downward should have a positive value. - force_direction (obj): Object reference of direction of force Alternative (str): Direction can also be given as 'X', 'Y', or 'Z', or name of a direction of which an object is already created. Output: - Load is created in 'Load' class. - If software is DIANA, and model is created, the load is added to the model. """ # Argument handling if not force_direction: force_direction = project.create_direction(name='-Z') if isinstance(force_direction, str): force_direction = project.create_direction(name=force_direction) # Find unique name for dead load counter = 0 for load_object in project.collections.loads: if 'Dead load' in load_object.name: nr = int(load_object.name.split('Dead load ')[-1]) if nr > counter: counter = nr counter += 1 # Applying load on surfaces connecting_shapes = [] for shape in shapes_list: if isinstance(shape, str): for shape_object in project.collections.surfaces: if shape == shape_object.name: connecting_shapes.append(shape_object) elif shape in project.collections.surfaces: connecting_shapes.append(shape) if len(connecting_shapes) > 0: for connecting_shape in connecting_shapes: project.create_surface_load( name='Dead load ' + str(counter).zfill(4), load_type='force', value=load_value, direction=force_direction, connecting_shapes=[{'connecting_shape': connecting_shape}], load_case=project.create_load_case(name='Dead load')) counter += 1 project.write_log( f"Geometry surface load of {load_value} N/m2 attached to dead load. Applied on shapes " f"{', '.join([x.name for x in connecting_shapes])}.") # Applying load on lines connecting_shapes = [] for shape in shapes_list: if isinstance(shape, str): for shape_object in project.collections.lines: if shape == shape_object.name: connecting_shapes.append(shape_object) elif shape in project.collections.lines: connecting_shapes.append(shape) if len(connecting_shapes) > 0: for connecting_shape in connecting_shapes: project.create_line_load( name='Dead load ' + str(counter).zfill(4), load_type='force', value=load_value, direction=force_direction, connecting_shapes=[{'connecting_shape': connecting_shape}], load_case=project.create_load_case(name='Dead load')) counter += 1 project.write_log( f"Geometry line load of {str(load_value)} N/m attached to dead load. Applied on shapes " f"{', '.join([x.name for x in connecting_shapes])}.") # Applying load on points connecting_shapes = [] for shape in shapes_list: if isinstance(shape, str): for shape_object in project.collections.points: if shape == shape_object.name: connecting_shapes.append(shape_object) elif shape in project.collections.points: connecting_shapes.append(shape) if len(connecting_shapes) > 0: for connecting_shape in connecting_shapes: project.create_point_load( name='Dead load ' + str(counter).zfill(4), load_type='force', value=load_value, direction=force_direction, connecting_shapes=[{ 'connecting_shape': connecting_shape}], load_case=project.create_load_case(name='Dead load')) counter += 1 project.write_log( f"Geometry point load of {str(load_value)}N attached to dead load. Applied on shapes " f"{', '.join([x.name for x in connecting_shapes])}.")
[docs]def _check_wall_height(project: ViiaProject) -> Tuple[int, Dict[str, float], Dict[str, float]]: """ This function checks the height of walls and floors, if the wall height is different from its corresponding floor height, a warning is shown to remind the engineer to check. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Returns tuple with floor number (integer), a dictionary with the z-coordinate of each floor and a dictionary with the floor height. Example output: (1, {'N0': 0.0, 'FU': -0.82, 'N1': 3.378}, {'N0': 3.378}). """ floor_z_dict = {} floor_height_dict = {} # Select the horizontal floor all_floors = [obj for obj in project.collections.floors if [round(abs(i), 1) for i in obj.normal_vector()] == [0, 0, 1.0]] # Floor number floor_num = int( sorted([floor.name[1] for floor in all_floors if floor.name[0] == 'N'], reverse=True)[0]) # Floor Z coordinate for floor in all_floors: if floor.name[:2] not in floor_z_dict: floor_z_dict[floor.name[:2]] = max([coord[2] for coord in floor.get_points()]) else: if max([coord[2] for coord in floor.get_points()]) != floor_z_dict[floor.name[:2]]: project.write_log(f"{floor} have a different height than the rest floor of this level.") else: pass # Floor height for num in range(floor_num): if num == 0: try: floor_z_dict['N0'] except KeyError: floor_z_dict['N0'] = 0 floor_height_dict[f'N{num}'] = floor_z_dict[f'N{num + 1}'] - floor_z_dict[f'N{num}'] # Check floor height and wall height for wall in project.collections.walls: max_z = max([coord[2] for coord in wall.get_points()]) min_z = min([coord[2] for coord in wall.get_points()]) try: if floor_height_dict[wall.name[:2]] != max_z - min_z: project.write_log(f"{wall.name} has a different height than the floor height, please check.") except KeyError: pass return floor_num, floor_z_dict, floor_height_dict
[docs]def viia_get_imposed_loadvalue(project: ViiaProject, category: str) -> Dict[str, float]: """ This function returns the value of the imposed load, based on the applied category. The category corresponds to the Eurocode NEN-EN-1991-1-1. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - category (str): The category of the imposed loads, refer to the basis of design (UPR). The category can be (only one category can be chosen): 'AFloor', 'AStairs', ABalc', 'BOffice', 'CSchool', 'C1_2', 'C3_5', 'DShop', 'E', 'F', 'HRoof'. Output: - Returns the value of the imposed load, the psi2 factor and the phi factor in a dictionary. """ # Get the VIIA laod constants viia_imposed_loads = _get_viia_load_constants(project=project)['imposed loads'] # Calculate variable loads for the correct category key = None if category == 'AFloor': key = 'A-floor' elif category == 'AStairs': key = 'A-stair' elif category == 'ABalc': key = 'A-balcony' elif category == 'BOffice': key = 'B-office' elif category == 'CSchool': key = 'C-school' elif category == 'C1_2': key = 'C2' elif category == 'C3_5': key = 'C5' elif category == 'DShop': key = 'D' elif category == 'E': project.write_log("WARNING: categoryegory E has default (extreme) value of 5kN/m2. Adjust by hand if required.") key = 'E' elif category == 'F': key = 'F' elif category == 'HRoof': key = 'H' if key: return {'value': viia_imposed_loads[key]['uniform'], 'psi2': viia_imposed_loads[key]['psi2'], 'phi': viia_imposed_loads[key]['phi']} raise KeyError(f"ERROR: Unknown imposed load, pleas check input for {category}.")
[docs]def viia_convert_imposed_loads_to_self_weight(project: ViiaProject): """ This function will convert any imposed loads created with the 'viia_create_loads' function to mass as part of the self weight of the surface shape. The 'psi' and 'phi2' factors will be applied. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - The loads are converted to masses and incorporated in the density of the material object. The material applied will be updated. - The imposed loads are removed from the model. The data to recreate the imposed load after converting to Only saved to the variable in the self weight is stored in the shape meta-data. - The load-combination for imposed loads is removed. """ # Set default SI units for dat-file in DIANA if project.rhdhvDIANA.run_diana and project.rhdhvDIANA.model_created: project.set_standard_units() # Remove the imposed loads and change the density counter = 0 for load in project.collections.loads: if load.meta_data is not None and 'imposed load' in load.meta_data: for connecting_shape in load.connecting_shapes: if not isinstance(connecting_shape['connecting_shape'], Surfaces): raise NotImplementedError("Currently only available for surface shapes.") project.viia_adjust_mass_of_surface( load=load.value * load.meta_data['imposed load']['phi'] * load.meta_data['imposed load']['psi2'], surface=connecting_shape['connecting_shape']) data = { 'as_load': False, 'category': load.meta_data['imposed load']['category'], 'phi': load.meta_data['imposed load']['phi'], 'psi2': load.meta_data['imposed load']['psi2'], 'shape geometry': connecting_shape['shape_geometry'], 'value': load.value} if connecting_shape['connecting_shape'].meta_data is None or \ 'imposed loads' not in connecting_shape['connecting_shape'].meta_data: connecting_shape['connecting_shape'].add_meta_data({ 'imposed loads': [data]}) else: connecting_shape['connecting_shape'].meta_data['imposed loads'].append(data) load.remove_load() load.meta_data['imposed load']['as_load'] = False counter += 1 # Check if any imposed loads were found and converted if counter == 0: raise ValueError("ERROR: Imposed loads are not present, please check before proceeding.") # Remove the load-case for imposed loads for load_case in project.collections.load_cases: if load_case.name == 'Imposed loads': project.collections.load_cases.remove(load_case) break # Remove the load-combination for imposed loads viia_remove_load_combination(project=project, load_combination_name='VAR')
[docs]def viia_convert_self_weight_to_imposed_loads(project: ViiaProject): """ This function will re-convert any imposed loads that was converted by viia_convert_imposed_loads_to_self_weight. The part of the self weight for the imposed loads is removed from the density material of the surface shape. The 'psi' and 'phi2' factors will be removed. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - The masses incorporated in the density of the material object are re-converted to loads. The material applied will be updated. - The imposed loads again created in the model and project.collections. The information used should be stored in the meta-data of the shape. - The load-combination for imposed loads is created. """ # Set default SI units for dat-file in DIANA if project.rhdhvDIANA.run_diana and project.rhdhvDIANA.model_created: project.set_standard_units() counter = 0 for shape in project.collections.shapes: if shape.meta_data is not None and 'imposed loads' in shape.meta_data: for item in shape.meta_data['imposed loads']: # Create the imposed load imposed_load = project.viia_create_loads( load='Imposed load', category=item['category'], connecting_shapes=[shape]) # Check the created load if imposed_load.meta_data['imposed load']['category'] != item['category'] or \ imposed_load.meta_data['imposed load']['phi'] != item['phi'] or \ imposed_load.meta_data['imposed load']['psi2'] != item['psi2']: raise ValueError("ERROR: The converted values do not comply the originals. Something went wrong.") # Counting the number of correctly created imposed loads counter += 1 # Remove the load from the self weight of the shape if isinstance(shape, Surfaces): project.viia_adjust_mass_of_surface( load=-item['value'] * item['phi'] * item['psi2'], surface=shape) # Remove the info on the shape for the imposed loads in the self weight del shape.meta_data['imposed loads'] # Check if any conversion is performed if counter == 0: raise ValueError("ERROR: No imposed loads modelled as self weight, please check before proceding.") # Create the load-combination for the static calculation viia_create_load_combination_static(project)
[docs]def viia_create_loads( project: ViiaProject, load: str, force_value: Optional[float] = None, force_direction: Optional[Union[Direction, List[float], str]] = None, connecting_shapes: Optional[List[Shapes]] = None, mode: Optional[List[int]] = None, ref_line_load: Optional[float] = None, category: Union[str, List[str]] = None, displacement_signals: Union[Path, str, Dict] = None): """ This function creates a given predefined load, coupled to the requested load-types for the NLTH and NLPO. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - load (str): determines which load is applied, available are default project loads: 'Self weight', 'Imposed load', 'Resting load', 'Rob-test', 'Base motion', 'Push_over_equivalent_acc', 'Push_over_modal', 'Push_over_uniform' and 'MRS_loads'. Based on this load selection additional arguments may be required. - load_value (float): Value of the load acting in negative z-direction if shape is a surface in [N/m2], if shape is a line in [N/m] or if shape is a point in [N]. A vertical load that acts downward should have a positive value. For the NLPO analysis, the default value is unit acceleration, in [m/s2]. - force_direction (obj): Object reference of direction of force Alternative (list of 3 floats or float): Direction of force, for example [1,0,0] Alternative (str): Direction can also be given as 'X', 'Y', or 'Z', or name of a direction of which an object is already created. Alternative (str): For the Redis pushover, provide one of the following: 'X_pos', 'X_neg', 'Y_pos' or 'Y_neg'. - connecting_shapes (list of objects): List with the object references on which the load will be applied. - mode (list of int): Integer of which mode is considered for modal pushover load-case. First item is x-direction dominating mode, second is Yy-direction dominating mode. - ref_line_load (float): Reference line-load in [N/m] for the case of the triangular load-case, see NLPO protocol. - category (str): The category of the imposed loads, refer to the basis of design (UPR). The category can be (only one category can be chosen): 'AFloor', 'AStairs', ABalc', 'BOffice', 'CSchool', 'C1_2', 'C3_5', 'DShop', 'E', 'F', 'HRoof'. - displacement_signals (Path, str or dict): Json file containing the displacement signals for the displacement controlled Redis push-over. Output: - Load object created in PY-memory if not yet present in project.collections.loadsets. - Geometry load is added to the model in DIANA if software is DIANA and model created. """ # Create load self weight if load == 'Self weight': if not any(load_object.name == 'Self weight' for load_object in project.collections.loads): model_load = project.create_model_load( name='Self weight', load_case=project.create_load_case(name='Dead load'), load_type='self-weight') project.write_log("Geometry load-case self weight is added to the model.") return model_load # Create imposed loads elif load == 'Imposed load': counter = 1 for _load in project.collections.loads: if 'Imposed' in _load.name: counter += 1 if not category: project.write_log("Error: Provide the category for imposed load.") return None if type(category) is not str: project.write_log("Error: Provide a single category for imposed load (not a list).") return None imposed_load = viia_get_imposed_loadvalue(project, category) if not connecting_shapes: project.write_log("Error: Provide surface shapes for imposed load.") return None surface_ref = list() for connecting_shape in connecting_shapes: surface_ref.append({ 'connecting_shape': connecting_shape, 'shape_geometry': connecting_shape.contour}) distributed_load = project.create_surface_load( name='Imposed-' + str(counter).zfill(3), load_type='force', value=imposed_load['value'], direction=project.create_direction(name='-Z'), connecting_shapes=surface_ref, load_case=project.create_load_case(name='Imposed loads')) # Store parameters in meta-data distributed_load.add_meta_data({ 'imposed load': { 'as_load': True, 'category': category, 'phi': imposed_load['phi'], 'psi2': imposed_load['psi2']}}) return distributed_load # Create resting loads elif load == 'Resting load': for shape in connecting_shapes: if shape in project.collections.floors: project.viia_create_surface_mass_floor(floor=shape, resting_load=category, extra_load=force_value) elif shape in project.collections.walls: project.viia_create_surface_mass_wall(wall=shape, extra_load=force_value) # Create load for the 'Rob-test' elif load == 'Rob-test': if not any(load_case.name == 'Rob-test' for load_case in project.collections.load_cases): counter = 1 # Apply deformation on surfaces of shallow foundation, if present for supported_surface_shape in project.viia_supported_surfaces: project.create_surface_load( name='Rob-test verplaatsingen ' + str(counter).zfill(3), load_type='translation', value=1.00000E+00, direction=project.create_direction(name='-Z'), connecting_shapes=[{ 'connecting_shape': supported_surface_shape}], load_case=project.create_load_case(name='Rob-test')) counter += 1 # Apply deformation on pile support, if present if len(project.collections.piles) > 0: for pile in project.collections.piles: bottom_node = pile.contour.get_bottom_node() project.create_point_load( name='Rob-test verplaatsingen ' + str(counter).zfill(3), load_type='translation', value=1.00000E+00, direction=project.create_direction(name='-Z'), connecting_shapes=[{ 'connecting_shape': pile, 'shape_geometry': bottom_node}], load_case=project.create_load_case(name='Rob-test')) counter += 1 project.write_log("Geometry load-case for Rob-test is added to the model.") # Create the load for the base excitation elif load == 'Base motion': project.create_model_load( load_type='base-excitation', load_case=project.create_load_case(name='Base motion X'), direction=project.create_direction(name='X'), value=1, name='Base motion X') project.create_model_load( load_type='base-excitation', load_case=project.create_load_case(name='Base motion Y'), direction=project.create_direction(name='Y'), value=1, name='Base motion Y') project.create_model_load( load_type='base-excitation', load_case=project.create_load_case(name='Base motion Z'), direction=project.create_direction(name='Z'), value=1, name='Base motion Z') project.write_log("Geometry load-case for flexbase base motion is added to the model.") # Create the load for sub-system push over using equivalent acceleration elif load == 'Push_over_equivalent_acc': # Get the VIIA load setting for unit acceleration force force_value = _get_viia_load_constants(project=project)['NLPO loads']['unit acceleration'] # Find x_walls and y_walls x_walls = \ [obj for obj in project.collections.walls if [round(abs(i), 1) for i in obj.normal_vector()] == [0, 1.0, 0]] y_walls = \ [obj for obj in project.collections.walls if [round(abs(i), 1) for i in obj.normal_vector()] == [1.0, 0, 0]] # Find floors except groudfloor and foundation all_floors = \ [obj for obj in project.collections.floors if [round(abs(i), 1) for i in obj.normal_vector()] == [0, 0, 1.0]] floors = [obj for obj in all_floors if obj.name[:2] not in ['N0', 'FU']] # Extend with possible regions x_walls.extend([region for obj in x_walls for region in obj.regions]) y_walls.extend([region for obj in y_walls for region in obj.regions]) floors.extend([region for obj in floors for region in obj.regions]) # Get shapes which should be excluded roof_floor = project.viia_get_flat_roofs() foundation_walls = [wall for wall in project.collections.walls if 'FUNDERINGSWAND' in wall.name] # Assign the default for multi-line load-case connecting_shapes_x = [ {'connecting_shape': wall} for wall in x_walls if wall not in foundation_walls and wall.material.__class__.__name__ == 'Masonry'] connecting_shapes_y = [ {'connecting_shape': wall} for wall in y_walls if wall not in foundation_walls and wall.material.__class__.__name__ == 'Masonry'] connecting_shapes_floors = [{'connecting_shape': floor} for floor in floors if floor not in roof_floor] # +X project.create_body_load( name='x_wall_+', load_case=project.create_load_case(name='NLPO_equi_acc_+X'), value=force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes_x) project.create_body_load( name='x_floor_+', load_case=project.create_load_case(name='NLPO_equi_acc_+X'), value=force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes_floors) # -X project.create_body_load( name='x_wall_-', load_case=project.create_load_case(name='NLPO_equi_acc_-X'), value=-force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes_x) project.create_body_load( name='x_floor_-', load_case=project.create_load_case(name='NLPO_equi_acc_-X'), value=-force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes_floors) # +Y project.create_body_load( name='y_wall_+', load_case=project.create_load_case(name='NLPO_equi_acc_+Y'), value=force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes_y) project.create_body_load( name='y_floor_+', load_case=project.create_load_case(name='NLPO_equi_acc_+Y'), value=force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes_floors) # -Y project.create_body_load( name='y_wall_-', load_case=project.create_load_case(name='NLPO_equi_acc_-Y'), value=-force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes_y) project.create_body_load( name='y_floor_-', load_case=project.create_load_case(name='NLPO_equi_acc_-Y'), value=-force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes_floors) project.write_log("Geometry load-case for push over (multi-line) is added to the model.") # Create the load for multiline push over using a triangular load elif load == 'Push_over_triangular_load': # Find x_walls and y_walls x_walls = [obj for obj in project.collections.walls if 'N0-' in obj.name and [round(abs(i), 1) for i in obj.normal_vector()] == [0, 1.0, 0] and obj.material.__class__.__name__ == 'Masonry'] y_walls = [obj for obj in project.collections.walls if 'N0-' in obj.name and [round(abs(i), 1) for i in obj.normal_vector()] == [1.0, 0, 0] and obj.material.__class__.__name__ == 'Masonry'] # NOTE: Extending with regions not required, as loads are applied to top edges # Define the highest z coordinate in all x_walls to determine the height of the unit load. z_coord_x = [] for x_wall in x_walls: for node in x_wall.contour.get_nodes(): if round(node.coordinates[2], 2) not in z_coord_x: z_coord_x.append(round(node.coordinates[2], 2)) z_coord_max_x = max(z_coord_x) # Define the force value for all X walls: for x_wall_idx, x_wall in enumerate(x_walls): # Determine the highest z-value of the wall z_wall_coordinates = [coord[2] for coord in x_wall.get_points()] z_top_wall = max(z_wall_coordinates) if fem_smaller(z_top_wall, 0): # The load cannot be found for zero or negative heights continue # Get the line objects for the top edges x_top_wall_edges = x_wall.get_top_edges() # Define the force value of the wall x_load_value = z_top_wall / z_coord_max_x * ref_line_load # Create the +X line loads for edge_idx, top_edge in enumerate(x_top_wall_edges): project.create_line_load( load_type='force', value=x_load_value, direction=project.create_direction(name='x'), load_case=project.create_load_case(name='NLPO_triangular_load_+X'), name=f'x_wall_+_{x_wall_idx + 1}_{edge_idx + 1}', connecting_shapes=[{'connecting_shape': x_wall, 'shape_geometry': top_edge}]) # Create the -X line loads for edge_idx, top_edge in enumerate(x_top_wall_edges): project.create_line_load( load_type='force', value=-x_load_value, direction=project.create_direction(name='x'), load_case=project.create_load_case(name='NLPO_triangular_load_-X'), name=f'x_wall_-_{x_wall_idx + 1}_{edge_idx + 1}', connecting_shapes=[{'connecting_shape': x_wall, 'shape_geometry': top_edge}]) # Define the highest z coordinate in all y_walls to determine the height of the unit load. z_coord_y = [] for y_wall in y_walls: for node in y_wall.contour.get_nodes(): if round(node.coordinates[2], 2) not in z_coord_y: z_coord_y.append(round(node.coordinates[2], 2)) z_coord_max_y = max(z_coord_y) # Define the force value for all Y walls: for y_wall_idx, y_wall in enumerate(y_walls): # Determine the highest z-value of the wall z_wall_coordinates = [coord[2] for coord in y_wall.get_points()] z_top_wall = max(z_wall_coordinates) if fem_smaller(z_top_wall, 0): # The load cannot be found for zero or negative heights continue # Get the line objects for the top edges y_top_wall_edges = y_wall.get_top_edges() # Define the force value of the wall y_load_value = z_top_wall / z_coord_max_y * ref_line_load # Create the +Y line loads for edge_idx, top_edge in enumerate(y_top_wall_edges): project.create_line_load( load_type='force', value=y_load_value, direction=project.create_direction(name='y'), load_case=project.create_load_case(name='NLPO_triangular_load_+Y'), name=f'y_wall_+_{y_wall_idx + 1}_{edge_idx + 1}', connecting_shapes=[{'connecting_shape': y_wall, 'shape_geometry': top_edge}]) # Create the -Y line loads for edge_idx, top_edge in enumerate(y_top_wall_edges): project.create_line_load( load_type='force', value=-y_load_value, direction=project.create_direction(name='y'), load_case=project.create_load_case(name='NLPO_triangular_load_-Y'), name=f'y_wall_-_{y_wall_idx + 1}_{edge_idx + 1}', connecting_shapes=[{'connecting_shape': y_wall, 'shape_geometry': top_edge}]) project.write_log("Geometry load-case for push over (multi-line) is added to the model.") # Create the load for single line push over using modal pushover load elif load == 'Push_over_modal': project.project_specific['modes'][0] = mode[0] project.project_specific['modes'][1] = mode[1] # Get the VIIA load setting for unit acceleration force force_value = _get_viia_load_constants(project=project)['NLPO loads']['unit acceleration'] # Attach to the floor all_floors = \ [obj for obj in project.collections.floors if [round(abs(i), 1) for i in obj.normal_vector()] == [0, 0, 1.0]] floors = [obj for obj in all_floors if obj.name[:2] not in ['N0', 'FU']] # Extend with possible regions floor_regions = [] for obj in floors: if obj.regions is not None: for region in obj.regions: floor_regions.append(region) floors.extend(floor_regions) connecting_shapes = [{'connecting_shape': floor} for floor in floors] # ±X,Y directions project.create_body_load( load_type='modal-pushover', name='all_x_+', load_case=project.create_load_case(name='NLPO_modal_po_+X'), value=force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes, mode=mode[0]) project.create_body_load( load_type='modal-pushover', name='all_x_-', load_case=project.create_load_case(name='NLPO_modal_po_-X'), value=-force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes, mode=mode[0]) project.create_body_load( load_type='modal-pushover', name='all_y_+', load_case=project.create_load_case(name='NLPO_modal_po_+Y'), value=force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes, mode=mode[1]) project.create_body_load( load_type='modal-pushover', name='all_y_-', load_case=project.create_load_case(name='NLPO_modal_po_-Y'), value=-force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes, mode=mode[1]) project.write_log("Geometry load-case for push over (single-line) is added to the model.") # Create the load for single line push over using uniform load elif load == 'Push_over_uniform': # Get the VIIA load setting for unit acceleration force force_value = _get_viia_load_constants(project=project)['NLPO loads']['unit acceleration'] # Connecting to floors all_floors = [obj for obj in project.collections.floors if [round(abs(i), 1) for i in obj.normal_vector()] == [0, 0, 1.0]] floors = [obj for obj in all_floors if obj.name[:2] not in ['N0', 'FU']] # Extend with possible regions floor_regions = [] for obj in floors: if obj.regions is not None: for region in obj.regions: floor_regions.append(region) floors.extend(floor_regions) connecting_shapes = [{'connecting_shape': floor} for floor in floors] # ±X, Y project.create_body_load( name='floor_x_+', load_case=project.create_load_case(name='NLPO_uniform_acc_+X'), value=force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes) project.create_body_load( name='floor_x_-', load_case=project.create_load_case(name='NLPO_uniform_acc_-X'), value=-force_value, direction=project.create_direction(name='x'), connecting_shapes=connecting_shapes) project.create_body_load( name='floor_y_+', load_case=project.create_load_case(name='NLPO_uniform_acc_+Y'), value=force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes) project.create_body_load( name='floor_y_-', load_case=project.create_load_case(name='NLPO_uniform_acc_-Y'), value=-force_value, direction=project.create_direction(name='y'), connecting_shapes=connecting_shapes) project.write_log("Geometry load-case for push over (single-line) is added to the model.") elif load == 'MRS_loads': # Check if load-case is already created if not any(load_case.name == 'MRS-loads' for load_case in project.collections.load_cases): # Creating load-case for MRS in x-direction project.create_model_load( load_type='base-excitation', load_case=project.create_load_case(name='MRS_x_direction'), direction=project.create_direction(name='X'), value=1, name='MRS_x_direction') # creating load-case for 'MRS_y_direction' project.create_model_load( load_type='base-excitation', load_case=project.create_load_case(name='MRS_y_direction'), direction=project.create_direction(name='Y'), value=1, name='MRS_y_direction') project.write_log("Model load (base-excitation) for MRS in x- and y-direction is added to the model.") else: project.write_log( "WARNING: MRS loads are already created, function to create loads is aborted. Please check.") # All other loads are created as dead load else: project.viia_create_dead_loads( shapes_list=connecting_shapes, load_value=force_value, force_direction=force_direction)
[docs]def viia_remove_loads(project: ViiaProject, load_type: str): """ This function removes a given predefined load, coupled to the requested load-types. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - load_type (str): name of the load that needs to be removed, available are: "Self weight", "Imposed load", "Rob Tests", "Base Motion", 'Push_over_equivalent_acc', 'Push_over_modal' and 'Push_over_uniform'. Other options are also possible such as: 'force', 'moment', 'projected-force', 'translation', 'rotation', 'hydrostatic', 'thermal', 'rotational-acceleration', 'self-weight', 'acceleration', 'centrifugal-force', 'base-excitation'. Output: - Geometry load is removed from the model and in DIANA. """ # Convenient mappings load_map = { "Self weight": 'self-weight', "Imposed load": 'force', "Base Motion": 'base-excitation'} load_type_mapped = load_map.get(load_type, load_type) removed = [] supports = [] if load_type_mapped == "Rob-test": for load in project.collections.loads: if "Rob-test verplaatsingen" in load.name: removed.append(load) elif load_type_mapped == 'Push_over_modal': for load in project.collections.loads: if load.name in ['all_x_+', 'all_x_-', 'all_y_+', 'all_y_-']: removed.append(load) project.write_log("Geometry load-case for modal pushover is removed from the model") elif load_type_mapped == 'Push_over_uniform': for load in project.collections.loads: if load.name in ['floor_y_-', 'floor_y_+', 'floor_x_-', 'floor_x_+']: removed.append(load) project.write_log("Geometry load-case for modal pushover is removed from the model.") else: for load in project.collections.loads: if load.load_type == load_type_mapped: removed.append(load) if len(removed) > 0: while len(removed) > 0: load = removed.pop(0) load.remove_load() while len(supports) > 0: support = supports.pop(0) support.remove_support() project.write_log(f"Geometry load type ({str(load_type)}) is removed from the model.") else: project.write_log(f"ERROR: {str(load_type)} for removal is not recognized, check for typing error.")
[docs]def viia_add_basemotion_signals( project: ViiaProject, signal_list: Optional[Union[List[int], str]] = None, use_unmatched_signals: bool = False) -> TimeseriesCombinationSet: """ This function applies the accelerations on the supports (surface of the shallow foundation, or piles). It can be applied in fixed or flexbase calculations. The normalised signals will be used to find the correct signal for the building, using the cluster and the AgS of the location. .. warning:: Some signals can have deviating time-steps. Please check the log. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - signal_list (list): Specification of the list of signals which need to be imported into PY-memory. Default value None, applying the signals 1, 2, 3, 5, 6, 7 and 9 as the other signals have deviating time-steps. Items in list should be integers 1 to 11. Default value is None, selecting default selection of signals. Input can also be provided as string, only 'default' is available. - use_unmatched_signals (bool): Switch to use the signals which an unmatched z-component. Default value False. The matched signals are used by default. Output: - Signals are added to class of TimeseriesCombinationSet, notification is given. """ # Check if the base motions are already loaded timeseries_combination_set = None if project.collections.timeseries_combination_sets: if len(project.collections.timeseries_combination_sets) > 1: present = ', '.join([comb_set.name for comb_set in project.collections.timeseries_combination_sets]) raise RuntimeError( "ERROR: Unexpected number of timeseries combination sets present in the model. This is not the default " f"workflow and breaks the VIIA logics. Timeseries present: {present}.") timeseries_combination_set = project.collections.timeseries_combination_sets[0] # Check input list of signals, if not provided default set will be used # Signals 4, 8, 10 and 11 have been left out in the default list, since these have deviating time-steps # (rep. 0.0024 and 0.01). if signal_list is None or signal_list in ['Default', 'default']: signal_list = project.project_specific['signal']['default signals'] # Cluster cluster = project.project_information['cluster_nen'] if not cluster: raise ValueError("ERROR: The cluster is not specified in project.project_information['cluster_nen'].") # Peak ground acceleration ags = 0 for spectrum in project.collections.response_spectra: if '2475' in spectrum.name: ags = spectrum.peak_ground_acceleration break # Factor for the number of applied signals nlth_factor = 0 if len(signal_list) > 6: nlth_factor = 1.1 if len(signal_list) > 10: nlth_factor = 1.0 if use_unmatched_signals: _sig = _sig_unmatched else: _sig = _sig_matched # Creating objects in Timeseries class base_signals = {} base_signal_set = [] for signal in signal_list: if isinstance(signal, int): pass elif isinstance(signal, str) and signal[0] == 'S': signal = int(signal.replace('S', '')) else: raise ValueError( f"ERROR: Input for signal is expected to be an integer or string (format 'S1'). Provided was {signal}") if signal not in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]: raise ValueError(f"ERROR: Signal should be in the range of S1 to S11, provided was S{signal}.") # Check if base-motion is already present if timeseries_combination_set: is_present = False for combination in timeseries_combination_set.timeseries_combinations: if f'base_signal_S{signal}' == combination.name: is_present = True break if is_present: continue base_signals[f'S{signal}'] = {} time_signal = deepcopy(getattr(_sig, f'cluster{cluster}_time_signal_{signal}')) prev_num = 0 for i, num in enumerate(time_signal): # check if signal has different time steps if i != 0 and round(num - prev_num, 10) != round(0.005, 10): project.write_log( f"WARNING: Signal {signal} of cluster {cluster} has deviating time steps. Step size is " f"{round(num - prev_num, 10)}. It is not handled automatically in the viiaPackage. Adjust your " f"dcf or choose another signal.") break prev_num = deepcopy(num) for direction in ['x', 'y', 'z']: base_signals[f'S{signal}'][direction] = project.create_timeseries_function( time_series=time_signal, value_series=[ factor * ags * project.importance_factor * nlth_factor for factor in deepcopy(getattr(_sig, f'cluster{cluster}_factor_{direction}_signal_{signal}'))], name=f'base_signal_{signal}_{direction}') # Due to issue in DIANA10.5 the accelerations at t=0 are set to zero base_signals[f'S{signal}']['x'].value_series[0] = 0.0 base_signals[f'S{signal}']['y'].value_series[0] = 0.0 base_signals[f'S{signal}']['z'].value_series[0] = 0.0 base_signal_set.append(project.create_timeseries_combination( x_direction=base_signals[f'S{signal}']['x'], y_direction=base_signals[f'S{signal}']['y'], z_direction=base_signals[f'S{signal}']['z'], name=f'base_signal_S{signal}')) if timeseries_combination_set is None: return project.create_timeseries_combination_set( timeseries_combinations=base_signal_set, name=f"base_signals_cluster{cluster}") if base_signal_set: for combination in base_signal_set: timeseries_combination_set.add_timeseries_combination(timeseries_combination=combination) return timeseries_combination_set
### =================================================================================================================== ### 6. Changing the time-signal ### ===================================================================================================================
[docs]def viia_change_signal(project: ViiaProject, new_signal: str, nr_time_steps: Optional[int] = None): """ This function enables to switch the signal in the model. Signals to be applied can be signal 1 to 11, although signal 8-11 should be provided first as input. Function works on base-signals and normal signals, depending on the available geometry load combinations. The following items are updated: - The material properties of the soil are adjusted (Rayleigh damping factors). - The time dependent factors for the load with the seismic signals is updated to the new signal. - The number of time-steps in the analysis are adjusted appropriately. - The requested output-items for time-steps is adjusted appropriately. - In the current analysis folder the adjusted dat- and dcf-files are updated. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - new_signal (str): Name of the signal to be applied, for example: 'S2'. - nr_time_steps (int): Overrule the number of time-steps to be used in the NLTH analysis. Default value None, time-steps are determined based on the length of the signal. Output: - The signal and dependent properties are updated in the model. - Notification and logging of changes. """ # Check requested signal if new_signal not in ['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9', 'S10', 'S11']: project.write_log(f"ERROR: New signal {new_signal} not recognised, use format 'S1' to 'S11'") return None if project.project_specific['signal']['current signal'] is None: project.write_log("ERROR: No signal present, signal can not be changed.") return None if project.project_specific['signal']['current signal'] == new_signal: project.write_log(f"WARNING: Signal {new_signal} already loaded, no changes performed.") return None # Check if the requested signal is provided for S8-S11 current_signals = [] current_cluster = project.project_information['cluster_nen'] for timeseries_combination_set in project.collections.timeseries_combination_sets: if timeseries_combination_set.name.split('_')[-1] == 'cluster' + current_cluster: current_signals = [ts.name.split('_')[-1] for ts in timeseries_combination_set.timeseries_combinations] if (any('Base motion' in load_combination.name for load_combination in project.collections.load_combinations) and new_signal not in current_signals) or \ (any('Earthquake' in load_combination.name for load_combination in project.collections.load_combinations)): project.write_log("ERROR: First provide signal as input.") return # Check if base motion is present, then these will be removed and replaced. if any('Base motion' in load_combination.name for load_combination in project.collections.load_combinations): viia_create_load_combination_base_motion(project, new_signal) elif any('Earthquake' in load_combination.name for load_combination in project.collections.load_combinations): viia_create_load_combination_earthquake(project, new_signal) # Adjust the Rayleigh parameters of the soil i = int(new_signal.split('S')[-1]) file_name = project.project_specific['soilblock']['soil data file'] _SRAData = __import__(file_name) rayleigh_parameters = _SRAData.RAYLEIGH[i - 1] old_rayleigh_parameters = [0, 0] project.set_standard_units() for material in project.collections.materials: if 'GRONDLAAG' in material.name: # Update the Rayleigh parameters in the material dictionary of soils (PY-memory) old_rayleigh_parameters = material.material_properties['rayleigh damping parameters'] # Update Rayleigh damping parameters a [rad/s] and b [s/rad] material.material_properties.update({'rayleigh damping parameters': rayleigh_parameters}) # Update the Rayleigh parameters in the DIANA model # This function updates the material in geometry and meshed model if available project.rhdhvDIANA.setParameter('MATERIAL', material.name, 'RAYDAM/RAYLEI', rayleigh_parameters) # Logging project.write_log(f"Rayleigh parameters have been changed for signal {str(new_signal)}.") project.write_log( f"Rayleigh parameters were {str(old_rayleigh_parameters)} and have been changed to " f"{str(rayleigh_parameters)}.", print_message=False) # There are no load-combinations with correct names present, function is aborted. else: project.write_log("ERROR: No seismic load-combinations present, signal cannot be changed.") return # Update current signal status project.project_specific['signal']['current signal'] = new_signal # Determine signal length of new signal time_steps = viia_get_time_steps_nlth(project=project, nr_time_steps=nr_time_steps) # Determine the analysis that are available in DIANA and which in PY-memory analyses_in_diana = project.rhdhvDIANA.analyses() if analyses_in_diana is None: analyses_in_diana = [] analyses_in_py = [analysis_obj.name for analysis_obj in project.collections.analyses] unknown_analyses = [elem for elem in analyses_in_diana if elem not in analyses_in_py] for unknown_analysis in unknown_analyses: project.write_log( f"WARNING: Analysis ({unknown_analysis}) in DIANA not recognised. This analysis will not be updated for " f"the new signal.") # Update the timestep_size attribute of the transient execute blocks in the analyses for analysis in project.collections.analyses: for block in analysis.calculation_blocks: time_block = False if isinstance(block, NonlinearAnalysis): for execute_block in block.execute_blocks: if isinstance(execute_block.steps, TimeSteps): time_block = True if execute_block.steps.steps != time_steps: execute_block.steps.steps = time_steps project.write_log( f"Signal length has been updated in analysis: {analysis.name} (from " f"{execute_block.steps.steps} to {time_steps}).") if time_block: for output_block in block.output_blocks: output_block.steps = ViiaSettings.output_steps( output_name=output_block.name, nr_steps=time_steps[0][-1])
### =================================================================================================================== ### 7. Load-combinations ### ===================================================================================================================
[docs]def viia_create_load_combination_static(project: ViiaProject): """ This function creates the geometry load combination ('DL') for static situation in DIANA. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combination object is created if it is not present yet. - Load-combination is created in DIANA (if software is DIANA and model is created) """ # Find or create the load-case for dead loads if not any(load_case.name == 'Dead load' for load_case in project.collections.load_cases): project.create_load_case(name='Dead load') load_case = 'Dead load' for load_case_object in project.collections.load_cases: if load_case_object.name == 'Dead load': load_case = load_case_object # Check if imposed loads are applied load_case_imposed = None for load_case_object in project.collections.load_cases: if load_case_object.name == 'Imposed loads': load_case_imposed = load_case_object # Create the load-combination for dead load if it not exists yet if not any(load_combination.name == 'DL' for load_combination in project.collections.load_combinations): project.create_load_combination(name='DL', factors={load_case: 1.0}) # Create the load-combination for imposed loads if it not exists yet and if it is required if load_case_imposed and not \ any(load_combination.name == 'VAR' for load_combination in project.collections.load_combinations): project.create_load_combination(name='VAR', factors={load_case_imposed: 1.0})
[docs]def viia_create_load_combination_rob_test(project: ViiaProject): """ This function creates the geometry load combination for the Rob-test situation in DIANA. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combination object is created if it is not present yet. - Load-combination is created in DIANA (if software is DIANA and model is created) """ # Find or create the load-case for the rob test if not any(load_case.name == 'Rob-test' for load_case in project.collections.load_cases): project.create_load_case(name='Rob-test') load_case = 'Rob-test' for load_case_object in project.collections.load_cases: if load_case_object.name == 'Rob-test': load_case = load_case_object # Create the load-combination if it not exists yet if not any(load_combination.name == 'Rob-test' for load_combination in project.collections.load_combinations): project.create_load_combination(name='Rob-test', factors={load_case: 1.0})
[docs]def viia_create_load_combination_response_spectrum(project: ViiaProject) -> List[LoadCombination]: """ This function creates the geometry load combinations for modal response spectrum analysis, the same name is retained from corresponding load-cases for x- and y-directions. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combination object is created if it is not present yet. - Load-combination is created in DIANA (if software is DIANA and model is created) """ # Find or create the load-case for dead loads if not any(load_case.name == 'Dead load' for load_case in project.collections.load_cases): project.create_load_case(name='Dead load') load_case = 'Dead load' for load_case_object in project.collections.load_cases: if load_case_object.name == 'Dead load': load_case = load_case_object # Removing load-combinations for load_combination in project.collections.load_combinations: if load_combination.name != 'DL': project.viia_remove_load_combination(load_combination_name=load_combination.name) # Create the load-combination if it not exists yet if not any( load_combination_object.name == 'DL' for load_combination_object in project.collections.load_combinations): project.create_load_combination(name='DL', factors={load_case: 1.0}) # Determine the importance factor based on consequence class importance_factor = _get_viia_importance_factor(project=project) # Collect the response spectrum for 2475 yr return period response_spectrum = None for spectrum in project.collections.response_spectra: if '2475' in spectrum.name: response_spectrum = spectrum break if response_spectrum is None: raise ValueError( f"ERROR: Seismic parameters could not be collected. Please make sure to load the required response " f"spectrum for 2475 yr return period.") data = response_spectrum.response_spectrum_parameters(q_factor=1.0, importance_factor=importance_factor, n=40) frequency_dependent_factors_dict = {} frequency_dependent_factors_frequency = [] frequency_dependent_factors_acceleration = [] for item in data: frequency_dependent_factors_dict[data[item]['frequency']] = data[item]['acceleration'] for item in sorted(frequency_dependent_factors_dict): frequency_dependent_factors_frequency.append(item) frequency_dependent_factors_acceleration.append(frequency_dependent_factors_dict[item]) frequency_dependent_factors = [frequency_dependent_factors_frequency, frequency_dependent_factors_acceleration] # Create load-combinations for modal response-spectrum analyses load_combinations = [] for load_case in project.collections.load_cases: if load_case.name in ['MRS_x_direction', 'MRS_y_direction']: load_combinations.append(project.create_load_combination( name=load_case.name, factors={load_case: 1.0}, frequency_dependent_load_factors=frequency_dependent_factors)) # Check if two load-combinations have been created if len(load_combinations) != 2: raise ValueError( f"ERROR: In the process of creating the load-combinations for MRS, the number of created items " f"'{len(load_combinations)}' is not equal to the expected number of items: 2. Please check the code before " f"proceeding with the analysis.") # Return the newly created load-combinations specific for MRS return load_combinations
[docs]def viia_create_load_combination_push_over(project: ViiaProject): """ This function creates the geometry load combinations for push over analysis, the same name is retained from corresponding load-cases for ±xy directions, both single and multi line cases. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combination object is created if it is not present yet. - Load-combination is created in DIANA (if software is DIANA and model is created) """ # Find or create the load-case for dead loads if not any(load_case.name == 'Dead load' for load_case in project.collections.load_cases): project.create_load_case(name='Dead load') load_case = 'Dead load' for load_case_object in project.collections.load_cases: if load_case_object.name == 'Dead load': load_case = load_case_object # Create the load-combination if it not exists yet if not any(load_combination.name == 'DL' for load_combination in project.collections.load_combinations): project.create_load_combination(name='DL', factors={load_case: 1.0}) # Combine the rest load with the dead load for load_case in project.collections.load_cases: if load_case.name != 'Dead load': project.create_load_combination(name='{}'.format(load_case.name), factors={load_case: 1.0})
[docs]def viia_create_load_combination_base_motion(project: ViiaProject, signal='S1'): """ This function creates the load-combinations for the seismic base motion situation in DIANA. If the dead load combination is not present yet, it will also be added first. The load-combinations with time signals are applied in the x-, y- and z-direction (3 load-combinations are created with the name 'Base motion' in it). Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - signal (str): Signal number of the signal to be applied, available signals are 'S1' to 'S11'. Default value is signal 1 'S1'. Note that you can only select the signals for which you provided the details in viia_add_basemotion_signals. Output: - 3 load-combination (x-, y- and z-direction) objects are updated with new signal and adjusted name. - If no Base motion combination is present the geometry load-combinations are created in DIANA (if software is DIANA and model created). - If these are already present, the geometry load-combination are updated (name and signal), and if the model is already meshed, it also updates the load-combinations in mesh. - The status variable 'CurrentSignal' in 'Project' is updated to the new signal. """ # Check if dead load (with self weight) is created viia_create_load_combination_static(project) # Select the signal current_cluster = project.project_information['cluster_nen'] timeseries_x, timeseries_y, timeseries_z = None, None, None for timeseries_combination_set in project.collections.timeseries_combination_sets: if timeseries_combination_set.name.split('_')[-1] == 'cluster' + current_cluster: timeseries_x = timeseries_combination_set.get_timeseries_combination(f'base_signal_{signal}').x_direction timeseries_y = timeseries_combination_set.get_timeseries_combination(f'base_signal_{signal}').y_direction timeseries_z = timeseries_combination_set.get_timeseries_combination(f'base_signal_{signal}').z_direction else: raise KeyError(f"ERROR: The cluster {current_cluster} is not in the base signals group list.") if timeseries_x is None or timeseries_y is None or timeseries_z is None: raise KeyError(f"ERROR: The timeseries for {current_cluster} cluster and signal {signal} could not be found.") # Increase the length of the signal with zero time dependent load factors nr_zeros = int(project.project_specific['signal']['extra time'] / 0.005) + 1 time = timeseries_x.time_series[-1] add_time = [] add_values = [] for i in range(nr_zeros): time += 0.005 add_time.append(time) add_values.append(0) timeseries_x.time_series += add_time timeseries_x.value_series += add_values timeseries_y.value_series += add_values timeseries_z.value_series += add_values # Check if there is already a seismic base motion load-combination present, it contains 'Base motion' in the name check_bool = True for load_combination_object in project.collections.load_combinations: if 'Base motion' in load_combination_object.name: check_bool = False break if check_bool: # Create load-combinations if seismic base motion load-combination is not present def _load_cases(load_case_name): """ Find the load-case for the base motions (they should be present).""" for load_case_object in project.collections.load_cases: if load_case_object.name == load_case_name: return load_case_object project.write_log("ERROR: First provide the load-cases for the base motions.") return load_case_name # Base motion application in x-direction project.create_load_combination( name=f'Base motion X {signal}', factors={_load_cases('Base motion X'): 1.0}, time_dependent_load_factors=timeseries_x) # Base motion application in y-direction project.create_load_combination( name=f'Base motion Y {signal}', factors={_load_cases('Base motion Y'): 1.0}, time_dependent_load_factors=timeseries_y) # Base motion application in z-direction project.create_load_combination( name=f'Base motion Z {signal}', factors={_load_cases('Base motion Z'): 1.0}, time_dependent_load_factors=timeseries_z) else: # Update load-combinations if seismic base motion load-combination for new signal for load_combination_object in project.collections.load_combinations: if 'Base motion X' in load_combination_object.name: load_combination_object.name = f'Base motion X {signal}' load_combination_object.time_dependent_load_factors = timeseries_x elif 'Base motion Y' in load_combination_object.name: load_combination_object.name = f'Base motion Y {signal}' load_combination_object.time_dependent_load_factors = timeseries_y elif 'Base motion Z' in load_combination_object.name: load_combination_object.name = f'Base motion Z {signal}' load_combination_object.time_dependent_load_factors = timeseries_z # Set the currently applied signal in Project class project.project_specific['signal']['current signal'] = signal
[docs]def viia_create_load_combination_earthquake(project: ViiaProject, signal='S1'): """ This function creates the load-combinations for the seismic situation in DIANA. If the dead load combination is not present yet, it will also be added first. The load-combinations with time signals are applied in the x-, y- and z-direction (3 load-combinations are created with the name 'Earthquake' in it). Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - signal (str): Signal number of the signal to be applied, available signals are 'S1' to 'S11'. Default value is signal 1 'S1'. Output: - 3 load-combination (x-, y- and z-direction) objects are updated with new signal and adjusted name. - If no Earthquake combination is present the geometry load-combinations are created in DIANA (if software is DIANA and model created). - If these are already present, the geometry load-combination are updated (name and signal), and if the model is already meshed, it also updates the load-combinations in mesh. - The status variable 'CurrentSignal' in 'Project' is updated to the new signal. """ # Check if dead load (with self weight) is created viia_create_load_combination_static(project) # Remove any base motion signals before applying earthquake signals viia_remove_load_combination_base_motion(project) # Get the signal data from the VIIA signal database signal_time, signal_factor_x, signal_factor_y, signal_factor_z = project.viia_get_signal(project, signal) # Increase the length of the signal with zero time dependent load factors nr_zeros = int(project.project_specific['signal']['extra time'] / 0.005) time = signal_time[-1] for i in range(nr_zeros): time += 0.005 signal_time.append(time) signal_factor_x.append(0) signal_factor_y.append(0) signal_factor_z.append(0) # Check if there is already a seismic load-combination present, it contains 'Earthquake' in the name check_bool = True for load_combination_object in project.collections.load_combinations: if 'Earthquake' in load_combination_object.name: check_bool = False break if check_bool: # Create load-combinations if seismic load-combination is not present # Find the load-case for the seismic loads (they should be present) def _load_cases(load_case_name): for load_case_object in project.collections.load_cases: if load_case_object.name == load_case_name: return load_case_object project.write_log("ERROR: First provide the load-cases for the seismic loads.") return load_case_name # Earthquake application in x-direction project.create_load_combination( name=f'Earthquake X {signal}', factors={_load_cases('Earthquake X'): 1.0}, time_dependent_load_factorstime_dependent_load_factors=project.create_timeseries_function( time_series=signal_time, value_series=signal_factor_x)) # Earthquake application in y-direction project.create_load_combination( name=f'Earthquake Y {signal}', factors={_load_cases('Earthquake Y'): 1.0}, time_dependent_load_factorstime_dependent_load_factors=project.create_timeseries_function( time_series=signal_time, value_series=signal_factor_y)) # Earthquake application in z-direction project.create_load_combination( name=f'Earthquake Z {signal}', factors={_load_cases('Earthquake Z'): 1.0}, time_dependent_load_factors=project.create_timeseries_function( time_series=signal_time, value_series=signal_factor_z)) else: # Update load-combinations if seismic load-combination for new signal for load_combination_object in project.collections.load_combinations: if 'Earthquake X' in load_combination_object.name: load_combination_object.update_name(f'Earthquake X {signal}') load_combination_object.update_signal([signal_time, signal_factor_x]) elif 'Earthquake Y' in load_combination_object.name: load_combination_object.update_name(f'Earthquake Y {signal}') load_combination_object.update_signal([signal_time, signal_factor_y]) elif 'Earthquake Z' in load_combination_object.name: load_combination_object.update_name(f'Earthquake Z {signal}') load_combination_object.update_signal([signal_time, signal_factor_z]) # Set the currently applied signal in Project class project.project_specific['signal']['current signal'] = signal
[docs]@rename_argument( old='loadcombination_name', new='load_combination_name', since='71.1.0', until='72.0.0', package='viiapackage') def viia_remove_load_combination(project: ViiaProject, load_combination_name: str): """ This function removes the requested load-combination. In case of Earthquake or Base Motion it is not necessary to remove x, y and z separately. The input should be then 'Earthquake' or 'Base Motion'. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - load_combination_name (str): Name of the load-combination that needs to be removed. All names of load-combinations in project.collections.load-combinations are valid. When removing 'Earthquake' or 'Base Motion', it is not necessary to remove those separately per direction. Output: - Geometry load-combination is removed in DIANA (if software is DIANA and model created). - If model also meshed, the mesh load-combination is removed in DIANA (if software is DIANA and model created). - 'LoadCombination' object is removed. - In case of a base motion or earthquake load-combination, the current signal is set to None. """ # Check if removal of base motions is requested if load_combination_name.lower() in ['base motion', 'base-motion']: for name_part in ['Base motion X', 'Base motion Y', 'Base motion Z']: for load_combination in project.collections.load_combinations: if name_part in load_combination.name: load_combination.remove_load_combination() # Set the currently applied signal to None if base motion is selected project.project_specific['signal']['current signal'] = None # Check if removal of base motions is requested elif load_combination_name.lower() == 'earthquake': for name_part in ['Earthquake X', 'Earthquake Y', 'Earthquake Z']: for load_combination in project.collections.load_combinations: if name_part in load_combination.name: load_combination.remove_load_combination() # Set the currently applied signal to None if earthquake is selected project.project_specific['signal']['current signal'] = None # Remove other load-combinations by name else: # Remove the load-combination (method of LoadCombination class) for load_combination in project.collections.load_combinations: if load_combination_name == load_combination.name: load_combination.remove_load_combination() break
[docs]def viia_remove_load_combination_rob_test(project: ViiaProject): """ This function removes the load-combination for the Rob-test situation. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load combination is removed. """ viia_remove_load_combination(project, 'Rob-test')
[docs]def viia_remove_load_combination_response_spectrum(project: ViiaProject): """ This function removes the load-combinations for the MRS analysis. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combinations are removed. """ viia_remove_load_combination(project=project, load_combination_name='MRS_x_direction') viia_remove_load_combination(project=project, load_combination_name='MRS_y_direction')
[docs]def viia_remove_load_combination_push_over(project: ViiaProject): """ This function removes the load-combinations for the pushover situation, including the Redis pushover. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combination is removed. """ for load_case in project.collections.load_cases: viia_remove_load_combination(project, load_case.name)
[docs]def viia_remove_load_combination_base_motion(project: ViiaProject): """ This function removes the load-combinations for the seismic base motion situation. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combinations for 3 base motions (x, y, and z) are removed. - The status variable 'CurrentSignal' in 'Project' is set to None. """ viia_remove_load_combination(project, 'Base motion')
[docs]def viia_remove_load_combination_earthquake(project: ViiaProject): """ This function removes the load-combination for the earthquake situation. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - Load-combinations for 3 earthquake directions (x, y, and z) are removed. - The status variable 'CurrentSignal' in 'Project' is set to None. """ viia_remove_load_combination(project, 'Earthquake')
### =================================================================================================================== ### 8. Creating the Response Spectrum ### ===================================================================================================================
[docs]def viia_create_response_spectra(project: ViiaProject): """ Function to get the parameters of the response spectrum from the NEN-webtool for the response spectra of the NPR9998. The response spectrum is created in the class 'ResponseSpectrum'. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - The response spectrum objects are created for all return periods and all directions for the location as defined in the Project class. """ project.write_log("Importing response spectra from website https://seismischekrachten.nen.nl.") # Check if the gps is available if 'gps_coordinaten_breedtegraad' not in project.project_information or \ 'gps_coordinaten_lengtegraad' not in project.project_information: project.write_log( "ERROR: GPS coordinates are not available in project information. " "Function viia_create_response_spectra aborted.") return # Select dataset if project.project_information['reference_period'] in ['t4', 't5', 't6']: dataset = 'GMMv6 d.d. 2020-07-01 Maaiveld' npr = 'NPR9998:2020' else: dataset = 'GMMv5 d.d. 2018-10-01 Maaiveld' npr = 'NPR9998:2018' # Get the data for the spectrum for the specific location data = fem_response_spectrum_parameters_npr( project=project, data_set=dataset, reference_period=project.project_information['reference_period'], longitude=project.project_information['gps_coordinaten_lengtegraad'], latitude=project.project_information['gps_coordinaten_breedtegraad']) # Create objects in the ResponseSpectrum class for the spectra if data: spectra = [] cluster = data[2475]['Horizontaal']['cluster'] project.project_information['cluster_nen'] = cluster for reference_period in data: for direction in data[reference_period]: spectra.append(project.create_NPR_response_spectrum( name=f'{npr}-{reference_period}-{direction}', peak_ground_acceleration=data[reference_period][direction]['ag_s'], plateau_factor=data[reference_period][direction]['p'], t_b=data[reference_period][direction]['t_b'], t_c=data[reference_period][direction]['t_c'], t_d=data[reference_period][direction]['t_d'], importance_factor=project.importance_factor, behaviour_factor=1.5, direction='horizontal')) else: project.write_log( "ERROR: An error occurred while retrieving spectra data from NEN-webtool. Response spectra are not " "created.") return False # Notification of process of getting the response spectrum parameters project.write_log("Response Spectra successfully imported for use in VIIA project.") return True
### =================================================================================================================== ### 9. End of script ### ===================================================================================================================