Source code for viiapackage.results.result_functions.viia_wall_displacements

### ===================================================================================================================
###   Function to create wall displacement graphs
### ===================================================================================================================
# Copyright ©VIIA 2024

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

# General imports
from __future__ import annotations
import json
from pathlib import Path
from typing import TYPE_CHECKING, Union, List, Tuple, Dict, Optional

# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.analyses import Analysis
from rhdhv_fem.output_items import DisplacementOutputItem
from rhdhv_fem.shapes import Wall
from rhdhv_fem.fem_math import fem_compare_coordinates
from rhdhv_fem.fem_tools import fem_create_folder
from rhdhv_fem.fem_config import Config

# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
    from viiapackage.viiaStatus import ViiaProject
from viiapackage.analyses.helper_functions import viia_find_3_mesh_nodes_on_wall
from viiapackage.viiaGeneral import viia_find_closest_mesh_node
from viiapackage.general.file_handling import viia_to_filename

# Import module matplotlib
import matplotlib
# Switch to a non-interactive backend to properly import matplotlib.pyplot
matplotlib.use(Config.MPL_NONINTERACTIVE(notify=False))
import matplotlib.pyplot as plt


### ===================================================================================================================
###   2. Function viia_wall_displacements
### ===================================================================================================================

[docs]def viia_wall_displacements( project: ViiaProject, wall_oop_tbfile: Path, signal: str, wall_list: List[Union[str, Wall]] = None, analysis: Optional[Analysis] = None) \ -> Optional[Tuple[Path, List[Path]]]: """ This function computes the in-plane and out-of-plane displacements for the walls through all the time steps of the time signal. Input: - project (obj): Project object containing collections and of fem objects and project variables. - wall_oop_tbfile (Path): Path to the OUTPUT_2.tb file. - signal (str): String representing the signal, can be S1 to S11. - wall_list (list): List of walls as object reference or wall-names as strings to be considered in this function. Default value is None, applying all walls except for the foundation walls. Output: - The absolute and relative in-plane and out-of-plane displacements for each wall requested in the wall_list (If no wall is specified then all walls are considered) are generated within the OOP_IP_displacements folder in the working folder. The folder and a list of create files are returned in a tuple. """ # Check if tb-file is present if '_OUTPUT_2.tb' not in wall_oop_tbfile.as_posix() or not wall_oop_tbfile.exists(): project.write_log( "WARNING: Input file not recognised for wall displacements function, check your input. No graphs are " "generated for the wall displacements.") return None # Check the signal number if signal not in [f"S{i}" for i in range(1, 12)]: project.write_log( f"WARNING: Signal needs to be between S1-S11, current value {signal} is not allowed. " f"No acceleration graphs generated.") return None # Read tb-file instance project.read_diana_tbfile(file=wall_oop_tbfile, analysis=analysis) software = 'diana' # Check for wall selection if wall_list is None: wall_list = project.collections.walls # Exclude any foundation walls foundation_layer = project.viia_get(collection='layers', name='F') wall_list = [wall for wall in wall_list if wall.layer is not foundation_layer] # Find the required nodes for function wall_names_list = list() for i, wall in enumerate(wall_list): for wall_object in project.collections.walls: if 'WANDEN' in wall_object.name and 'LIJNMASSA' not in wall_object.name and \ 'SPOUWANKER' not in wall_object.name: if isinstance(wall, str) and wall_object.name == wall: wall_names_list.append(wall_object) elif not isinstance(wall, str) and wall_object == wall: wall_names_list.append(wall_object) if analysis is None and len(project.collections.analyses) != 1: raise LookupError( f"ERROR: Multiple instances of Analysis found={project.collections.analyses}. The wall displacements can " f"only be made if there is one analysis in the project.") elif analysis is None: analysis = project.collections.analyses[0] if not isinstance(analysis, Analysis): raise ValueError(f"ERROR: The analysis was not properly provided. Check your input for analysis={analysis}.") # Get results mesh nodes and output items for output_block in analysis.get_all_output_blocks(): if output_block.name == 'OUTPUT_2': output_items = output_block.output_items result_mesh_nodes = output_block.manual_nodes break else: raise LookupError(f"No OUTPUT_2 output block found in {analysis}.") # Collect output items output_item_x = None output_item_y = None for output_item in output_items: if isinstance(output_item, DisplacementOutputItem): if output_item.engineering_notation == 'U_x_tot': if output_item_x is not None: raise RuntimeError( f"ERROR: Multiple displacement output-items in x-direction found in {output_block.name} of " f"analysis {analysis}, this is not expected.") output_item_x = output_item elif output_item.engineering_notation == 'U_y_tot': if output_item_y is not None: raise RuntimeError( f"ERROR: Multiple displacement output-items in y-direction found in {output_block.name} of " f"analysis {analysis}, this is not expected.") output_item_y = output_item if output_item_x is None or output_item_y is None: raise ValueError( f"ERROR: Could not find the correct output-items for the wall displacement functionality in " f"{output_block.name} of analysis {analysis}. Found values x-direction: {output_item_x} and y-direction " f"{output_item_y}.") # Get, filter and sort analysis references calculation_block = [ calculation_block for calculation_block in analysis.calculation_blocks if getattr(calculation_block, 'output_blocks', []) and output_block in getattr(calculation_block, 'output_blocks', [])] if len(calculation_block) != 1: raise LookupError( "ERROR: Multiple possible calculation_blocks found. OUTPUT_2 output is specified in multiple calculation " "blocks this is not allowed.") calculation_block = calculation_block[0] analysis_references = [ analysis_reference for analysis_reference in project.collections.stepped_analysis_reference if analysis_reference.analysis == analysis and analysis_reference.calculation_block == calculation_block and analysis_reference.historic_envelope is None and analysis_reference.meta_data is not None] analysis_references = sorted(analysis_references, key=lambda analysis_ref: analysis_ref.step_nr) # Check if all the three nodes on the walls are in the result file output nodes wall_nodes = [] for wall in wall_list: temp_list = list() nodes_to_check, coordinates_3_nodes = viia_find_3_mesh_nodes_on_wall(project=project, wall=wall) # Find all the nodes that from result nodes that are on the wall nodes_to_search = [mesh_node for mesh_node in wall.mesh.get_meshnodes() if mesh_node in result_mesh_nodes] # Check if there are three evaluation nodes from the results file on the same wall if len(nodes_to_search) != 3: # removing un-necessary nodes from the list - 'nodes_to_search' nodes_to_del = list() for node in nodes_to_search: if node not in nodes_to_check: nodes_to_del.append(node) for node in nodes_to_del: nodes_to_search.remove(node) # Check the min, middle, max nodes separately and replace with the result node if needed for i, node in enumerate(nodes_to_check): if node in nodes_to_search: temp_list.append(node) else: # Find the closest node to the coordinate from the result nodes closest_node = viia_find_closest_mesh_node( project=project, target_point=coordinates_3_nodes[i], mesh_nodes=nodes_to_search, direction='Z', precision=1) if closest_node is None: raise NotImplementedError( f"ERROR: The node at the same height of as the point at coordinates {coordinates_3_nodes[i]} " f"on {wall.name} cannot be found in the current dat-file input, please check your input for " f"the dat-file.") temp_list.append(closest_node) wall_nodes.append({'wall': wall, 'nodes': temp_list}) # Get times time_list = [analysis_reference.meta_data['time'] for analysis_reference in analysis_references] # Create a directory folder = project.current_analysis_folder / 'OOP_IP_displacements' fem_create_folder(folder) files = [] data = {} # Collect the results for item in wall_nodes: wall = item['wall'] mesh_nodes = item['nodes'] nodes = dict() z_nodes = [mesh_node.coordinates[2] for mesh_node in mesh_nodes] if fem_compare_coordinates([z_nodes[0]], [z_nodes[1]]) or \ fem_compare_coordinates([z_nodes[0]], [z_nodes[2]]) or \ fem_compare_coordinates([z_nodes[1]], [z_nodes[2]]): project.write_log( f"WARNING: At least two of the three evaluation nodes {mesh_nodes} on wall '{wall.name}' are on the " f"same z-level, please check your dat-file and evaluation nodes. Graph of wall displacements not " f"generated.") continue for node in mesh_nodes: if node.coordinates[2] == max(z_nodes): nodes['top'] = node elif node.coordinates[2] == min(z_nodes): nodes['bottom'] = node else: nodes['center'] = node normal_vector = wall.normal_vector() out_of_plane_list = list() in_plane_list = list() interstorey_drift_perpendicular_list = list() displacements_oop_bottom_list = list() displacements_oop_center_list = list() displacements_oop_top_list = list() top_node = nodes['top'] bottom_node = nodes['bottom'] center_node = nodes['center'] for analysis_reference in analysis_references: x_bottom = wall.results.get_result_value( output_item=output_item_x, analysis_reference=analysis_reference, software=software, mesh_element=None, mesh_node=bottom_node) y_bottom = wall.results.get_result_value( output_item=output_item_y, analysis_reference=analysis_reference, software=software, mesh_element=None, mesh_node=bottom_node) x_center = wall.results.get_result_value( output_item=output_item_x, analysis_reference=analysis_reference, software=software, mesh_element=None, mesh_node=center_node) y_center = wall.results.get_result_value( output_item=output_item_y, analysis_reference=analysis_reference, software=software, mesh_element=None, mesh_node=center_node) x_top = wall.results.get_result_value( output_item=output_item_x, analysis_reference=analysis_reference, software=software, mesh_element=None, mesh_node=top_node) y_top = wall.results.get_result_value( output_item=output_item_y, analysis_reference=analysis_reference, software=software, mesh_element=None, mesh_node=top_node) displacements_oop_bottom_list.append(normal_vector[0] * x_bottom + normal_vector[1] * y_bottom) displacements_oop_center_list.append(normal_vector[0] * x_center + normal_vector[1] * y_center) displacements_oop_top_list.append(normal_vector[0] * x_top + normal_vector[1] * y_top) out_of_plane_list.append( # Displacement out-of-plane at center (normal_vector[0] * x_center + normal_vector[1] * y_center) - # (Displacement out-of-plane at top + displacement out-of-plane at bottom) / 2 ((normal_vector[0] * x_top + normal_vector[1] * y_top + normal_vector[0] * x_bottom + normal_vector[1] * y_bottom) / 2)) in_plane_list.append( # Displacement in-plane at top (normal_vector[1] * x_top + normal_vector[0] * y_top) - # Displacement in-plane at bottom (normal_vector[1] * x_bottom + normal_vector[0] * y_bottom)) interstorey_drift_perpendicular_list.append( # Displacement out-of-plane at top (normal_vector[0] * x_top + normal_vector[1] * y_top) - # Displacement out-of-plane at bottom (normal_vector[0] * x_bottom + normal_vector[1] * y_bottom)) # Collect the data to be stored data[wall.name] = { 'width': wall.get_width(), 'horizontal_segments': wall.get_segments(), 'height': wall.get_height(), 'thickness': wall.get_thickness(), 'displacements_oop_bottom': displacements_oop_bottom_list, 'displacements_oop_center': displacements_oop_center_list, 'displacements_oop_top': displacements_oop_top_list, 'out_of_plane_displacements': out_of_plane_list, 'interstorey_drift_perpendicular': interstorey_drift_perpendicular_list, 'interstorey_drift_parallel': in_plane_list} # Create the graph (close existing previous ones plt.close() fig = plt.figure(figsize=(15, 5)) # Apply VIIA graph style style_file = Path(project.viia_settings.project_specific_package_location) / 'viiaGraph.mplstyle' plt.style.use(style_file.as_posix()) ax1 = plt.subplot2grid((1, 1), (0, 0)) ax1.plot(time_list, [displ * 1000 for displ in displacements_oop_bottom_list], label='Bottom') ax1.plot(time_list, [displ * 1000 for displ in displacements_oop_center_list], label='Middle') ax1.plot(time_list, [displ * 1000 for displ in displacements_oop_top_list], label='Top') ax1.spines['bottom'].set_position('zero') # ax1.annotate(labelAnnotation, xy=positionAnnotation) plt.xlabel('Time [s]') plt.ylabel('Displacement [mm]') wall_name_file = viia_to_filename(wall.name) plt.title('Wall displacement ' + str(wall.name)) plt.legend() # Save the graph to the same folder filename = folder / f'Wall_{wall_name_file}_total_displacements.png' plt.savefig(filename.as_posix()) project.write_log(f"The total displacement plot was created for {wall.name}: {filename.as_posix()}.") files.append(filename) # Create the graph (close existing previous ones) plt.close() fig = plt.figure(figsize=(15, 5)) # Apply VIIA graph style style_file = Path(project.viia_settings.project_specific_package_location) / 'viiaGraph.mplstyle' plt.style.use(style_file.as_posix()) ax1 = plt.subplot2grid((1, 1), (0, 0)) ax1.plot( time_list, [displ * 1000 for displ in out_of_plane_list], label='Out-of-plane displacement') ax1.plot( time_list, [displ * 1000 for displ in interstorey_drift_perpendicular_list], label='Interstorey drift, perpendicular to wall') ax1.plot( time_list, [displ * 1000 for displ in in_plane_list], label='Interstorey drift, parallel to wall') ax1.spines['bottom'].set_position('zero') plt.xlabel('Time [s]') plt.ylabel('Relative displacement [mm]') plt.title('Relative displacement wall ' + str(wall.name)) plt.legend() # Save the graph to the same folder filename = folder / f'Wall_{wall_name_file}_relative_displacements.png' plt.savefig(filename.as_posix()) project.write_log(f"The relative displacement plot was created for {wall.name}: {filename.as_posix()}.") files.append(filename) # Store the data in separate json-file _viia_create_wall_displacements_json(project=project, data=data) # Notify user of finishing the acceleration graphs project.write_log(f"Wall displacement graphs for signal {signal} created in {folder.as_posix()}.") return folder, files
### =================================================================================================================== ### 3. Function to create the wall displacements json-file for VIIA ### ===================================================================================================================
[docs]def _viia_create_wall_displacements_json(project: ViiaProject, data: Dict[str, Dict[str, List[float]]]) -> Path: """ Function to store the results of the wall displacements (in VIIA format) in a json-file in the current analysis folder. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - data (dict): Dictionary with the data of the graphs per wall. Output: - Generates a json-file in the current analysis-folder with the data of the wall displacements. - Returns the path of the file created. """ # Create json-file dump in the current analysis folder if project.current_analysis_folder is None: raise ValueError("ERROR: The analysis folder was not set correctly, please provide correct folder.") dumpfile = project.current_analysis_folder / 'wall_displacements.json' with open(dumpfile, 'w') as fd: json.dump(data, fd, indent=2, sort_keys=True) return dumpfile
### =================================================================================================================== ### 4. End of script ### ===================================================================================================================