1003 lines
44 KiB
Python
1003 lines
44 KiB
Python
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)
|