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.
- honeybee_radiance/__init__.py +11 -0
- honeybee_radiance/__main__.py +4 -0
- honeybee_radiance/_extend_honeybee.py +93 -0
- honeybee_radiance/cli/__init__.py +88 -0
- honeybee_radiance/cli/dc.py +400 -0
- honeybee_radiance/cli/edit.py +529 -0
- honeybee_radiance/cli/glare.py +118 -0
- honeybee_radiance/cli/grid.py +859 -0
- honeybee_radiance/cli/lib.py +458 -0
- honeybee_radiance/cli/modifier.py +133 -0
- honeybee_radiance/cli/mtx.py +226 -0
- honeybee_radiance/cli/multiphase.py +1034 -0
- honeybee_radiance/cli/octree.py +640 -0
- honeybee_radiance/cli/postprocess.py +1186 -0
- honeybee_radiance/cli/raytrace.py +219 -0
- honeybee_radiance/cli/rpict.py +125 -0
- honeybee_radiance/cli/schedule.py +56 -0
- honeybee_radiance/cli/setconfig.py +63 -0
- honeybee_radiance/cli/sky.py +545 -0
- honeybee_radiance/cli/study.py +66 -0
- honeybee_radiance/cli/sunpath.py +331 -0
- honeybee_radiance/cli/threephase.py +255 -0
- honeybee_radiance/cli/translate.py +400 -0
- honeybee_radiance/cli/util.py +121 -0
- honeybee_radiance/cli/view.py +261 -0
- honeybee_radiance/cli/viewfactor.py +347 -0
- honeybee_radiance/config.json +6 -0
- honeybee_radiance/config.py +427 -0
- honeybee_radiance/dictutil.py +50 -0
- honeybee_radiance/dynamic/__init__.py +5 -0
- honeybee_radiance/dynamic/group.py +479 -0
- honeybee_radiance/dynamic/multiphase.py +557 -0
- honeybee_radiance/dynamic/state.py +718 -0
- honeybee_radiance/dynamic/stategeo.py +352 -0
- honeybee_radiance/geometry/__init__.py +13 -0
- honeybee_radiance/geometry/bubble.py +42 -0
- honeybee_radiance/geometry/cone.py +215 -0
- honeybee_radiance/geometry/cup.py +54 -0
- honeybee_radiance/geometry/cylinder.py +197 -0
- honeybee_radiance/geometry/geometrybase.py +37 -0
- honeybee_radiance/geometry/instance.py +40 -0
- honeybee_radiance/geometry/mesh.py +38 -0
- honeybee_radiance/geometry/polygon.py +174 -0
- honeybee_radiance/geometry/ring.py +214 -0
- honeybee_radiance/geometry/source.py +182 -0
- honeybee_radiance/geometry/sphere.py +178 -0
- honeybee_radiance/geometry/tube.py +46 -0
- honeybee_radiance/lib/__init__.py +1 -0
- honeybee_radiance/lib/_loadmodifiers.py +72 -0
- honeybee_radiance/lib/_loadmodifiersets.py +69 -0
- honeybee_radiance/lib/modifiers.py +58 -0
- honeybee_radiance/lib/modifiersets.py +63 -0
- honeybee_radiance/lightpath.py +204 -0
- honeybee_radiance/lightsource/__init__.py +1 -0
- honeybee_radiance/lightsource/_gendaylit.py +479 -0
- honeybee_radiance/lightsource/dictutil.py +49 -0
- honeybee_radiance/lightsource/ground.py +160 -0
- honeybee_radiance/lightsource/sky/__init__.py +7 -0
- honeybee_radiance/lightsource/sky/_skybase.py +177 -0
- honeybee_radiance/lightsource/sky/certainirradiance.py +232 -0
- honeybee_radiance/lightsource/sky/cie.py +378 -0
- honeybee_radiance/lightsource/sky/climatebased.py +501 -0
- honeybee_radiance/lightsource/sky/hemisphere.py +160 -0
- honeybee_radiance/lightsource/sky/skydome.py +113 -0
- honeybee_radiance/lightsource/sky/skymatrix.py +163 -0
- honeybee_radiance/lightsource/sky/strutil.py +34 -0
- honeybee_radiance/lightsource/sky/sunmatrix.py +212 -0
- honeybee_radiance/lightsource/sunpath.py +247 -0
- honeybee_radiance/modifier/__init__.py +3 -0
- honeybee_radiance/modifier/material/__init__.py +30 -0
- honeybee_radiance/modifier/material/absdf.py +477 -0
- honeybee_radiance/modifier/material/antimatter.py +54 -0
- honeybee_radiance/modifier/material/ashik2.py +51 -0
- honeybee_radiance/modifier/material/brtdfunc.py +81 -0
- honeybee_radiance/modifier/material/bsdf.py +292 -0
- honeybee_radiance/modifier/material/dielectric.py +53 -0
- honeybee_radiance/modifier/material/glass.py +431 -0
- honeybee_radiance/modifier/material/glow.py +246 -0
- honeybee_radiance/modifier/material/illum.py +51 -0
- honeybee_radiance/modifier/material/interface.py +49 -0
- honeybee_radiance/modifier/material/light.py +206 -0
- honeybee_radiance/modifier/material/materialbase.py +36 -0
- honeybee_radiance/modifier/material/metal.py +167 -0
- honeybee_radiance/modifier/material/metal2.py +41 -0
- honeybee_radiance/modifier/material/metdata.py +41 -0
- honeybee_radiance/modifier/material/metfunc.py +41 -0
- honeybee_radiance/modifier/material/mirror.py +340 -0
- honeybee_radiance/modifier/material/mist.py +86 -0
- honeybee_radiance/modifier/material/plasdata.py +58 -0
- honeybee_radiance/modifier/material/plasfunc.py +59 -0
- honeybee_radiance/modifier/material/plastic.py +354 -0
- honeybee_radiance/modifier/material/plastic2.py +58 -0
- honeybee_radiance/modifier/material/prism1.py +57 -0
- honeybee_radiance/modifier/material/prism2.py +48 -0
- honeybee_radiance/modifier/material/spotlight.py +50 -0
- honeybee_radiance/modifier/material/trans.py +518 -0
- honeybee_radiance/modifier/material/trans2.py +49 -0
- honeybee_radiance/modifier/material/transdata.py +50 -0
- honeybee_radiance/modifier/material/transfunc.py +53 -0
- honeybee_radiance/modifier/mixture/__init__.py +6 -0
- honeybee_radiance/modifier/mixture/mixdata.py +49 -0
- honeybee_radiance/modifier/mixture/mixfunc.py +54 -0
- honeybee_radiance/modifier/mixture/mixpict.py +52 -0
- honeybee_radiance/modifier/mixture/mixtext.py +66 -0
- honeybee_radiance/modifier/mixture/mixturebase.py +28 -0
- honeybee_radiance/modifier/modifierbase.py +40 -0
- honeybee_radiance/modifier/pattern/__init__.py +9 -0
- honeybee_radiance/modifier/pattern/brightdata.py +49 -0
- honeybee_radiance/modifier/pattern/brightfunc.py +47 -0
- honeybee_radiance/modifier/pattern/brighttext.py +81 -0
- honeybee_radiance/modifier/pattern/colordata.py +56 -0
- honeybee_radiance/modifier/pattern/colorfunc.py +47 -0
- honeybee_radiance/modifier/pattern/colorpict.py +54 -0
- honeybee_radiance/modifier/pattern/colortext.py +73 -0
- honeybee_radiance/modifier/pattern/patternbase.py +34 -0
- honeybee_radiance/modifier/texture/__init__.py +4 -0
- honeybee_radiance/modifier/texture/texdata.py +29 -0
- honeybee_radiance/modifier/texture/texfunc.py +26 -0
- honeybee_radiance/modifier/texture/texturebase.py +27 -0
- honeybee_radiance/modifierset.py +1091 -0
- honeybee_radiance/mutil.py +60 -0
- honeybee_radiance/postprocess/__init__.py +1 -0
- honeybee_radiance/postprocess/annual.py +108 -0
- honeybee_radiance/postprocess/annualdaylight.py +425 -0
- honeybee_radiance/postprocess/annualglare.py +201 -0
- honeybee_radiance/postprocess/annualirradiance.py +187 -0
- honeybee_radiance/postprocess/electriclight.py +119 -0
- honeybee_radiance/postprocess/en17037.py +261 -0
- honeybee_radiance/postprocess/leed.py +304 -0
- honeybee_radiance/postprocess/solartracking.py +90 -0
- honeybee_radiance/primitive.py +554 -0
- honeybee_radiance/properties/__init__.py +1 -0
- honeybee_radiance/properties/_base.py +390 -0
- honeybee_radiance/properties/aperture.py +197 -0
- honeybee_radiance/properties/door.py +198 -0
- honeybee_radiance/properties/face.py +123 -0
- honeybee_radiance/properties/model.py +1291 -0
- honeybee_radiance/properties/room.py +490 -0
- honeybee_radiance/properties/shade.py +186 -0
- honeybee_radiance/properties/shademesh.py +116 -0
- honeybee_radiance/putil.py +44 -0
- honeybee_radiance/reader.py +214 -0
- honeybee_radiance/sensor.py +166 -0
- honeybee_radiance/sensorgrid.py +1008 -0
- honeybee_radiance/view.py +1101 -0
- honeybee_radiance/writer.py +951 -0
- honeybee_radiance-1.66.190.dist-info/METADATA +89 -0
- honeybee_radiance-1.66.190.dist-info/RECORD +152 -0
- honeybee_radiance-1.66.190.dist-info/WHEEL +5 -0
- honeybee_radiance-1.66.190.dist-info/entry_points.txt +2 -0
- honeybee_radiance-1.66.190.dist-info/licenses/LICENSE +661 -0
- 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)
|