Coverage for bim2sim/utilities/visualize_spaces.py: 0%
61 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
1"""
2Thermal Zone Visualization Module
3=================================
5This module provides functionality to visualize thermal zones and save the
6output as an image. It includes functions to convert RGB values to
7OCC-compatible colors and to generate visualizations of thermal zones grouped
8by various criteria.
10Example:
11 For an example, please see e4_visualize_zone_binding.py in PluginTEASER.
13Functions:
14 rgb_color(rgb): Returns an OCC viewer compatible color quantity based on
15 r,g,b values.
16 visualize_zones(zone_dict, folder_structure): Visualizes the thermal
17 zones and saves the picture as a .png.
19Notes:
20 Any additional information about the module, its purpose, and its usage
21 can be included here.
22"""
23import logging
24from pathlib import Path
25from typing import Union
27import numpy as np
28import ifcopenshell.geom
29from OCC.Display.SimpleGui import init_display
30from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB
31from PIL import Image, ImageDraw
33from bim2sim.elements.bps_elements import ThermalZone
35logger = logging.getLogger(__name__)
38def rgb_color(rgb) -> Quantity_Color:
39 """Returns a OCC viewer compatible color quantity based on r,g,b values.
41 Args:
42 rgb: must be a tuple with 3 values [0,1]. e.g. (0, 0.5, 0.7)
44 Returns:
45 Quantity_Color object which is compatible with with the OCC viewer.
46 """
47 return Quantity_Color(rgb[0], rgb[1], rgb[2], Quantity_TOC_RGB)
50def visualize_zones(
51 thermal_zones: list[ThermalZone],
52 path: Path,
53 filename: Union[str, None] = None):
54 """Visualizes the ThermalZone element entities and saves the picture as
55 a .png. Fetches the ThermalZone which are grouped before and creates an
56 abstract building image, where each grouped zone has its own color.
57 Afterwards, a legend is added with zone names and corresponding colors.
58 The file is exported as .png to the export folder.
60 Args:
61 thermal_zones: list of ThermalZone and AggregatedThermalZone instances
62 path: pathlib Path where image is exported to
63 filename: str of filename
65 Returns:
66 No return value, image is saved directly.
67 """
68 settings = ifcopenshell.geom.settings()
69 settings.set(settings.USE_PYTHON_OPENCASCADE, True)
70 settings.set(settings.USE_WORLD_COORDS, True)
71 settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
72 settings.set(settings.INCLUDE_CURVES, True)
74 # Call init_display
75 # TODO this messes with the logger, but method like below doesn't work
76 # with open(os.devnull, 'w') as devnull:
77 display, start_display, add_menu, add_function_to_menu = init_display(
78 display_triedron=False, background_gradient_color1=3 * [255],
79 background_gradient_color2=3 * [255], size=(1920, 1080))
81 legend = {}
82 num = 1
83 for tz in thermal_zones:
84 name = tz.name
85 rgb_tuple = tuple((np.random.choice(range(256), size=3)))
86 rgb_tuple_norm = tuple([round(x / 256, 2) for x in rgb_tuple])
87 if name in list(legend.keys()):
88 name = name + ' ' + str(num)
89 num += 1
90 legend[name] = rgb_tuple
91 color = rgb_color(rgb_tuple_norm)
92 # handle AggregatedThermalZone
93 if hasattr(tz, "elements"):
94 zones = tz.elements
95 for zone in zones:
96 display.DisplayShape(zone.space_shape, update=True,
97 color=color, transparency=0.5)
98 # handle normal ThermalZone
99 else:
100 display.DisplayShape(tz.space_shape, update=True,
101 color=color, transparency=0.5)
102 sorted_legend = {}
103 for k in sorted(legend, key=len, reverse=False):
104 sorted_legend[k] = legend[k]
106 nr_zones = len(thermal_zones)
107 if not filename:
108 filename = f"zoning_visualization_{str(nr_zones)}_zones.png"
110 save_path = Path(path / filename)
111 display.View.Dump(str(save_path))
113 text_size = 25
114 zone_image = Image.open(save_path)
115 image_editable = ImageDraw.Draw(zone_image)
116 zone_image_size_y = zone_image.size[1]
118 rec_size = 20
119 space = 30
120 buffer = 10
122 legend_height = len(sorted_legend) * (text_size + space / 3) + buffer
123 x0 = 0
124 rec_to_text_spacing = 10
125 y0 = zone_image_size_y - legend_height
127 for zone_name, color in sorted_legend.items():
128 xy = [(x0 + rec_to_text_spacing, y0),
129 (x0 + + rec_to_text_spacing + rec_size, y0 + rec_size)]
130 image_editable.rectangle(xy, fill=color, outline=None, width=text_size)
131 image_editable.text(
132 (x0 + rec_to_text_spacing + rec_size + 10, y0), zone_name,
133 (0, 0, 0))
134 y0 += space
136 zone_image.save(save_path)
137 logger.info(f"Exported visualization of combined ThermalZone instances to "
138 f"{save_path}.")