Source code for viiapackage.analyses.nlpo_helper_functions.viia_locate_piers

### ===================================================================================================================
###  FUNCTION: Find to locate the piers in the building
### ===================================================================================================================
# Copyright ©VIIA 2024

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

# General imports
from __future__ import annotations
from typing import TYPE_CHECKING, List
from copy import deepcopy

# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.fem_math import fem_normal_vector
from rhdhv_fem.shapes import Wall
from rhdhv_fem.fem_math import fem_greater, fem_smaller

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


### ===================================================================================================================
###   2. Function viia_locate_piers
### ===================================================================================================================

[docs]def viia_locate_piers( project: ViiaProject, walls: List[Wall], allow_reduction_bottom: bool = True, allow_reduction_top: bool = True, tolerance: float = 0.001) -> List[dict]: """ Function to identify piers within walls and collect relevant coordinates, positioning and workplane for further processing. Input: - project (obj): VIIA project object containing collections of fem objects and project variables. - walls (list): List of object references of walls to be considered. - allow_reduction_bottom (bool): Select to take into account a reduction of the effective wall height due to the thickness of the supporting floor at the bottom of the wall. Default value True. - allow_reduction_top (bool): Select to take into account a reduction of the effective wall height due to the thickness of the supporting floor at the top of the wall. Default value True. - tolerance (float): Tolerance value for determining whether wall coordinates are constant in a direction. Default value is 0.001. Output: - Returns a list of piers, containing relevant coordinates, positioning and workplane for further processing. """ def my_sort(my_list): """ Sorts a list with floats and stores their corresponding indices.""" sorted_list = [my_list[0]] index_list = [0] for outer_index, outer_item in enumerate(my_list): if outer_index != 0: if fem_smaller(outer_item, sorted_list[0]): sorted_list.insert(0, outer_item) index_list.insert(0, outer_index) elif fem_greater(outer_item, sorted_list[-1]): sorted_list.append(outer_item) index_list.append(outer_index) else: for inner_index, inner_item in enumerate(sorted_list): if fem_smaller(inner_item, outer_item) and \ fem_smaller(outer_item, sorted_list[inner_index + 1]): sorted_list.insert(inner_index + 1, outer_item) index_list.insert(inner_index + 1, outer_index) break return sorted_list, index_list def copy_points(surface): """ Create list of copies of points on the contiour of the requested surface and its openings.""" # Deepcopy points on contour of surface copied_points = [deepcopy(surface.contour.get_points())] # Add deepcopies of points on contour of openings in the surface for opening in surface.openings: copied_points.append(deepcopy(opening.get_points())) # Return list return copied_points piers = [] for wall in walls: # Determine the convenient work direction # Assume the worst, until proven differently x_is_constant = True y_is_constant = True z_is_constant = True # Collect points points = copy_points(wall) for i in range(0, len(points[0]) - 1): if fem_greater(abs(points[0][i][0] - points[0][i+1][0]), tolerance): x_is_constant = False if fem_greater(abs(points[0][i][1] - points[0][i+1][1]), tolerance): y_is_constant = False if fem_greater(abs(points[0][i][2] - points[0][i+1][2]), tolerance): z_is_constant = False # Check for nonsensical cases if z_is_constant: project.write_log( f"WARNING: z-coordinates of {wall.name} are constant. It may not act as a pier.") if x_is_constant and y_is_constant: project.write_log( f"WARNING: Coordinates of {wall.name} in both x- and y-direction are constant. " f"It may not act as a pier.") if fem_normal_vector(points[0])[2] != 0.0: project.write_log( f"WARNING: {wall.name} appears to be inclined. It's contributions will not be taken into account.") # creating a list of all opening points for wall in consideration opening_points_list = [] for opening in wall.openings: for point in opening.get_points(): opening_points_list.append(point) # get four corner points of the wall in consideration, min and max. if y_is_constant: # For the case when wall is along x axis, get x_min, x_max, z_min, and z_max x_coord_list = [] z_coord_list = [] for item in wall.contour.get_points(): x_coord_list.append(item[0]) z_coord_list.append(item[2]) x_min = min(x_coord_list) x_max = max(x_coord_list) z_min = min(z_coord_list) z_max = max(z_coord_list) elif x_is_constant: # write code for the case when wall is along y axis, get y_min, y_max, z_min and z_max y_coord_list = [] z_coord_list = [] for item in wall.contour.get_points(): y_coord_list.append(item[1]) z_coord_list.append(item[2]) y_min = min(y_coord_list) y_max = max(y_coord_list) z_min = min(z_coord_list) z_max = max(z_coord_list) # check if any of the openings is touching the wall corners, initialize boolean 'wall_corners' to False wall_corners = False if y_is_constant: for opening in wall.openings: for i in range(len(opening.get_points())): if opening.get_points()[i][0] in list([x_min, x_max]) and \ opening.get_points()[i][2] in list([z_min, z_max]): wall_corners = True elif x_is_constant: for opening in wall.openings: for i in range(len(opening.get_points())): if opening.get_points()[i][1] in list([y_min, y_max]) and \ opening.get_points()[i][2] in list([z_min, z_max]): wall_corners = True # This if else will remove nodes of 'wall openings' from 'points[0]' if not wall_corners: if len(points[0]) == 4: pass else: # collecting all points of openings in a list opening_points_list = [] for opening in wall.openings: for point in opening.get_points(): opening_points_list.append(point) # taking intersection of wall points and opening points intersection_list = [value for value in wall.contour.get_points() if value in opening_points_list] # removing the items in intersection list from points[0], this will leave only 4 nodes in points[0] # and the for loop after this one will execute properly. for item in intersection_list: points[0].remove(item) # Code to handle the situation when openings are touching the wall corners, for example the situation in # object 917A, for the washing area west side wall. # if wall_corners is True, it means that there's an opening that is sharing one of its nodes with the corner of # our wall. Then we can't remove this opening node from points[0] else: openings_inclusion = [] if y_is_constant: # add code for walls that are in xz plane for opening in wall.openings: for i in range(len(opening.get_points())): if opening.get_points()[i][0] in list([x_min, x_max]) and \ opening.get_points()[i][2] in list([z_min, z_max]): openings_inclusion.append(opening.get_points()[i]) elif x_is_constant: # add code for walls that are in yz plane for opening in wall.openings: for i in range(len(opening.get_points())): if opening.get_points()[i][1] in list([y_min, y_max]) and \ opening.get_points()[i][2] in list([z_min, z_max]): openings_inclusion.append(opening.get_points()[i]) intersection_list = [value for value in wall.contour.get_points() if value in opening_points_list \ if value not in openings_inclusion] # removing points that are in 'intersection_list' but not in 'openings_inclusion' for item in intersection_list: points[0].remove(item) if len(points[0]) == 4: if not x_is_constant: workplane = 'xz' x_or_y = 0 elif not y_is_constant: workplane = 'yz' x_or_y = 1 sort_xy, index_xy = my_sort( [points[0][0][x_or_y], points[0][1][x_or_y], points[0][2][x_or_y], points[0][3][x_or_y]]) sort_z, index_z = my_sort( [points[0][0][2], points[0][1][2], points[0][2][2], points[0][3][2]]) # Assign the status of each point of the wall for pointnr, point in enumerate(points[0]): if (pointnr == index_xy[0] or pointnr == index_xy[1]) and ( pointnr == index_z[0] or pointnr == index_z[1]): lower_left_nr = pointnr lower_left_xyz = point elif (pointnr == index_xy[2] or pointnr == index_xy[3]) and ( pointnr == index_z[0] or pointnr == index_z[1]): lower_right_nr = pointnr lower_right_xyz = point elif (pointnr == index_xy[0] or pointnr == index_xy[1]) and ( pointnr == index_z[2] or pointnr == index_z[3]): upper_left_nr = pointnr upper_left_xyz = point elif (pointnr == index_xy[2] or pointnr == index_xy[3]) and ( pointnr == index_z[2] or pointnr == index_z[3]): upper_right_nr = pointnr upper_right_xyz = point try: _ = lower_left_nr _ = lower_right_nr _ = upper_left_nr _ = upper_right_nr except NameError: project.write_log( f"WARNING: The shape of {wall.name} is not supported." f"Its contributions are not taken into account.") # Reduce the effective height of the wall based on the connected floor thickness if allow_reduction_bottom: deduct_low = False for floor in project.collections.floors: deduction_ll = False deduction_lr = False floor_points = copy_points(floor) for point in floor_points[0]: if lower_left_xyz == point: deduction_ll = True break if deduction_ll: for point in floor_points[0]: if lower_right_xyz == point: deduction_lr = True if deduction_ll and deduction_lr: if 'PLANKEN' in floor.name: thickness_found = float(floor.name.split('PLANKEN-')[1].split('-')[0]) elif 'PLATEN' in floor.name: thickness_found = float(floor.name.split('PLATEN-')[1].split('-')[0]) else: thickness_found = floor.geometry.geometry_model.thickness if deduct_low: deduct_thickness_low = max(deduct_thickness_low, thickness_found) else: deduct_low = True deduct_thickness_low = thickness_found if deduct_low: lower_left_xyz[2] += deduct_thickness_low/2 lower_right_xyz[2] += deduct_thickness_low/2 if allow_reduction_top: deduct_up = False for floor in project.collections.floors: deduction_ul = False deduction_ur = False floor_points = copy_points(floor) for point in floor_points[0]: if upper_left_xyz == point: deduction_ul = True break if deduction_ul: for point in floor_points[0]: if upper_right_xyz == point: deduction_ur = True if deduction_ul and deduction_ur: if 'PLANKEN' in floor.name: thickness_found = float(floor.name.split('PLANKEN-')[1].split('-')[0]) elif 'PLATEN' in floor.name: thickness_found = float(floor.name.split('PLATEN-')[1].split('-')[0]) else: thickness_found = floor.geometry.geometry_model.thickness if deduct_up: deduct_thickness_up = max(deduct_thickness_up, thickness_found) else: deduct_up = True deduct_thickness_up = thickness_found if deduct_up: upper_left_xyz[2] -= deduct_thickness_up/2 upper_right_xyz[2] -= deduct_thickness_up/2 # Assign work coordinates for walls without openings if len(points) == 1: piers.append({ 'coordinates': { 'RHS': { 'LLH': lower_left_xyz, 'LLL': lower_left_xyz, 'LRH': lower_right_xyz, 'LRL': lower_right_xyz, 'ULH': upper_left_xyz, 'ULL': upper_left_xyz, 'URH': upper_right_xyz, 'URL': upper_right_xyz}, 'LHS': { 'LLH': lower_left_xyz, 'LLL': lower_left_xyz, 'LRH': lower_right_xyz, 'LRL': lower_right_xyz, 'ULH': upper_left_xyz, 'ULL': upper_left_xyz, 'URH': upper_right_xyz, 'URL': upper_right_xyz}}, 'parent wall': wall, 'workplane': workplane, 'side': None}) else: # Sort coordinates of openings in wall list_min_xy = [] list_max_xy = [] for pointlist in range(1, len(points)): all_xy = [] for point in points[pointlist]: all_xy.append(point[x_or_y]) list_min_xy.append(min(all_xy)) list_max_xy.append(max(all_xy)) sort_min_xy, index_min_xy = my_sort(list_min_xy) sort_max_xy, index_max_xy = my_sort(list_max_xy) # Check for overlapping openings for i in range(len(index_min_xy)): if index_min_xy[i] == index_max_xy[i]: if i != len(index_min_xy) - 1: if index_max_xy[i] < index_min_xy[i + 1]: pass else: project.write_log( f"WARNING: Overlap between openings detected in x-direction. This case is not supported. " f"The contributions of {wall.name} are not taken into account.") for openingnr in range(0,len(points) - 1): if openingnr == 0: side = 'left' else: side = 'middle' openingnr_rhs = index_min_xy[openingnr] + 1 sort_xy, index_xy = my_sort( [points[openingnr_rhs][0][x_or_y], points[openingnr_rhs][1][x_or_y], points[openingnr_rhs][2][x_or_y], points[openingnr_rhs][3][x_or_y]]) sort_z, index_z = my_sort( [points[openingnr_rhs][0][2], points[openingnr_rhs][1][2], points[openingnr_rhs][2][2], points[openingnr_rhs][3][2]]) # Assign the status of each point of the opening for pointnr, point in enumerate(points[openingnr_rhs]): if (pointnr == index_xy[0] or pointnr == index_xy[1]) and ( pointnr == index_z[0] or pointnr == index_z[1]): lower_left_opening_right_nr = pointnr lower_left_opening_right_xyz = point elif (pointnr == index_xy[2] or pointnr == index_xy[3]) and ( pointnr == index_z[0] or pointnr == index_z[1]): lower_right_opening_right_nr = pointnr lower_right_opening_right_xyz = point elif (pointnr == index_xy[0] or pointnr == index_xy[1]) and ( pointnr == index_z[2] or pointnr == index_z[3]): upper_left_opening_right_nr = pointnr upper_left_opening_right_xyz = point elif (pointnr == index_xy[2] or pointnr == index_xy[3]) and ( pointnr == index_z[2] or pointnr == index_z[3]): upper_right_opening_right_nr = pointnr upper_right_opening_right_xyz = point try: _ = lower_left_opening_right_nr _ = lower_right_opening_right_nr _ = upper_left_opening_right_nr _ = upper_right_opening_right_nr except NameError: project.write_log( f"WARNING: the shape of {wall.name} is not supported. " f"Its contributions are not taken into account.") if side == 'middle': openingnr_lhs = index_min_xy[openingnr - 1] + 1 sort_xy, index_xy = my_sort( [points[openingnr_lhs][0][x_or_y], points[openingnr_lhs][1][x_or_y], points[openingnr_lhs][2][x_or_y], points[openingnr_lhs][3][x_or_y]]) sort_z, index_z = my_sort( [points[openingnr_lhs][0][2], points[openingnr_lhs][1][2], points[openingnr_lhs][2][2], points[openingnr_lhs][3][2]]) # Assign the status of each point of the opening for pointnr, point in enumerate(points[openingnr_lhs]): if (pointnr == index_xy[0] or pointnr == index_xy[1]) and ( pointnr == index_z[0] or pointnr == index_z[1]): lower_left_opening_left_nr = pointnr lower_left_opening_left_xyz = point elif (pointnr == index_xy[2] or pointnr == index_xy[3]) and ( pointnr == index_z[0] or pointnr == index_z[1]): lower_right_opening_left_nr = pointnr lower_right_opening_left_xyz = point elif (pointnr == index_xy[0] or pointnr == index_xy[1]) and ( pointnr == index_z[2] or pointnr == index_z[3]): upper_left_opening_left_nr = pointnr upper_left_opening_left_xyz = point elif (pointnr == index_xy[2] or pointnr == index_xy[3]) and ( pointnr == index_z[2] or pointnr == index_z[3]): upper_right_opening_left_nr = pointnr upper_right_opening_left_xyz = point try: _ = lower_left_opening_left_nr _ = lower_right_opening_left_nr _ = upper_left_opening_left_nr _ = upper_right_opening_left_nr except NameError: project.write_log( f"WARNING: the shape of {wall.name} is not supported. " f"Its contributions are not taken into account.") lower_left_high_left_xyz = [lower_left_xyz[0],lower_left_xyz[1],lower_left_opening_left_xyz[2]] lower_right_high_left_xyz = [lower_left_xyz[0], lower_left_xyz[1], lower_left_opening_left_xyz[2]] upper_left_low_left_xyz = [upper_left_xyz[0], upper_left_xyz[1], upper_left_opening_left_xyz[2]] upper_right_low_left_xyz = [upper_left_xyz[0], upper_left_xyz[1], upper_left_opening_left_xyz[2]] # Find interpolated z-coordinates for upper_right_high and lower_right_low z_upper_right_high = (upper_right_xyz[x_or_y] - upper_left_opening_right_xyz[x_or_y]) / \ (upper_right_xyz[x_or_y] - upper_left_xyz[x_or_y]) *\ (upper_right_xyz[2] - upper_left_xyz[2]) + upper_left_xyz[2] z_lower_right_low = (lower_right_xyz[x_or_y] - lower_left_opening_right_xyz[x_or_y]) / \ (lower_right_xyz[x_or_y] - lower_left_xyz[x_or_y]) * \ (lower_right_xyz[2] - lower_left_xyz[2]) + lower_left_xyz[2] # Find points on pier lower_left_high_right_xyz = [ lower_left_xyz[0], lower_left_xyz[1], lower_left_opening_right_xyz[2]] lower_left_low_xyz = [ lower_left_xyz[0], lower_left_xyz[1], lower_left_xyz[2]] lower_right_high_right_xyz = [ lower_left_opening_right_xyz[0], lower_left_opening_right_xyz[1], lower_left_opening_right_xyz[2]] lower_right_low_xyz = [ lower_left_opening_right_xyz[0], lower_left_opening_right_xyz[1], z_lower_right_low] upper_left_high_xyz = [ upper_left_xyz[0], upper_left_xyz[1], upper_left_xyz[2]] upper_left_low_right_xyz = [ upper_left_xyz[0], upper_left_xyz[1], upper_left_opening_right_xyz[2]] upper_right_high_xyz = [ upper_left_opening_right_xyz[0], upper_left_opening_right_xyz[1], z_upper_right_high] upper_right_low_right_xyz = [ upper_left_opening_right_xyz[0], upper_left_opening_right_xyz[1], upper_left_opening_right_xyz[2]] # Save pier if side == 'left': # The loop below will ensure that there are no line piers (i.e. piers with no thickness and for # which LLH=LRH, LLL=LRL, ULH=URH and ULL=URL). This situation mighr arise when there is an opening # flush with a wall edge. if lower_left_high_right_xyz != lower_right_high_right_xyz and \ lower_left_low_xyz != lower_right_low_xyz and \ upper_left_high_xyz != upper_right_high_xyz and \ upper_left_low_right_xyz != upper_right_low_right_xyz: piers.append({ 'coordinates': { 'RHS': { 'LLH': lower_left_high_right_xyz, 'LLL': lower_left_low_xyz, 'LRH': lower_right_high_right_xyz, 'LRL': lower_right_low_xyz, 'ULH': upper_left_high_xyz, 'ULL': upper_left_low_right_xyz, 'URH': upper_right_high_xyz, 'URL': upper_right_low_right_xyz}, 'LHS': { 'LLH': None, 'LLL': None, 'LRH': None, 'LRL': None, 'ULH': None, 'ULL': None, 'URH': None, 'URL': None}}, 'parent wall': wall, 'workplane': workplane, 'side': side}) elif side == 'middle': # the loop belowwill ensure that there are no line piers (i.e. piers with no thickness and for which # LLH=LRH, LLL=LRL, ULH=URH and ULL=URL). This situation mighr arise when there is an opening flush # with a wall edge. if lower_left_high_right_xyz != lower_right_high_right_xyz and \ lower_left_low_xyz != lower_right_low_xyz and \ upper_left_high_xyz != upper_right_high_xyz and \ upper_left_low_right_xyz != upper_right_low_right_xyz: piers.append({ 'coordinates': { 'RHS': { 'LLH': lower_left_high_right_xyz, 'LLL': lower_left_low_xyz, 'LRH': lower_right_high_right_xyz, 'LRL': lower_right_low_xyz, 'ULH': upper_left_high_xyz, 'ULL': upper_left_low_right_xyz, 'URH': upper_right_high_xyz, 'URL': upper_right_low_right_xyz}, 'LHS': { 'LLH': lower_left_high_left_xyz, 'LLL': lower_left_low_xyz, 'LRH': lower_right_high_left_xyz, 'LRL': lower_right_low_xyz, 'ULH': upper_left_high_xyz, 'ULL': upper_left_low_left_xyz, 'URH': upper_right_high_xyz, 'URL': upper_right_low_left_xyz}}, 'parent wall': wall, 'workplane': workplane, 'side': side}) # Update geometry of "remainder" of wall z_upper_left_high = (upper_right_xyz[x_or_y] - upper_right_opening_right_xyz[x_or_y]) / \ (upper_right_xyz[x_or_y] - upper_left_xyz[x_or_y]) *\ (upper_right_xyz[2] - upper_left_xyz[2]) + upper_left_xyz[2] z_lower_left_high = (lower_right_xyz[x_or_y] - lower_right_opening_right_xyz[x_or_y]) / \ (lower_right_xyz[x_or_y] - lower_left_xyz[x_or_y]) * \ (lower_right_xyz[2] - lower_left_xyz[2]) + lower_left_xyz[2] lower_left_xyz = [ lower_right_opening_right_xyz[0], lower_right_opening_right_xyz[1], z_lower_left_high] upper_left_xyz = [ upper_right_opening_right_xyz[0], upper_right_opening_right_xyz[1], z_upper_left_high] # The remaining pier has to be approached from the right-side of the looped opening, # thus uses a different approach: # Find points on pier lower_left_high_right_xyz = [lower_left_xyz[0], lower_left_xyz[1], lower_right_opening_right_xyz[2]] lower_left_low_xyz = [lower_left_xyz[0], lower_left_xyz[1], lower_left_xyz[2]] lower_right_high_right_xyz = [lower_right_xyz[0], lower_right_xyz[1], lower_right_opening_right_xyz[2]] lower_right_low_xyz = [lower_right_xyz[0], lower_right_xyz[1], lower_right_xyz[2]] upper_left_high_xyz = [upper_left_xyz[0], upper_left_xyz[1], upper_left_xyz[2]] upper_left_low_right_xyz = [upper_left_xyz[0], upper_left_xyz[1], upper_right_opening_right_xyz[2]] upper_right_high_xyz = [upper_right_xyz[0], upper_right_xyz[1], upper_right_xyz[2]] upper_right_low_right_xyz = [upper_right_xyz[0], upper_right_xyz[1], upper_right_opening_right_xyz[2]] # the loop belowwill ensure that there are no line piers (i.e. piers with no thickness and for which # LLH=LRH, LLL=LRL, ULH=URH and ULL=URL). This situation mighr arise when there is an opening flush with # a wall edge. if lower_left_high_right_xyz != lower_right_high_right_xyz and \ lower_left_low_xyz != lower_right_low_xyz and \ upper_left_high_xyz != upper_right_high_xyz and \ upper_left_low_right_xyz != upper_right_low_right_xyz: piers.append({ 'coordinates': { 'RHS': { 'LLH': None, 'LLL': None, 'LRH': None, 'LRL': None, 'ULH': None, 'ULL': None, 'URH': None, 'URL': None}, 'LHS': { 'LLH': lower_left_high_right_xyz, 'LLL': lower_left_low_xyz, 'LRH': lower_right_high_right_xyz, 'LRL': lower_right_low_xyz, 'ULH': upper_left_high_xyz, 'ULL': upper_left_low_right_xyz, 'URH': upper_right_high_xyz, 'URL': upper_right_low_right_xyz}}, 'parent wall': wall, 'workplane': workplane, 'side': 'right'}) else: project.write_log( "The function failed to identify piers because the functionality is limited to four point openings. " "The function works, but to get results please offset all openings from wall edges.") return piers
### =================================================================================================================== ### 3. End of script ### ===================================================================================================================