Source code for viiapackage.viiaShapeOperations

### =============================================================================================================== ###
###                                                                                                                 ###
###                                             viiaShapeOperations.py                                              ###
###                                                                                                                 ###
### =============================================================================================================== ###
# This module ``viiaShapeOperations`` contains the functions that add or adjust the shapes in the model.

# 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. Cavity wall ties

#   3. Align with grid

#   4. Beam operations

#   5. Non linear concrete (discrete reinforcement)

#   6. Footings

#   7. Surface shape operations

#   8. Shorten walls (MRS)

#   9. Wall to column (MRS)

#   10. Walls with openings on contour (NL: 'Kantelen wanden')

#   11. Update line masses

#   12. Wooden beam floors (NL: Houten balklaag vloeren HBV)

#   13. Find max name number

#   14. Name number operations

#   15. Compute projection on surface

#   16. End of script

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

# General imports
from __future__ import annotations
from typing import TYPE_CHECKING, List, Union, Optional, Tuple
from copy import deepcopy
from warnings import warn
import numpy as np
import math

# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.fem_math import fem_round_coordinates, fem_dot_product_vector, fem_cross_product_vector, \
    fem_compare_coordinates, fem_intersection_of_two_lines, fem_point_in_surface, fem_vector_in_plane, \
    fem_3d_to_2d_projection, fem_inverse_3d_to_2d_projection, fem_distance_coordinates, fem_unit_vector, \
    fem_intersection_of_two_line_segments, fem_find_intersections, fem_point_to_plane, fem_plane_plane_intersection, \
    fem_vector_2_points, fem_longest_distance, fem_greater, fem_smaller, fem_translate_vector_to_coordinate_system, \
    fem_parallel_vectors, fem_flip_vector, fem_angle_between_2_vectors, fem_horizontal_vector_in_plane
from rhdhv_fem.fem_tools import fem_find_object, fem_axis_to_string
from rhdhv_fem.fem_config import Config
from rhdhv_fem.shape_geometries import Polyline
from rhdhv_fem.tools.fem_wall_tools import fem_split_all_walls, fem_split_wall_vertical
from rhdhv_fem.shapes import Shapes, Wall, Roof, Lintel, Beam, Column, Surfaces
from rhdhv_fem.fem_shapes import fem_connect_shape
from rhdhv_fem.diana_utils.diana_config import DianaConfig

# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
    from viiapackage.viiaStatus import ViiaProject


### ===================================================================================================================
###   2. Helper functions
### ===================================================================================================================

class DuplicateError(ValueError):
    """ Custom error message for duplication of shapes when creating geometry."""
    pass


def _viia_check_input(project: ViiaProject):
    warn('WARNING: The check input function is deprecated, please contact the automating-team if you require the '
         'functionality.', DeprecationWarning, stacklevel=2)


### ===================================================================================================================
###   3. Functions for cavity walls
### ===================================================================================================================

[docs]def viia_create_cavity_wall_ties_from_list( project: ViiaProject, cavity_wall_list: List[Union[List[Union[str, Wall, dict]], str, Wall]], **kwargs): """ This function creates cavity walls for a list of walls. Properties for each cavity wall are specified by an accompanying dictionary of keyword arguments. Cavity wall pairs of which their origin walls are connected at either ends, are automatically connected. Given walls should not cross one another and only connect at their ends. Among the given walls no more than two walls should connect in the same point. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - cavity_wall_list (list of lists of a string or object and dictionary): A list of lists containing data about the walls for which cavity walls are to be created. Examples of input can be found below. - cavity (float): Cavity distance between the walls. - outer_wall_thickness (float): Optional thickness of the outer wall in [m]. If nothing is provided, thickness will be equal to the inner leaf. - outer_wall_material (str): Optional material of the outer wall. If nothing is provided, material will be equal to the inner leaf. - flip (bool): Toggle to flip the direction of creation of the wall. By default the outer wall is created in the direction of the normal vector of the wall. - no_ties (bool): Toggle to prevent creation of cavity wall ties. Default is False. - connect_outer_leafs (bool): Toggle to connect edges of outer leafs. Default is True. - dummy_plates (bool): Toggle to create dummy plates under the walls. Default is True. - edge_distance (float): Distance from which to snap points to openings or contour, if desired. Default is 0.25m - ties_per_m2 (float): Number of ties per m2. Default value is 4 ties/m2. - wall_tie_distance (float): Distance between ties, in [m]. Default is 0.5m. - snap_to_openings (bool): Toggle for snapping of cavity ties to opening lines. Default is True. - snap_to_contour (bool): Toggle for snapping of cavity ties to contour lines. Default is True. - offset_base (float): Optional offset of the ties in the direction of the base of the wall in [m]. Default is 0 - offset_vertical (float): Optional offset of the ties in the vertical direction in [m]. Default is 0. - complete_output (bool): Optional toggle to return all newly created shapes. Default is False. - lines_to_snap (list): List of Line object to snap the ties nearby Output: - If complete_output argument is False (default), a list of the newly created outer leaf objects is returned. If complete_output is set to True, three lists are returned. First list has the outer leaf objects, second list the wall tie objects, and third list the dummy plates. Examples: Different types of input can be given to this function. The basic input is as follows: .. code-block:: python project.viia_create_cavity_wall_ties_from_list( cavity_wall_list=['wall_1', 'wall_2', 'wall_3' .. etc.], cavity=0.1) This will create outer leafs, cavity wall ties and dummy plates for all the walls in the cavity wall list, with a cavity of 0.1m. The rest of the arguments will have their default value. It is also possible to provide different arguments to a specific wall. In this case a list should be provided that includes the wall and a dictionary of keyword arguments, like so: .. code-block:: python project.viia_create_cavity_wall_ties_from_list( cavity_wall_list=['wall_1', ['wall_2', {'cavity': 0.2}], 'wall_3' .. etc.], cavity=0.1) This will create all outer leafs, cavity wall ties and dummy plates with a cavity of 0.1m, except for wall with id 2 the cavity will be 0.2m. Note that this can be done for all keyword arguments described in the input parameters above. Input may therefore also look something like this: .. code-block:: python project.viia_create_cavity_wall_ties_from_list( cavity_wall_list=[ ['wall_1', {'cavity': 0.18, 'wall_tie_distance': 0.7, 'outer_wall_thickness': 0.15}], ['wall_2', {'flip': True, 'no_ties': True}], 'wall_3', 'wall_4', ['wall_5', {'dummy_plates': True, 'edge_distance': 0.3, 'offset_vertical': 0.1, 'flip': True}] ], cavity = 0.3, dummy_plates=False, offset_base=0.25, snap_to_contour=False) """ # Set the default keyword arguments default_kwargs = { 'no_ties': False, 'dummy_plates': True, 'adjust_dummy_plates': True, 'connect_outer_leafs': True, 'complete_output': False} # Update with given kwargs overall_kwargs = default_kwargs overall_kwargs.update(kwargs) # Initiate lists wall_tie_data_list = [] total_connecting_lines = [] checked_connections = [] # Initiate output lists inner_leafs = [] outer_leafs = [] dummy_plates = [] wall_ties = [] for i, wall_item in enumerate(cavity_wall_list): # Begin with all default keyword arguments, they will be updated if overrides are given all_kwargs = deepcopy(overall_kwargs) # Check if specific kwargs are given for this wall if isinstance(wall_item, list): if len(wall_item) != 2: raise AttributeError( "ERROR: Input like ['wall_x', {key: value, key: value, .. }] is expected for each entry in " f"cavity_wall_list, not {wall_item}.") # Extract data from input inner_leaf, specific_kwargs = wall_item if not isinstance(specific_kwargs, dict): raise AttributeError( "ERROR: Specific keyword arguments should be given in dictionary format, like so: ['wall_x', " "{key: value, key: value, .. }].") # Update general keyword arguments with specifics for this wall all_kwargs.update(specific_kwargs) elif isinstance(wall_item, (str, Wall)): inner_leaf = wall_item else: raise AttributeError( "ERROR: Wrong input given to create_cavity_wall_ties_from_list, see docstring for more info.") # Convert string to Wall object, if given if isinstance(inner_leaf, str): if inner_leaf.split('_')[0].lower() == 'wall': inner_leaf = project.viia_get(collection='walls', id=int(inner_leaf.split('_')[1])) else: raise ValueError( f"ERROR: Wrong input given, should be for instance wall_1 or roof_5, not {inner_leaf}.") # Add inner leaf object to list inner_leafs.append(inner_leaf) # Collect keyword arguments for each function outer_leaf_kwarg_names = ['cavity', 'outer_wall_thickness', 'outer_wall_material', 'flip'] outer_leaf_kwargs = {key: all_kwargs.get(key) for key in outer_leaf_kwarg_names if key in all_kwargs.keys()} wall_tie_kwarg_names = [ 'edge_distance', 'wall_tie_distance', 'snap_to_openings', 'snap_to_contour', 'offset_base', 'offset_vertical', 'lines_to_snap', 'ties_per_m2'] wall_tie_kwargs = {key: all_kwargs.get(key) for key in wall_tie_kwarg_names if key in all_kwargs.keys()} # Create outer leaf outer_leaf = project.viia_create_outer_leaf(wall=inner_leaf, **outer_leaf_kwargs) outer_leafs.append(outer_leaf) # Save data for creation of ties for later wall_tie_data_list.append([inner_leaf, outer_leaf, wall_tie_kwargs, all_kwargs]) # Loop over the wall tie data list for inner_leaf, outer_leaf, wall_tie_kwargs, all_kwargs in wall_tie_data_list: # Connect outer leafs if desired if overall_kwargs.get('connect_outer_leafs') is True: # Loop over other inner leafs for other_inner_leaf_index, other_inner_leaf in enumerate(inner_leafs): if other_inner_leaf == inner_leaf: continue # Check if this connection has already been made already_checked = False for checked_connection in checked_connections: if inner_leaf in checked_connection and other_inner_leaf in checked_connection: already_checked = True break if already_checked: continue # Add checked walls and perform connect shapes checked_connections.append([inner_leaf, other_inner_leaf]) fem_connect_shape(shape=inner_leaf, shapes_list=[other_inner_leaf]) # Check if the other inner leaf is connected to the current inner leaf connecting_lines = inner_leaf.get_connecting_lines(shape=other_inner_leaf, include_openings=False) if connecting_lines: # If this line has already been checked, it means more than two inner leafs connect here. wrong_connection = False for line in connecting_lines: if line in total_connecting_lines: project.write_log( f"WARNING: Maximum of two outer leafs can be connected in one point. Connection " f"between outer leafs of {inner_leaf} and {other_inner_leaf} is skipped.") wrong_connection = True break total_connecting_lines.append(line) # Continue if connection is not allowed if wrong_connection: continue # Find the outer leaf other_outer_leaf = outer_leafs[other_inner_leaf_index] # Connect cavity walls of which origin walls are connected project.viia_connect_cavity_walls(outer_leaf, other_outer_leaf) # Check if the walls are still intersecting in a way that is not allowed if any((fem_plane_plane_intersection( plane1=inner_leaf.contour.get_points(), plane2=other_outer_leaf.contour.get_points(), infinite_planes=False), fem_plane_plane_intersection( plane1=other_inner_leaf.contour.get_points(), plane2=outer_leaf.contour.get_points(), infinite_planes=False))): project.write_log( f"WARNING: Connecting outer leafs of {inner_leaf} and {other_inner_leaf} resulted in a " f"cavity wall intersection. Try setting flip to True for one of these walls.") # Create wall ties if desired if all_kwargs.get('no_ties') is not True: wall_ties.extend(project.viia_create_cavity_wall_ties(inner_leaf, outer_leaf, **wall_tie_kwargs)) # Create dummy plate if desired if all_kwargs.get('dummy_plates') is True: dummy_plate = project.viia_create_dummy_plate_between_walls(inner_leaf, outer_leaf) dummy_plates.append(dummy_plate) # Give complete output if desired if overall_kwargs.get('complete_output') is True: return outer_leafs, wall_ties, dummy_plates return outer_leafs
### =================================================================================================================== ### 4. Function to align with grid ### ===================================================================================================================
[docs]def viia_align_with_grid( project: ViiaProject, shapes: List[Shapes], margin: float, x_grid: List[float], y_grid: List[float], z_grid: List[float], merge_shapegeometries: bool = False): """ Function for aligning the nodes of shapes to a given grid. If this results in a out of plane displacement of a surface, the user will be notified. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - shapes (list of obj): List of object references of the shapes to be aligned. - margin (float): Distance between the nodes and the level to determine if the node needs to be moved, in [m]. - x_grid (list with floats): List with grid in x-direction, in [m]. - y_grid (list with floats): List with grid in y-direction, in [m]. - z_grid (list with floats): List with grid in z-direction, in [m]. - merge_shapegeometries (bool): Indicates if the shapegeometries should be merged after aligning them. When multiple shapes are aligned this boolean should only be True for the last function call out of speed reasons. Default value False. Output: - The nodes within the set levels is translated in the given direction. """ project.align_with_grid( shapes_list=shapes, margin=margin, x_grid=x_grid, y_grid=y_grid, z_grid=z_grid, merge_shapegeometries=merge_shapegeometries)
### =================================================================================================================== ### 5. Functions for beam operations ### =================================================================================================================== def viia_create_all_lintels( project: ViiaProject, lintel_material: str, lintel_type: str, collection: Union[str, List[Union[Wall, str]]] = 'All', support_length: float = 0.1, lintel_slab: bool = True) \ -> List[Lintel]: """ This function creates a lintel above all wall openings in the model. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - lintel_material (str): The material of the lintel, all VIIA materials available. - lintel_type (str): The profile of the lintel, all profiles available as for beams. Use VIIA naming convention. - collection (list of obj): List of object references of walls for which all the openings will get a lintel on top of the opening with the provided material and geometry. Alternatively the list can be provided as list of names of the walls. Also input of the name of the layer can be given, in which all the lintels for the openings of the walls in the layer are created. Default value is 'All', which will apply lintels for all openings in the model. - support_length (float): The length of the support, in [m]. This is the distance from the corner of the opening to the end of the lintel. Default value is 0.1. - lintel_slab (bool): By default is True, which means dummy plates will be created between outer leaf and inner leaf on the openings of a cavity wall. If False, lintel beams will be created on both inner and outer leaves. Output: - Lintels are created for the openings in the provided collection. All lintels in this function have the same material and geometry. """ # Check if input as list is provided wall_list = [] if isinstance(collection, list): for item in collection: if isinstance(item, Wall): wall_list.append(item) elif isinstance(item, str): wall = project.viia_get(collection='walls', name=item) if isinstance(wall, Wall): wall_list.append(wall) else: raise ValueError(f"ERROR: Input for viia_create_all_lintels for collection is incorrect '{item}'.") else: raise ValueError(f"ERROR: Input for viia_create_all_lintels for collection is incorrect '{item}'.") # Else input is provided as string elif isinstance(collection, str): if collection.lower() == 'all': wall_list = project.collections.walls # Check if layer is input and exists else: layer = project.find(description=collection, collection='layers') if layer: wall_list = layer.walls else: raise ValueError( f"ERROR: Input for viia_create_all_lintels for collection is incorrect '{collection}'.") else: raise ValueError(f"ERROR: Input for viia_create_all_lintels for collection is incorrect '{collection}'.") # Check if cavity lintel slab is needed cavity_inner_wall_list = [] other_walls = [] if lintel_slab: for wall in wall_list[:]: if wall.id > 9000: outer_wall = wall inner_wall = project.viia_get('walls', id=wall.id - 9000) else: outer_wall = project.viia_get('walls', id=wall.id + 9000) inner_wall = wall if outer_wall: if inner_wall not in cavity_inner_wall_list: cavity_inner_wall_list.append(inner_wall) else: other_walls.append(wall) else: other_walls = wall_list # Create the lintels created_lintels = [] # Lintel beams for wall in other_walls: # Check if there are openings in the wall if wall.openings: for i, opening in enumerate(wall.openings): try: created_lintels.append(viia_create_lintel( project=project, wall=wall, opening_number=i+1, lintel_material=lintel_material, lintel_type=lintel_type, support_length=support_length)) except DuplicateError: project.write_log(f"WARNING: Wall {wall.name} already has a lintel attached, opening skipped.") # Lintel slabs if cavity_inner_wall_list: for inner_wall in cavity_inner_wall_list: # Check if there are openings in the wall if inner_wall.openings: for i, opening in enumerate(inner_wall.openings): try: created_lintels.append(viia_create_lintel( project=project, wall=inner_wall, opening_number=i+1, lintel_material=lintel_material, lintel_type=lintel_type, support_length=support_length, lintel_slab=lintel_slab)) except DuplicateError: project.write_log( f"WARNING: Wall {inner_wall.name} already has a lintel attached, opening skipped.") # Notification end of process project.write_log(f"{len(created_lintels)} '{lintel_type}' lintels have been added successfully to the model.") return created_lintels
[docs]def viia_create_lintel( project: ViiaProject, wall: Union[Wall, str, int], opening_number: int, lintel_material: str = 'LIN-BETON', lintel_type: str = '100x100', support_length: float = 0.1, lintel_slab: bool = False) -> Lintel: """ This function creates a lintel for a given opening in a wall. .. note:: The top side of the opening consists of at least two points on the same maximum height in z-direction. .. note:: The lintel will be applied within the contour of the wall (if edge of wall is smaller than the support_length, the lintel will stop at the edge. .. note:: When the lintel slab is needed for cavity walls, a dummy plate with 100mm thickness will be created as a Floor object. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object reference of the wall in which the opening is located for the lintel. Alternative the name of a wall can be provided. - opening_number (int): The number (>= 1) of the opening (first opening is number 1). - lintel_material (str): The material of the lintel, all VIIA materials available. Default is LIN-BETON. - lintel_type (str): The profile of the lintel, all profiles available as for beams. Use VIIA naming convention. Default is 100x100. - support_length (float): The length of the support in [m]. This is the distance from the corner of the opening to the end of the lintel. Default value is 0.1. - lintel_slab (boolean): By default is False. When set to True, a dummy plate will be created between outer leaf and inner leaf on the opening of the cavity wall Output: - Lintel is created in PY memory. - If model is already created in DIANA, it will also add the lintel to the model and assign properties for material, geometry and data. .. warning:: Lintels that lie on the same line (due to two adjacent windows on the same height) can theoretically overlap. For the default support length of 0.1m this is unlikely, but when higher support lengths are specified, this can occur. """ # Check if provided wall is an object or find the wall if string is provided if isinstance(wall, (str, int)): wall = fem_find_object(reference=wall, collection=project.collections.walls) if not isinstance(wall, Wall): raise ValueError(f"ERROR: {wall} is not recognised, check for typing error.") # Check if opening exists if wall.openings and (opening_number < 1 or opening_number > len(wall.openings)): raise ValueError( f"ERROR: Opening number {opening_number} in {wall.name} is not recognised, check for typing error.") # Set the numbering to the python style opening_number -= 1 # Create a list of the top points of the opening corner_nodes = [] double_node = [] for line in wall.openings[opening_number].get_top_horizontal_lines(): for node in line.get_nodes(): if node in corner_nodes or node in double_node: corner_nodes.remove(node) double_node.append(node) else: corner_nodes.append(node) corner_nodes = [corner_nodes[0].coordinates, corner_nodes[1].coordinates] if len(corner_nodes) < 2: raise ValueError( f"ERROR: Opening {opening_number} in {wall.name} does not have two nodes at the topside. This is not yet " f"available for viia_create_lintel function. Please create lintel manually.") elif len(corner_nodes) > 2: raise ValueError( f"ERROR: Opening {opening_number} in {wall.name} contains multiple topside nodes. This is not yet " f"available for viia_create_lintel function. Please create lintel manually.") # Determine the points of the lintel by adding the support length in the direction of the two nodes lintel_vector = fem_unit_vector(vector=[corner_nodes[1][i] - corner_nodes[0][i] for i in range(3)]) support_x = lintel_vector[0] * support_length support_y = lintel_vector[1] * support_length lintel_nodes = [ [corner_nodes[0][0] - support_x, corner_nodes[0][1] - support_y, corner_nodes[0][2]], [corner_nodes[1][0] + support_x, corner_nodes[1][1] + support_y, corner_nodes[1][2]]] # Create list with points (extracted from the nodes) of the object wall_object_points = [deepcopy(wall.contour.get_points())] for opening in wall.openings: wall_object_points.append(opening.get_points()) # Check if lintel is within wall and not within other opening for node_num in range(2): if not fem_point_in_surface(point=lintel_nodes[node_num], surface=wall_object_points[0]) or \ any([fem_point_in_surface(point=lintel_nodes[node_num], surface=wall_object_points[i]) for i in range(1, len(wall_object_points)) if lintel_nodes[node_num] not in wall_object_points[i]]): # Start or endpoint is not in the wall and needs to be moved intersections = [] for i in range(len(wall_object_points)): # Select the sub-list from the wall (except the opening where the lintel is applied on) if not i == opening_number + 1: for j in range(len(wall_object_points[i])): # Collect the lines of the contour if j == len(wall_object_points[i]) - 1: edge_points = [wall_object_points[i][j], wall_object_points[i][0]] else: edge_points = [wall_object_points[i][j], wall_object_points[i][j + 1]] # Get intersections, which can return 'NoIntersection', one point, or list of several points segment_intersections = fem_intersection_of_two_line_segments( line1=edge_points, line2=[corner_nodes[node_num], lintel_nodes[node_num]], return_intersection=True) if segment_intersections == 'NoIntersection': pass elif isinstance(segment_intersections[0], (int, float)): # If flat list returned, one intersection if segment_intersections not in intersections: intersections.append(segment_intersections) elif isinstance(segment_intersections[0], list): # If nested list returned, multiple intersections for segment_intersection in segment_intersections: if segment_intersection not in intersections: intersections.append(segment_intersection) # Check if intersections are found if not intersections: raise ValueError( f"ERROR: No intersections found between lintel and wall {wall.name}. Opening {opening_number} " f"might not be fully within wall. Please create lintel manually instead.") # Calculate distances between corner node and intersections distances = [fem_distance_coordinates(coordinate1=corner_nodes[node_num], coordinate2=intersection) for intersection in intersections] # Get the shortest distance to node to determine the support length remaining index_sort = [sorted(distances).index(distance) for distance in distances] lintel_nodes[node_num] = [fem_round_coordinates( coordinate=intersections[index_sort[0]][j], precision=project.check_precision) for j in range(3)] # if lintel slab needed, calculte points on outer wall if lintel_slab: outer_nodes = [] outer_wall = project.viia_get(collection='walls', id=wall.id+9000) if outer_wall is None: print() outer_nodes.append(fem_point_to_plane( point=lintel_nodes[1], plane=outer_wall.contour.get_points(), return_intersection=True)) outer_nodes.append(fem_point_to_plane( point=lintel_nodes[0], plane=outer_wall.contour.get_points(), return_intersection=True)) # Properties to make lintel object level = str(wall.name.split('-')[0]) nr = 0 for lintel_object in project.collections.lintels: if int(lintel_object.name.split('-')[-1]) > nr: nr = int(lintel_object.name.split('-')[-1]) lintel_name = f'{level}-LATEI-{lintel_material}-{lintel_type}-{nr + 1}' lintel_material = project.viia_create_material(material_name=lintel_material) # Properly modify lintel_type before calling project.geometries lintel_type = lintel_type.replace('BALK-', '') if not lintel_type.startswith('LATEI'): lintel_type = f'LATEI-{lintel_type}' lintel_geometry = project.viia_create_geometry( geometry_name=lintel_type, material_object=lintel_material, shape_object=None, class_type='Lintel') # Check if lintel is already present for proj_lintel in project.collections.lintels: if proj_lintel.connecting_wall is wall: proj_lintel_points = proj_lintel.contour.get_points() if (fem_compare_coordinates(proj_lintel_points[0], lintel_nodes[0]) and fem_compare_coordinates(proj_lintel_points[1], lintel_nodes[1])) or \ (fem_compare_coordinates(proj_lintel_points[0], lintel_nodes[1]) and fem_compare_coordinates(proj_lintel_points[1], lintel_nodes[0])): raise DuplicateError("ERROR: There already exists a lintel in this location, please check.") # Creation of lintel in PY memory lintel = project.create_lintel( name=lintel_name, contour=project.create_line(lintel_nodes), material=lintel_material, geometry=lintel_geometry, connecting_wall=wall) lintel.id = nr if lintel_slab: project.viia_create_floor( name=wall.layer, material='LIN-DUMMY-PLATE', geometry=100, points=[[lintel_nodes[0], lintel_nodes[1], outer_nodes[0], outer_nodes[1]]], element_x_axis=fem_unit_vector(fem_vector_2_points(point1=lintel_nodes[0], point2=lintel_nodes[1]))) # Add lintel to layer layer = project.find(wall.name.split('-')[0], 'layers') layer.add_shape(lintel) # Logging project.write_log(f"{lintel.name} has been successfully added to wall {lintel.connecting_wall.name}.") return lintel
[docs]def viia_truss(project: ViiaProject, line_object: Union[Column, Beam]) -> bool: """ Function to make a truss ('STAAF') of a beam ('BALK'). A beam is able to bend, a truss is only able to transfer axial forces. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - line_object (obj): Beam or column that will be changed into a truss. Output: - The name of the object is slightly changed; 'BALK' is replaced with 'STAAF' - The attribute 'elementclass_type' of the object is set to 'ENHTR3' (enhanced 3d truss element). - If model is created it will be updated in DIANA. """ # Check if beam exists if line_object not in project.collections.beams + project.collections.columns: raise ValueError( f"ERROR: Beam '{line_object.name}' has not been found. Make sure it is in collection of beams or columns.") # Create new geometry with 'STAAF' in the geometry name and with truss as element class # Replacing 'BALK' with 'STAAF' is only necessary for the purpose of uniqueness of the geometry area = line_object.geometry.geometry_model.area line_object.geometry = project.viia_geometries(geometry_name=f'STAAF-{round(area*1000000, 2)}') line_object.set_mesh_properties( elementsize=line_object.contour.get_length(), meshtype=line_object.meshtype, meshorder=line_object.meshorder) # Create in DIANA if model is created and running in DIANA if project.rhdhvDIANA.run_diana and project.rhdhvDIANA.model_created: project.rhdhvDIANA.setElementClassType('SHAPE', [line_object.name], line_object.get_elementclass()) project.write_log(f"{line_object.name} transformed from beam to truss.") return True
### =================================================================================================================== ### 6. Functions for non-linear concrete (discrete reinforcement) ### ===================================================================================================================
[docs]def viia_create_nonlinear_concrete_wall( project: ViiaProject, wall: Union[Wall, str], rebar_position='Center', concrete_material='BETON-C20/25', rebar_material='LIN-WAP-B500', horizontal_rebar='R12_150', vertical_rebar='R12_150', rebar_distance: float = 0.035): """ This function replaces a previously modelled linear concrete wall with a non-linear concrete wall. It adjusts the properties of the original wall to the specified concrete strength and applies grid reinforcement. It maintains existing openings and can be applied on walls in all directions. Optional is the inclusion of eccentricity. .. note:: x-axis is expected to be horizontal. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): The wall that will be modelled with non-linear material properties and rebar added. Alternative (str): Name of the wall. The wall must have been created already in PY-memory. - rebar_position (str): Not obligatory, default value is 'Center'. In that case no eccentricity is applied. If 'Eccentric' the grid reinforcement will be applied with corresponding eccentricity. - concrete_material (str): The name of the new non-linear concrete material. - rebar_material (str): The name of the material used for the reinforcement. - vertical_rebar (str): The definition of the vertical reinforcement that is used. - horizontal_rebar (str): The definition of the horizontal reinforcement that is used. - rebar_distance (float) in m: Distance from center of the reinforcement to the edge of the wall, i.e. coverage. Output: - The linear concrete wall is replaced with a non-linear reinforced concrete wall and modelled in DIANA or SCIA. For example: >>> viia_create_nonlinear_concrete_wall(wall5) This example will replace the linear concrete wall in the model with a non-linear concrete wall with a double grid reinforcement in the center. >>> viia_create_nonlinear_concrete_wall(wall5, Type='Eccentric', horizontal_rebar='2R8_200') In this example, the grid reinforcement has been applied with eccentricity, meaning that one reinforcement sheet is added at both sides of the wall. Also, different horizontal rebar is specified: 2Ø8-200. """ # Argument handling if isinstance(wall, str): for wall_obj in project.collections.walls: if wall_obj.name == wall: wall = wall_obj break # Check if wall is in Collections if wall not in project.collections.walls: project.write_log( f"ERROR: Wall not found for viia_create_nonlinear_concrete_wall function, " f"please check input '{wall.name}'.") return None # Check input if rebar_position.lower() == 'center' or rebar_position.lower() == 'eccentric': pass else: project.write_log(f"ERROR: Input for 'Type' has to be 'Center' or 'Eccentric'. Check your input.") return None if 'LIN-BETON' not in wall.material.name: project.write_log(f"ERROR: Input wall is not a linear concrete wall. Check your input '{wall.name}'.") return None # Get geometry object of wall geometry_object = wall.geometry eccentricity_in = 0 eccentricity_out = 0 reinforcement_name = None # Compute new names for wall and reinforcement old_wall_name = wall.name new_wall_name = wall.name.split('-')[0] + '-WANDEN-' + concrete_material + '-' + \ wall.name.split('-')[-2] + '-' + wall.name.split('-')[-1] # Variables handling new_contour = deepcopy(wall.contour.get_points()) reinforcement_geometry_name = None if rebar_position.lower() == 'center': # Set double reinforcement, because two reinforcement grids are centered if horizontal_rebar[0] == 'R': horizontal_rebar = '2' + horizontal_rebar else: number_of_bars_horizontal = int(horizontal_rebar.split('R')[0]) horizontal_rebar = str(2 * number_of_bars_horizontal) + 'R' + horizontal_rebar.split('R')[1] if vertical_rebar[0] == 'R': vertical_rebar = '2' + vertical_rebar else: number_of_bars_vertical = int(vertical_rebar.split('R')[0]) vertical_rebar = str(2 * number_of_bars_vertical) + 'R' + vertical_rebar.split('R')[1] reinforcement_name = wall.name.split('-')[0] + '-WANDEN-' + rebar_material + '-' + wall.name[-1] reinforcement_geometry_name = 'WAPENING-' + horizontal_rebar + 'x' + vertical_rebar + '-' + \ fem_axis_to_string(project, fem_horizontal_vector_in_plane(wall.contour.get_points(), project.check_precision))\ .replace('[', '(').replace(']', ')') else: reinforcement_name_in = wall.name.split('-')[0] + '-WANDEN-IN-' + rebar_material + '-' + wall.name[-1] reinforcement_name_out = wall.name.split('-')[0] + '-WANDEN-OUT-' + rebar_material + '-' + wall.name[-1] eccentricity_out = round(geometry_object.geometry_model.thickness/2 - rebar_distance, 3) eccentricity_in = -eccentricity_out reinforcement_geometry_name_in = 'WAPENING-' + horizontal_rebar + 'x' + vertical_rebar + '-' + \ fem_axis_to_string(project, fem_horizontal_vector_in_plane(wall.contour.get_points(), project.check_precision))\ .replace('[', '(').replace(']', ')') reinforcement_geometry_name_out = 'WAPENING-' + horizontal_rebar + 'x' + vertical_rebar + '-' + \ fem_axis_to_string(project, fem_horizontal_vector_in_plane(wall.contour.get_points(), project.check_precision))\ .replace('[', '(').replace(']', ')') # Apply reinforcement eccentricity if rebar_position.lower() == 'eccentric': # With the function the below, the outward normal vector is found based on the floor surface of the level outward_normal_direction = wall.outward_vector() new_contour_in = deepcopy(new_contour) for i in range(len(new_contour_in)): new_contour_in[i][0] = new_contour_in[i][0] + outward_normal_direction[0] * eccentricity_in new_contour_in[i][1] = new_contour_in[i][1] + outward_normal_direction[1] * eccentricity_in new_contour_in[i][2] = new_contour_in[i][2] + outward_normal_direction[2] * eccentricity_in new_contour_out = deepcopy(new_contour) for i in range(len(new_contour_out)): new_contour_out[i][0] = new_contour_out[i][0] + outward_normal_direction[0] * eccentricity_out new_contour_out[i][1] = new_contour_out[i][1] + outward_normal_direction[1] * eccentricity_out new_contour_out[i][2] = new_contour_out[i][2] + outward_normal_direction[2] * eccentricity_out if wall.openings: new_opening_in = deepcopy([opening.get_points() for opening in wall.openings]) for opening in new_opening_in: for i in range(len(opening)): opening[i][0] = opening[i][0] + outward_normal_direction[0] * eccentricity_in opening[i][1] = opening[i][1] + outward_normal_direction[1] * eccentricity_in opening[i][2] = opening[i][2] + outward_normal_direction[2] * eccentricity_in new_opening_in_polylines = [project.create_polyline(points) for points in new_opening_in] new_opening_out = deepcopy([opening.get_points() for opening in wall.openings]) for opening in new_opening_out: for i in range(len(opening)): opening[i][0] = opening[i][0] + outward_normal_direction[0] * eccentricity_out opening[i][1] = opening[i][1] + outward_normal_direction[1] * eccentricity_out opening[i][2] = opening[i][2] + outward_normal_direction[2] * eccentricity_out new_opening_out_polylines = [project.create_polyline(points) for points in new_opening_out] else: new_opening_in_polylines = [] new_opening_out_polylines = [] # Update the attributes of the object wall.set_name(new_wall_name) wall.material = project.viia_create_material(concrete_material) rebar_material = project.viia_create_material(rebar_material) # Create the new shapes for the reinforcement in PY-memory and in model if created if rebar_position.lower() == 'center': new_contour = project.create_polyline(new_contour) reinforcement_geometry = project.viia_create_geometry( geometry_name=reinforcement_geometry_name, material_object=rebar_material, shape_object=wall) surface_reinforcement = project.create_specified_main_surface_reinforcement( name=reinforcement_name, contour=new_contour, material=rebar_material, geometry=reinforcement_geometry, openings=wall.openings, host_members=[wall]) if wall.layer: wall.layer.add_shape(surface_reinforcement) else: new_contour_in = project.create_polyline(new_contour_in) new_contour_out = project.create_polyline(new_contour_out) reinforcement_geometry_in = project.viia_create_geometry( geometry_name=reinforcement_geometry_name_in, material_object=rebar_material, shape_object=wall) reinforcement_geometry_out = project.viia_create_geometry( geometry_name=reinforcement_geometry_name_out, material_object=rebar_material, shape_object=wall) surface_reinforcement_in = project.create_specified_main_surface_reinforcement( name=reinforcement_name_in, contour=new_contour_in, material=rebar_material, geometry=reinforcement_geometry_in, openings=new_opening_in_polylines, host_members=[wall]) surface_reinforcement_out = project.create_specified_main_surface_reinforcement( name=reinforcement_name_out, contour=new_contour_out, material=rebar_material, geometry=reinforcement_geometry_out, openings=new_opening_out_polylines, host_members=[wall]) if wall.layer: wall.layer.add_shapes([surface_reinforcement_in, surface_reinforcement_out]) # Notifications for user project.write_log( f"Non-linear concrete and reinforcement applied on wall '{old_wall_name}' " f"and is updated to wall '{wall.name}'.")
### =================================================================================================================== ### 8. Functions for surface shape operations ### ===================================================================================================================
[docs]def viia_split_all_walls(project: ViiaProject): """ Function to make all openings storey height and replace the weight with line loads. .. warning:: Line loads are not yet being applied. But a message is displayed which shows the size of the load. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - All the openings of the wall are made storey height. By doing this the wall is split and a new object is made for the second wall. If one of the walls has no openings nothing happens. """ fem_split_all_walls(project=project)
def viia_extend_wall_to_wall_horizontal(project, wall: Wall, target_wall: Wall): """ Function to extend a wall to another wall. .. warning:: Walls must be vertical surface, and no openings on the line that will be extended. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object reference for Wall object that needs to be extended. - target_wall (obj): Object reference for Wall object that is the target surface for the wall to be extended to. Output: - The wall is extended to the target wall. """ return project.extend_wall_to_surface_horizontal(wall=wall, target_surface=target_wall)
[docs]def viia_split_wall_horizontal( project, wall: Wall, splitting_height: Union[int, float], layer: Optional[str] = None, reference_point: Optional[List[float]] = None, precision: int = Config.CHECK_PRECISION) -> Wall: """ Function to split a wall horizontally based on a height (z-coordinate). Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object reference for Wall object that needs to be split. - splitting height (int, float): Height at which the wall will be split horizontally, the z-coordinate, in [m]. - layer (str): Optional input if the newly created part of the original wall should be in another layer. For example when the original wall is in 'N0' you can specify 'N1' so the split part is added to layer N1. If not provided, the newly created wall is added to the same layer as the existing wall. - reference_point (list of 3 floats): Reference point that should be in the part of the wall that will be the original wall. The other part will be the new wall. If not specified, the new wall will be the upper part. Output: - The wall is split in 2 parts, the original wall is adjusted and a new wall is created. - The new wall is generally the lowest of the 2 walls, except when the new wall belongs at a lower storey. - The new wall is returned. """ # Split the wall horizontally in FEM-client new_wall = wall.split_horizontal(z=splitting_height, reference_point=reference_point, precision=precision) # Set the layer if specified if layer: layer = project.viia_create_layer(name=layer, shapes=[new_wall]) new_wall.name = '-'.join([layer.name] + new_wall.name.split('-')[1:]) # Update the ID and the ID in the name new_wall.name = '-'.join(new_wall.name.replace('-SPLIT', '').split('-')[:-1] + [str(new_wall.id)]) # Return the newly created wall return new_wall
[docs]def viia_split_wall_vertical(wall: 'Wall', point: List[float], direction: List[float] = None, reference_point: List[float] = None): """ Function to split a wall vertically based on a given point and possibly direction. If no direction is specified, the normal vector of the wall is used. The point and direction essentially specify a vertical cutting plane that is used to split the wall in two parts. .. note:: The direction specifies the direction parallel to the cutting plane, not perpendicular! Input: - wall (obj): Wall object that needs to be split. - point (list of 3 floats): Point at which the wall must be split vertically. NOTE: This point doesn't necessarily need to be in plane of the wall, because it's projected to the wall. - direction (list of 3 floats): Direction in which the point is projected to the wall and parallel direction of the cutting plane that is used to split the wall. If no direction is specified, the normal vector of the wall is used. - reference_point (list of 3 floats): Reference point that should be in the part of the wall that will be the original wall. The other part will be the new wall. If not specified, the new wall will be the smaller part. Output: - The wall is split in 2 parts, the original wall is adjusted and a new wall is created. - The new wall is part in which the reference point is not found, otherwise it will be the smaller of the 2 parts. - The new wall is returned. """ return fem_split_wall_vertical(wall, point, direction, reference_point)
[docs]def viia_wall_split_floor_heights(Wall_main: 'Wall'): """ Function to split a wall according to the levels of the floors. Input: - Wall_main: The function will automatically loop over the wall object. Output: The wall is split at the heights of the floors. Example: >>> viia_wall_split_floor_heights(Wall1) """ project = Wall_main.project wall_points_list = [] wall_lines_list = [] vertical_line_list = [] floor_list = [] z_coordinate_list = [] z_wall_list = [] for wall in project.collections.walls: if wall == Wall_main: wall_lines_list.append(wall.contour.get_lines()) wall_points_list.append(wall.contour.get_points()) for point in wall_points_list[0]: z_wall_list.append(point[2]) z_max_wall = max(z_wall_list) z_min_wall = min(z_wall_list) # Getting all the floors that are connected to the wall # Note - These include all floors whose edge is partially or fully connected to the wall for floor in project.collections.floors: floor_lines_list = floor.contour.get_lines() for line1 in floor_lines_list: for line2 in wall_lines_list[0]: if line1.__class__.__name__ == 'Line' and line2.__class__.__name__ == 'Line': line1 = [line1.get_startnode().coordinates, line1.get_endnode().coordinates] line2 = [line2.get_startnode().coordinates, line2.get_endnode().coordinates] check1 = [element for element in line1 if element in line2] if check1 is not None: floor_list.append(floor.name) pass else: if fem_intersection_of_two_line_segments(line1=line1, line2=line2): floor_list.append(floor.name) floor_list = list(set(floor_list)) # Getting only the vertical lines in the walls for line in wall_lines_list[0]: start_node = line.get_startnode() end_node = line.get_endnode() if start_node.coordinates[2] != end_node.coordinates[2]: vertical_line_list.append(line) # Getting the points on the wall edge that are in the same height as the floor # and removing that floors that are connected to the edge of the wall, # but not spanning along the wall x_wall_line_start = vertical_line_list[0].get_startnode().coordinates[0] y_wall_line_start = vertical_line_list[0].get_startnode().coordinates[1] x_wall_line_end = vertical_line_list[1].get_startnode().coordinates[0] y_wall_line_end = vertical_line_list[1].get_startnode().coordinates[1] for floor1 in project.collections.floors: for floor2 in floor_list: if floor2 == floor1.name: floor_lines_list = floor1.contour.get_points() z_wall_line = floor_lines_list[0][2] p1 = [x_wall_line_start, y_wall_line_start, z_wall_line] p2 = [x_wall_line_end, y_wall_line_end, z_wall_line] if floor1.is_point_in_shape(p1) or floor1.is_point_in_shape(p2): floor_list.remove(floor2) # Creating the new walls for floor1 in project.collections.floors: for floor2 in floor_list: if floor2 == floor1.name: floor_lines_list = floor1.contour.get_points() z_coordinate_list.append(floor_lines_list[0][2]) z_coordinate_list.append(z_max_wall) z_coordinate_list.append(z_min_wall) z_coordinate_list = list(set(z_coordinate_list)) z_coordinate_list.sort(reverse=False) for i in range(len(z_coordinate_list) - 1): project.viia_create_wall( name=Wall_main.name + '-' + str(i+1), material=Wall_main.material, geometry=Wall_main.geometry, points=[[ [x_wall_line_start, y_wall_line_start, z_coordinate_list[i]], [x_wall_line_start, y_wall_line_start, z_coordinate_list[i + 1]], [x_wall_line_end, y_wall_line_end, z_coordinate_list[i + 1]], [x_wall_line_end, y_wall_line_end, z_coordinate_list[i]]]]) # Removing existing wall for wall in project.collections.walls: if wall == Wall_main: wall.remove_shape()
[docs]def viia_walls_split_floor_heights(project: ViiaProject): """ Function to split all walls according to the levels of the floors. The function will automatically loop over the wall objects. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: The walls are split at the heights of the floors. Limitation: This can only be used for rectangular walls Example: >>> viia_walls_split_floor_heights() """ Wall_list = [] for wall in project.collections.walls: Wall_list.append(wall) for wall in Wall_list: viia_wall_split_floor_heights(wall) return
[docs]def viia_split_surface( project: ViiaProject, target: Union[Surfaces, str], tool: Union[Surfaces, Polyline, List[List[float]], str], keep: Optional[str] = 'largest'): """ Function to split (update the contour) a surface using the intersection with a tool object. Input - target (obj): Object reference of 'Surfaces' that is the target of the splitting. Alt (str): Shapes can be inputted as a strings e.g. 'roof_1'. Allowed shapes are floors, walls, roofs and fstrips. Also, the full name of the surface shape can be provided. - tool (obj): Object reference of 'Surfaces' containing the tool surface-shape used to split the other. Alt (obj): Object reference of 'Polyline' containing the contour of the tool surface. Alt (list of list of 3 float): List of points that represent the tool surface. Alt (str): Shapes can be inputted as a strings e.g. 'floor_8'. Allowed shapes floors, walls, roofs and fstrips. Also, the full name of the surface shape can be provided. - keep (str): Indicate which surface(s) to keep. Options are 'largest', 'smallest', 'top', 'bottom'. Default value is 'largest'. Output: - If splitting was performed correctly, the Surfaces object's contour is updated. """ # Check the target input if isinstance(target, str): if target.split('_')[0].lower() == 'wall': target_shape = project.viia_get(collection='walls', id=int(target.split('_')[1])) elif target.split('_')[0].lower() == 'roof': target_shape = project.viia_get(collection='roofs', id=int(target.split('_')[1])) elif target.split('_')[0].lower() == 'floor': target_shape = project.viia_get(collection='floors', id=int(target.split('_')[1])) elif target.split('_')[0].lower() == 'fstrip': target_shape = project.viia_get(collection='fstrips', id=int(target.split('_')[1])) else: target_shape = project.viia_get(collection='surfaces', name=target) if target_shape is None: raise ValueError( f"ERROR: Wrong string input given for target, should be for example 'wall_1' or 'roof_5', or the " f"exact name of the shape. Provided was '{target}'.") target = target_shape if not isinstance(target, Surfaces): project.write_log(f"WARNING: Target object {target} could not be found, the split function was not performed.") return # Check the target input if isinstance(tool, str): if tool.split('_')[0].lower() == 'wall': tool_shape = project.viia_get(collection='walls', id=int(tool.split('_')[1])) elif tool.split('_')[0].lower() == 'roof': tool_shape = project.viia_get(collection='roofs', id=int(tool.split('_')[1])) elif tool.split('_')[0].lower() == 'floor': tool_shape = project.viia_get(collection='floors', id=int(tool.split('_')[1])) elif tool.split('_')[0].lower() == 'fstrip': tool_shape = project.viia_get(collection='fstrips', id=int(tool.split('_')[1])) else: tool_shape = project.viia_get(collection='surfaces', name=target) if tool_shape is None: raise ValueError( f"ERROR: Wrong string input given for tool, should be for example 'wall_1' or 'roof_5', or the " f"exact name of the shape. Provided was '{tool}'.") tool = tool_shape if not isinstance(tool, (Surfaces, Polyline, List)): project.write_log(f"ERROR: Tool object {tool} could not be found, the split function was not performed.") return # Execute the split surface function in fem-client return target.split_surface(tool_surface=tool, keep=keep)
### =================================================================================================================== ### 8. Functions to shorten walls (MRS) ### ===================================================================================================================
[docs]def viia_connected_walls(wall: Wall) -> Tuple[dict, dict]: """ This function checks if at least two points of a wall lie inside the boundaries of another wall. Openings are neglected. ..note :: Do not apply this function on walls which are not parallel to x=0 or y=0. Input: - WallObject: Name of the wall object e.g. Wall12 or Wall120 (names of the object must not be used) Output: - Returns two dictionaries as tuple. The first dictionary contains connected walls at the minimum side of the wall with the thickness of the connected walls and the direction of the wall. The second dictionary contains connected walls at the maximum side of the wall with the thickness of the connected walls and the direction of the wall. """ project = wall.project MinDic = {} MaxDic = {} Mindic = False Maxdic = False precision = 3 ConnectedWallList = '' PointList1 = [x for y in range(len(wall.points[0])) for x in wall.points[0][y]] xmin = min(PointList1[0::3]) xmax = max(PointList1[0::3]) ymin = min(PointList1[1::3]) ymax = max(PointList1[1::3]) zmin = min(PointList1[2::3]) zmax = max(PointList1[2::3]) ''' In case of a wall-T connection only the web of the T needs to be resized and not the flanges. This parts checks if the WallObject is part of the flange of the T and at what side(s) it continues. Correct deconnection: Frontview Topview |‾‾‾‾‾‾|‾‾‾‾‾‾| | | | | | |______|______| _____________ ''' # this loop checks if there is a wall connected in parallel to the WallObject that is passed to this function. for i in project.collections.walls: if i != wall: # declaration of counter variables # tracks the number of points that are connected at the min and max side of the WallObject. PIPAmount = 0 PIPMinAmount = 0 PIPMaxAmount = 0 normal_vector = i.normal_direction() try: # Calculate crossproduct between the normal_vectors of the 2 walls. if == 0: sheets are parallel CROSS = fem_cross_product_vector(normal_vector, wall.normal_direction()) except TypeError: print(i.name, i.points[0]) break # only walls which are parallel to the WallObject need to be checked further in this part. if [abs(round(x, 3)) for x in CROSS] == [0.0, 0.0, 0.0]: # if CROSS == 0: walls are parallel # determine the max and min coordinates of the wall which is found to be parallel to WallObject. PointList = [x for y in range(len(i.points[0])) for x in i.points[0][y]] # add all xyz vallues to 1 list ixmin = min(PointList[0::3]) # calculate min x ixmax = max(PointList[0::3]) # calculate max x iymin = min(PointList[1::3]) # calculate min y iymax = max(PointList[1::3]) # calculate max y # this loop checks whether the found parallel wall is connected to the WallObject and at which side (min or max side) # all points of the WallObject are checked. for k in range(len(wall.points[0])): if wall.points[0][k] in i.points[0]: # check if point k of WallObject is in WallObject as Well #check if the wall i is connected at the minside of WallObject. if (fem_smaller(abs(xmin - ixmax), 0.001) and abs(normal_vector[1]) == 1.0) or \ (fem_smaller(abs(ymin - iymax), 0.001) and abs(normal_vector[0]) == 1.0): PIPMinAmount += 1 # count how many points of the plane are connecten to the perpendicular plane # check if the wall i is connected at the max side of WallObject. elif (fem_smaller(abs(ixmin - xmax), 0.001) and abs(normal_vector[1]) == 1.0) or \ (fem_smaller(abs(iymin - ymax), 0.001) and abs(normal_vector[0]) == 1.0): PIPMaxAmount += 1 # count how many points of the plane are connecten to the perpendicular plane if PIPMaxAmount > 0: # check if walls are connected at Max-side of WallObject Maxdic = True # set var to True. This means The WallObject is continues at its max side if PIPMinAmount > 0: # check if walls are connected at Min-side of WallObject Mindic = True # set var to True. This means The WallObject is continues at its min side ''' In this part it is checked wether the WallObject needs to be resized. It will also be checked what side of the wall needs to be resized and by how much the wall needs to be resized. The resizing amount is based on the thickness of the perpendicularly connected wall. ''' for i in project.collections.walls: # loop over all walls to check them if they are connected to the WallObject if i != wall: # exclude the WallObject normal_vector = i.normal_direction() PointList2 = [x for y in range(len(i.points[0])) for x in i.points[0][y]] # add all xyz vallues to 1 list ixmin = min(PointList2[0::3]) # calculate min x ixmax = max(PointList2[0::3]) # calculate max x iymin = min(PointList2[1::3]) # calculate min y iymax = max(PointList2[1::3]) # calculate max y izmax = max(PointList2[2::3]) # calculate max y izmin = min(PointList2[2::3]) # calculate max y Continue = False # check if connected Connections = len([1 for x in wall.points[0] if x in i.points[0]]) if fem_greater(izmax, zmin) and fem_greater(zmax, izmin): # check if planes are on the same level if fem_smaller(abs(xmin-ixmax), 0.001) and fem_greater(ymax + 0.001, iymin) and \ fem_greater(iymax + 0.001, ymax): # four checks to see if the planes are connected Continue = True elif fem_smaller(abs(ymin-iymax), 0.001) and fem_greater(xmax + 0.001, ixmin) and \ fem_greater(ixmax + 0.001, xmax): Continue = True elif fem_smaller(abs(ixmin-xmax), 0.001) and fem_greater(ymax + 0.001, iymin) and \ fem_greater(iymax + 0.001, ymax): Continue = True elif fem_smaller(abs(iymin-ymax), 0.001) and fem_greater(xmax + 0.001, ixmin) and \ fem_greater(ixmax + 0.001, xmax): Continue = True if Connections>1: Continue=True if Continue == True: normal_vector = i.normal_direction() # create normal vector of the wall thats being checked PIPAmount = 0 # count how many points of the plane are connecten to the perpendicular plane for k in range(len(wall.points[0])): # loop over all points DOT = fem_dot_product_vector(normal_vector, [round(x,precision) for y in range(0,3) for x in # calculate dot product. if DOT == 0 the two planes are perpendicular to each other [i.points[0][0][y] - wall.points[0][k][y]]]) if abs(round(DOT, precision)) == 0.0 : # check if normal_vectors are perpendicular PIPAmount += 1 # count how many points of the plane are connecten to the perpendicular plane if PIPAmount == 2: # at least 2 points need to be connected Thickness = i.geometry.name.split('-')[1] # take thickness from geometry. this is de decrease size if wall.points[0][k][0] == xmin and xmax != xmin and Mindic == False and \ fem_greater(ymax-0.001, iymin): # define in a dictionary what part of the wall needs to be resized, the direction... ConnectedWallList+='min '+i.name+' /n ' MinDic[wall] = [wall.normal_direction(), 'min', Thickness] # ... in which the wall needs to be resized and by how much the wall needs to be ... if wall.points[0][k][0] == xmin and xmax != xmin and Mindic == False and \ fem_smaller(ymax +0.001, iymax): # define in a dictionary what part of the wall needs to be resized, the direction... ConnectedWallList+='min '+i.name+' /n ' MinDic[wall] = [wall.normal_direction(), 'min', Thickness] # ... in which the wall needs to be resized and by how much the wall needs to be ... elif wall.points[0][k][0] == xmax and xmax != xmin and Maxdic == False and \ fem_greater(ymax - 0.001, iymin): # ... resized. In case Mindic or Maxdic is True: the wall is part of the flange ... ConnectedWallList+='max '+i.name+' /n ' MaxDic[wall] = [wall.normal_direction(), 'max', Thickness] # ... of a wall T connection, and therefore doesnt need to be resized. elif wall.points[0][k][0] == xmax and xmax != xmin and Maxdic == False and \ fem_smaller(ymax +0.001, iymax): # ... resized. In case Mindic or Maxdic is True: the wall is part of the flange ... ConnectedWallList+='max '+i.name+' /n ' MaxDic[wall] = [wall.normal_direction(), 'max', Thickness] # ... of a wall T connection, and therefore doesnt need to be resized. elif wall.points[0][k][1] == ymin and ymax != ymin and Mindic == False and \ fem_greater(xmax -0.001, ixmin): ConnectedWallList+='min '+i.name+' /n ' MinDic[wall] = [wall.normal_direction(), 'min', Thickness] elif wall.points[0][k][1] == ymin and ymax != ymin and Mindic == False and \ fem_smaller(xmax +0.001, ixmax): ConnectedWallList+='min '+i.name+' /n ' MinDic[wall] = [wall.normal_direction(), 'min', Thickness] elif wall.points[0][k][1] == ymax and ymax != ymin and Maxdic == False and \ fem_greater(xmax -0.001, ixmin): ConnectedWallList+='max '+i.name+' /n ' MaxDic[wall] = [wall.normal_direction(), 'max', Thickness] elif wall.points[0][k][1] == ymax and ymax != ymin and Maxdic == False and \ fem_smaller(xmax +0.001, ixmax): ConnectedWallList+='max '+i.name+' /n ' MaxDic[wall] = [wall.normal_direction(), 'max', Thickness] ### =============================================================================================================== ### 8.1.3 Part III: Check if WallObject is part of a x wall X connection ### =============================================================================================================== ''' If the WallObject is part of a wall X connection, none of the walls in the X are resized yet. 1 of the 2 continues walls needs to be resized. Correct deconnection: Situation Solution | | | | __|_|__ ___| |___ |+| |-| ‾‾|‾|‾‾ ‾‾‾| |‾‾‾ ''' xmaxcheck1, xmincheck1, ymaxcheck1, ymincheck1 = [0 for x in range(4)] # variables to check if the perpendicular planes are at different sides of the WallOBject xmaxcheck2, xmincheck2, ymaxcheck2, ymincheck2 = [0 for x in range(4)] MinXWall, MaxXWall, MinYWall, MaxYWall = [0 for x in range(4)] if Maxdic == True or Mindic == True: # In part 1 these have been set to True for i in project.collections.walls: if i != wall: # exclude the WallObject PointList2 = [x for y in range(len(i.points[0])) for x in i.points[0][y]] # add all xyz vallues to 1 list ixmin = min(PointList2[0::3]) # calculate min x ixmax = max(PointList2[0::3]) # calculate max x iymin = min(PointList2[1::3]) # calculate min y iymax = max(PointList2[1::3]) # calculate max y izmax = max(PointList2[2::3]) # calculate max y izmin = min(PointList2[2::3]) # calculate max y Continue = False # check if connected if fem_greater(izmax, zmin) and fem_greater(zmax, izmin): # check if planes are on the same level if fem_smaller(abs(xmin-ixmax), 0.001) and fem_greater(ymax + 0.001, iymin) and \ fem_greater(iymax + 0.001, ymax): # four checks to see if the planes are connected Continue = True elif fem_smaller(abs(ymin-iymax), 0.01) and fem_greater(xmax + 0.001, ixmin) and \ fem_greater(ixmax + 0.001, xmax): Continue = True elif fem_smaller(abs(ixmin-xmax), 0.001) and fem_greater(ymax + 0.001, iymin) and \ fem_greater(iymax + 0.001, ymax): Continue = True elif fem_smaller(abs(iymin-ymax), 0.01) and fem_greater(xmax + 0.001, ixmin) and \ fem_greater(ixmax + 0.001, xmax): Continue = True if Continue == True: normal_vector = i.normal_direction() # create normal vector of the wall thats being checked PIPAmount = 0 # count how many points of the plane are connecten to the perpendicular plane for k in range(len(wall.points[0])): DOT = fem_dot_product_vector(normal_vector, [round(x,precision) for y in range(0,3) for x in # calculate dot product [i.points[0][0][y] - wall.points[0][k][y]]]) if round(DOT, precision) == 0.0 or round(DOT, precision) == -0.0: # check if normal_vectors are perpendicular ''' check if two nodes are connected ''' if 'iixmin' not in locals(): iixmin, iiymin, iizmin, iixmax, iiymax, iizmax = ixmin, iymin, izmin, ixmax, iymax, izmax Continue2 = False else: if fem_greater(izmax, iizmin) and fem_greater(iizmax, izmin): if fem_smaller(abs(iixmin-ixmax), 0.001) and fem_greater(iiymax + 0.001, iymin) and \ fem_greater(iymax + 0.001, iiymax): Continue2 = True elif fem_smaller(abs(iiymin-iymax), 0.01) and fem_greater(iixmax + 0.001, ixmin) and \ fem_greater(ixmax + 0.001, iixmax): Continue2 = True elif fem_smaller(abs(ixmin-iixmax), 0.001) and fem_greater(iiymax + 0.001, iymin) and \ fem_greater(iymax + 0.001, iiymax): Continue2 = True elif fem_smaller(abs(iymin-iiymax), 0.01) and fem_greater(iixmax + 0.001, ixmin) and \ fem_greater(ixmax + 0.001, iixmax): Continue2 = True # Some extra checks if Continue2 == True: Thickness = i.geometry.name.split('-')[1] # take thickness from geometry. this is de decrease size if wall.points[0][k][0] == xmin and xmax != xmin and Mindic == True and fem_greater(ymax-0.001, iymin) and ymincheck1 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='min '+i.name+' /n ' MinXWall += 1 ymincheck1 += 1 elif wall.points[0][k][0] == xmin and xmax != xmin and Mindic == True and fem_smaller(ymax +0.001, iymax) and ymincheck2 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='min '+i.name+' /n ' MinXWall += 1 ymincheck2 += 1 elif wall.points[0][k][0] == xmax and xmax != xmin and Maxdic == True and fem_greater(ymax - 0.001, iymin) and ymaxcheck1 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='max '+i.name+' /n ' MaxXWall += 1 ymaxcheck1 += 1 elif wall.points[0][k][0] == xmax and xmax != xmin and Maxdic == True and fem_smaller(ymax +0.001, iymax) and ymaxcheck2 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='max '+i.name+' /n ' MaxXWall += 1 ymaxcheck2 += 1 elif wall.points[0][k][1] == ymin and ymax != ymin and Mindic == True and fem_greater(xmax -0.001, ixmin) and xmincheck1 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='min '+i.name+' /n ' MinYWall += 1 xmincheck1 += 1 elif wall.points[0][k][1] == ymin and ymax != ymin and Mindic == True and fem_smaller(xmax +0.001, ixmax) and xmincheck2 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='min '+i.name+' /n ' MinYWall += 1 xmincheck2 += 1 elif wall.points[0][k][1] == ymax and ymax != ymin and Maxdic == True and fem_greater(xmax -0.001, ixmin) and xmaxcheck1 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='max '+i.name+' /n ' MaxYWall += 1 xmaxcheck1 += 1 elif wall.points[0][k][1] == ymax and ymax != ymin and Maxdic == True and fem_smaller(xmax +0.001, ixmax) and xmaxcheck2 < 1 and i.name not in ConnectedWallList: ConnectedWallList+='max '+i.name+' /n ' MaxYWall += 1 xmaxcheck2 += 1 # Check if the wall needs to be resized and than add the Resize information to a dictionary if MinXWall > 1: MinDic[wall] = [wall.normal_direction(), 'min', Thickness] if MaxXWall > 1: MaxDic[wall] = [wall.normal_direction(), 'max', Thickness] if MinYWall > 1: MinDic[wall] = [wall.normal_direction(), 'min', Thickness] if MaxYWall > 1: MaxDic[wall] = [wall.normal_direction(), 'max', Thickness] return MinDic, MaxDic
[docs]def viia_wall_resize(wall: Wall, side: str, resize: float, resize_direction: List[float]): """ Functions which shortens a wall. Input: - wall (obj): Object reference of wall to be resized. side: the side which needs to be resized. e.g. 'min' as for the side with the minimum x or y value or 'both' for both sides. Resize: size in m which needs to be cut off the wall. e.g. 0.05 to cut off 50 milimeters ResizeDirection: the direction in which the resizing must take place: e.g. [1,0,0] for resizing in x-direction Returns: The wall will be resized in the viia Python database Limitations: Do not apply this function on walls which are not parallel to x=0 or y=0 """ PointList = [x for y in range(len(wall.points[0])) for x in wall.points[0][y]] xmin = min(PointList[0::3]) xmax = max(PointList[0::3]) ymin = min(PointList[1::3]) ymax = max(PointList[1::3]) resize /= 2 if side == 'min' or side == 'both': for i in range(len(wall.points[0])): if [abs(float(x)) for x in resize_direction] == [0.0, 1.0, 0.0]: if fem_smaller(wall.points[0][i][0], xmin + resize): wall.points[0][i][0] = xmin + resize point_list = [[i for i in wall.points[0] if wall.points[0].count(i) < 2]] for i in range(1, len(wall.points)): point_list.append(wall.points[i]) wall.points = point_list if side == 'max' or side == 'both': for i in range(len(wall.points[0])): if [abs(float(x)) for x in resize_direction] == [0.0, 1.0, 0.0]: if fem_greater(wall.points[0][i][0], xmax - resize): wall.points[0][i][0] = xmax - resize if side == 'min' or side == 'both': for i in range(len(wall.points[0])): if [abs(float(x)) for x in resize_direction] == [1.0, 0.0, 0.0]: if fem_smaller(wall.points[0][i][1], ymin + resize): wall.points[0][i][1] = ymin + resize if side == 'max' or side == 'both': for i in range(len(wall.points[0])): if [abs(float(x)) for x in resize_direction] == [1.0, 0.0, 0.0]: if fem_greater(wall.points[0][i][1], ymax - resize): wall.points[0][i][1] = ymax - resize if side == 'min' or side == 'both': for i in range(len(wall.points[0])): if [abs(float(x)) for x in resize_direction] != [0.0, 1.0, 0.0] and \ [abs(float(x)) for x in resize_direction] != [1.0, 0.0, 0.0]: if fem_smaller(wall.points[0][i][1], ymin + abs(resize * resize_direction[0])): wall.points[0][i][1] = ymin + abs(resize * resize_direction[0]) if fem_smaller(wall.points[0][i][0], xmin + abs(resize * resize_direction[1])): wall.points[0][i][0] = xmin + abs(resize * resize_direction[1]) if side == 'max' or side == 'both': for i in range(len(wall.points[0])): if [abs(float(x)) for x in resize_direction] != [0.0, 1.0, 0.0] and \ [abs(float(x)) for x in resize_direction] != [1.0, 0.0, 0.0]: if fem_greater(wall.points[0][i][1], ymax - abs(resize * resize_direction[0])): wall.points[0][i][1] = ymax - abs(resize * resize_direction[0]) if fem_greater(wall.points[0][i][0], xmax - abs(resize * resize_direction[1])): wall.points[0][i][0] = xmax - abs(resize * resize_direction[1]) return
[docs]def viia_auto_wall_resize(wall): """ This function Resizes a wall automatically. Input: - wall (obj): Object reference of wall to be resized. Output: - The points of the resized walls will be updated. In case the Wall object is not orientated parallel to x=0 or y=0 this will be written to the log. """ for ConnectedWall in viia_connected_walls(wall)[0]: viia_wall_resize(ConnectedWall, viia_connected_walls(wall)[0][ConnectedWall][1], float(viia_connected_walls(wall)[0][ConnectedWall][2]) / 1000, viia_connected_walls(wall)[0][ConnectedWall][0]) for ConnectedWall in viia_connected_walls(wall)[1]: viia_wall_resize(ConnectedWall, viia_connected_walls(wall)[1][ConnectedWall][1], float(viia_connected_walls(wall)[1][ConnectedWall][2]) / 1000, viia_connected_walls(wall)[1][ConnectedWall][0]) PointList1 = [x for y in range(len(wall.points[0])) for x in wall.points[0][y]] xmin = min(PointList1[0::3]) xmax = max(PointList1[0::3]) ymin = min(PointList1[1::3]) ymax = max(PointList1[1::3]) for i in range(len(wall.points)): if i > 0: PointList2 = [x for y in range(len(wall.points[i])) for x in wall.points[i][y]] opening_xmin = min(PointList2[0::3]) opening_xmax = max(PointList2[0::3]) opening_ymin = min(PointList2[1::3]) opening_ymax = max(PointList2[1::3]) if abs(float(wall.normal_direction()[0])) == 1.0: if fem_smaller(opening_ymin, ymin) or fem_greater(opening_ymax, ymax): wall.project.write_log(f"Outside boundary = {wall.points[0]} Opening = {wall.points[i]}") wall.project.write_log( f"Wall {wall.name} has openings which are not located inside the boundaries of the surface.") if abs(float(wall.normal_direction()[1])) == 1.0: if fem_smaller(opening_xmin, xmin) or fem_greater(opening_xmax, xmax): wall.project.write_log(f"Outside boundary = {wall.points[0]} Opening = {wall.points[i]}") wall.project.write_log( f"Wall {wall.name} has openings which are not located inside the boundaries of the surface.") return
[docs]def viia_manual_wall_resize(walls: List[Wall], side: str, resize: float, resize_direction: List[float]): """ This function resize wall(s) manually. Input: - walls (list of obj): List of object references of walls. - side (str): The side of the wall which needs to be resized. e.g. 'min' as for the side with the minimum x or y value or 'both' for both sides. - resize (str): Size, in [m], which needs to be cut off the wall. e.g. 0.05 to cut off 50 milimeters. - resize_direction (list of 3 float): The direction in which the resizing must take place: e.g. [1, 0, 0] for resizing in x-direction. Output: - The points of the resized walls will be updated. """ for wall in walls: viia_wall_resize(wall=wall, side=side, resize=resize, resize_direction=resize_direction) return
### =============================================================================================================== ### 8.5 viiaAutoWallResize ### ===============================================================================================================
[docs]def viia_auto_resize_all_walls(project: ViiaProject): """ This function resizes all walls automatically. Pay attention when using, unwanted walls may be shortened. .. note:: All walls must be defined in the viia Python Database. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - The points of the Auto resized walls will be updated in the python database. """ project.write_log("Starting resizing all walls in the model.") counter = 0 for wall in project.collections.walls: if abs(wall.normal_direction()[1]) == 1.0: viia_auto_wall_resize(wall=wall) counter += 1 elif abs(wall.normal_direction()[0]) != 1.0: viia_auto_wall_resize(wall=wall) for wall in project.collections.walls: if abs(wall.normal_direction()[0]) == 1.0: viia_auto_wall_resize(wall) counter +=1 elif abs(wall.normal_direction()[1]) != 1.0: viia_auto_wall_resize(wall) counter += 1 project.write_log(f"{counter} walls have been shortened. Function to resize walls finished.")
### =================================================================================================================== ### 9. Wall to column (MRS) ### ===================================================================================================================
[docs]def viia_wall_to_columns(project: ViiaProject, ref_width: float = 1.0): """ This function converts all walls with a width smaller than the reference width into columns. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - ref_width (float): Maximum width of wall that will be converted to column in [m]. Default value is 1m. Output: - Walls with a width less than the reference width are deleted and replaced by columns of the same dimension. """ project.convert_all_walls_to_columns(reference_width=ref_width)
[docs]def viia_convert_all_columns_to_walls(project: ViiaProject, reference_width: float = 1.0) -> List[Wall]: """ This function converts all columns with a width larger than the provided dimension to walls, with the same properties. The width of the wall is considered the height of the cross-section (local z-axis), the thickness is considered the width of the cross-section (local y-axis). Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - reference_width (float): Minimum height of the cross-section of the column that will be converted to wall, in [m]. Default value is 1m. Output: - All columns with a cross-sectional height (z-axis) larger than the reference width, will be converted to walls. - Naming convention according VIIA project is applied. - Returns list of all newly created walls. """ def _rename(column_name: str) -> str: if 'KOLOMMEN' in column_name: column_name = column_name.replace('KOLOMMEN', 'WANDEN') new_id = project.find_max_id(project.collections.walls) + 1 return '-'.join(column_name.split('-')[:-1]).split('x')[0] + f'-{new_id}' def _get_geometry(geometry_name): # Check if this geometry has already been created for geometry_object in project.collections.geometries: if geometry_object.name == geometry_name: return geometry_object # If it is not created yet, it is created return project.viia_geometries(geometry_name) walls = project.convert_all_columns_to_walls(reference_width=reference_width, rename_function=_rename) for wall in walls: if wall.geometry.name.startswith('Thickness '): wall.geometry = _get_geometry( geometry_name='-'.join(['WAND', str(1000 * wall.geometry.geometry_model.thickness)])) return walls
### =================================================================================================================== ### 10. Walls with openings on contour (NL: 'Kantelen wanden') ### ===================================================================================================================
[docs]def viia_openings_on_contour_wall(project: ViiaProject, wall: Union[Wall, str], margin: float = 0.005): """ In Dutch: 'Kantelen wanden'. This function analyses a wall object for incorrectly defined openings which are part of the main element. The element is adapted such that an opening will be added to the element. These incorrectly defined openings are caused by the revit export having openings on the boundary of an element. This script will add by default a 5 mm margin between the element boundary and the generated opening to prevent errors with openings laying outside of the element itself. ..warning:: this script will not work correctly if the wall geometry is non-rectangular (for example if the wall has a slanted side). ..note:: this functions applies a projection to 2D and back. very small rounding errors might be introduced. ..note:: in previous versions nodal rounding to 1 mm was applied, this is now removed. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - wall (obj): Object refeence of the wall that needs to be checked and adapted for incorrect openings. Alternativa (str): Name of the wall. The wall must be available in the PY-memory. - margin (float): The margin in [m] between the element boundary and the generated opening to prevent errors with openings laying outside the element itself. Output: - The wall passed to this function will have the geometry adapted with a new element geometry and added openings that have been found that were incorrectly part of the main element. """ # Argument handling if isinstance(wall, str): wall = fem_find_object(reference=wall, collection=project.collections.walls) # Check if wall is in Collections if wall not in project.collections.walls: raise ValueError( f"ERROR: Wall not found for viia_openings_on_contour_wall function, please check input {wall.name}.") # The original 3D object points are stored to apply the reverse 3D 2D projection at the end. original_object_points = [wall.contour.get_points()] for opening in wall.openings: original_object_points.append(opening.get_points()) # This function will apply the openings_on_contour_wall routine on a 2D defined element. Therefore a 3D>2D # projection is applied here to the points of the wall_object. wall_object_2d = deepcopy(wall) wall_object_2d.contour = fem_3d_to_2d_projection(wall.contour.get_points()) wall_object_2d.openings = list() for opening in wall.openings: wall_object_2d.openings.append(fem_3d_to_2d_projection(opening.get_points())) # Point_list[0] takes every coordinate in the first line of an element. alle entries are double # structure is: [ x-coordinate 1, y-coordinate 1, x-coordinate 2, y-coordinate 2 etc.] point_list = [x for y in range(len(wall_object_2d.points[0])) for x in wall_object_2d.points[0][y]] # Rounding appears no longer necessary commented out for now AK 12-10-2018 # to prevent small precision errors this function will apply a rounding to 1 mm accuracy. # point_list_rounded = [] # for x in point_list: # point_list_rounded.append(round(x, 3)) point_list_rounded = deepcopy(point_list) # Determine the outer boundaries of the wall along the x-axis and y-axis xmin = min(point_list_rounded[0::2]) xmax = max(point_list_rounded[0::2]) ymin = min(point_list_rounded[1::2]) ymax = max(point_list_rounded[1::2]) # This list variable will store the index of the points which are not on the outer boundary # After initial loop this will contain non-unique entries. cleanup happens after opening_points = [] # This loop checks for points which are not on the outer x boundary # When found the index of that point is added to the list which should be turned into openings for number, xcoor in enumerate(point_list_rounded[0::2], start=0): if (xcoor != xmin) and (xcoor != xmax): opening_points.append(number) # This loop checks for points which are not on the outer y boundary # When found the index of that point is added to the list which should be turned into openings for number, ycoor in enumerate(point_list_rounded[1::2], start=0): if (ycoor != ymin) and (ycoor != ymax): opening_points.append(number) # Makes the indices list of points unique by transforming back and forth between set and list opening_points = list(set(opening_points)) # Determine the indices of points which should be the main element # First generate a list structure with all point indices element_points = [] for index in range(len(wall_object_2d.points[0])): element_points.append(index) # Find the intersection between the total set and opening_points subset # For convenience both element_points and opening_points are converted to sets. element_points = set(element_points) opening_points = set(opening_points) # This function removes all items of a subset (opening_points) from the main set element_points. # Sorted is added to maintain the order of nodes correctly subset_element = sorted(element_points.difference(opening_points)) # Now we have the result. a list of indices which should be made into openings # and a list of indices which will make up the main element element_points = list(subset_element) opening_points = list(opening_points) # Bugfix 18-6 # This loop reorders the opening points in case an opening is found that spans across the last and first node of # the opening. this is done with a check on the first and last node being present in the opening list # then the list will be reordered such that the starting point is the first node which is consecutive from the the # last node. so it starts by looking backwards from the last node until there is a node in the opening list which is # not consecutive from this point. if ((len(point_list_rounded) / 2) - 1) in opening_points and 0 in opening_points: n_end = opening_points.index((len(point_list_rounded) / 2) - 1) while opening_points[n_end - 1] == opening_points[n_end] - 1: n_end = n_end - 1 opening_points = opening_points[n_end:] + opening_points[:n_end] # Following step is to check how many openings have to be created. # To check this an opening ends when the current and next point are on an outer boundary line. # In this list multiple lists will be stored with each entry being a list of indices of nodes that make up 1 opening # to be created. This list should contain all entries of opening_points split into n groups of openings. # openings = [] # Temporary variable that stores where the opening_ponts must be split split_help_var = [] # In this loop every node that is found part of the opening_points set is checked to see whether that node is # on a boundary. if the current node and the one after it are both on a boundary the opening ends and new # opening starts. for counter, n in enumerate(opening_points): xcoor_node = point_list_rounded[(2 * n)] ycoor_node = point_list_rounded[(2 * n) + 1] if (xcoor_node == xmin) or (xcoor_node == xmax) or (ycoor_node == ymin) or (ycoor_node == ymax): # Exception must be made here if the node in question is the last node. then the following node is the # first node of the element. if (2 * n + 1) == (len(point_list_rounded) - 1): xcoor_following_node = point_list_rounded[0] ycoor_following_node = point_list_rounded[1] else: xcoor_following_node = point_list_rounded[(2 * (n + 1))] ycoor_following_node = point_list_rounded[(2 * (n + 1)) + 1] # If the following node is also on the boundary it is added here to the list where the openings # must be split. if (xcoor_following_node == xmin) or (xcoor_following_node == xmax) or \ (ycoor_following_node == ymin) or (ycoor_following_node == ymax): split_help_var.append(counter + 1) # Sub function that partitions alist into multiple sub-lists using a set of indices def partition(alist, indices): return [alist[l:m] for l, m in zip([0] + indices, indices + [None])] # Apply the help function from above to split the opening points list into the distinct openings. openings = partition(opening_points, split_help_var) # If only one opening has to be created the split operation above will result in the following # openings = [ [first entry with entire opening_points list], [empty list]] # therefor in this case we cut off the empty list at the end to find our result if [] in openings: openings = openings[:-1] # Check for 1 node openings. these are generated when a non-essential node is in the element on the boundary. # These 1 node openings will be removed here. for index, ope in enumerate(openings): if len(ope) == 1: openings.remove(openings[index]) # Check for 3 node corner openings. one node must be appended to those. # Check performed by seeing if the triangle spans a corner. this means the first opening point and last point # will be on a different boundary. one will be on a horizontal boundary and one on a vertical boundary # for every opening that a node must be appended here we will store the index of the opening and the node geo tri_ope_index = [] tri_ope_geo = [] # In this loop all cases of corner openings are checked by first finding the coordinates and checking against # all the 8 cases: 4 corners and 2 possible orientations of the triangle (CW or CCW). # when a triangular corner opening is found the index of the opening in the opening list is stored and the # geometry of the omitted node that must be appended to the opening (this is on the boundary intersection). for index, ope in enumerate(openings): if len(ope) == 3: xcoor_first_node = point_list_rounded[(2 * ope[0])] ycoor_first_node = point_list_rounded[(2 * ope[0]) + 1] xcoor_third_node = point_list_rounded[(2 * ope[2])] ycoor_third_node = point_list_rounded[(2 * ope[2]) + 1] if (xcoor_first_node == xmin) and (ycoor_third_node == ymin): tri_ope_index.append(index) tri_ope_geo.append([xmin, ymin]) elif (xcoor_first_node == xmin) and (ycoor_third_node == ymax): tri_ope_index.append(index) tri_ope_geo.append([xmin, ymax]) elif (xcoor_first_node == xmax) and (ycoor_third_node == ymin): tri_ope_index.append(index) tri_ope_geo.append([xmax, ymin]) elif (xcoor_first_node == xmax) and (ycoor_third_node == ymax): tri_ope_index.append(index) tri_ope_geo.append([xmax, ymax]) elif (xcoor_third_node == xmin) and (ycoor_first_node == ymin): tri_ope_index.append(index) tri_ope_geo.append([xmin, ymin]) elif (xcoor_third_node == xmin) and (ycoor_first_node == ymax): tri_ope_index.append(index) tri_ope_geo.append([xmin, ymax]) elif (xcoor_third_node == xmax) and (ycoor_first_node == ymin): tri_ope_index.append(index) tri_ope_geo.append([xmax, ymin]) elif (xcoor_third_node == xmax) and (ycoor_first_node == ymax): tri_ope_index.append(index) tri_ope_geo.append([xmax, ymax]) list_tri_ope = list(zip(tri_ope_index[:], tri_ope_geo[:])) dict_tri_ope = dict(list_tri_ope) ##### second part. generating new geometry. ########## # This list will contain the points of the main element. this will be i.points[0] after completion. new_element_geo = [] # First step is check the element points. if after finding openings it is less than 4 it will be regenerated if len(element_points) <= 3: # in this case an element will be generated based on the calculated outer boundaries of the element. new_element_geo = [[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]] else: for point_indices in element_points: new_element_geo.append( [point_list_rounded[(2 * point_indices)], point_list_rounded[(2 * point_indices) + 1]]) # Firstly all opening coordinates are generated in this list which houses the new geometry. new_openings_geo = [] # In this double loop for each opening every node coordinate is looked up in the pointslist and added to the # Iist of new openings geo. for opening_geo_index in range(len(openings)): # A new list element is generated here which will house every node of a specific opening. new_geo_specific_opening = [] for opening_geo_node_indices in range(len(openings[opening_geo_index])): # In this loop the coordinates of the nodes of the specific opening are looked up and stored. new_geo_specific_opening.append( [point_list_rounded[(2 * openings[opening_geo_index][opening_geo_node_indices])], point_list_rounded[(2 * openings[opening_geo_index][opening_geo_node_indices]) + 1]]) # Additionally the calculated nodes are appended here in case of triangular corner openings if opening_geo_index in tri_ope_index: new_geo_specific_opening.append(dict_tri_ope[opening_geo_index]) # The geometry of the specific opening which is generated above here is added to the full list of # opening geometries. new_openings_geo.append(new_geo_specific_opening) # Next step is to adapt the openings so they fall inside the boundary of the element. # A small default spacing is used at the edges of the element to make sure the opening is inside the plane for opening_index in range(len(new_openings_geo)): for opening_node_indices in range(len(new_openings_geo[opening_index])): if new_openings_geo[opening_index][opening_node_indices][0] == xmin: new_openings_geo[opening_index][opening_node_indices][0] = \ new_openings_geo[opening_index][opening_node_indices][0] + margin elif new_openings_geo[opening_index][opening_node_indices][0] == xmax: new_openings_geo[opening_index][opening_node_indices][0] = \ new_openings_geo[opening_index][opening_node_indices][0] - margin elif new_openings_geo[opening_index][opening_node_indices][1] == ymin: new_openings_geo[opening_index][opening_node_indices][1] = \ new_openings_geo[opening_index][opening_node_indices][1] + margin elif new_openings_geo[opening_index][opening_node_indices][1] == ymax: new_openings_geo[opening_index][opening_node_indices][1] = \ new_openings_geo[opening_index][opening_node_indices][1] - margin ######## now the new geometry will be built up. ############### # The element will be new_element_geo as points[0]. # The present openings will be maintained and appended with the new generated openings. # Adapting geometry is only applied when there are non-recognized openings found. if len(openings) != 0: # To prevent out of plane errors the existing openings will be rounded in the same precision as used # in this function to determine the new element and opening geometries. # BEWARE: small loss of accuracy!! # for k in range(len(wall_object_2d.points)): # for j in range(len(wall_object_2d.points[k])): # wall_object_2d.points[k][j] = [round(elem, 3) for elem in wall_object_2d.points[k][j]] # The generated new main element replaces the current geometry of the element. wall_object_2d.points[0] = new_element_geo # The newly generated openings will be added to the end of the current element. for opening_index in range(len(new_openings_geo)): wall_object_2d.points.append(new_openings_geo[opening_index]) # Reverse the 3D to 2D projection to return to the original 3 dimensional coordinate system wall.contour = project.create_polyline( fem_inverse_3d_to_2d_projection(original_object_points[0], wall_object_2d.points)) wall.openings = [] for i in range(1, len(original_object_points)): wall.openings.append(fem_inverse_3d_to_2d_projection(original_object_points[i], wall_object_2d.points)) # If no geometry needs to be adapted the nodes still need to be reversed to the 3D coordinate system. else: wall.contour = project.create_polyline( fem_inverse_3d_to_2d_projection(original_object_points[0], wall_object_2d.points)) wall.openings = [] for i in range(1, len(original_object_points)): wall.openings.append(fem_inverse_3d_to_2d_projection(original_object_points[i], wall_object_2d.points)) # Round coordinates for i in range(0, len(wall.points)): for j in range(0, len(wall.points[i])): if isinstance(wall.points[i][j], float): wall.points[i][j] = round(wall.points[i][j], project.rounding_precision) elif isinstance(wall.points[i][j], list): for k in range(0, len(wall.points[i][j])): if isinstance(wall.points[i][j][k], float): wall.points[i][j][k] = round(wall.points[i][j][k], project.rounding_precision) return
### =================================================================================================================== ### 11. Update line masses ### ===================================================================================================================
[docs]def viia_change_linemass(project: ViiaProject): """ Conversion function to changes line-mass elements from enhanced trusses into 3D beams. Line masses should be modelled in DIANA. Properties are assigned to the elements in the model. No updates in PY memory. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. Output: - The line-masses are updated in DIANA """ linmass_list = [] # Constants for dummy beam dimension = project.project_specific['dummy_dimension_linemass'] stiffness = project.project_specific['stiffness'] # Find all shapes that have the geometry 'LIJNMASSA' for shape in project.collections.shapes: if 'LIJNMASSA' in shape.geometry.name: linmass_list.append(shape.name) # Change element class from enhanced truss to 3D beam with dummy parameters project.rhdhvDIANA.setElementClassType( 'SHAPE', linmass_list, DianaConfig.elementclasses['lines']['3d class-iii beam elements']) project.rhdhvDIANA.addGeometry( 'LIJNMASSA_V2', 'LINE', DianaConfig.elementclasses['lines']['3d class-iii beam elements'], []) project.rhdhvDIANA.setParameter('GEOMET', 'LIJNMASSA_V2', 'SHAPE/RECTAN', [dimension, dimension]) project.rhdhvDIANA.assignGeometry('LIJNMASSA_V2', 'SHAPE', linmass_list) # Adjust density and stiffness of 'LIJNMASSA' material for material_name in project.collections.materials: if 'LIJNMASSA' in material_name: project.rhdhvDIANA.setParameter('MATERIAL', material_name, 'LINEAR/ELASTI/YOUNG', stiffness) density = float(material_name.split('-')[2]) / dimension**2 project.rhdhvDIANA.setParameter('MATERIAL', material_name, 'LINEAR/MASS/DENSIT', density)
### =================================================================================================================== ### 12. Functions for wooden beam floors (NL: Houten balklaag vloeren HBV) ### ===================================================================================================================
[docs]def viia_wooden_beam_floors( project: ViiaProject, floor, start_wall_distance=None, material_beams='LIN-HOUT', material_sheeting='LIN-HOUT', only_beams: bool = False, offset: Optional[float] = None): """ This function removes the floor with wooden beams and planks modelled with equivalent properties and replaces it with a floor modelled with beams and a plane. The beams are placed in direction of the local x-axis. The center to center distance is taken from the floor name, also beam dimensions and thickness. The offset around the slab is set to 0.1m. The material 'LIN-HOUT' is coupled to beams and slab. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - floor (obj): Object reference of the floor that needs to be replaced with beams and sheeting. Alternative (str): Name of the floor that needs to be replaced. The floor must be available in PY-memory. - start_wall_distance (float): Distance from start of floor to the first beam. Starting point is the outer point orthogonal to span. Default value is 'None'. - material_beams (str): Name of the material model to be used for the beams. Default value is 'LIN-HOUT'. - material_sheeting (str): Name of the material model to be used for the sheeting (planks or sheeting). Default value is 'LIN-HOUT'. - only_beams (bool): Switch to only create beams and remove the surfaces. - offset (float): Value to offset the contour of the new floor, in [m]. Output: - The floor is replaced in the model with a floor of beams and a slab. .. warning:: Material of slab needs to be adjusted (reduced shear stiffness). .. warning:: Eccentricity of slab needs to be added. """ # Argument handling if isinstance(floor, str): original = floor floor = fem_find_object(reference=floor, collection=project.collections.floors + project.collections.roofs) if floor is None: raise ValueError( f"ERROR: Floor could not be found for viia_wooden_beam_floors function, please check input '{floor}'.") # Check if floor is in Collections if floor not in project.collections.floors + project.collections.roofs: raise ValueError( f"ERROR: Floor not found for viia_wooden_beam_floors function, please check input '{floor.name}'.") # Check if the floor is a HBV floor (wooden beam floor with planks) if 'HBV' not in floor.material.name: raise ValueError( f"ERROR: The requested floor '{floor.name}' is not a wooden beam floor (HBV-floor). Material of floor: " f"{floor.material.name}.") # Input check start_wall_distance: if start_wall_distance: if not isinstance(start_wall_distance, (float, int)): raise ValueError("ERROR: The distance between the start wall and first beam should be a float value.") # Dimensions of the beam and planks is derived from the material name in [m] # Example of expected material.name = 'LIN-HBV-PLANKEN-0.018-0.05-0.15-0.6' height_beams = float(floor.material.name.split('-')[-2]) width_beams = float(floor.material.name.split('-')[-3]) ctc_beams = float(floor.material.name.split('-')[-1]) thickness_planks = float(floor.material.name.split('-')[-4]) level = floor.name.split('-')[0] # Find directions of floor span_direction = floor.element_x_axis.vector if not fem_vector_in_plane(vector=span_direction, plane=floor.contour.get_points()): raise ValueError( "ERROR: The span_direction of the local x-axis is not in plane, correct before using this " "viia_wooden_beam_floors function.") plane_normal_vector = floor.normal_vector() perp_direction = fem_cross_product_vector(span_direction, plane_normal_vector) # Find center point by measuring distance to first point of the plane along a line through that point with vector 1 r_coordinates_plane = [] coordinates_plane = floor.contour.get_points() for i in range(len(coordinates_plane)): coordinate = fem_intersection_of_two_lines(coordinates_plane[0], perp_direction, coordinates_plane[i], span_direction) if coordinate == 'Parallel': r_coordinates_plane.append(0) else: r0 = coordinate[0] - coordinates_plane[0][0] r1 = coordinate[1] - coordinates_plane[0][1] r2 = coordinate[2] - coordinates_plane[0][2] factor = 0 if perp_direction[0] != 0: factor = r0 / perp_direction[0] if perp_direction[1] != 0: factor = r1 / perp_direction[1] if perp_direction[2] != 0: factor = r2 / perp_direction[2] if factor < 0: r = -math.sqrt(r0 * r0 + r1 * r1 + r2 * r2) else: r = math.sqrt(r0 * r0 + r1 * r1 + r2 * r2) r_coordinates_plane.append(r) # Find center along that R-axis r_max = 0 r_min = 0 for i in range(len(r_coordinates_plane)): if fem_greater(r_coordinates_plane[i], r_max): r_max = r_coordinates_plane[i] elif fem_smaller(r_coordinates_plane[i], r_min): r_min = r_coordinates_plane[i] length = r_max - r_min r_center = r_min + length/2 if not start_wall_distance: # Distribute beams along length nr_beams = int(length / ctc_beams) + 1 rest_wall_length = (length - (nr_beams - 1) * ctc_beams) / 2 if fem_smaller(rest_wall_length, project.elementsize / 2): nr_beams = nr_beams - 1 r = r_center - (nr_beams - 1) / 2 * ctc_beams else: nr_beams = int(length - start_wall_distance / ctc_beams) + 1 rest_wall_length = (length - (nr_beams - 1) * ctc_beams)/2 if fem_smaller(rest_wall_length, project.elementsize/2): nr_beams = nr_beams - 1 r = start_wall_distance # Starting coordinate of the first beam coordinates_list = [] for i in range(0, nr_beams): coordinate = [coordinates_plane[0][0] + perp_direction[0] * r, coordinates_plane[0][1] + perp_direction[1] * r, coordinates_plane[0][2] + perp_direction[2] * r] intersections = fem_find_intersections(point=coordinate, direction=span_direction, surface=coordinates_plane) coordinates_list.append(intersections) r = r + ctc_beams # Find starting number for beam numbers max_beam_nr = project.find_max_id(project.collections.beams) geometry_name_beam = str(int(width_beams * 1000)) + 'x' + str(int(height_beams * 1000)) # Create the object for the beams. Check for bottom and top, check for openings en check for length. counter = 1 name_beams = [] is_roof_beam = False if isinstance(floor, Roof): is_roof_beam = True for i in range(nr_beams): for j in range(int(len(coordinates_list[i])/2)): nr = max_beam_nr + counter beam_name = level + '-BALKEN-' + material_beams + '-' + geometry_name_beam + '-' + str(nr) name_beams.append(beam_name) new_beam = project.viia_create_beam( name=beam_name, material=material_beams, geometry=geometry_name_beam, points=[coordinates_list[i][0], coordinates_list[i][1]], is_roof_beam=is_roof_beam) new_beam.update_local_z_axis(plane_normal_vector) counter += 1 # Make a floor that has an offset around equal to the thickness of the surrounding walls. if not only_beams: if offset is None or round(offset, project.rounding_precision) == 0: points = [floor.contour.get_points()] elif isinstance(offset, float): new_plane = [] for i in range(len(coordinates_plane)): # Determine the direction orthogonal to the direction given and orthogonal to the normal_vector of the # plane. if i == len(coordinates_plane)-1: direction = [ coordinates_plane[0][0] - coordinates_plane[i][0], coordinates_plane[0][1] - coordinates_plane[i][1], coordinates_plane[0][2] - coordinates_plane[i][2]] else: direction = [ coordinates_plane[i + 1][0] - coordinates_plane[i][0], coordinates_plane[i + 1][1] - coordinates_plane[i][1], coordinates_plane[i + 1][2] - coordinates_plane[i][2]] if direction != [0.0, 0.0, 0.0]: if plane_normal_vector[0] * direction[2] - plane_normal_vector[2] * direction[0] != 0: b = 1 a = b * (plane_normal_vector[2] * direction[1] - plane_normal_vector[1] * direction[2]) / \ (plane_normal_vector[0] * direction[2] - plane_normal_vector[2] * direction[0]) if a == -0.0: a = 0 if direction[2] != 0: c = (-a * direction[0] - b * direction[1]) / direction[2] elif plane_normal_vector[2] != 1: c = (-a * plane_normal_vector[0] - b * plane_normal_vector[1]) / plane_normal_vector[2] else: c = 0 else: a = 1 b = -a * (plane_normal_vector[2]*direction[0] + plane_normal_vector[0] * direction[2]) / \ (plane_normal_vector[2] * direction[1] - plane_normal_vector[1] * direction[2]) if b == -0.0: b = 0 if direction[2] != 0: c = (-a * direction[0] - b * direction[1]) / direction[2] elif plane_normal_vector[2] != 1: c = (-a * plane_normal_vector[0] - b * plane_normal_vector[1]) / plane_normal_vector[2] else: c = 0 length = math.sqrt(a**2 + b**2 + c**2) center_point = [coordinates_plane[i][0] + direction[0]/2 + a * offset / length, coordinates_plane[i][1] + direction[1]/2 + b * offset / length, coordinates_plane[i][2] + direction[2]/2 + c * offset / length] new_point = [coordinates_plane[i][0] + a * offset / length, coordinates_plane[i][1] + b * offset / length, coordinates_plane[i][2] + c * offset / length] if not fem_point_in_surface(point=center_point, surface=coordinates_plane): new_point = [coordinates_plane[i][0] - a * offset / length, coordinates_plane[i][1] - b * offset / length, coordinates_plane[i][2] - c * offset / length] new_plane.append([new_point, direction]) new_plane_point = [] for i in range(len(new_plane)): if i == 0: new_plane_point.append( fem_intersection_of_two_lines(new_plane[len(coordinates_plane) - 1][0], new_plane[len(coordinates_plane) - 1][1], new_plane[0][0], new_plane[0][1])) else: new_plane_point.append( fem_intersection_of_two_lines(new_plane[i - 1][0], new_plane[i - 1][1], new_plane[i][0], new_plane[i][1])) points = [new_plane_point] # Add opening points for opening in getattr(floor, 'openings', []): points.append(opening.get_points()) # Determine the geometry name based on the material name of the original floor # Example of expected name of floor material: 'LIN-HBV-PLANKEN-0.018-0.05-0.15-0.6' geometry_name_floor = \ 'VLOER' + floor.material.name.split('-')[2] + '-' + str(int(thickness_planks * 1000)) + '-' + \ floor.geometry.name.split('-')[-1] if 'VLOER' in floor.name: floor_name_floor = level + '-VLOEREN-' + material_sheeting + '-' + geometry_name_floor + '-' + \ floor.name[-1] # Create the new floor sheeting new_floor = project.viia_create_floor( name=floor_name_floor, material=material_sheeting, geometry=geometry_name_floor, points=points) elif 'DAK' in floor.name: floor_name_floor = level + '-DAKEN-' + material_sheeting + '-' + geometry_name_floor + '-' + floor.name[-1] # Create the new floor sheeting new_floor = project.viia_create_roof( name=floor_name_floor, material=material_sheeting, geometry=geometry_name_floor, points=points) else: floor_name_floor = None new_floor = None project.write_log("ERROR: The type of the floor is not recognized.", True) # Remove the former floor floor.remove_shape() # Notifications for user project.write_log(str(counter - 1) + " beams have been added to wooden floor.", True) if only_beams: return name_beams else: return floor_name_floor, name_beams
### =================================================================================================================== ### 13. Add openings to surfaces ### ===================================================================================================================
[docs]def viia_add_openings(project: ViiaProject, opening_data_list: List): """ Function to easily add openings to viia walls or sloped roofs, based on the measurements commonly specified in the inspection data. A list is expected that contains the data for the openings. Each row in this list represents a surface object to which openings need to be added. This function can be used for walls and sloped roofs. It is possible to supply the object to which openings need to be added, but also a simple name like 'wall_5' can be used. Input: - project: VIIA project object containing collections of fem objects and project variables. - opening_data_list: List that contains the data for the openings. Each row represents a wall or sloped roof. All measurements should be provided in [m] Example: An example for an opening_data_list is: .. code-block:: python openings = [['wall_1', [[0.6, 2.1, 0.2, 2.1], [0.3, 1.2, 0, 2.3], [0.3, 2.1, 0.2, 2.1]]], [wall_object, [[0.7, 2.4, 0, 2.1]], True], ['roof_1', [[0.3, 1.2, 0, 2.3], [0.3, 2.1, 0.2, 2.1]]]] For walls: Expected input is a list of either a simple wall name or the wall object itself, opening data for that wall, and an optional boolean to flip the direction of the openings. In the opening data, each sublists represents an opening. The four values in this sublistrepresent distance to opening, width of the opening, height from bottom of wall to opening (borstwering) and height of the opening (hoogte kozijn). The distance to the opening is the distance to the edge of the wall for the first opening in the list, and the distance to the previous opening for each subsequent opening. The optional boolean can be used to flip the direction in which the openings are added to the wall. For roofs: Similar to wall openings, each sublist in the opening data represents an opening. Again, the first two values are distance to opening and width of the opening. Third value is the height from the floor to the bottom of the opening, measured in absolute z-coordinates. The fourth value is again the height of the opening, but measured in the plane of the sloped roof. Also here it is possible to flip the direction. """ # Joining input for same object reference, such that openings are treated in one go opening_data_list_joined = [] for opening_data in opening_data_list: if opening_data[0] in [opening_data[0] for opening_data in opening_data_list_joined]: for opening_data_joined in opening_data_list: if opening_data[0] == opening_data_joined[0]: opening_data_joined.append(opening_data[1:]) else: opening_data_list_joined.append(opening_data) # Loop through list with all the openings for opening_data in opening_data_list_joined: shape = None if isinstance(opening_data[0], (Wall, Roof)): shape = opening_data[0] elif isinstance(opening_data[0], str): if opening_data[0].split('_')[0].lower() == 'wall': shape = project.viia_get(collection='walls', id=int(opening_data[0].split('_')[1])) elif opening_data[0].split('_')[0].lower() == 'roof': shape = project.viia_get(collection='roofs', id=int(opening_data[0].split('_')[1])) else: raise ValueError( f"ERROR: Wrong input given, should be for instance wall_1 or roof_5, not {opening_data[0]}.") if shape is None: project.write_log(f"ERROR: Object with name {opening_data[0]} could not be found, no openings added.") continue object_measurements = opening_data[1] object_flip = False if len(opening_data) == 3: object_flip = opening_data[2] if isinstance(shape, Wall): viia_add_wall_openings(wall=shape, opening_list=object_measurements, flip=object_flip) elif isinstance(shape, Roof): viia_add_roof_openings(project=project, roof=shape, opening_list=object_measurements, flip=object_flip) else: raise NotImplementedError(f"ERROR: Functionality not available for objects of type {type(shape)}.")
[docs]def viia_add_wall_openings(wall: Wall, opening_list: List[List], flip: bool = False): """ Function to easily add openings to walls, based on measurements commonly specified in inspection data. This function is used by viia_add_openings, if a wall object is detected. Openings are specified by supplying a list of four values; distance to opening, width of opening, height of opening above bottom line of the wall and a height of the opening. Input: - wall (obj): Object reference of wall, where to apply the opening(s) on. - opening_list (list): List that contains the data for the openings. All measurements should be given in [m]. - flip (bool): Select to flip the direction of the openings. Default value False. Output: - Openings are added to the wall. Example: An example of an opening list is: opening_list = [[0.6, 2.1, 0.2, 2.1], [0.3, 1.2, 0, 2.3]] This creates two openings. The first opening is at a distance 0.6 from the edge of the wall, has dimensions width x height = 2.1 x 2.1, and is situated 0.2 above the bottom edge of the wall. The second opening is located at a distance 0.3 from the previous opening, has dimensions width x height = 1.2 x 2.3 and starts from the bottom of the wall. """ bottom_lines = wall.get_bottom_edges() if bottom_lines is None: raise ValueError("ERROR: This wall is horizontal, for which this function is not designed for.") if len(bottom_lines) == 0: raise ValueError( f"ERROR: An issue was encountered in finding the bottom edge of wall {wall.name}. Please check.") points = deepcopy( fem_longest_distance(coordinate_list=[point for line in bottom_lines for point in line.get_points()])) # Determine the startpoint and direction for the input direction = fem_unit_vector(fem_vector_2_points(point1=points[0], point2=points[1])) local_x = points[0] if flip: direction = fem_unit_vector(fem_vector_2_points(point1=points[1], point2=points[0])) local_x = points[1] created_openings = [] for opening in opening_list: spacing = opening[0] width = opening[1] bw = opening[2] height = opening[3] current_opening_points = [] # First move to opening move_vector = [i * spacing for i in direction] for i in range(3): local_x[i] += move_vector[i] current_opening_points.append([local_x[0], local_x[1], local_x[2] + bw]) current_opening_points.append([local_x[0], local_x[1], local_x[2] + bw + height]) # Now move the width of the opening move_vector = [i * width for i in direction] for i in range(3): local_x[i] += move_vector[i] current_opening_points.append([local_x[0], local_x[1], local_x[2] + bw + height]) current_opening_points.append([local_x[0], local_x[1], local_x[2] + bw]) created_openings.append(wall.add_opening(polyline=current_opening_points)) # Return list of all created openings in this function return created_openings
[docs]def viia_add_roof_openings(project: ViiaProject, roof: Roof, opening_list: List[List], flip: bool = False): """ Function to easily add openings to roofs, based on measurements commonly specified in inspection data. This function is used by viia_add_openings, if a roof object is detected. Openings are specified by supplying a list of four values; distance to opening, width of opening, height of opening above floor level and a height of the opening in the plane of the roof. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - roof (obj): Object reference of roof on which the opening(s) are applied. - opening_list (list): List that contains the data for the openings. All measurements should be given in [m]. - flip (bool): Select to flip the direction in which the openings are added to the shape. Default value False. Output: - Opening(s) are added to the roof. Example: An example of an opening list is: opening_list = [[0.3, 1.2, 0, 2.3], [0.3, 2.1, 0.2, 2.1]] This creates two openings. The first opening is at a distance 0.3m from the edge of the roof (the order of the edges is determined by the local axes of the roof), and has dimensions width x height = 1.2m x 2.3m, and starts from the bottom edge of the roof. The second opening is located at a distance 0.3m from the previous opening, has dimensions width x height = 2.1m x 2.1m in the plane of the roof and starts from 0.2m above the bottom edge of the roof in absolute z coordinates. """ # Check if roof is horizontal and suggest to use add_floor_openings if roof.contour.is_horizontal(): raise ValueError( "ERROR: For horizontal roofs, you should use the function viia_add_floor_openings instead of " "viia_add_roof_openings.") bottom_lines = roof.get_bottom_edges() if bottom_lines is None: raise ValueError("ERROR: The roof is horizontal, for which this function is not designed for.") if len(bottom_lines) == 0: raise ValueError( f"ERROR: An issue was encountered in finding the bottom edge of the roof {roof.name}. Please check.") points = deepcopy( fem_longest_distance(coordinate_list=[point for line in bottom_lines for point in line.get_points()])) # Calculate angle between roof and horizontal angle_roof = roof.get_angle() # Construct the local coordinate system local_origin = points[0] vector = fem_vector_2_points(points[0], points[1]) if fem_parallel_vectors( vector_1=vector, vector_2=roof.y_axis_direction().vector, anti_parallel_vectors=True, precision=project.check_precision): if not fem_parallel_vectors( vector_1=vector, vector_2=roof.y_axis_direction().vector, anti_parallel_vectors=False, precision=project.check_precision): local_origin = points[1] local_x_axis = roof.y_axis_direction().vector if roof.x_axis_direction().vector[2] < 0: local_y_axis = fem_flip_vector(a=roof.x_axis_direction().vector) else: local_y_axis = roof.x_axis_direction().vector elif fem_parallel_vectors( vector_1=vector, vector_2=roof.x_axis_direction().vector, anti_parallel_vectors=True, precision=project.check_precision): if not fem_parallel_vectors( vector_1=vector, vector_2=roof.x_axis_direction().vector, anti_parallel_vectors=False, precision=project.check_precision): local_origin = points[1] local_x_axis = roof.x_axis_direction().vector if roof.y_axis_direction().vector[2] < 0: local_y_axis = fem_flip_vector(a=roof.y_axis_direction().vector) else: local_y_axis = roof.y_axis_direction().vector else: raise ValueError( f"ERROR: Bottom edge of the roof {roof.name} is not aligned with either local x axis or local y axis of " f"the roof. This is not supported by the function viia_add_roof_openings.") local_coordinate_matrix = [local_x_axis, local_y_axis, fem_cross_product_vector(a=local_x_axis, b=local_y_axis)] global_coordinate_matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] x = 0 if flip: x = fem_longest_distance( coordinate_list=[point for line in bottom_lines for point in line.get_points()], return_type='distance') for opening in opening_list: distance_parallel_to_bottom = opening[0] width_parallel_to_bottom = opening[1] bw = opening[2] width_perp_to_bottom = opening[3] # Flip direction if requested by user if flip: distance_parallel_to_bottom = -distance_parallel_to_bottom width_parallel_to_bottom = -width_parallel_to_bottom # First opening point x += distance_parallel_to_bottom y = bw / math.sin(angle_roof) global_point_1 = ( np.array(local_origin) + np.array(fem_translate_vector_to_coordinate_system( vector=[x, y, 0], current_coordinate_matrix=local_coordinate_matrix, new_coordinate_matrix=global_coordinate_matrix))).tolist() # Second opening point y += width_perp_to_bottom global_point_2 = ( np.array(local_origin) + np.array(fem_translate_vector_to_coordinate_system( vector=[x, y, 0], current_coordinate_matrix=local_coordinate_matrix, new_coordinate_matrix=global_coordinate_matrix))).tolist() # Third opening point x += width_parallel_to_bottom global_point_3 = ( np.array(local_origin) + np.array(fem_translate_vector_to_coordinate_system( vector=[x, y, 0], current_coordinate_matrix=local_coordinate_matrix, new_coordinate_matrix=global_coordinate_matrix))).tolist() # Fourth opening point y -= width_perp_to_bottom global_point_4 = ( np.array(local_origin) + np.array(fem_translate_vector_to_coordinate_system( vector=[x, y, 0], current_coordinate_matrix=local_coordinate_matrix, new_coordinate_matrix=global_coordinate_matrix))).tolist() # Add the opening to the roof roof.add_opening([global_point_1, global_point_2, global_point_3, global_point_4])
### =================================================================================================================== ### 14. Name number operations ### =================================================================================================================== def viia_find_max_name_number(project: ViiaProject, collection, select_foundationwalls: bool = False) -> int: """ Function that finds the highest number that is used in the name of objects within a specified collection. The number that is returned is an integer. Input: - project (obj): Project object containing collections and of fem objects and project variables. - collection (list with object references): List of the objects for which the name number will be checked. - select_foundationwalls (bool): Optional parameter to find foundation walls. Output: - Returns the highest integer found in the names of objects in the collection. """ max_name_number = 0 for collection_object in collection: name = collection_object.name name_category = name.split('-')[1] name_number_part = name.split('-')[-1] if select_foundationwalls and name_category == 'WANDEN': continue # Try converting to integer, continue if for instance '2B' is found in case of a cavity wall try: name_number_part_int = int(name_number_part) except ValueError: continue # Check if the number is higher than previously found number and overwrite if name_number_part_int > max_name_number: max_name_number = name_number_part_int return max_name_number def viia_find_max_id(project: ViiaProject, collection: list): """ Function to find the highest used number in the ID attribute of the objects. This function is viia specific, and overwrites the function defines in the fem-package. The difference between this function and the one in the fem-package, is that this function skips the ids of cavity walls. Since they are set to start at 9000. Input: - project (obj): Viia Project object containing collections of fem objects and project variables. - collection (list with object references): List of the objects for which the ID attribute will be checked. Output: - Returns the highest integer found in the collection of items. """ # Initialise nr = 0 # Find the currently highest ID in collection of objects provided for item in collection: if hasattr(item, 'ID'): # If shape is a cavity wall, skip it if isinstance(item, Wall) and item.id > 9000: continue elif item.id > nr: nr = item.id elif hasattr(item, 'id'): # If shape is a cavity wall, skip it if isinstance(item, Wall) and item.id > 9000: continue if item.id > nr: nr = item.id return int(nr) ### =================================================================================================================== ### 15. Compute projection on surface ### =================================================================================================================== def viia_projection_on_surface(project: ViiaProject, point: List[Union[float, Shapes, str]]): """ Function that calculates the unknown coordinate of a point by projection on a 2-dimensional shape. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - point (list with 3 floats or 2 floats and 1 shape): If 3 floats are given, the output is the unmodified point. If 2 floats and 1 shape is given, the unknown coordinate at the index of the shape is calculated by projection on this shape. Shapes can be inputted as objects or strings e.g. [19.2, roof_1, 6] OR [19.2, 'roof_1', 6]. Output: - Returns the coordinates of the projected point. """ shapes = [[coordinate, i] for i, coordinate in enumerate(point) if not isinstance(coordinate, (int, float))] if len(shapes) > 1: raise ValueError('A minimum of 2 numerical coordinates per point is required.') elif len(shapes) == 1: shape, index = shapes[0][:] if isinstance(shape, str): if shape.split('_')[0].lower() == 'wall': shape = project.viia_get(collection='walls', id=int(shape.split('_')[1])) elif shape.split('_')[0].lower() == 'roof': shape = project.viia_get(collection='roofs', id=int(shape.split('_')[1])) elif shape.split('_')[0].lower() == 'floor': shape = project.viia_get(collection='floors', id=int(shape.split('_')[1])) elif shape.split('_')[0].lower() == 'fstrip': shape = project.viia_get(collection='fstrips', id=int(shape.split('_')[1])) else: raise ValueError(f'Wrong input given, should be for instance wall_1 or roof_5, not {shape}') plane = shape.get_points() help_point = point help_point[index] = 0 direction = [0, 0, 0] direction[index] = 1 return fem_point_to_plane(help_point, plane, direction) elif len(shapes) == 0: return point ### =================================================================================================================== ### 16. End of script ### ===================================================================================================================