import math from statistics import mean from helix.calculators.subarray_helper import get_subarray_sizes_and_rows from helix.constants.dxf_validation import INVALID_DUAL_TILT_DESIGN from helix.constants.panel_type import PanelType from helix.constants.subarray import SUBARRAY_SIZE_BIG from helix.constants.system_type import SystemType from helix.helpers.polygon_helper import * from helix.models.coordinate import Coordinate from helix.models.dxf.dxf_error import DXFError from helix.models.dxf.graph_direction import GraphDirection from helix.models.dxf.graph_node import GraphNode from helix.models.dxf.graph_node_store import GraphNodeStore from helix.models.dxf.polygon import Polygon from helix.models.panel import Panel from helix.validators.dxf_layer_validator import DXFLayerValidator class DXFHelper(object): def __init__(self): self.aurora_detector = DXFLayerValidator() def is_new_aurora_format(self): """ Determine if it's the new dxf format return boolean """ return self.aurora_detector.is_new_aurora_format() @staticmethod def generate_panels(modules, translated_modules): """Joins the modules and creates Parameters: modules (list): List of obtained modules translated_modules (list): A list containing the shifted positions of the modules Returns: list """ panels = [] for idx, module in enumerate(modules): x_center = mean([x for x, _ in module.points]) y_center = mean([y for _, y in module.points]) rotation = module.determine_orientation() x_translated_center = mean([x for x, _ in translated_modules[idx].points]) y_translated_center = mean([y for _, y in translated_modules[idx].points]) panels.append(Panel(id=idx + 1, coordinate=Coordinate( x_translated_center, y_translated_center, rotation), original_coordinate=Coordinate(x_center, y_center, rotation))) return panels @staticmethod def should_consolidate_modules(modules, system_type, module_constants): """Determine if the modules should be consolidated into panel pairs. Some dual tilt files came with separate modules, this method determines if we should consolidate them. Arguments: modules (list) List of helix.models.dxf.Polygon system_type (object) Type of system selected single tilt, dual tilt module_constants (object) helix.constants.module_type_constants.dual_tilt_96_cell_constants.DualTilt96CellConstants Returns: boolean """ if system_type != SystemType.dualTilt: return False for module in modules: points = module.sorted_points() p1 = points[0] other_points = sorted(points[1:], key=lambda x: (x[0] - p1[0]) ** 2 + (x[1] - p1[1]) ** 2) p2 = other_points[1] if (math.hypot(p2[0] - p1[0], p2[1] - p1[1]) - min(module_constants.panel_spacing)) < 1e-3: return True return False @staticmethod def consolidate_dual_tilt_modules(original_modules, system_type, pair_spacing): """Consolidate the dual tilt modules. This method is called with a certain type of dual tilt files that had the modules separated Parameters: original_modules (list) Contains the list of modules from the dxf file system_type (object) Type of system selected single tilt, dual tilt pair_spacing (object) Contains the possible pair spacing that a dual tilt system will have """ if system_type == SystemType.dualTilt: modules = [] while len(original_modules) > 0: current_module = original_modules.pop(0) modules.append(current_module) found_pair = False for idx, potential_pair in enumerate(original_modules): if current_module.shares_module_on_long_edge(potential_pair, pair_spacing): current_module.consolidate_with(potential_pair, pair_spacing) original_modules.pop(idx) found_pair = True break if not found_pair: raise DXFError(INVALID_DUAL_TILT_DESIGN) else: return original_modules return modules def build_polygons(self, dxf_entities): """Obtain two types of polygon objects, that represent modules and buildings. It's important to understand that the polygons are scaled using inches. Arguments: dxf_entities (list) This list contains dxfgrabber.entities """ building_lines = [] module_lines = [] for entity in dxf_entities: self.aurora_detector.add_layer(entity.layer) if entity.layer == 'Modules': # is a module module_lines.append(entity) elif entity.layer == 'Roofs' or entity.layer == 'Buildings': # is a building/roof line building_lines.append(entity) self.aurora_detector.determine_file() inches_per_feet = 12 buildings = [p.scale(inches_per_feet, inches_per_feet) for p in DXFHelper.generate_polygons(building_lines)] modules = [p.scale(inches_per_feet, inches_per_feet) for p in DXFHelper.generate_polygons(module_lines)] return buildings, modules @staticmethod def generate_polygons(lines): polygons = [] for line in lines: if len(polygons) == 0 or not polygons[-1].continues_with_line(line): polygons.append(Polygon(line)) elif line.end in polygons[-1].points: continue else: polygons[-1].points.append(line.end) return polygons @staticmethod def translate_towards_origin(buildings, modules): """Obtains the minimum value of x and y points and shifts all the modules and buildings. Arguments: buildings (list): List of buildings modules (list): List of modules Returns: tuple """ min_x = float('inf') # positive infinity min_y = float('inf') for polygon in buildings + modules: min_x = min(min_x, min(x for x, _ in polygon.points)) min_y = min(min_y, min(y for _, y in polygon.points)) translated_buildings = [Polygon(points=[(x - min_x, y - min_y) for x, y in polygon.points]) for polygon in buildings] translated_modules = [Polygon(points=[(x - min_x, y - min_y) for x, y in polygon.points]) for polygon in modules] return translated_buildings, translated_modules @staticmethod def get_polygons_counterclockwise(polygons): """Checks if polygon's points are in counterclockwise order, if not reverse the order. Arguments: polygons (list): List of polygons Returns: list of polygons with points in counterclockwise order """ output_polygons = [] for polygon in polygons: points = polygon.points # checking if building's points are in clockwise order cumulative = 0 for i in range(len(points)): current = points[i - 1] next = points[i] cumulative += (next[0] - current[0]) * (next[1] + current[1]) clockwise = cumulative > 0 # change to counter-clockwise if necessary # further code assumes we have counter-clockwise points for the building if clockwise: points = list(reversed(points)) output_polygons.append(Polygon(points=[(x, y) for x, y in points])) return output_polygons @staticmethod def build_node_graph(panels, spacing): nodes = [] node_store = GraphNodeStore() for panel in panels: x_spacing = spacing[0] y_spacing = spacing[1] node = GraphNode(panel, x_spacing, y_spacing) nodes.append(node) node_store.add_node(node) for node in nodes: if len(node.neighboring_nodes()) == 8: continue for x in (0, 1, -1): for y in (0, 1, -1): if x == y == 0: continue rotation = math.radians(node.coordinate.rotation) x_spacing = (x * node.x_spacing * math.cos(rotation)) - (y * node.y_spacing * math.sin(rotation)) y_spacing = (x * node.x_spacing * math.sin(rotation)) + (y * node.y_spacing * math.cos(rotation)) coordinate = Coordinate(node.coordinate.x + x_spacing, node.coordinate.y + y_spacing, node.coordinate.rotation) if coordinate.x < 0 or coordinate.y < 0: continue direction = GraphDirection((x, y)) if node.has_existing_neighbor(direction): continue neighbor = node_store.find_coordinate(coordinate) if neighbor: node.add_neighbor(neighbor, direction) if len(node.neighboring_nodes()) == 0: raise DXFError("Error - invalid module spacing. Please check to make sure the correct system type and panel spacing are present") return nodes @staticmethod def detect_subarrays(nodes, panels): subarray_number = 0 def walk_graph_and_assign_subarray(node, subarray_number): if node.panel.subarray is not None: return node.panel.subarray = subarray_number for neighbor in node.neighboring_nodes(): walk_graph_and_assign_subarray(neighbor, subarray_number) for node in nodes: if node.panel.subarray is None: subarray_number += 1 try: walk_graph_and_assign_subarray(node, subarray_number) except RecursionError: raise DXFError(SUBARRAY_SIZE_BIG) panels.sort(key=lambda p: p.subarray) subarray_list = get_subarray_sizes_and_rows(panels) subarrays = {} for subarray in subarray_list: subarray_number = subarray.subarray_number if not subarrays.get(subarray.subarray_number): subarrays[subarray_number] = subarray else: subarrays[subarray_number].size += subarray.size return list(subarrays.values()) @staticmethod def detect_panel_types(nodes): for node in nodes: ordinal_neighbors = node.ordinal_neighbors() ordinal_neighbors_count = len(ordinal_neighbors) if ordinal_neighbors_count == 4: node.panel.panel_type = PanelType.Middle elif ordinal_neighbors_count <= 2: node.panel.panel_type = PanelType.Corner elif ordinal_neighbors.get(GraphDirection.North) is None or ordinal_neighbors.get(GraphDirection.South) is None: node.panel.panel_type = PanelType.NorthSouth elif ordinal_neighbors.get(GraphDirection.East) is None or ordinal_neighbors.get(GraphDirection.West) is None: node.panel.panel_type = PanelType.EastWest else: raise DXFError("Invalid Array") pass @staticmethod def detect_wind_zones(panels, buildings, modules, l_b, system_type): if system_type == SystemType.dualTilt: DXFHelper.__detect_dual_tilt_wind_zones__(panels, buildings, modules, l_b) else: DXFHelper.__detect_single_tilt_wind_zones__(panels, buildings, modules, l_b) @staticmethod def __detect_dual_tilt_wind_zones__(panels, buildings, modules, l_b): sorted_panels = sorted(panels, key=lambda p: p.id) wind_zone_a = DXFHelper.__generate_wind_zone_a_dual_tilt__(buildings, l_b) fuzzy_wind_zone_a = DXFHelper.__generate_wind_zone_a_fuzzy_dual_tilt__(buildings, l_b) wind_zone_b = DXFHelper.__generate_wind_zone_b_dual_tilt__(buildings, l_b) fuzzy_wind_zone_b = DXFHelper.__generate_wind_zone_b_fuzzy_dual_tilt__(buildings, l_b) wind_zone_c = DXFHelper.__generate_wind_zone_c_dual_tilt__(buildings, l_b) fuzzy_wind_zone_c = DXFHelper.__generate_wind_zone_c_fuzzy_dual_tilt__(buildings, l_b) wind_zone_d = DXFHelper.__generate_wind_zone_d_dual_tilt__(buildings, l_b) fuzzy_wind_zone_d = DXFHelper.__generate_wind_zone_d_fuzzy_dual_tilt__(buildings, l_b) for idx, panel in enumerate(sorted_panels): module = modules[idx] if DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_a): panel.wind_zone = 0 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_a): panel.wind_zone = 0 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_b): panel.wind_zone = 1 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_b): panel.wind_zone = 1 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_c): panel.wind_zone = 2 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_c): panel.wind_zone = 2 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_d): panel.wind_zone = 3 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_d): panel.wind_zone = 3 panel.fuzzy_wind_zone = True else: # wind zone E panel.wind_zone = 4 @staticmethod def __detect_single_tilt_wind_zones__(panels, buildings, modules, l_b): sorted_panels = sorted(panels, key=lambda p: p.id) panel_orientation = panels[0].coordinate.rotation wind_zone_a = DXFHelper.__generate_wind_zone_a_single_tilt__(buildings, l_b, panel_orientation) fuzzy_wind_zone_a = DXFHelper.__generate_wind_zone_a_fuzzy_single_tilt__(buildings, l_b) wind_zone_b = DXFHelper.__generate_wind_zone_b_single_tilt__(buildings, l_b, panel_orientation) fuzzy_wind_zone_b = DXFHelper.__generate_wind_zone_b_fuzzy_single_tilt__(buildings, l_b) wind_zone_c = DXFHelper.__generate_wind_zone_c_single_tilt__(buildings, l_b, panel_orientation) fuzzy_wind_zone_c = DXFHelper.__generate_wind_zone_c_fuzzy_single_tilt__(buildings, l_b) wind_zone_d = DXFHelper.__generate_wind_zone_d_single_tilt__(buildings, l_b, panel_orientation) fuzzy_wind_zone_d = DXFHelper.__generate_wind_zone_d_fuzzy_single_tilt__(buildings, l_b) wind_zone_i = DXFHelper.__generate_wind_zone_i_single_tilt__(buildings, l_b, panel_orientation) wind_zone_h = DXFHelper.__generate_wind_zone_h_single_tilt__(buildings, l_b, panel_orientation) wind_zone_j = DXFHelper.__generate_wind_zone_j_single_tilt__(buildings, l_b, panel_orientation) wind_zone_e = DXFHelper.__generate_wind_zone_e_single_tilt__(buildings, l_b, panel_orientation) wind_zone_f = DXFHelper.__generate_wind_zone_f_single_tilt__(buildings, l_b, panel_orientation) wind_zone_g = DXFHelper.__generate_wind_zone_g_single_tilt__(buildings, l_b, panel_orientation) for idx, panel in enumerate(sorted_panels): module = modules[idx] if DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_a): panel.wind_zone = 0 elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_i): panel.wind_zone = 8 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_a): panel.wind_zone = 0 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_j): panel.wind_zone = 9 elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_b): panel.wind_zone = 1 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_b): panel.wind_zone = 1 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_h): panel.wind_zone = 7 elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_c): panel.wind_zone = 2 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_c): panel.wind_zone = 2 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_e): panel.wind_zone = 4 elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_f): panel.wind_zone = 5 elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_d): panel.wind_zone = 3 elif DXFHelper.__is_panel_in_wind_zone__(module, fuzzy_wind_zone_d): panel.wind_zone = 3 panel.fuzzy_wind_zone = True elif DXFHelper.__is_panel_in_wind_zone__(module, wind_zone_g): panel.wind_zone = 6 else: panel.wind_zone = 10 @staticmethod def __is_panel_in_wind_zone__(module, wind_zone): for x, y in module.points: for wind_subzone in wind_zone: if point_inside_polygon(x, y, wind_subzone): return True return False @staticmethod def __compute_segment_direction(p1, p2): """ Computes direction of a segment. Points taken from building outline are assumed to be in counterclockwise order. :param p1: first point :param p2: second point :return: tuple representing orientation ('north', 'south', 'east', 'west') and variation in degrees from ideally directed segment """ segment_angle = (math.degrees(math.atan2(p1[1] - p2[1], p1[0] - p2[0])) + 360) % 360 if segment_angle >= 315 or segment_angle < 45: return 'north', segment_angle if segment_angle < 45 else 360 - segment_angle elif 45 <= segment_angle < 135: return 'west', abs(90 - segment_angle) elif 135 <= segment_angle < 225: return 'south', abs(180 - segment_angle) else: return 'east', abs(270 - segment_angle) @staticmethod def compute_corner_directions(vertex, prev, next, angle_correction): """ Determines if point is located in north/east corner :param vertex: point located in the corner :param prev: point previous to vertex, assuming counterclockwise order :param next: point next to vertex, assuming counterclockwise order :param angle_correction: points are rotated by this angle first, in degrees :return: (is_north, is_east) tuple """ rotated = DXFHelper.__rotate_points([vertex, prev, next], math.radians(angle_correction)) vertex_rotated, prev_rotated, next_rotated = rotated[0], rotated[1], rotated[2] prev_segment_orientation, prev_dev = DXFHelper.__compute_segment_direction(prev_rotated, vertex_rotated) next_segment_orientation, next_dev = DXFHelper.__compute_segment_direction(vertex_rotated, next_rotated) is_north = prev_segment_orientation == 'north' or next_segment_orientation == 'north' dirs = (prev_segment_orientation, next_segment_orientation) if 'north' in dirs and 'south' not in dirs: is_north = True elif 'south' in dirs and 'north' not in dirs: is_north = False elif dirs == ('north', 'south'): is_north = prev_dev < next_dev elif dirs == ('south', 'north'): is_north = next_dev < prev_dev elif dirs == ('east', 'west'): is_north = True elif dirs == ('west', 'east'): is_north = False elif dirs == ('west', 'west') or dirs == ('east', 'east'): is_north = prev_rotated[0] > next_rotated[0] if 'east' in dirs and 'west' not in dirs: is_east = True elif 'west' in dirs and 'east' not in dirs: is_east = False elif dirs == ('east', 'west'): is_east = prev_dev < next_dev elif dirs == ('west', 'east'): is_east = next_dev < prev_dev elif dirs == ('north', 'south'): is_east = False elif dirs == ('south', 'north'): is_east = True elif dirs == ('north', 'north') or dirs == ('south', 'south'): is_east = prev_rotated[1] < next_rotated[1] return is_north, is_east @staticmethod def __rotate_points(points, angle): """ :param points: points to be rotated as list of tuples :param angle: angle in radians :return: list of rotated points """ sin = math.sin(angle) cos = math.cos(angle) return [(x * cos - y * sin, x * sin + y * cos) for x, y in points] @staticmethod def __rotate_point(point, angle): return DXFHelper.__rotate_points([point], angle)[0] @staticmethod def __generate_wind_zone__(buildings, scaling_factor, points_callback, panel_orientation): """ Important: polygons representing buildings are expected to have points in counterclockwise order """ wind_zones = [] for building in buildings: for idx, vertex in enumerate(building.points): prev = building.points[(idx - 1) % len(building.points)] next = building.points[(idx + 1) % len(building.points)] a_sq = (vertex[0] - prev[0]) ** 2 + (vertex[1] - prev[1]) ** 2 b_sq = (vertex[0] - next[0]) ** 2 + (vertex[1] - next[1]) ** 2 c_sq = (next[0] - prev[0]) ** 2 + (next[1] - prev[1]) ** 2 distance_to_prev = math.sqrt(a_sq) distance_to_next = math.sqrt(b_sq) angle = math.acos((c_sq - a_sq - b_sq) / (-2 * distance_to_prev * distance_to_next)) # angles between x-axis and line created by current and next/previous vertex angle_to_next = math.atan2(next[1] - vertex[1], next[0] - vertex[0]) angle_to_prev = math.atan2(prev[1] - vertex[1], prev[0] - vertex[0]) is_north, is_east = DXFHelper.compute_corner_directions(vertex, prev, next, -panel_orientation) relative_angle = ((angle_to_next - angle_to_prev + 2 * math.pi) % (2 * math.pi)) - math.pi if abs(angle_to_next - math.radians(panel_orientation)) > abs(angle_to_prev - math.radians(panel_orientation)): max_distance = distance_to_next else: max_distance = distance_to_prev orientation = angle_to_next def generate_point(x, y): d_x, d_y = DXFHelper.__rotate_point((x, y), orientation) return vertex[0] + d_x, vertex[1] + d_y if angle <= math.radians(135) and relative_angle >= 0: points = points_callback(angle, is_north, is_east, max_distance) if points: wind_zones.append([generate_point(scaling_factor * x, scaling_factor * y) for x, y in points]) return wind_zones @staticmethod def __generate_dual_tilt_wind_zone_with_transforms__(buildings, scaling_factor, points): def callback(angle, *_): if angle < math.radians(80): return [] rotation = angle - math.radians(90) def inner_transform(x, y, perform_rotation, rotation_value=rotation): if perform_rotation: return DXFHelper.__rotate_point((x, y), rotation_value) else: return x, y transformed_points = [] for x, y, apply_rotation, rotation_scale in points: if rotation_scale is not None: rotation_value = rotation_scale * rotation else: rotation_value = rotation transformed_points.append(inner_transform(x, y, apply_rotation, rotation_value)) return transformed_points return DXFHelper.__generate_wind_zone__(buildings, scaling_factor, callback, 0) @staticmethod def __generate_wind_zone_a_dual_tilt__(buildings, scaling_factor): wind_zone_points = [ (0, 0, False, None), (2, 0, False, None), (2, 1, False, None), (1, 1, True, 0.5), (1, 2, True, None), (0, 2, True, None) ] return DXFHelper.__generate_dual_tilt_wind_zone_with_transforms__(buildings, scaling_factor, wind_zone_points) @staticmethod def __generate_wind_zone_b_dual_tilt__(buildings, scaling_factor): wind_zone_points = [ (1, 1, True, 0.5), (2, 1, False, None), (2, 0, False, None), (2, 0, False, None), (4, 0, False, None), (4, 2, False, None), (2, 2, True, 0.5), (2, 4, True, None), (0, 4, True, None), (0, 2, True, None), (1, 2, True, None), ] return DXFHelper.__generate_dual_tilt_wind_zone_with_transforms__(buildings, scaling_factor, wind_zone_points) @staticmethod def __generate_wind_zone_c_dual_tilt__(buildings, scaling_factor): wind_zone_points = [ (2, 2, True, 0.5), (4, 2, False, None), (4, 0, False, None), (6, 0, False, None), (6, 3, False, None), (3, 3, True, 0.5), (3, 6, True, None), (0, 6, True, None), (0, 4, True, None), (2, 4, True, None), ] return DXFHelper.__generate_dual_tilt_wind_zone_with_transforms__(buildings, scaling_factor, wind_zone_points) @staticmethod def __generate_wind_zone_d_dual_tilt__(buildings, scaling_factor): wind_zone_points = [ (3, 3, True, 0.5), (6, 3, False, None), (6, 0, False, None), (8, 0, False, None), (8, 4, False, None), (4, 8, True, None), (0, 8, True, None), (0, 6, True, None), (3, 6, True, None), ] return DXFHelper.__generate_dual_tilt_wind_zone_with_transforms__(buildings, scaling_factor, wind_zone_points) @staticmethod def __generate_fuzzy_wind_zone_dual_tilt__(buildings, scaling_factor, inner_l_b, outer_l_b): def callback(angle, *_): if angle >= math.radians(80): return [] number_points_on_inner_arc = int(math.ceil(inner_l_b * scaling_factor / 10)) number_points_on_outer_arc = int(math.ceil(outer_l_b * scaling_factor / 10)) inner_points_length = (inner_l_b, number_points_on_inner_arc) outer_points_length = (outer_l_b, number_points_on_outer_arc) points = [] if inner_l_b == 0: points.append((0, 0)) for idx, (l_b_length, number_points) in enumerate((inner_points_length, outer_points_length)): if number_points == 0: continue for step in range(number_points + 1): sub_angle = (angle / number_points) * step x = l_b_length * math.cos(sub_angle) y = l_b_length * math.sin(sub_angle) if idx == 0: points.append((x, y)) else: points.insert(0, (x, y)) return points return DXFHelper.__generate_wind_zone__(buildings, scaling_factor, callback, 0) @staticmethod def __generate_wind_zone_a_fuzzy_dual_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_dual_tilt__(buildings, scaling_factor, 0, math.sqrt(5)) @staticmethod def __generate_wind_zone_b_fuzzy_dual_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_dual_tilt__(buildings, scaling_factor, math.sqrt(5), math.sqrt(20)) @staticmethod def __generate_wind_zone_c_fuzzy_dual_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_dual_tilt__(buildings, scaling_factor, math.sqrt(20), math.sqrt(45)) @staticmethod def __generate_wind_zone_d_fuzzy_dual_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_dual_tilt__(buildings, scaling_factor, math.sqrt(45), math.sqrt(80)) # Single Tilt @staticmethod def __generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, northern_zone, points, panel_orientation): def callback(angle, is_north, is_east, max_distance): if is_north != northern_zone: return [] if angle < math.radians(80): return [] rotation = angle - math.radians(90) def inner_transform(x, y, perform_rotation, rotation_value=rotation): if y == -1: y = max_distance / scaling_factor if not is_east: perform_rotation = not perform_rotation # swap x,y coordinates - reflection about line x = y new_x = y new_y = x x = new_x y = new_y if perform_rotation: return DXFHelper.__rotate_point((x, y), rotation_value) else: return x, y transformed_points = [] for x, y, apply_rotation, rotation_scale in points: if rotation_scale is not None: rotation_value = rotation_scale * rotation else: rotation_value = rotation transformed_points.append(inner_transform(x, y, apply_rotation, rotation_value)) return transformed_points return DXFHelper.__generate_wind_zone__(buildings, scaling_factor, callback, panel_orientation) @staticmethod def __generate_wind_zone_a_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (0, 0, False, None), (0, 2, True, None), (2, 2, True, 0.5), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, True, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_b_single_tilt__(buildings, scaling_factor, panel_orientation): first_wind_zone_points = [ (0, 0, False, None), (2, 0, False, None), (2, 2, True, 0.5), ] first_wind_zone_polygons = DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, True, first_wind_zone_points, panel_orientation) second_wind_zone_points = [ (0, 2, True, None), (0, 4, True, None), (1, 4, True, None), (2, 3, True, None), (2, 2, True, 0.5), ] second_wind_zone_polygons = DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, True, second_wind_zone_points, panel_orientation) return first_wind_zone_polygons + second_wind_zone_polygons @staticmethod def __generate_wind_zone_c_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (2, 0, False, None), (4, 0, False, None), (4, 4, True, 0.5), (2, 6, True, None), (0, 6, True, None), (0, 4, True, None), (1, 4, True, None), (2, 3, True, None), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, True, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_d_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (4, 0, False, None), (6, 0, False, None), (6, 6, True, 0.5), (4, 8, True, None), (0, 8, True, None), (0, 6, True, None), (2, 6, True, None), (2, 6, True, None), (4, 4, True, 0.5), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, True, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_i_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (0, 0, False, None), (0, 2, True, None), (1.5, 2, True, 2.0 / 3.0), (1, 0, False, None), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, False, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_h_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (0, 2, True, None), (0, 4, True, None), (2, 4, True, 2.0 / 3.0), (1.5, 2, True, 2.0 / 3.0), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, False, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_j_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (1, 0, False, None), (3, 0, False, None), (3, 2, True, 2.0 / 3.0), (1.5, 2, True, 2.0 / 3.0), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, False, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_e_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (0, 4, True, None), (0, 7, True, None), (3, 7, True, 2.0 / 3.0), (2, 4, True, 2.0 / 3.0), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, False, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_f_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (3, 0, False, None), (8, 0, False, None), (8, 2, False, None), (3, 7, True, 2.0 / 3.0), (2, 4, True, 2.0 / 3.0), (1.5, 2, True, 2.0 / 3.0), (3, 2, True, 2.0 / 3.0), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, False, wind_zone_points, panel_orientation) @staticmethod def __generate_wind_zone_g_single_tilt__(buildings, scaling_factor, panel_orientation): wind_zone_points = [ (0, 7, True, None), (0, -1, True, None), (4, -1, True, None), (4, 6, True, 2.0 / 3.0), (3, 7, True, 2.0 / 3.0), ] return DXFHelper.__generate_single_tilt_wind_zone_with_transforms__(buildings, scaling_factor, False, wind_zone_points, panel_orientation) @staticmethod def __generate_fuzzy_wind_zone_single_tilt__(buildings, scaling_factor, inner_l_b, outer_l_b): def callback(angle, *_): if angle >= math.radians(80): return [] number_points_on_inner_arc = int(math.ceil(inner_l_b * scaling_factor / 10)) number_points_on_outer_arc = int(math.ceil(outer_l_b * scaling_factor / 10)) inner_points_length = (inner_l_b, number_points_on_inner_arc) outer_points_length = (outer_l_b, number_points_on_outer_arc) points = [] if inner_l_b == 0: points.append((0, 0)) for idx, (l_b_length, number_points) in enumerate((inner_points_length, outer_points_length)): if number_points == 0: continue start = 0 end = number_points for step in range(start, end + 1): sub_angle = (angle / number_points) * step x = l_b_length * math.cos(sub_angle) y = l_b_length * math.sin(sub_angle) if idx == 0: points.append((x, y)) else: points.insert(0, (x, y)) return points return DXFHelper.__generate_wind_zone__(buildings, scaling_factor, callback, 0) @staticmethod def __generate_wind_zone_a_fuzzy_single_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_single_tilt__(buildings, scaling_factor, 0, math.sqrt(8)) @staticmethod def __generate_wind_zone_b_fuzzy_single_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_single_tilt__(buildings, scaling_factor, math.sqrt(8), math.sqrt(20)) @staticmethod def __generate_wind_zone_c_fuzzy_single_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_single_tilt__(buildings, scaling_factor, math.sqrt(20), math.sqrt(44)) @staticmethod def __generate_wind_zone_d_fuzzy_single_tilt__(buildings, scaling_factor): return DXFHelper.__generate_fuzzy_wind_zone_single_tilt__(buildings, scaling_factor, math.sqrt(44), math.sqrt(80)) @staticmethod def l_b_polygons(buildings, scaling_factor, system_type, panel_orientation): if system_type == SystemType.dualTilt: return DXFHelper.__dual_tilt_wind_zone_polygons__(buildings, scaling_factor) else: return DXFHelper.__single_tilt_wind_zone_polygons__(buildings, scaling_factor, panel_orientation) @staticmethod def __dual_tilt_wind_zone_polygons__(buildings, scaling_factor): wind_zone_a = DXFHelper.__generate_wind_zone_a_dual_tilt__(buildings, scaling_factor) wind_zone_a += DXFHelper.__generate_wind_zone_a_fuzzy_dual_tilt__(buildings, scaling_factor) polygons = [] for wind_zone in wind_zone_a: polygon = Polygon(points=wind_zone) polygon.color = "red" polygons.append(polygon) wind_zone_b = DXFHelper.__generate_wind_zone_b_dual_tilt__(buildings, scaling_factor) wind_zone_b += DXFHelper.__generate_wind_zone_b_fuzzy_dual_tilt__(buildings, scaling_factor) for wind_zone in wind_zone_b: polygon = Polygon(points=wind_zone) polygon.color = "green" polygons.append(polygon) wind_zone_c = DXFHelper.__generate_wind_zone_c_dual_tilt__(buildings, scaling_factor) wind_zone_c += DXFHelper.__generate_wind_zone_c_fuzzy_dual_tilt__(buildings, scaling_factor) for wind_zone in wind_zone_c: polygon = Polygon(points=wind_zone) polygon.color = "blue" polygons.append(polygon) wind_zone_d = DXFHelper.__generate_wind_zone_d_dual_tilt__(buildings, scaling_factor) wind_zone_d += DXFHelper.__generate_wind_zone_d_fuzzy_dual_tilt__(buildings, scaling_factor) for wind_zone in wind_zone_d: polygon = Polygon(points=wind_zone) polygon.color = "black" polygons.append(polygon) return reversed(polygons) @staticmethod def __single_tilt_wind_zone_polygons__(buildings, scaling_factor, panel_orientation): wind_zone_a = DXFHelper.__generate_wind_zone_a_single_tilt__(buildings, scaling_factor, panel_orientation) wind_zone_a += DXFHelper.__generate_wind_zone_a_fuzzy_single_tilt__(buildings, scaling_factor) polygons = [] for wind_zone in wind_zone_a: polygon = Polygon(points=wind_zone) polygon.color = "red" polygons.append(polygon) wind_zone_i = DXFHelper.__generate_wind_zone_i_single_tilt__(buildings, scaling_factor, panel_orientation) for wind_zone in wind_zone_i: polygon = Polygon(points=wind_zone) polygon.color = "orange" polygons.append(polygon) wind_zone_j = DXFHelper.__generate_wind_zone_j_single_tilt__(buildings, scaling_factor, panel_orientation) for wind_zone in wind_zone_j: polygon = Polygon(points=wind_zone) polygon.color = "forestgreen" polygons.append(polygon) wind_zone_b = DXFHelper.__generate_wind_zone_b_single_tilt__(buildings, scaling_factor, panel_orientation) wind_zone_b += DXFHelper.__generate_wind_zone_b_fuzzy_single_tilt__(buildings, scaling_factor) for wind_zone in wind_zone_b: polygon = Polygon(points=wind_zone) polygon.color = "green" polygons.append(polygon) wind_zone_h = DXFHelper.__generate_wind_zone_h_single_tilt__(buildings, scaling_factor, panel_orientation) for wind_zone in wind_zone_h: polygon = Polygon(points=wind_zone) polygon.color = "yellow" polygons.append(polygon) wind_zone_c = DXFHelper.__generate_wind_zone_c_single_tilt__(buildings, scaling_factor, panel_orientation) wind_zone_c += DXFHelper.__generate_wind_zone_c_fuzzy_single_tilt__(buildings, scaling_factor) for wind_zone in wind_zone_c: polygon = Polygon(points=wind_zone) polygon.color = "blue" polygons.append(polygon) wind_zone_e = DXFHelper.__generate_wind_zone_e_single_tilt__(buildings, scaling_factor, panel_orientation) for wind_zone in wind_zone_e: polygon = Polygon(points=wind_zone) polygon.color = "purple" polygons.append(polygon) wind_zone_f = DXFHelper.__generate_wind_zone_f_single_tilt__(buildings, scaling_factor, panel_orientation) for wind_zone in wind_zone_f: polygon = Polygon(points=wind_zone) polygon.color = "grey" polygons.append(polygon) wind_zone_d = DXFHelper.__generate_wind_zone_d_single_tilt__(buildings, scaling_factor, panel_orientation) wind_zone_d += DXFHelper.__generate_wind_zone_d_fuzzy_single_tilt__(buildings, scaling_factor) for wind_zone in wind_zone_d: polygon = Polygon(points=wind_zone) polygon.color = "black" polygons.append(polygon) wind_zone_g = DXFHelper.__generate_wind_zone_g_single_tilt__(buildings, scaling_factor, panel_orientation) for wind_zone in wind_zone_g: polygon = Polygon(points=wind_zone) polygon.color = "hotpink" polygons.append(polygon) return reversed(polygons)