Coverage for bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/utils/utils_visualization.py: 0%

161 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 17:09 +0000

1import colorsys 

2from pathlib import Path 

3from typing import List 

4 

5import ifcopenshell 

6import ifcopenshell.geom 

7import pandas as pd 

8from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB 

9from OCC.Core.TopoDS import TopoDS_Shape 

10from OCC.Core.gp import gp_Pnt 

11from OCC.Display.SimpleGui import init_display 

12from PIL import Image, ImageFont, ImageDraw 

13 

14from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.utils import \ 

15 PostprocessingUtils 

16from bim2sim.utilities.pyocc_tools import PyOCCTools 

17 

18 

19class VisualizationUtils: 

20 

21 @staticmethod 

22 def _display_shape_of_space_boundaries(elements): 

23 """Display topoDS_shapes of space boundaries""" 

24 display, start_display, add_menu, add_function_to_menu = init_display() 

25 colors = ['blue', 'red', 'magenta', 'yellow', 'green', 'white', 'cyan'] 

26 col = 0 

27 for inst in elements: 

28 if elements[inst].ifc.is_a('IfcRelSpaceBoundary'): 

29 col += 1 

30 bound = elements[inst] 

31 if bound.bound_element is None: 

32 continue 

33 if not bound.bound_element.ifc.is_a("IfcWall"): 

34 pass 

35 try: 

36 display.DisplayShape(bound.bound_shape, color=colors[(col - 1) % len(colors)]) 

37 except: 

38 continue 

39 display.FitAll() 

40 start_display() 

41 

42 @staticmethod 

43 def _display_bound_normal_orientation(elements): 

44 display, start_display, add_menu, add_function_to_menu = init_display() 

45 col = 0 

46 for inst in elements: 

47 if not elements[inst].ifc.is_a('IfcSpace'): 

48 continue 

49 space = elements[inst] 

50 for bound in space.space_boundaries: 

51 face_towards_center = space.space_center.XYZ() - \ 

52 bound.bound_center 

53 face_towards_center.Normalize() 

54 dot = face_towards_center.Dot(bound.bound_normal) 

55 if dot > 0: 

56 display.DisplayShape(bound.bound_shape, color="red") 

57 else: 

58 display.DisplayShape(bound.bound_shape, color="green") 

59 display.FitAll() 

60 start_display() 

61 

62 @staticmethod 

63 def display_occ_shapes(shapes: List[TopoDS_Shape]): 

64 """Display topoDS_shapes of space boundaries""" 

65 display, start_display, add_menu, add_function_to_menu = init_display() 

66 for shape in shapes: 

67 try: 

68 display.DisplayShape(shape) 

69 except: 

70 continue 

71 display.FitAll() 

72 start_display() 

73 

74 @staticmethod 

75 def rgb_color(rgb) -> Quantity_Color: 

76 """Returns a OCC viewer compatible color quantity based on r,g, 

77 b values. 

78 Args: 

79 rgb: must be a tuple with 3 values [0,1]. e.g. (0, 0.5, 0.7) 

80 Returns: 

81 Quantity_Color object which is compatible with with the OCC viewer. 

82 """ 

83 return Quantity_Color(rgb[0], rgb[1], rgb[2], Quantity_TOC_RGB) 

84 

85 @staticmethod 

86 def rgb(minimum, maximum, value): 

87 minimum, maximum = float(minimum), float(maximum) 

88 ratio = 2 * (value - minimum) / (maximum - minimum) 

89 r = int(max(0, 255 * (1 - ratio))) / 255 

90 b = int(max(0, abs(255 * (ratio - 1)))) / 255 

91 g = 1 

92 return (r, g, b) 

93 

94 @staticmethod 

95 def interpolate_to_rgb(minimum, maximum, value, color_min=50, 

96 color_max=340): 

97 s = 1 

98 l = 0.5 

99 h = (color_min + (color_max - color_min) / (maximum - minimum) * ( 

100 value - minimum)) / 360 

101 r, g, b = colorsys.hls_to_rgb(h, l, s) 

102 

103 return r, g, b 

104 

105 @staticmethod 

106 def get_column_from_ep_results(csv_name: str, column_key: str) -> \ 

107 pd.DataFrame: 

108 """ 

109 This function extracts a column per energyplus column_key from a 

110 dataframe. It removes the full column_key from the column name 

111 afterwards, such that only the zone identifier (guid) of the column 

112 name remains as column key. 

113 

114 Args: 

115 csv_name: csv name including file name as string 

116 column_key: full variable key that is removed afterwards from 

117 column name. Must not include the zone guid 

118 Returns: 

119 df: pandas dataframe 

120 """ 

121 res_df = pd.read_csv(csv_name) 

122 # extract data that includes the column_key 

123 df = PostprocessingUtils._extract_cols_from_df( 

124 res_df, column_key) 

125 # rename column name to zone guid (remove column_key from column 

126 # name and keep guid only) 

127 df.columns = [ 

128 col.replace(column_key, '') for col in 

129 df.columns] 

130 return df 

131 

132 @staticmethod 

133 def add_legend(save_path, paths, minimum, maximum, unit, 

134 text_size=25, text_color=(0, 0, 0,), legend_height=800, 

135 title=None): 

136 # add legend to 3D image 

137 im = Image.open(save_path) 

138 draw = ImageDraw.Draw(im) 

139 #font_path = folder_structure.assets / 'fonts' / 'arial.ttf' 

140 #title_font = ImageFont.truetype(str(font_path), text_size) 

141 blind_counter = 0 

142 im_width, im_heigth = im.size 

143 line_width = int(legend_height / maximum) 

144 xmin = 20 

145 xmax = 100 

146 xbuffer = 20 

147 ybuffer = 30 

148 draw.text(((xmax+xmin)/2, 200 - ybuffer), unit, text_color, 

149 #font=title_font, 

150 anchor='ms') 

151 if title: 

152 draw.text((im_width/2, text_size/2 + ybuffer), title, text_color, 

153 #font=title_font, 

154 anchor='ms') 

155 mid_count = 0 

156 for i in range(0, int(maximum)): 

157 color = VisualizationUtils.interpolate_to_rgb(minimum, maximum, i) 

158 color = tuple(tuple([int(color[0] * 255), int(color[1] * 255), 

159 int(color[2] * 255)])) 

160 draw.line([(xmin, 200 + i * line_width), 

161 (xmax, 200 + i * line_width)], color, width=line_width) 

162 if i == 0: 

163 draw.text((xmax + xbuffer, 200 + i * line_width), str(i), 

164 text_color, #font=title_font, 

165 anchor='ms') 

166 elif i % (int(maximum / 2)) == 0 and mid_count == 0: 

167 mid_count += 1 

168 blind_counter += 1 

169 draw.text((xmax + xbuffer, 200 + i * line_width), str(i), 

170 text_color, #font=title_font, 

171 anchor='ms') 

172 draw.text((xmax + xbuffer, 200 + int(maximum) * line_width), 

173 str(int(maximum)), text_color, #font=title_font, 

174 anchor='ms') 

175 im.save(str(save_path).strip('.png') + '_legende.png') 

176 

177 @staticmethod 

178 def visualize_zones(zone_dict, export_path, paths): 

179 """Visualizes the thermalzones and saves the picture as a .png. 

180 Fetches the thermalzones which are grouped before and creates an 

181 abstract 

182 building image, where each grouped zone has its own color. Afterwards a 

183 legend is added with zone names and corresponding colors. The file is 

184 exported as .png to the export folder. 

185 Args: 

186 zone_dict: dict that has a grouping string as key and list of 

187 zones as 

188 values. 

189 folder_structure: instance of Folderstructure which is assigend 

190 to the 

191 projects attribute self.paths. 

192 Returns: 

193 No return value, image is saved directly. 

194 """ 

195 

196 # todo multi storage floor plan where all floors are placed 

197 # next to each other 

198 csv_name = export_path / 'eplusout.csv' 

199 full_key_total_heat_rate = ' IDEAL LOADS AIR SYSTEM:Zone Ideal Loads ' \ 

200 'Zone Total Heating Rate [W](Hourly)' 

201 zone_total_heating_rate = \ 

202 VisualizationUtils.get_column_from_ep_results( 

203 csv_name=csv_name, column_key=full_key_total_heat_rate) 

204 # get maximum total heating rate per zone 

205 max_zone_total_heat_rate = zone_total_heating_rate.max() 

206 # fill new dict with key: guid and value :zone net area 

207 area_dict = {} 

208 for i, (guid, zone) in enumerate(zone_dict.items()): 

209 area_dict[guid.upper()] = zone.net_area.m 

210 # add zone net heat area to maximum heat area dataframe 

211 max_heat_rate_df = pd.DataFrame(max_zone_total_heat_rate, 

212 columns=['max_zone_total_heat_rate']) 

213 max_heat_rate_df['net_area'] = pd.DataFrame.from_dict(area_dict, 

214 orient='index') 

215 # compute maximum total heat rate per zone area (normalization) 

216 max_heat_rate_df['max_per_area'] = \ 

217 max_heat_rate_df['max_zone_total_heat_rate'] / max_heat_rate_df[ 

218 'net_area'] 

219 

220 # add colored 3D plot of space geometry 

221 minimum = 0 

222 maximum = max_heat_rate_df['max_per_area'].max() 

223 

224 settings = ifcopenshell.geom.settings() 

225 settings.set(settings.USE_PYTHON_OPENCASCADE, True) 

226 settings.set(settings.USE_WORLD_COORDS, True) 

227 settings.set(settings.EXCLUDE_SOLIDS_AND_SURFACES, False) 

228 settings.set(settings.INCLUDE_CURVES, True) 

229 

230 display, start_display, add_menu, add_function_to_menu = init_display( 

231 display_triedron=False, background_gradient_color1=3 * [255], 

232 background_gradient_color2=3 * [255], size=(1920, 1080)) 

233 

234 for i, (guid, zone) in enumerate(zone_dict.items()): 

235 current_value = max_heat_rate_df['max_per_area'].loc[guid.upper()] 

236 color = VisualizationUtils.rgb_color( 

237 VisualizationUtils.interpolate_to_rgb(minimum, maximum, 

238 current_value)) 

239 display.DisplayShape(zone.space_shape, update=True, color=color, 

240 transparency=0.5) 

241 nr_zones = len(zone_dict) 

242 filename = 'zonemodel_' + str(nr_zones) + '.png' 

243 

244 save_path = Path(export_path / filename) 

245 display.View.Dump(str(save_path)) 

246 

247 # add floorplan 

248 floorplan_dict = {} 

249 

250 for i, (guid, zone) in enumerate(zone_dict.items()): 

251 floorplan_dict[guid.upper()] = zone.footprint_shape 

252 

253 storey_list = list(set([zone.storey for i, (guid, zone) in 

254 enumerate(zone_dict.items())])) 

255 

256 for storey in storey_list: 

257 display, start_display, add_menu, add_function_to_menu = \ 

258 init_display(display_triedron=False, 

259 background_gradient_color1=3 * [255], 

260 background_gradient_color2=3 * [255], 

261 size=(1920, 1080)) 

262 for zone in storey.thermal_zones: 

263 guid = zone.guid 

264 shape = floorplan_dict.get(guid.upper()) 

265 current_value = max_heat_rate_df['max_per_area'].loc[ 

266 guid.upper()] 

267 color = VisualizationUtils.rgb_color( 

268 VisualizationUtils.interpolate_to_rgb(minimum, maximum, 

269 current_value)) 

270 display.DisplayShape(shape, update=True, color=color, 

271 transparency=0) 

272 center = PyOCCTools.get_center_of_face(shape) 

273 if not center: 

274 center = PyOCCTools.get_center_of_shape(shape) 

275 display.DisplayMessage( 

276 gp_Pnt(center.X(), center.Y(), center.Z() + 1.), 

277 str(round(current_value, 2))+' W/m2', 

278 message_color=(0, 0, 0)) 

279 display.View_Top() 

280 display.FitAll() 

281 this_floorplan_path = str(str(save_path).strip('.png') + storey.name + 

282 '_floorplan.png') 

283 display.View.Dump(this_floorplan_path) 

284 VisualizationUtils.add_legend( 

285 this_floorplan_path, paths, minimum, maximum, 

286 unit='W/m²' 

287 ) 

288 #todo: rescale underlying image so that the floorplan does not 

289 # overlap with legend. 

290 

291 VisualizationUtils.add_legend( 

292 save_path, paths, minimum, maximum, unit='W/m²', 

293 title='Maximum total heating rate per area' 

294 )