honeybee-radiance 1.66.190__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of honeybee-radiance might be problematic. Click here for more details.

Files changed (152) hide show
  1. honeybee_radiance/__init__.py +11 -0
  2. honeybee_radiance/__main__.py +4 -0
  3. honeybee_radiance/_extend_honeybee.py +93 -0
  4. honeybee_radiance/cli/__init__.py +88 -0
  5. honeybee_radiance/cli/dc.py +400 -0
  6. honeybee_radiance/cli/edit.py +529 -0
  7. honeybee_radiance/cli/glare.py +118 -0
  8. honeybee_radiance/cli/grid.py +859 -0
  9. honeybee_radiance/cli/lib.py +458 -0
  10. honeybee_radiance/cli/modifier.py +133 -0
  11. honeybee_radiance/cli/mtx.py +226 -0
  12. honeybee_radiance/cli/multiphase.py +1034 -0
  13. honeybee_radiance/cli/octree.py +640 -0
  14. honeybee_radiance/cli/postprocess.py +1186 -0
  15. honeybee_radiance/cli/raytrace.py +219 -0
  16. honeybee_radiance/cli/rpict.py +125 -0
  17. honeybee_radiance/cli/schedule.py +56 -0
  18. honeybee_radiance/cli/setconfig.py +63 -0
  19. honeybee_radiance/cli/sky.py +545 -0
  20. honeybee_radiance/cli/study.py +66 -0
  21. honeybee_radiance/cli/sunpath.py +331 -0
  22. honeybee_radiance/cli/threephase.py +255 -0
  23. honeybee_radiance/cli/translate.py +400 -0
  24. honeybee_radiance/cli/util.py +121 -0
  25. honeybee_radiance/cli/view.py +261 -0
  26. honeybee_radiance/cli/viewfactor.py +347 -0
  27. honeybee_radiance/config.json +6 -0
  28. honeybee_radiance/config.py +427 -0
  29. honeybee_radiance/dictutil.py +50 -0
  30. honeybee_radiance/dynamic/__init__.py +5 -0
  31. honeybee_radiance/dynamic/group.py +479 -0
  32. honeybee_radiance/dynamic/multiphase.py +557 -0
  33. honeybee_radiance/dynamic/state.py +718 -0
  34. honeybee_radiance/dynamic/stategeo.py +352 -0
  35. honeybee_radiance/geometry/__init__.py +13 -0
  36. honeybee_radiance/geometry/bubble.py +42 -0
  37. honeybee_radiance/geometry/cone.py +215 -0
  38. honeybee_radiance/geometry/cup.py +54 -0
  39. honeybee_radiance/geometry/cylinder.py +197 -0
  40. honeybee_radiance/geometry/geometrybase.py +37 -0
  41. honeybee_radiance/geometry/instance.py +40 -0
  42. honeybee_radiance/geometry/mesh.py +38 -0
  43. honeybee_radiance/geometry/polygon.py +174 -0
  44. honeybee_radiance/geometry/ring.py +214 -0
  45. honeybee_radiance/geometry/source.py +182 -0
  46. honeybee_radiance/geometry/sphere.py +178 -0
  47. honeybee_radiance/geometry/tube.py +46 -0
  48. honeybee_radiance/lib/__init__.py +1 -0
  49. honeybee_radiance/lib/_loadmodifiers.py +72 -0
  50. honeybee_radiance/lib/_loadmodifiersets.py +69 -0
  51. honeybee_radiance/lib/modifiers.py +58 -0
  52. honeybee_radiance/lib/modifiersets.py +63 -0
  53. honeybee_radiance/lightpath.py +204 -0
  54. honeybee_radiance/lightsource/__init__.py +1 -0
  55. honeybee_radiance/lightsource/_gendaylit.py +479 -0
  56. honeybee_radiance/lightsource/dictutil.py +49 -0
  57. honeybee_radiance/lightsource/ground.py +160 -0
  58. honeybee_radiance/lightsource/sky/__init__.py +7 -0
  59. honeybee_radiance/lightsource/sky/_skybase.py +177 -0
  60. honeybee_radiance/lightsource/sky/certainirradiance.py +232 -0
  61. honeybee_radiance/lightsource/sky/cie.py +378 -0
  62. honeybee_radiance/lightsource/sky/climatebased.py +501 -0
  63. honeybee_radiance/lightsource/sky/hemisphere.py +160 -0
  64. honeybee_radiance/lightsource/sky/skydome.py +113 -0
  65. honeybee_radiance/lightsource/sky/skymatrix.py +163 -0
  66. honeybee_radiance/lightsource/sky/strutil.py +34 -0
  67. honeybee_radiance/lightsource/sky/sunmatrix.py +212 -0
  68. honeybee_radiance/lightsource/sunpath.py +247 -0
  69. honeybee_radiance/modifier/__init__.py +3 -0
  70. honeybee_radiance/modifier/material/__init__.py +30 -0
  71. honeybee_radiance/modifier/material/absdf.py +477 -0
  72. honeybee_radiance/modifier/material/antimatter.py +54 -0
  73. honeybee_radiance/modifier/material/ashik2.py +51 -0
  74. honeybee_radiance/modifier/material/brtdfunc.py +81 -0
  75. honeybee_radiance/modifier/material/bsdf.py +292 -0
  76. honeybee_radiance/modifier/material/dielectric.py +53 -0
  77. honeybee_radiance/modifier/material/glass.py +431 -0
  78. honeybee_radiance/modifier/material/glow.py +246 -0
  79. honeybee_radiance/modifier/material/illum.py +51 -0
  80. honeybee_radiance/modifier/material/interface.py +49 -0
  81. honeybee_radiance/modifier/material/light.py +206 -0
  82. honeybee_radiance/modifier/material/materialbase.py +36 -0
  83. honeybee_radiance/modifier/material/metal.py +167 -0
  84. honeybee_radiance/modifier/material/metal2.py +41 -0
  85. honeybee_radiance/modifier/material/metdata.py +41 -0
  86. honeybee_radiance/modifier/material/metfunc.py +41 -0
  87. honeybee_radiance/modifier/material/mirror.py +340 -0
  88. honeybee_radiance/modifier/material/mist.py +86 -0
  89. honeybee_radiance/modifier/material/plasdata.py +58 -0
  90. honeybee_radiance/modifier/material/plasfunc.py +59 -0
  91. honeybee_radiance/modifier/material/plastic.py +354 -0
  92. honeybee_radiance/modifier/material/plastic2.py +58 -0
  93. honeybee_radiance/modifier/material/prism1.py +57 -0
  94. honeybee_radiance/modifier/material/prism2.py +48 -0
  95. honeybee_radiance/modifier/material/spotlight.py +50 -0
  96. honeybee_radiance/modifier/material/trans.py +518 -0
  97. honeybee_radiance/modifier/material/trans2.py +49 -0
  98. honeybee_radiance/modifier/material/transdata.py +50 -0
  99. honeybee_radiance/modifier/material/transfunc.py +53 -0
  100. honeybee_radiance/modifier/mixture/__init__.py +6 -0
  101. honeybee_radiance/modifier/mixture/mixdata.py +49 -0
  102. honeybee_radiance/modifier/mixture/mixfunc.py +54 -0
  103. honeybee_radiance/modifier/mixture/mixpict.py +52 -0
  104. honeybee_radiance/modifier/mixture/mixtext.py +66 -0
  105. honeybee_radiance/modifier/mixture/mixturebase.py +28 -0
  106. honeybee_radiance/modifier/modifierbase.py +40 -0
  107. honeybee_radiance/modifier/pattern/__init__.py +9 -0
  108. honeybee_radiance/modifier/pattern/brightdata.py +49 -0
  109. honeybee_radiance/modifier/pattern/brightfunc.py +47 -0
  110. honeybee_radiance/modifier/pattern/brighttext.py +81 -0
  111. honeybee_radiance/modifier/pattern/colordata.py +56 -0
  112. honeybee_radiance/modifier/pattern/colorfunc.py +47 -0
  113. honeybee_radiance/modifier/pattern/colorpict.py +54 -0
  114. honeybee_radiance/modifier/pattern/colortext.py +73 -0
  115. honeybee_radiance/modifier/pattern/patternbase.py +34 -0
  116. honeybee_radiance/modifier/texture/__init__.py +4 -0
  117. honeybee_radiance/modifier/texture/texdata.py +29 -0
  118. honeybee_radiance/modifier/texture/texfunc.py +26 -0
  119. honeybee_radiance/modifier/texture/texturebase.py +27 -0
  120. honeybee_radiance/modifierset.py +1091 -0
  121. honeybee_radiance/mutil.py +60 -0
  122. honeybee_radiance/postprocess/__init__.py +1 -0
  123. honeybee_radiance/postprocess/annual.py +108 -0
  124. honeybee_radiance/postprocess/annualdaylight.py +425 -0
  125. honeybee_radiance/postprocess/annualglare.py +201 -0
  126. honeybee_radiance/postprocess/annualirradiance.py +187 -0
  127. honeybee_radiance/postprocess/electriclight.py +119 -0
  128. honeybee_radiance/postprocess/en17037.py +261 -0
  129. honeybee_radiance/postprocess/leed.py +304 -0
  130. honeybee_radiance/postprocess/solartracking.py +90 -0
  131. honeybee_radiance/primitive.py +554 -0
  132. honeybee_radiance/properties/__init__.py +1 -0
  133. honeybee_radiance/properties/_base.py +390 -0
  134. honeybee_radiance/properties/aperture.py +197 -0
  135. honeybee_radiance/properties/door.py +198 -0
  136. honeybee_radiance/properties/face.py +123 -0
  137. honeybee_radiance/properties/model.py +1291 -0
  138. honeybee_radiance/properties/room.py +490 -0
  139. honeybee_radiance/properties/shade.py +186 -0
  140. honeybee_radiance/properties/shademesh.py +116 -0
  141. honeybee_radiance/putil.py +44 -0
  142. honeybee_radiance/reader.py +214 -0
  143. honeybee_radiance/sensor.py +166 -0
  144. honeybee_radiance/sensorgrid.py +1008 -0
  145. honeybee_radiance/view.py +1101 -0
  146. honeybee_radiance/writer.py +951 -0
  147. honeybee_radiance-1.66.190.dist-info/METADATA +89 -0
  148. honeybee_radiance-1.66.190.dist-info/RECORD +152 -0
  149. honeybee_radiance-1.66.190.dist-info/WHEEL +5 -0
  150. honeybee_radiance-1.66.190.dist-info/entry_points.txt +2 -0
  151. honeybee_radiance-1.66.190.dist-info/licenses/LICENSE +661 -0
  152. honeybee_radiance-1.66.190.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1291 @@
1
+ # coding=utf-8
2
+ """Model Radiance Properties."""
3
+ from honeybee.extensionutil import model_extension_dicts
4
+ from honeybee.checkdup import check_duplicate_identifiers
5
+ from honeybee.boundarycondition import Surface
6
+ from honeybee.typing import invalid_dict_error, clean_rad_string, clean_and_id_rad_string
7
+ from honeybee.model import Model
8
+
9
+ from ..sensorgrid import SensorGrid
10
+ from ..view import View
11
+ from ..dynamic.group import DynamicShadeGroup, DynamicSubFaceGroup
12
+ from ..modifierset import ModifierSet
13
+ from ..mutil import dict_to_modifier # imports all modifiers classes
14
+ from ..modifier.material import aBSDF, BSDF
15
+ from ..lib.modifiers import black, generic_context
16
+ from ..lib.modifiersets import generic_modifier_set_visible
17
+
18
+ try:
19
+ from itertools import izip as zip # python 2
20
+ except ImportError:
21
+ pass # python 3
22
+
23
+
24
+ class ModelRadianceProperties(object):
25
+ """Radiance Properties for Honeybee Model.
26
+
27
+ Args:
28
+ host: A honeybee_core Model object that hosts these properties.
29
+
30
+ Properties:
31
+ * host
32
+ * sensor_grids
33
+ * views
34
+ * modifiers
35
+ * blk_modifiers
36
+ * room_modifiers
37
+ * face_modifiers
38
+ * shade_modifiers
39
+ * bsdf_modifiers
40
+ * modifier_sets
41
+ * global_modifier_set
42
+ * dynamic_shade_groups
43
+ * dynamic_subface_groups
44
+ * shade_group_identifiers
45
+ * subface_group_identifiers
46
+ * has_sensor_grids
47
+ * has_views
48
+ """
49
+ ERROR_MAP = {
50
+ '010001': 'check_duplicate_modifier_identifiers',
51
+ '010002': 'check_duplicate_modifier_set_identifiers',
52
+ '010003': 'check_duplicate_sensor_grid_identifiers',
53
+ '010004': 'check_duplicate_view_identifiers',
54
+ '010005': 'check_sensor_grid_rooms_in_model',
55
+ '010006': 'check_view_rooms_in_model'
56
+ }
57
+
58
+ def __init__(self, host, sensor_grids=None, views=None):
59
+ """Initialize Model radiance properties."""
60
+ self._host = host
61
+ self.sensor_grids = sensor_grids
62
+ self.views = views
63
+
64
+ @property
65
+ def host(self):
66
+ """Get the Model object hosting these properties."""
67
+ return self._host
68
+
69
+ @property
70
+ def sensor_grids(self):
71
+ """Get or set an array of SensorGrids that are associated with the model."""
72
+ return tuple(self._sensor_grids)
73
+
74
+ @sensor_grids.setter
75
+ def sensor_grids(self, value):
76
+ if value:
77
+ try:
78
+ self._sensor_grids = list(value)
79
+ for obj in self._sensor_grids:
80
+ assert isinstance(obj, SensorGrid), 'Expected SensorGrid for Model' \
81
+ ' sensor_grids. Got {}.'.format(type(value))
82
+ except (ValueError, TypeError):
83
+ raise TypeError(
84
+ 'Model sensor_grids must be an array. Got {}.'.format(type(value)))
85
+ else:
86
+ self._sensor_grids = []
87
+
88
+ @property
89
+ def views(self):
90
+ """Get or set an array of Views that are associated with the model."""
91
+ return tuple(self._views)
92
+
93
+ @views.setter
94
+ def views(self, value):
95
+ if value:
96
+ try:
97
+ self._views = list(value)
98
+ for obj in self._views:
99
+ assert isinstance(obj, View), 'Expected View for Model' \
100
+ ' views. Got {}.'.format(type(value))
101
+ except (ValueError, TypeError):
102
+ raise TypeError(
103
+ 'Model views must be an array. Got {}.'.format(type(value)))
104
+ else:
105
+ self._views = []
106
+
107
+ @property
108
+ def modifiers(self):
109
+ """A list of all unique modifiers in the model.
110
+
111
+ This includes modifiers across all Faces, Apertures, Doors, Shades, Room
112
+ ModifierSets, and modifiers for any dynamic states assigned to these objects.
113
+
114
+ However, it excludes modifiers in the default modifier set. It also excludes
115
+ blk_modifiers and the modifier_direct of any states, which can be obtained
116
+ separately from the blk_modifiers property.
117
+ """
118
+ all_mods = self.room_modifiers + self.face_modifiers + self.shade_modifiers
119
+ return list(set(all_mods))
120
+
121
+ @property
122
+ def blk_modifiers(self):
123
+ """A list of all unique modifier_blk in the model.
124
+
125
+ This includes modifier_blk across all Faces, Apertures, Doors, and Shades.
126
+ It also includes modifier_direct for any dynamic states assigned to
127
+ these objects.
128
+ """
129
+ modifiers = [black]
130
+ for face in self.host.faces: # check all orphaned Face modifiers
131
+ self._check_and_add_face_modifier_blk(face, modifiers)
132
+ for ap in self.host.orphaned_apertures: # check all Aperture modifiers
133
+ self._check_and_add_dynamic_obj_modifier_blk(ap, modifiers)
134
+ for dr in self.host.orphaned_doors: # check all Door modifiers
135
+ self._check_and_add_dynamic_obj_modifier_blk(dr, modifiers)
136
+ for shade in self.host.shades:
137
+ self._check_and_add_dynamic_obj_modifier_blk(shade, modifiers)
138
+ for sm in self.host.shade_meshes: # check all ShadeMesh modifiers
139
+ self._check_and_add_obj_modifier_blk(sm, modifiers)
140
+ return list(set(modifiers))
141
+
142
+ @property
143
+ def room_modifiers(self):
144
+ """A list of all unique modifiers assigned to Room ModifierSets."""
145
+ room_mods = []
146
+ for cnstr_set in self.modifier_sets:
147
+ room_mods.extend(cnstr_set.modified_modifiers_unique)
148
+ return list(set(room_mods))
149
+
150
+ @property
151
+ def face_modifiers(self):
152
+ """A list of all unique modifiers assigned to Faces, Apertures and Doors.
153
+
154
+ This includes both objects that are a part of Rooms as well as orphaned
155
+ objects. It does not include the modifiers of any shades assigned to these
156
+ objects. Nor does it include any blk modifiers.
157
+ """
158
+ modifiers = []
159
+ for face in self.host.faces: # check all orphaned Face modifiers
160
+ self._check_and_add_face_modifier(face, modifiers)
161
+ for ap in self.host.orphaned_apertures: # check all Aperture modifiers
162
+ self._check_and_add_dynamic_obj_modifier(ap, modifiers)
163
+ for dr in self.host.orphaned_doors: # check all Door modifiers
164
+ self._check_and_add_dynamic_obj_modifier(dr, modifiers)
165
+ return list(set(modifiers))
166
+
167
+ @property
168
+ def shade_modifiers(self):
169
+ """A list of all unique modifiers assigned to Shade and ShadeMeshes in the model.
170
+ """
171
+ modifiers = []
172
+ for room in self.host.rooms:
173
+ self._check_and_add_room_modifier_shade(room, modifiers)
174
+ for face in self.host.orphaned_faces:
175
+ self._check_and_add_face_modifier_shade(face, modifiers)
176
+ for ap in self.host.orphaned_apertures:
177
+ self._check_and_add_obj_modifier_shade(ap, modifiers)
178
+ for dr in self.host.orphaned_doors:
179
+ self._check_and_add_obj_modifier_shade(dr, modifiers)
180
+ for shade in self.host.orphaned_shades:
181
+ self._check_and_add_orphaned_shade_modifier(shade, modifiers)
182
+ for shade_mesh in self.host.shade_meshes:
183
+ self._check_and_add_shade_mesh_modifier(shade_mesh, modifiers)
184
+ return list(set(modifiers))
185
+
186
+ @property
187
+ def bsdf_modifiers(self):
188
+ """A list of all unique BSDF modifiers in the model.
189
+
190
+ This includes any BSDF modifiers in both the Model.modifiers and the
191
+ Model.blk_modifiers.
192
+ """
193
+ all_mods = self.modifiers + self.blk_modifiers
194
+ return list(set(mod for mod in all_mods if isinstance(mod, (aBSDF, BSDF))))
195
+
196
+ @property
197
+ def modifier_sets(self):
198
+ """A list of all unique Room-Assigned ModifierSets in the Model."""
199
+ modifier_sets = []
200
+ for room in self.host.rooms:
201
+ if room.properties.radiance._modifier_set is not None:
202
+ if not self._instance_in_array(room.properties.radiance._modifier_set,
203
+ modifier_sets):
204
+ modifier_sets.append(room.properties.radiance._modifier_set)
205
+ return list(set(modifier_sets)) # catch equivalent modifier sets
206
+
207
+ @property
208
+ def global_modifier_set(self):
209
+ """The global radiance modifier set.
210
+
211
+ This is what is used whenever no modifier has been assigned to a given
212
+ Face/Aperture/Door/Shade and there is no modifier_set assigned to the
213
+ parent Room.
214
+ """
215
+ return generic_modifier_set_visible
216
+
217
+ @property
218
+ def dynamic_shade_groups(self):
219
+ """Get a list of DynamicShadeGroups in the model.
220
+
221
+ These can be used to write dynamic shades into radiance files.
222
+ """
223
+ # gather all of the shades with a common identifier into groups
224
+ group_dict = {}
225
+ for shade in self.host.shades:
226
+ if shade.properties.radiance._dynamic_group_identifier:
227
+ group_id = shade.properties.radiance._dynamic_group_identifier
228
+ try:
229
+ group_dict[group_id].append(shade)
230
+ except KeyError:
231
+ group_dict[group_id] = [shade]
232
+ # return DynamicShadeGroup objects
233
+ return [DynamicShadeGroup(ident, shades)for ident, shades in group_dict.items()]
234
+
235
+ @property
236
+ def dynamic_subface_groups(self):
237
+ """Get a list of DynamicSubFaceGroups in the model.
238
+
239
+ These can be used to write dynamic Apertures and Doors into radiance files.
240
+ """
241
+ # gather all of the subfaces with a common identifier into groups
242
+ group_dict = {}
243
+ for subface in self.host.apertures + self.host.doors:
244
+ if subface.properties.radiance._dynamic_group_identifier:
245
+ group_id = subface.properties.radiance._dynamic_group_identifier
246
+ try:
247
+ group_dict[group_id].append(subface)
248
+ except KeyError:
249
+ group_dict[group_id] = [subface]
250
+ # return DynamicSubFaceGroup objects
251
+ return [DynamicSubFaceGroup(ident, subf)for ident, subf in group_dict.items()]
252
+
253
+ @property
254
+ def shade_group_identifiers(self):
255
+ """Get a list of identifiers for all the DynamicShadeGroups in the model."""
256
+ group_ids = set()
257
+ for shade in self.host.shades:
258
+ if shade.properties.radiance._dynamic_group_identifier:
259
+ group_ids.add(shade.properties.radiance._dynamic_group_identifier)
260
+ return list(group_ids)
261
+
262
+ @property
263
+ def subface_group_identifiers(self):
264
+ """Get a list of identifiers for all the DynamicSubFaceGroups in the model."""
265
+ group_ids = set()
266
+ for subface in self.host.apertures + self.host.doors:
267
+ if subface.properties.radiance._dynamic_group_identifier:
268
+ group_ids.add(subface.properties.radiance._dynamic_group_identifier)
269
+ return list(group_ids)
270
+
271
+ @property
272
+ def has_sensor_grids(self):
273
+ """Get a boolean for whether there are sensor grids assigned to the model."""
274
+ return len(self._sensor_grids) != 0
275
+
276
+ @property
277
+ def has_views(self):
278
+ """Get a boolean for whether there are views assigned to the model."""
279
+ return len(self._views) != 0
280
+
281
+ def remove_sensor_grids(self):
282
+ """Remove all sensor grids from the model."""
283
+ self._sensor_grids = []
284
+
285
+ def remove_views(self):
286
+ """Remove all views from the model."""
287
+ self._views = []
288
+
289
+ def add_sensor_grid(self, sensor_grid):
290
+ """Add a SensorGrid to this model.
291
+
292
+ Args:
293
+ sensor_grid: A SensorGrid to add to this model.
294
+ """
295
+ assert isinstance(sensor_grid, SensorGrid), \
296
+ 'Expected SensorGrid. Got {}.'.format(type(sensor_grid))
297
+ self._sensor_grids.append(sensor_grid)
298
+
299
+ def add_view(self, view):
300
+ """Add a View to this model.
301
+
302
+ Args:
303
+ view: A View to add to this model.
304
+ """
305
+ assert isinstance(view, View), 'Expected View. Got {}.'.format(type(view))
306
+ self._views.append(view)
307
+
308
+ def add_sensor_grids(self, sensor_grids):
309
+ """Add a list of SensorGrids to this model."""
310
+ for grid in sensor_grids:
311
+ self.add_sensor_grid(grid)
312
+
313
+ def add_views(self, views):
314
+ """Add a list of Views to this model."""
315
+ for view in views:
316
+ self.add_view(view)
317
+
318
+ def faces_by_blk(self):
319
+ """Get all Faces in the model separated by their blk property.
320
+
321
+ This method will also ensure that any faces with Surface boundary condition
322
+ are not duplicated in the result but are rather offset from the base face
323
+ to ensure modifiers on opposite sides of interior Faces are accounted for.
324
+ Furthermore, this method also ensures that any static interior sub-faces
325
+ (with Surface
326
+ boundary condition) only have one of such objects in the output lists.
327
+
328
+ Returns:
329
+ A tuple with 2 lists:
330
+
331
+ - faces: A list of all faces without a unique modifier_blk.
332
+
333
+ - faces_blk: A list of all opaque faces that have a unique modifier_blk.
334
+ """
335
+ faces, faces_blk = [], []
336
+ interior_faces, offset = set(), self.host.tolerance * -2
337
+ for face in self.host.faces:
338
+ if isinstance(face.boundary_condition, Surface):
339
+ if face.identifier in interior_faces:
340
+ face = face.duplicate()
341
+ face.move(face.normal * offset)
342
+ else:
343
+ interior_faces.add(face.boundary_condition.boundary_condition_object)
344
+ for subf in face.apertures + face.doors:
345
+ if subf.properties.radiance.dynamic_group_identifier is None:
346
+ if subf.properties.radiance._modifier_blk:
347
+ faces_blk.append(subf)
348
+ else:
349
+ faces.append(subf)
350
+ if face.properties.radiance._modifier_blk:
351
+ faces_blk.append(face)
352
+ else:
353
+ faces.append(face)
354
+ return faces, faces_blk
355
+
356
+ def subfaces_by_blk(self):
357
+ """Get model exterior sub-faces (Apertures, Doors) grouped by their blk property.
358
+
359
+ Dynamic sub-faces will be excluded from the output lists.
360
+
361
+ Returns:
362
+ A tuple with 2 lists:
363
+
364
+ - subfaces: A list of all sub-faces without a unique modifier_blk
365
+ (just using the default black).
366
+
367
+ - subfaces_blk: A list of all sub-faces that have a unique modifier_blk.
368
+ """
369
+ subfaces, subfaces_blk = [], []
370
+ for subf in self.host.apertures + self.host.doors:
371
+ if subf.properties.radiance.dynamic_group_identifier:
372
+ continue # sub-face will be accounted for in the dynamic objects
373
+ if isinstance(subf.boundary_condition, Surface):
374
+ continue # static interior apertures are part of the scene
375
+ if subf.properties.radiance._modifier_blk:
376
+ subfaces_blk.append(subf)
377
+ else:
378
+ subfaces.append(subf)
379
+ return subfaces, subfaces_blk
380
+
381
+ def shades_by_blk(self):
382
+ """Get all Shades in the model separated by their blk property.
383
+
384
+ Dynamic shades will be excluded from the output lists.
385
+
386
+ Returns:
387
+ A tuple with 2 lists:
388
+
389
+ - shades: A list of all opaque shades without a unique modifier_blk
390
+ (just using the default black or transparent modifier).
391
+
392
+ - shades_blk: A list of all opaque shades that have a unique modifier_blk.
393
+ """
394
+ shades, shades_blk = [], []
395
+ for shade in self.host.shades:
396
+ if shade.properties.radiance.dynamic_group_identifier:
397
+ continue # shade will be accounted for in the dynamic objects
398
+ if shade.properties.radiance._modifier_blk:
399
+ shades_blk.append(shade)
400
+ else:
401
+ shades.append(shade)
402
+ return shades, shades_blk
403
+
404
+ def shade_meshes_by_blk(self):
405
+ """Get all ShadeMeshes in the model separated by their blk property.
406
+
407
+ Returns:
408
+ A tuple with 2 lists:
409
+
410
+ - shade_meshes: A list of all shade meshes without a unique
411
+ modifier_blk (just using the default black or transparent modifier).
412
+
413
+ - shade_meshes_blk: A list of all shade meshes that have a unique modifier_blk.
414
+ """
415
+ shade_meshes, shade_meshes_blk = [], []
416
+ for shade in self.host.shade_meshes:
417
+ if shade.properties.radiance._modifier_blk:
418
+ shade_meshes_blk.append(shade)
419
+ else:
420
+ shade_meshes.append(shade)
421
+ return shade_meshes, shade_meshes_blk
422
+
423
+ def move(self, moving_vec):
424
+ """Move all sensor_grid and view geometry along a vector.
425
+
426
+ Args:
427
+ moving_vec: A ladybug_geometry Vector3D with the direction and distance
428
+ to move the objects.
429
+ """
430
+ for grid in self._sensor_grids:
431
+ grid.move(moving_vec)
432
+ for view in self._views:
433
+ view.move(moving_vec)
434
+
435
+ def rotate(self, axis, angle, origin):
436
+ """Rotate all sensor_grid and view geometry.
437
+
438
+ Args:
439
+ axis: A ladybug_geometry Vector3D axis representing the axis of rotation.
440
+ angle: An angle for rotation in degrees.
441
+ origin: A ladybug_geometry Point3D for the origin around which the
442
+ object will be rotated.
443
+ """
444
+ for grid in self._sensor_grids:
445
+ grid.rotate(axis, angle, origin)
446
+ for view in self._views:
447
+ view.rotate(axis, angle, origin)
448
+
449
+ def rotate_xy(self, angle, origin):
450
+ """Rotate all sensor_grids and views counterclockwise in the world XY plane.
451
+
452
+ Args:
453
+ angle: An angle in degrees.
454
+ origin: A ladybug_geometry Point3D for the origin around which the
455
+ object will be rotated.
456
+ """
457
+ for grid in self._sensor_grids:
458
+ grid.rotate_xy(angle, origin)
459
+ for view in self._views:
460
+ view.rotate_xy(angle, origin)
461
+
462
+ def reflect(self, plane):
463
+ """Reflect all sensor_grid and view geometry across a plane.
464
+
465
+ Args:
466
+ plane: A ladybug_geometry Plane across which the object will
467
+ be reflected.
468
+ """
469
+ for grid in self._sensor_grids:
470
+ grid.reflect(plane)
471
+ for view in self._views:
472
+ view.reflect(plane)
473
+
474
+ def scale(self, factor, origin=None):
475
+ """Scale all sensor_grid and view geometry by a factor.
476
+
477
+ Args:
478
+ factor: A number representing how much the object should be scaled.
479
+ origin: A ladybug_geometry Point3D representing the origin from which
480
+ to scale. If None, it will be scaled from the World origin (0, 0, 0).
481
+ """
482
+ for grid in self._sensor_grids:
483
+ grid.scale(factor, origin)
484
+ for view in self._views:
485
+ view.scale(factor, origin)
486
+
487
+ def generate_exterior_face_sensor_grid(
488
+ self, dimension, offset=0.1, face_type='Wall', punched_geometry=False):
489
+ """Get a radiance SensorGrid generated from all exterior Faces of this Model.
490
+
491
+ The Face geometry without windows punched into it will be used. This
492
+ will be None if the Model has no exterior Faces.
493
+
494
+ Args:
495
+ dimension: The dimension of the grid cells as a number.
496
+ offset: A number for how far to offset the grid from the base face.
497
+ Positive numbers indicate an offset towards the exterior. (Default
498
+ is 0.1, which will offset the grid to be 0.1 unit from the faces).
499
+ face_type: Text to specify the type of face that will be used to
500
+ generate grids. Note that only Faces with Outdoors boundary
501
+ conditions will be used, meaning that most Floors will typically
502
+ be excluded unless they represent the underside of a cantilever.
503
+ Choose from the following. (Default: Wall).
504
+
505
+ * Wall
506
+ * Roof
507
+ * Floor
508
+ * All
509
+
510
+ punched_geometry: Boolean to note whether the punched_geometry of the faces
511
+ should be used (True) with the areas of sub-faces removed from the grid
512
+ or the full geometry should be used (False). (Default:False).
513
+
514
+ Returns:
515
+ A honeybee_radiance SensorGrid generated from the exterior Faces
516
+ of the model. Will be None if the Model has no exterior Faces.
517
+ """
518
+ # generate the mesh grid from the exterior Faces
519
+ face_grid = self.host.generate_exterior_face_grid(
520
+ dimension, offset, face_type, punched_geometry)
521
+ if face_grid is None: # no valid mesh could be generated
522
+ return None
523
+ # create the sensor grid from the mesh
524
+ f_nm = 'Faces' if face_type.title() == 'All' else face_type.title()
525
+ grid_name = '{}_Exterior{}'.format(self.host.display_name, f_nm)
526
+ sensor_grid = SensorGrid.from_mesh3d(clean_rad_string(grid_name), face_grid)
527
+ sensor_grid.display_name = grid_name
528
+ return sensor_grid
529
+
530
+ def generate_exterior_aperture_sensor_grid(
531
+ self, dimension, offset=0.1, aperture_type='All'):
532
+ """Get a radiance SensorGrid generated from all exterior Apertures of this Model.
533
+
534
+ This will be None if the Model has no exterior Apertures.
535
+
536
+ Args:
537
+ dimension: The dimension of the grid cells as a number.
538
+ offset: A number for how far to offset the grid from the base aperture.
539
+ Positive numbers indicate an offset towards the exterior while
540
+ negative numbers indicate an offset towards the interior, essentially
541
+ modeling the value of sun on the building interior. (Default
542
+ is 0.1, which will offset the grid to be 0.1 unit from the aperture).
543
+ aperture_type: Text to specify the type of Aperture that will be used to
544
+ generate grids. Window indicates Apertures in Walls. Choose from
545
+ the following. (Default: All).
546
+
547
+ * Window
548
+ * Skylight
549
+ * All
550
+
551
+ Returns:
552
+ A honeybee_radiance SensorGrid generated from the exterior Apertures
553
+ of the model. Will be None if the Model has no exterior Apertures.
554
+ """
555
+ # generate the mesh grid from the exterior Apertures
556
+ ap_grid = self.host.generate_exterior_aperture_grid(
557
+ dimension, offset, aperture_type)
558
+ if ap_grid is None: # no valid mesh could be generated
559
+ return None
560
+ # create the sensor grid from the mesh
561
+ f_nm = 'Apertures' if aperture_type.title() == 'All' else aperture_type.title()
562
+ grid_name = '{}_Exterior{}'.format(self.host.display_name, f_nm)
563
+ sensor_grid = SensorGrid.from_mesh3d(clean_rad_string(grid_name), ap_grid)
564
+ sensor_grid.display_name = grid_name
565
+ return sensor_grid
566
+
567
+ def check_for_extension(self, raise_exception=True, detailed=False):
568
+ """Check that the Model is valid for Radiance simulation.
569
+
570
+ This process includes all relevant honeybee-core checks as well as checks
571
+ that apply only for Radiance.
572
+
573
+ Args:
574
+ raise_exception: Boolean to note whether a ValueError should be raised
575
+ if any errors are found. If False, this method will simply
576
+ return a text string with all errors that were found. (Default: True).
577
+ detailed: Boolean for whether the returned object is a detailed list of
578
+ dicts with error info or a string with a message. (Default: False).
579
+
580
+ Returns:
581
+ A text string with all errors that were found or a list if detailed is True.
582
+ This string (or list) will be empty if no errors were found.
583
+ """
584
+ # set up defaults to ensure the method runs correctly
585
+ detailed = False if raise_exception else detailed
586
+ msgs = []
587
+ tol = self.host.tolerance
588
+ ang_tol = self.host.angle_tolerance
589
+
590
+ # perform checks for duplicate identifiers, which might mess with other checks
591
+ msgs.append(self.host.check_all_duplicate_identifiers(False, detailed))
592
+
593
+ # perform several checks for the Honeybee schema geometry rules
594
+ msgs.append(self.host.check_planar(tol, False, detailed))
595
+ msgs.append(self.host.check_self_intersecting(tol, False, detailed))
596
+ msgs.append(self.host.check_degenerate_rooms(tol, False, detailed))
597
+
598
+ # perform geometry checks related to parent-child relationships
599
+ msgs.append(self.host.check_sub_faces_valid(tol, ang_tol, False, detailed))
600
+ msgs.append(self.host.check_sub_faces_overlapping(tol, False, detailed))
601
+ msgs.append(self.host.check_rooms_solid(tol, ang_tol, False, detailed))
602
+
603
+ # perform checks that are specific to Radiance
604
+ msgs.append(self.check_duplicate_sensor_grid_identifiers(False, detailed))
605
+ msgs.append(self.check_duplicate_view_identifiers(False, detailed))
606
+ msgs.append(self.check_sensor_grid_rooms_in_model(False, detailed))
607
+ msgs.append(self.check_view_rooms_in_model(False, detailed))
608
+ # output a final report of errors or raise an exception
609
+ full_msgs = [msg for msg in msgs if msg]
610
+ if detailed:
611
+ return [m for msg in full_msgs for m in msg]
612
+ full_msg = '\n'.join(full_msgs)
613
+ if raise_exception and len(full_msgs) != 0:
614
+ raise ValueError(full_msg)
615
+ return full_msg
616
+
617
+ def check_generic(self, raise_exception=True, detailed=False):
618
+ """Check generic of the aspects of the Model radiance properties.
619
+
620
+ This includes checks for everything except duplicate identifiers for
621
+ modifiers and modifier sets. Typically, these checks just add to the
622
+ validation time without providing useful information since extension
623
+ objects with duplicate IDs are lost during HBJSON serialization. Checks
624
+ for duplicate sensor grid and view IDs are still performed.
625
+
626
+ Args:
627
+ raise_exception: Boolean to note whether a ValueError should be raised
628
+ if any errors are found. If False, this method will simply
629
+ return a text string with all errors that were found.
630
+ detailed: Boolean for whether the returned object is a detailed list of
631
+ dicts with error info or a string with a message. (Default: False).
632
+
633
+ Returns:
634
+ A text string with all errors that were found or a list if detailed is True.
635
+ This string (or list) will be empty if no errors were found.
636
+ """
637
+ # set up defaults to ensure the method runs correctly
638
+ detailed = False if raise_exception else detailed
639
+ msgs = []
640
+ # perform checks for specific radiance simulation rules
641
+ msgs.append(self.check_duplicate_sensor_grid_identifiers(False, detailed))
642
+ msgs.append(self.check_duplicate_view_identifiers(False, detailed))
643
+ msgs.append(self.check_sensor_grid_rooms_in_model(False, detailed))
644
+ msgs.append(self.check_view_rooms_in_model(False, detailed))
645
+ # output a final report of errors or raise an exception
646
+ full_msgs = [msg for msg in msgs if msg]
647
+ if detailed:
648
+ return [m for msg in full_msgs for m in msg]
649
+ full_msg = '\n'.join(full_msgs)
650
+ if raise_exception and len(full_msgs) != 0:
651
+ raise ValueError(full_msg)
652
+ return full_msg
653
+
654
+ def check_all(self, raise_exception=True, detailed=False):
655
+ """Check all of the aspects of the Model radiance properties.
656
+
657
+ Args:
658
+ raise_exception: Boolean to note whether a ValueError should be raised
659
+ if any errors are found. If False, this method will simply
660
+ return a text string with all errors that were found. (Default: True).
661
+ detailed: Boolean for whether the returned object is a detailed list of
662
+ dicts with error info or a string with a message. (Default: False).
663
+
664
+ Returns:
665
+ A text string with all errors that were found or a list if detailed is True.
666
+ This string (or list) will be empty if no errors were found.
667
+ """
668
+ # set up defaults to ensure the method runs correctly
669
+ detailed = False if raise_exception else detailed
670
+ msgs = []
671
+ # perform checks for duplicate identifiers
672
+ msgs.append(self.check_all_duplicate_identifiers(False, detailed))
673
+ # perform checks for specific radiance simulation rules
674
+ msgs.append(self.check_sensor_grid_rooms_in_model(False, detailed))
675
+ msgs.append(self.check_view_rooms_in_model(False, detailed))
676
+ # output a final report of errors or raise an exception
677
+ full_msgs = [msg for msg in msgs if msg]
678
+ if detailed:
679
+ return [m for msg in full_msgs for m in msg]
680
+ full_msg = '\n'.join(full_msgs)
681
+ if raise_exception and len(full_msgs) != 0:
682
+ raise ValueError(full_msg)
683
+ return full_msg
684
+
685
+ def check_all_duplicate_identifiers(self, raise_exception=True, detailed=False):
686
+ """Check that there are no duplicate identifiers for any radiance objects.
687
+
688
+ This includes Modifiers, ModifierSets, SensorGrids, and Views.
689
+
690
+ Args:
691
+ raise_exception: Boolean to note whether a ValueError should be raised
692
+ if any duplicate identifiers are found. If False, this method will simply
693
+ return a text string with all errors that were found. (Default: True).
694
+ detailed: Boolean for whether the returned object is a detailed list of
695
+ dicts with error info or a string with a message. (Default: False).
696
+
697
+ Returns:
698
+ A text string with all errors that were found or a list if detailed is True.
699
+ This string (or list) will be empty if no errors were found.
700
+ """
701
+ # set up defaults to ensure the method runs correctly
702
+ detailed = False if raise_exception else detailed
703
+ msgs = []
704
+ # perform checks for duplicate identifiers
705
+ msgs.append(self.check_duplicate_modifier_identifiers(False, detailed))
706
+ msgs.append(self.check_duplicate_modifier_set_identifiers(False, detailed))
707
+ msgs.append(self.check_duplicate_sensor_grid_identifiers(False, detailed))
708
+ msgs.append(self.check_duplicate_view_identifiers(False, detailed))
709
+ # output a final report of errors or raise an exception
710
+ full_msgs = [msg for msg in msgs if msg]
711
+ if detailed:
712
+ return [m for msg in full_msgs for m in msg]
713
+ full_msg = '\n'.join(full_msgs)
714
+ if raise_exception and len(full_msgs) != 0:
715
+ raise ValueError(full_msg)
716
+ return full_msg
717
+
718
+ def check_duplicate_modifier_identifiers(self, raise_exception=True, detailed=False):
719
+ """Check that there are no duplicate Modifier identifiers in the model.
720
+
721
+ Args:
722
+ raise_exception: Boolean to note whether a ValueError should be raised
723
+ if duplicate identifiers are found. (Default: True).
724
+ detailed: Boolean for whether the returned object is a detailed list of
725
+ dicts with error info or a string with a message. (Default: False).
726
+
727
+ Returns:
728
+ A string with the message or a list with a dictionary if detailed is True.
729
+ """
730
+ return check_duplicate_identifiers(
731
+ self.modifiers, raise_exception, 'Radiance Modifier',
732
+ detailed, '010001', 'Radiance', error_type='Duplicate Modifier Identifier')
733
+
734
+ def check_duplicate_modifier_set_identifiers(
735
+ self, raise_exception=True, detailed=False):
736
+ """Check that there are no duplicate ModifierSet identifiers in the model.
737
+
738
+ Args:
739
+ raise_exception: Boolean to note whether a ValueError should be raised
740
+ if duplicate identifiers are found. (Default: True).
741
+ detailed: Boolean for whether the returned object is a detailed list of
742
+ dicts with error info or a string with a message. (Default: False).
743
+
744
+ Returns:
745
+ A string with the message or a list with a dictionary if detailed is True.
746
+ """
747
+ return check_duplicate_identifiers(
748
+ self.modifier_sets, raise_exception, 'ModifierSet',
749
+ detailed, '010002', 'Radiance',
750
+ error_type='Duplicate ModifierSet Identifier')
751
+
752
+ def check_duplicate_sensor_grid_identifiers(
753
+ self, raise_exception=True, detailed=False):
754
+ """Check that there are no duplicate SensorGrid identifiers in the model.
755
+
756
+ Args:
757
+ raise_exception: Boolean to note whether a ValueError should be raised
758
+ if duplicate identifiers are found. (Default: True).
759
+ detailed: Boolean for whether the returned object is a detailed list of
760
+ dicts with error info or a string with a message. (Default: False).
761
+
762
+ Returns:
763
+ A string with the message or a list with a dictionary if detailed is True.
764
+ """
765
+ return check_duplicate_identifiers(
766
+ self.sensor_grids, raise_exception, 'SensorGrid',
767
+ detailed, '010003', 'Radiance', error_type='Duplicate SensorGrid Identifier')
768
+
769
+ def check_duplicate_view_identifiers(self, raise_exception=True, detailed=False):
770
+ """Check that there are no duplicate View identifiers in the model.
771
+
772
+ Args:
773
+ raise_exception: Boolean to note whether a ValueError should be raised
774
+ if duplicate identifiers are found. (Default: True).
775
+ detailed: Boolean for whether the returned object is a detailed list of
776
+ dicts with error info or a string with a message. (Default: False).
777
+
778
+ Returns:
779
+ A string with the message or a list with a dictionary if detailed is True.
780
+ """
781
+ return check_duplicate_identifiers(
782
+ self.views, raise_exception, 'View', detailed, '010004', 'Radiance',
783
+ error_type='Duplicate View Identifier')
784
+
785
+ def check_sensor_grid_rooms_in_model(self, raise_exception=True, detailed=False):
786
+ """Check that the room_identifiers of SenorGrids are in the model.
787
+
788
+ Args:
789
+ raise_exception: Boolean to note whether a ValueError should be raised if
790
+ SensorGrids reference Rooms that are not in the Model. (Default: True).
791
+ detailed: Boolean for whether the returned object is a detailed list of
792
+ dicts with error info or a string with a message. (Default: False).
793
+
794
+ Returns:
795
+ A string with the message or a list with a dictionary if detailed is True.
796
+ """
797
+ detailed = False if raise_exception else detailed
798
+ # gather a list of all the missing rooms
799
+ grid_ids = [(grid, grid.room_identifier) for grid in self.sensor_grids
800
+ if grid.room_identifier is not None]
801
+ room_ids = set(room.identifier for room in self.host.rooms)
802
+ missing_rooms = [] if detailed else set()
803
+ for grid in grid_ids:
804
+ if grid[1] not in room_ids:
805
+ if detailed:
806
+ missing_rooms.append(grid[0])
807
+ else:
808
+ missing_rooms.add(grid[1])
809
+ # if missing rooms were found, then report the issue
810
+ if len(missing_rooms) != 0:
811
+ if detailed:
812
+ all_err = []
813
+ for grid in missing_rooms:
814
+ msg = 'SensorGrid "{}" has a room_identifier that is not in the ' \
815
+ 'Model: "{}"'.format(grid.identifier, grid.room_identifier)
816
+ error_dict = {
817
+ 'type': 'ValidationError',
818
+ 'code': '010005',
819
+ 'error_type': 'SensorGrid Room Not In Model',
820
+ 'extension_type': 'Radiance',
821
+ 'element_type': 'SensorGrid',
822
+ 'element_id': [grid.identifier],
823
+ 'element_name': [grid.display_name],
824
+ 'message': msg
825
+ }
826
+ all_err.append(error_dict)
827
+ return all_err
828
+ else:
829
+ msg = 'The model has the following missing rooms referenced by sensor ' \
830
+ 'grids:\n{}'.format('\n'.join(missing_rooms))
831
+ if raise_exception:
832
+ raise ValueError(msg)
833
+ return msg
834
+ return [] if detailed else ''
835
+
836
+ def check_view_rooms_in_model(self, raise_exception=True, detailed=False):
837
+ """Check that the room_identifiers of Views are in the model.
838
+
839
+ Args:
840
+ raise_exception: Boolean to note whether a ValueError should be raised if
841
+ Views reference Rooms that are not in the Model. (Default: True).
842
+ detailed: Boolean for whether the returned object is a detailed list of
843
+ dicts with error info or a string with a message. (Default: False).
844
+
845
+ Returns:
846
+ A string with the message or a list with a dictionary if detailed is True.
847
+ """
848
+ detailed = False if raise_exception else detailed
849
+ # gather a list of all the missing rooms
850
+ view_ids = [(view, view.room_identifier) for view in self.views
851
+ if view.room_identifier is not None]
852
+ room_ids = set(room.identifier for room in self.host.rooms)
853
+ missing_rooms = [] if detailed else set()
854
+ for view in view_ids:
855
+ if view[1] not in room_ids:
856
+ if detailed:
857
+ missing_rooms.append(view[0])
858
+ else:
859
+ missing_rooms.add(view[1])
860
+ if len(missing_rooms) != 0:
861
+ if detailed:
862
+ all_err = []
863
+ for view in missing_rooms:
864
+ msg = 'View "{}" has a room_identifier that is not in the ' \
865
+ 'Model: "{}"'.format(view.identifier, view.room_identifier)
866
+ error_dict = {
867
+ 'type': 'ValidationError',
868
+ 'code': '010006',
869
+ 'error_type': 'View Room Not In Model',
870
+ 'extension_type': 'Radiance',
871
+ 'element_type': 'View',
872
+ 'element_id': [view.identifier],
873
+ 'element_name': [view.display_name],
874
+ 'message': msg
875
+ }
876
+ all_err.append(error_dict)
877
+ return all_err
878
+ else:
879
+ msg = 'The model has the following missing rooms referenced by ' \
880
+ 'views:\n{}'.format('\n'.join(missing_rooms))
881
+ if raise_exception:
882
+ raise ValueError(msg)
883
+ return msg
884
+ return [] if detailed else ''
885
+
886
+ def merge_duplicate_identifier_grids(self):
887
+ """Merge SensorGrids in the Model with the same identifier together.
888
+
889
+ This is one automated way of fixing check_duplicate_sensor_grid_identifiers
890
+ failures. In this case, it will be assumed that the user intended to
891
+ make the sensor grids with the same identifier apart of the same
892
+ SensorGrid object. The other possible way to address SensorGrids with
893
+ duplicate identifiers is to give each grid a unique identifier if the
894
+ truly are meant to be separate.
895
+ """
896
+ # first group all grids with the same identifier
897
+ grid_dict = {}
898
+ for grid in self.sensor_grids:
899
+ try:
900
+ grid_dict[grid.identifier].append(grid)
901
+ except KeyError:
902
+ grid_dict[grid.identifier] = [grid]
903
+ # merge girds together if they have the same ID
904
+ merged_grids = []
905
+ for grids in grid_dict.values():
906
+ if len(grids) == 1:
907
+ merged_grids.append(grids[0])
908
+ else:
909
+ merged_grid = SensorGrid.from_merged_grids(grids)
910
+ merged_grids.append(merged_grid)
911
+ self.sensor_grids = merged_grids
912
+
913
+ def apply_properties_from_dict(self, data):
914
+ """Apply the radiance properties of a dictionary to the host Model of this object.
915
+
916
+ Args:
917
+ data: A dictionary representation of an entire honeybee-core Model.
918
+ Note that this dictionary must have ModelRadianceProperties in order
919
+ for this method to successfully apply the radiance properties.
920
+ """
921
+ assert 'radiance' in data['properties'], \
922
+ 'Dictionary possesses no ModelRadianceProperties.'
923
+
924
+ modifiers, modifier_sets = self.load_properties_from_dict(data)
925
+
926
+ # collect lists of radiance property dictionaries
927
+ room_e_dicts, face_e_dicts, shd_e_dicts, ap_e_dicts, dr_e_dicts = \
928
+ model_extension_dicts(data, 'radiance', [], [], [], [], [])
929
+
930
+ # apply radiance properties to objects using the radiance property dictionaries
931
+ for room, r_dict in zip(self.host.rooms, room_e_dicts):
932
+ if r_dict is not None:
933
+ room.properties.radiance.apply_properties_from_dict(
934
+ r_dict, modifier_sets)
935
+ for face, f_dict in zip(self.host.faces, face_e_dicts):
936
+ if f_dict is not None:
937
+ face.properties.radiance.apply_properties_from_dict(
938
+ f_dict, modifiers)
939
+ for aperture, a_dict in zip(self.host.apertures, ap_e_dicts):
940
+ if a_dict is not None:
941
+ aperture.properties.radiance.apply_properties_from_dict(
942
+ a_dict, modifiers)
943
+ for door, d_dict in zip(self.host.doors, dr_e_dicts):
944
+ if d_dict is not None:
945
+ door.properties.radiance.apply_properties_from_dict(
946
+ d_dict, modifiers)
947
+ # apply properties to the Shades with separation of Shades from ShadeMesh
948
+ all_shades = self.host.shades + self.host._shade_meshes
949
+ for shade, s_dict in zip(all_shades, shd_e_dicts):
950
+ if s_dict is not None:
951
+ shade.properties.radiance.apply_properties_from_dict(
952
+ s_dict, modifiers)
953
+
954
+ # apply the sensor grids and views if they are in the data.
955
+ rad_data = data['properties']['radiance']
956
+ if 'sensor_grids' in rad_data and rad_data['sensor_grids'] is not None:
957
+ self.sensor_grids = \
958
+ [SensorGrid.from_dict(grid) for grid in rad_data['sensor_grids']]
959
+ if 'views' in rad_data and rad_data['views'] is not None:
960
+ self.views = [View.from_dict(view) for view in rad_data['views']]
961
+
962
+ def to_dict(self):
963
+ """Return Model radiance properties as a dictionary."""
964
+ base = {'radiance': {'type': 'ModelRadianceProperties'}}
965
+
966
+ # add the global modifier set to the dictionary
967
+ gs = self.global_modifier_set.to_dict(abridged=True, none_for_defaults=False)
968
+ gs['type'] = 'GlobalModifierSet'
969
+ del gs['identifier']
970
+ g_mods = self.global_modifier_set.modifiers_unique
971
+ gs['modifiers'] = [mod.to_dict() for mod in g_mods]
972
+ gs['context_modifier'] = generic_context.identifier
973
+ gs['modifiers'].append(generic_context.to_dict())
974
+ base['radiance']['global_modifier_set'] = gs
975
+
976
+ # add all ModifierSets to the dictionary
977
+ base['radiance']['modifier_sets'] = []
978
+ modifier_sets = self.modifier_sets
979
+ for mod_set in modifier_sets:
980
+ base['radiance']['modifier_sets'].append(mod_set.to_dict(abridged=True))
981
+
982
+ # add all unique Modifiers to the dictionary
983
+ room_mods = []
984
+ for mod_set in modifier_sets:
985
+ room_mods.extend(mod_set.modified_modifiers_unique)
986
+ all_mods = room_mods + self.face_modifiers + self.shade_modifiers
987
+ modifiers = list(set(all_mods))
988
+ base['radiance']['modifiers'] = []
989
+ for mod in modifiers:
990
+ base['radiance']['modifiers'].append(mod.to_dict())
991
+
992
+ # add the sensor grids and views to the dictionary
993
+ if len(self._sensor_grids) != 0:
994
+ base['radiance']['sensor_grids'] = \
995
+ [grid.to_dict() for grid in self._sensor_grids]
996
+ if len(self._views) != 0:
997
+ base['radiance']['views'] = [view.to_dict() for view in self._views]
998
+
999
+ return base
1000
+
1001
+ def duplicate(self, new_host=None):
1002
+ """Get a copy of this object.
1003
+
1004
+ new_host: A new Model object that hosts these properties.
1005
+ If None, the properties will be duplicated with the same host.
1006
+ """
1007
+ _host = new_host or self._host
1008
+ new_grids = [sg.duplicate() for sg in self._sensor_grids]
1009
+ new_views = [vw.duplicate() for vw in self._views]
1010
+ return ModelRadianceProperties(_host, new_grids, new_views)
1011
+
1012
+ @staticmethod
1013
+ def load_properties_from_dict(data):
1014
+ """Load model radiance properties of a dictionary to Python objects.
1015
+
1016
+ Loaded objects include Modifiers and ModifierSets.
1017
+
1018
+ The function is called when re-serializing a Model object from a dictionary
1019
+ to load honeybee_radiance objects into their Python object form before
1020
+ applying them to the Model geometry.
1021
+
1022
+ Args:
1023
+ data: A dictionary representation of an entire honeybee-core Model.
1024
+ Note that this dictionary must have ModelRadianceProperties in order
1025
+ for this method to successfully load the radiance properties.
1026
+
1027
+ Returns:
1028
+ A tuple with two elements
1029
+
1030
+ - modifiers: A dictionary with identifiers of modifiers as keys and Python
1031
+ modifier objects as values.
1032
+
1033
+ - modifier_sets: A dictionary with identifiers of modifier sets as keys
1034
+ and Python modifier set objects as values.
1035
+ """
1036
+ assert 'radiance' in data['properties'], \
1037
+ 'Dictionary possesses no ModelRadianceProperties.'
1038
+
1039
+ # process all modifiers in the ModelRadianceProperties dictionary
1040
+ modifiers = {}
1041
+ if 'modifiers' in data['properties']['radiance'] and \
1042
+ data['properties']['radiance']['modifiers'] is not None:
1043
+ for mod in data['properties']['radiance']['modifiers']:
1044
+ try:
1045
+ modifiers[mod['identifier']] = dict_to_modifier(mod)
1046
+ except Exception as e:
1047
+ invalid_dict_error(mod, e)
1048
+
1049
+ # process all modifier sets in the ModelRadianceProperties dictionary
1050
+ modifier_sets = {}
1051
+ if 'modifier_sets' in data['properties']['radiance'] and \
1052
+ data['properties']['radiance']['modifier_sets'] is not None:
1053
+ if 'modifier_sets' in data['properties']['radiance'] and \
1054
+ data['properties']['radiance']['modifier_sets'] is not None:
1055
+ for m_set in data['properties']['radiance']['modifier_sets']:
1056
+ try:
1057
+ if m_set['type'] == 'ModifierSet':
1058
+ modifier_sets[m_set['identifier']] = \
1059
+ ModifierSet.from_dict(m_set)
1060
+ else:
1061
+ modifier_sets[m_set['identifier']] = \
1062
+ ModifierSet.from_dict_abridged(m_set, modifiers)
1063
+ except Exception as e:
1064
+ invalid_dict_error(m_set, e)
1065
+
1066
+ return modifiers, modifier_sets
1067
+
1068
+ @staticmethod
1069
+ def dump_properties_to_dict(modifiers=None, modifier_sets=None):
1070
+ """Get a ModelRadianceProperties dictionary from arrays of Python objects.
1071
+
1072
+ Args:
1073
+ modifiers: A list or tuple of radiance modifier objects.
1074
+ modifier_sets: A list or tuple of modifier set objects.
1075
+
1076
+ Returns:
1077
+ data: A dictionary representation of ModelRadianceProperties. Note that
1078
+ all objects in this dictionary will follow the abridged schema.
1079
+ """
1080
+ # process the modifiers and modifier sets
1081
+ all_m = [] if modifiers is None else list(modifiers)
1082
+ all_mod_sets = [] if modifier_sets is None else list(modifier_sets)
1083
+ for mod_set in all_mod_sets:
1084
+ all_m.extend(mod_set.modified_modifiers)
1085
+
1086
+ # get sets of unique objects
1087
+ all_mods = set(all_m)
1088
+
1089
+ # add all object dictionaries into one object
1090
+ data = {'type': 'ModelRadianceProperties'}
1091
+ data['modifiers'] = [m.to_dict() for m in all_mods]
1092
+ data['modifier_sets'] = [ms.to_dict(abridged=True) for ms in all_mod_sets]
1093
+ return data
1094
+
1095
+ @staticmethod
1096
+ def reset_resource_ids_in_dict(
1097
+ data, add_uuid=False, reset_modifiers=True, reset_modifier_sets=True):
1098
+ """Reset the identifiers of radiance resource objects in a Model dictionary.
1099
+
1100
+ This is useful when human-readable names are needed when the model is
1101
+ exported to other formats like Rad and the uniqueness of the
1102
+ identifiers is less of a concern.
1103
+
1104
+ Args:
1105
+ data: A dictionary representation of an entire honeybee-core Model.
1106
+ Note that this dictionary must have ModelRadianceProperties in order
1107
+ for this method to successfully edit the radiance properties.
1108
+ add_uuid: Boolean to note whether newly-generated resource object IDs
1109
+ should be derived only from a cleaned display_name (False) or
1110
+ whether this new ID should also have a unique set of 8 characters
1111
+ appended to it to guarantee uniqueness. (Default: False).
1112
+ reset_modifiers: Boolean to note whether the IDs of all modifiers in
1113
+ the model should be reset or kept. (Default: True).
1114
+ reset_modifier_sets: Boolean to note whether the IDs of all modifier
1115
+ sets in the model should be reset or kept. (Default: True).
1116
+
1117
+ Returns:
1118
+ A new Model dictionary with the resource identifiers reset. All references
1119
+ to the reset resources will be correct and valid in the resulting dictionary,
1120
+ assuming that the input is valid.
1121
+ """
1122
+ model = Model.from_dict(data)
1123
+ modifiers, modifier_sets = \
1124
+ model.properties.radiance.load_properties_from_dict(data)
1125
+ res_func = clean_and_id_rad_string if add_uuid else clean_rad_string
1126
+
1127
+ # change the identifiers of the modifiers
1128
+ if reset_modifiers:
1129
+ model_mods = set()
1130
+ for mod in model.properties.radiance.modifiers:
1131
+ mod.unlock()
1132
+ old_id, new_id = mod.identifier, res_func(mod.display_name)
1133
+ mod.identifier = new_id
1134
+ modifiers[old_id].unlock()
1135
+ modifiers[old_id].identifier = new_id
1136
+ model_mods.add(old_id)
1137
+ for old_id, mod in modifiers.items():
1138
+ if old_id not in model_mods:
1139
+ mod.unlock()
1140
+ mod.identifier = res_func(mod.display_name)
1141
+
1142
+ # change the identifiers of the modifier_sets
1143
+ if reset_modifier_sets:
1144
+ model_ms = set()
1145
+ for ms in model.properties.radiance.modifier_sets:
1146
+ ms.unlock()
1147
+ old_id, new_id = ms.identifier, res_func(ms.display_name)
1148
+ ms.identifier = new_id
1149
+ modifier_sets[old_id].unlock()
1150
+ modifier_sets[old_id].identifier = new_id
1151
+ model_ms.add(old_id)
1152
+ for old_id, ms in modifier_sets.items():
1153
+ if old_id not in model_ms:
1154
+ ms.unlock()
1155
+ ms.identifier = res_func(ms.display_name)
1156
+
1157
+ # create the model dictionary and update any unreferenced resources
1158
+ model_dict = model.to_dict()
1159
+ mr_props = model_dict['properties']['radiance']
1160
+ mr_props['modifiers'] = [mod.to_dict() for mod in modifiers.values()]
1161
+ mr_props['modifier_sets'] = \
1162
+ [ms.to_dict(abridged=True) for ms in modifier_sets.values()]
1163
+ return model_dict
1164
+
1165
+ def _check_and_add_room_modifier_shade(self, room, modifiers):
1166
+ """Check if a modifier is assigned to a Room's shades and add it to a list."""
1167
+ self._check_and_add_obj_modifier_shade(room, modifiers)
1168
+ for face in room.faces: # check all Face modifiers
1169
+ self._check_and_add_face_modifier_shade(face, modifiers)
1170
+
1171
+ def _check_and_add_face_modifier_shade(self, face, modifiers):
1172
+ """Check if a modifier is assigned to a Face's shades and add it to a list."""
1173
+ self._check_and_add_obj_modifier_shade(face, modifiers)
1174
+ for ap in face.apertures: # check all Aperture modifiers
1175
+ self._check_and_add_obj_modifier_shade(ap, modifiers)
1176
+ for dr in face.doors: # check all Door Shade modifiers
1177
+ self._check_and_add_obj_modifier_shade(dr, modifiers)
1178
+
1179
+ def _check_and_add_obj_modifier_shade(self, subf, modifiers):
1180
+ """Check if a modifier is assigned to an object's shades and add it to a list."""
1181
+ for shade in subf.shades:
1182
+ self._check_and_add_dynamic_obj_modifier(shade, modifiers)
1183
+
1184
+ def _check_and_add_face_modifier(self, face, modifiers):
1185
+ """Check if a modifier is assigned to a face and add it to a list."""
1186
+ self._check_and_add_obj_modifier(face, modifiers)
1187
+ for ap in face.apertures: # check all Aperture modifiers
1188
+ self._check_and_add_dynamic_obj_modifier(ap, modifiers)
1189
+ for dr in face.doors: # check all Door modifiers
1190
+ self._check_and_add_dynamic_obj_modifier(dr, modifiers)
1191
+
1192
+ def _check_and_add_face_modifier_blk(self, face, modifiers):
1193
+ """Check if a modifier_blk is assigned to a face and add it to a list."""
1194
+ self._check_and_add_obj_modifier_blk(face, modifiers)
1195
+ for ap in face.apertures: # check all Aperture modifiers
1196
+ self._check_and_add_dynamic_obj_modifier_blk(ap, modifiers)
1197
+ for dr in face.doors: # check all Door modifiers
1198
+ self._check_and_add_dynamic_obj_modifier_blk(dr, modifiers)
1199
+
1200
+ def _check_and_add_obj_modifier(self, obj, modifiers):
1201
+ """Check if a modifier is assigned to an object and add it to a list."""
1202
+ mod = obj.properties.radiance._modifier
1203
+ if mod is not None:
1204
+ if not self._instance_in_array(mod, modifiers):
1205
+ modifiers.append(mod)
1206
+
1207
+ def _check_and_add_dynamic_obj_modifier(self, obj, modifiers):
1208
+ """Check if a modifier is assigned to a dynamic object and add it to a list."""
1209
+ mod = obj.properties.radiance._modifier
1210
+ if mod is not None:
1211
+ if not self._instance_in_array(mod, modifiers):
1212
+ modifiers.append(mod)
1213
+ for st in obj.properties.radiance._states:
1214
+ stm = (st._modifier, st._modifier_direct) + \
1215
+ tuple(s.modifier for s in st._shades)
1216
+ for mod in stm:
1217
+ if mod is not None:
1218
+ if not self._instance_in_array(mod, modifiers):
1219
+ modifiers.append(mod)
1220
+
1221
+ def _check_and_add_obj_modifier_blk(self, obj, modifiers):
1222
+ """Check if a modifier_blk is assigned to an object and add it to a list."""
1223
+ mod = obj.properties.radiance._modifier_blk
1224
+ if mod is not None:
1225
+ if not self._instance_in_array(mod, modifiers):
1226
+ modifiers.append(mod)
1227
+
1228
+ def _check_and_add_dynamic_obj_modifier_blk(self, obj, modifiers):
1229
+ """Check if a modifier_blk is assigned to a dynamic object and add it to a list.
1230
+ """
1231
+ mod = obj.properties.radiance._modifier_blk
1232
+ if mod is not None:
1233
+ if not self._instance_in_array(mod, modifiers):
1234
+ modifiers.append(mod)
1235
+ for st in obj.properties.radiance._states:
1236
+ for s in st._shades:
1237
+ mod = s.modifier
1238
+ if mod is not None:
1239
+ if not self._instance_in_array(mod, modifiers):
1240
+ modifiers.append(mod)
1241
+
1242
+ def _check_and_add_orphaned_shade_modifier(self, obj, modifiers):
1243
+ """Check if a modifier is assigned to an object and add it to a list."""
1244
+ mod = obj.properties.radiance._modifier
1245
+ if mod is not None:
1246
+ if not self._instance_in_array(mod, modifiers):
1247
+ modifiers.append(mod)
1248
+ else:
1249
+ def_mod = generic_context if obj.is_detached else \
1250
+ generic_modifier_set_visible.shade_set.exterior_modifier
1251
+ if not self._instance_in_array(def_mod, modifiers):
1252
+ modifiers.append(def_mod)
1253
+ for st in obj.properties.radiance._states:
1254
+ stm = (st._modifier, st._modifier_direct) + \
1255
+ tuple(s.modifier for s in st._shades)
1256
+ for mod in stm:
1257
+ if mod is not None:
1258
+ if not self._instance_in_array(mod, modifiers):
1259
+ modifiers.append(mod)
1260
+
1261
+ def _check_and_add_shade_mesh_modifier(self, obj, modifiers):
1262
+ """Check if a modifier is assigned to an object and add it to a list."""
1263
+ mod = obj.properties.radiance._modifier
1264
+ if mod is not None:
1265
+ if not self._instance_in_array(mod, modifiers):
1266
+ modifiers.append(mod)
1267
+ else:
1268
+ def_mod = generic_context if obj.is_detached else \
1269
+ generic_modifier_set_visible.shade_set.exterior_modifier
1270
+ if not self._instance_in_array(def_mod, modifiers):
1271
+ modifiers.append(def_mod)
1272
+
1273
+ @staticmethod
1274
+ def _instance_in_array(object_instance, object_array):
1275
+ """Check if a specific object instance is already in an array.
1276
+
1277
+ This can be much faster than `if object_instance in object_array`
1278
+ when you expect to be testing a lot of the same instance of an object for
1279
+ inclusion in an array since the builtin method uses an == operator to
1280
+ test inclusion.
1281
+ """
1282
+ for val in object_array:
1283
+ if val is object_instance:
1284
+ return True
1285
+ return False
1286
+
1287
+ def ToString(self):
1288
+ return self.__repr__()
1289
+
1290
+ def __repr__(self):
1291
+ return 'Model Radiance Properties: [host: {}]'.format(self.host.display_name)