Coverage for bim2sim / utilities / visualize_spaces.py: 0%
61 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-18 09:34 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-18 09:34 +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.PRECISION, 1e-6)
72 settings.set(
73 "dimensionality",
74 ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) # 2
75 # settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
76 # settings.set(settings.INCLUDE_CURVES, True)
78 # Call init_display
79 # TODO this messes with the logger, but method like below doesn't work
80 # with open(os.devnull, 'w') as devnull:
81 display, start_display, add_menu, add_function_to_menu = init_display(
82 display_triedron=False, background_gradient_color1=3 * [255],
83 background_gradient_color2=3 * [255], size=(1920, 1080))
85 legend = {}
86 num = 1
87 for tz in thermal_zones:
88 name = tz.name
89 rgb_tuple = tuple((np.random.choice(range(256), size=3)))
90 rgb_tuple_norm = tuple([round(x / 256, 2) for x in rgb_tuple])
91 if name in list(legend.keys()):
92 name = name + ' ' + str(num)
93 num += 1
94 legend[name] = rgb_tuple
95 color = rgb_color(rgb_tuple_norm)
96 # handle AggregatedThermalZone
97 if hasattr(tz, "elements"):
98 zones = tz.elements
99 for zone in zones:
100 display.DisplayShape(zone.space_shape, update=True,
101 color=color, transparency=0.5)
102 # handle normal ThermalZone
103 else:
104 display.DisplayShape(tz.space_shape, update=True,
105 color=color, transparency=0.5)
106 sorted_legend = {}
107 for k in sorted(legend, key=len, reverse=False):
108 sorted_legend[k] = legend[k]
110 nr_zones = len(thermal_zones)
111 if not filename:
112 filename = f"zoning_visualization_{str(nr_zones)}_zones.png"
114 save_path = Path(path / filename)
115 display.View.Dump(str(save_path))
117 text_size = 25
118 zone_image = Image.open(save_path)
119 image_editable = ImageDraw.Draw(zone_image)
120 zone_image_size_y = zone_image.size[1]
122 rec_size = 20
123 space = 30
124 buffer = 10
126 legend_height = len(sorted_legend) * (text_size + space / 3) + buffer
127 x0 = 0
128 rec_to_text_spacing = 10
129 y0 = zone_image_size_y - legend_height
131 for zone_name, color in sorted_legend.items():
132 xy = [(x0 + rec_to_text_spacing, y0),
133 (x0 + + rec_to_text_spacing + rec_size, y0 + rec_size)]
134 image_editable.rectangle(xy, fill=color, outline=None, width=text_size)
135 image_editable.text(
136 (x0 + rec_to_text_spacing + rec_size + 10, y0), zone_name,
137 (0, 0, 0))
138 y0 += space
140 zone_image.save(save_path)
141 logger.info(f"Exported visualization of combined ThermalZone instances to "
142 f"{save_path}.")