from svgwrite.container import Group import svgwrite from svgwrite.shapes import Rect, Polygon from svgwrite.text import Text import wand from wand.api import library from wand.image import Image from helix.calculators.subarray_helper import extract_subarray from helix.constants.color import Color from helix.constants.system_type import SystemType class ImagePresenter(object): def __init__(self, system_type, module_type): self.system_type = system_type self.module_type = module_type def generate_image(self, panels, subarrays): image_dimensions = (620, 710) if len(panels) < 3000: svg_string = self.generate_array_svg(image_dimensions, panels, subarrays) else: svg_string = ImagePresenter.svg_too_large_string(image_dimensions) with Image(blob=svg_string.encode('utf-8'), format='svg') as img: with wand.color.Color('transparent') as background_color: library.MagickSetBackgroundColor(img.wand, background_color.resource) png_image = img.make_blob('png') return png_image @staticmethod def svg_too_large_string(image_dimensions): dwg = svgwrite.Drawing(size=image_dimensions) dwg.add(Text("Array may be too large, please try again or break up array into smaller components", x=[image_dimensions[0] / 2], y=[image_dimensions[1] / 2], text_anchor="middle")) return dwg.tostring() def generate_array_svg(self, image_dimensions, panels, subarrays): dwg = svgwrite.Drawing(size=image_dimensions) array_dimensions = self.calculate_max_array_dimensions(panels, subarrays) module_constants = self.system_type.module_constants(self.module_type) array_width, array_height = array_dimensions spacing_x, spacing_y = module_constants.presenter_spacing spacing_x *= 10 spacing_y *= 10 scaling_x = image_dimensions[0] / (array_width * spacing_x) scaling_y = image_dimensions[1] / (array_height * spacing_y) scaling_factor = min(scaling_x, scaling_y) for subarray in subarrays: dwg.add(self.draw_subarray(panels, subarray, array_dimensions, scaling_factor)) return dwg.tostring() def draw_subarray(self, all_panels, subarray, array_dimensions, scaling_factor): module_constants = self.system_type.module_constants(self.module_type) array_width, array_height = array_dimensions spacing_x, spacing_y = module_constants.presenter_spacing spacing_x *= 10 spacing_y *= 10 panels = extract_subarray(all_panels, subarray.subarray_number) subarray_group = Group(transform="scale(%f, %f)" % (scaling_factor, scaling_factor)) for panel in panels: group = self.draw_panel(panel, subarray, array_height - 1, (spacing_x, spacing_y)) subarray_group.add(group) return subarray_group def draw_panel(self, panel, subarray, max_height, panel_dimensions): origin = subarray.origin width, height = panel_dimensions border_width = 0.5 inner_width = width - border_width inner_height = height - border_width inner_dimensions = (inner_width - border_width, inner_height - border_width) x = (panel.coordinate.x + origin.x) * width y = (max_height - (panel.coordinate.y + origin.y)) * height group = Group(stroke_width=border_width, transform="translate(%f, %f)" % (x, y)) # FIXME: look into not having to do this. fill_color, secondary_fill_color, border_color, text_color = self.colors(panel) # draw the background, with a white border around everything background_rect = Rect(insert=(0, 0), size=panel_dimensions, fill=fill_color.value, stroke=Color.array_background.value) group.add(background_rect) # IFF we have both seismic and wind anchors, draw the panel as the "half wind, half seismic" thing. # This means we add a triangle that divides the panel. if secondary_fill_color is not None: points = [ (border_width, border_width), (inner_width, inner_height), (inner_width, border_width) ] dual_anchor_indicator = Polygon(points, fill=secondary_fill_color.value, stroke=secondary_fill_color.value) group.add(dual_anchor_indicator) # draw a border rect. # This doesn't need to be drawn if we don't have any wind or seismic anchors. if border_color != fill_color: border_rect = Rect(insert=(border_width, border_width), size=inner_dimensions, stroke=border_color.value, fill_opacity=0) group.add(border_rect) # If dual-tilt, draw the little triangle on the right side of the panel # Use a hardcoded border value, because this won't change. if self.system_type == SystemType.dualTilt: points = [ (width / 2, border_width), (width / 2, inner_height), (inner_width, height / 2), ] dual_anchor_indicator = Polygon(points, fill_opacity=0, stroke=Color.border.value, stroke_width=border_width / 2) group.add(dual_anchor_indicator) # add all the text! ballast_text = self.text_overlay(str(panel.ballast), panel_dimensions, 0.3, 0.55, "2.5px", text_color) group.add(ballast_text) anchors_text = self.text_overlay(str(panel.wind_anchors + panel.seismic_anchors), panel_dimensions, 0.7, 0.55, "2.5px", text_color) group.add(anchors_text) return group @staticmethod def text_overlay(text, dimensions, x, y, font_size, color): return Text( text, x=[dimensions[0] * x], y=[dimensions[1] * y], font_size=font_size, font_family='sans-serif', text_anchor="middle", fill=color.value ) @staticmethod def calculate_max_array_dimensions(all_panels, subarrays): actual_y_coordinates = [] actual_x_coordinates = [] for subarray in subarrays: panels = extract_subarray(all_panels, subarray.subarray_number) actual_y_coordinates += [panel.coordinate.y + subarray.origin.y for panel in panels] actual_x_coordinates += [panel.coordinate.x + subarray.origin.x for panel in panels] return max(actual_x_coordinates) + 1, max(actual_y_coordinates) + 1 @staticmethod def colors(panel): fill = Color.default_panel_background secondary = None border = Color.border text = Color.dark_text if panel.seismic_anchors > 0 and panel.wind_anchors > 0: fill = Color.seismic_background secondary = Color.wind_background elif panel.seismic_anchors > 0: fill = Color.seismic_background elif panel.wind_anchors > 0: fill = Color.wind_background else: border = Color.default_panel_background text = Color.light_text return fill, secondary, border, text