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,951 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Methods to write to rad."""
|
|
3
|
+
from honeybee_radiance.sensorgrid import SensorGrid
|
|
4
|
+
from ladybug.futil import write_to_file_by_name, preparedir
|
|
5
|
+
from honeybee.config import folders
|
|
6
|
+
from honeybee.face import Face
|
|
7
|
+
from honeybee.boundarycondition import Surface
|
|
8
|
+
from honeybee.facetype import AirBoundary
|
|
9
|
+
from honeybee_radiance_folder.folder import ModelFolder
|
|
10
|
+
import honeybee_radiance_folder.config as folder_config
|
|
11
|
+
|
|
12
|
+
from .geometry import Polygon
|
|
13
|
+
from .modifier.material import aBSDF, BSDF, Trans
|
|
14
|
+
from .lib.modifiers import black
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import json
|
|
19
|
+
import shutil
|
|
20
|
+
import re
|
|
21
|
+
import itertools
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def shade_mesh_to_rad(shade_mesh, blk=False):
|
|
26
|
+
"""Generate a RAD string representation of a ShadeMesh.
|
|
27
|
+
|
|
28
|
+
Note that the resulting string does not include modifier definitions.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
shade: A honeybee ShadeMesh for which a RAD representation will be returned.
|
|
32
|
+
blk: Boolean to note whether the "blacked out" version of the Shade should
|
|
33
|
+
be output, which is useful for direct studies and isolation studies
|
|
34
|
+
to understand the contribution of individual apertures.
|
|
35
|
+
"""
|
|
36
|
+
rad_prop = shade_mesh.properties.radiance
|
|
37
|
+
modifier = rad_prop.modifier_blk if blk else rad_prop.modifier
|
|
38
|
+
base_geo = modifier.identifier + ' polygon {} 0 0 {} {}'
|
|
39
|
+
shd_id = shade_mesh.identifier
|
|
40
|
+
geo_strs = []
|
|
41
|
+
str_vertices = tuple(tuple(str(v) for v in pt.to_array())
|
|
42
|
+
for pt in shade_mesh.vertices)
|
|
43
|
+
for fi, f_geo in enumerate(shade_mesh.faces):
|
|
44
|
+
coords = tuple(v for pt in f_geo for v in str_vertices[pt])
|
|
45
|
+
poly_id = '{}_{}'.format(shd_id, fi)
|
|
46
|
+
geo_str = base_geo.format(poly_id, len(coords), ' '.join(coords))
|
|
47
|
+
geo_strs.append(geo_str)
|
|
48
|
+
return '\n'.join(geo_strs)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def shade_to_rad(shade, blk=False, minimal=False):
|
|
52
|
+
"""Generate a RAD string representation of a Shade.
|
|
53
|
+
|
|
54
|
+
Note that the resulting string does not include modifier definitions. Nor
|
|
55
|
+
does it include any states for dynamic geometry.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
shade: A honeybee Shade for which a RAD representation will be returned.
|
|
59
|
+
blk: Boolean to note whether the "blacked out" version of the Shade should
|
|
60
|
+
be output, which is useful for direct studies and isolation studies
|
|
61
|
+
to understand the contribution of individual apertures.
|
|
62
|
+
minimal: Boolean to note whether the radiance string should be written
|
|
63
|
+
in a minimal format (with spaces instead of line breaks). Default: False.
|
|
64
|
+
"""
|
|
65
|
+
rad_prop = shade.properties.radiance
|
|
66
|
+
modifier = rad_prop.modifier_blk if blk else rad_prop.modifier
|
|
67
|
+
rad_poly = Polygon(shade.identifier, shade.vertices, modifier)
|
|
68
|
+
return rad_poly.to_radiance(minimal, False, False)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def door_to_rad(door, blk=False, minimal=False):
|
|
72
|
+
"""Generate a RAD string representation of a Door.
|
|
73
|
+
|
|
74
|
+
Note that the resulting string does not include modifier definitions. Nor
|
|
75
|
+
does it include any states for dynamic geometry. However, it does include
|
|
76
|
+
any of the shades assigned to the Door.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
door: A honeybee Door for which a RAD representation will be returned.
|
|
80
|
+
blk: Boolean to note whether the "blacked out" version of the Door should
|
|
81
|
+
be output, which is useful for direct studies and isolation studies
|
|
82
|
+
to understand the contribution of individual apertures.
|
|
83
|
+
minimal: Boolean to note whether the radiance string should be written
|
|
84
|
+
in a minimal format (with spaces instead of line breaks). Default: False.
|
|
85
|
+
"""
|
|
86
|
+
rad_prop = door.properties.radiance
|
|
87
|
+
modifier = rad_prop.modifier_blk if blk else rad_prop.modifier
|
|
88
|
+
rad_poly = Polygon(door.identifier, door.vertices, modifier)
|
|
89
|
+
door_strs = [rad_poly.to_radiance(minimal, False, False)]
|
|
90
|
+
for shd in door.shades:
|
|
91
|
+
door_strs.append(shade_to_rad(shd, blk, minimal))
|
|
92
|
+
return '\n\n'.join(door_strs)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def aperture_to_rad(aperture, blk=False, minimal=False):
|
|
96
|
+
"""Generate a RAD string representation of an Aperture.
|
|
97
|
+
|
|
98
|
+
Note that the resulting string does not include modifier definitions. Nor
|
|
99
|
+
does it include any states for dynamic geometry. However, it does include
|
|
100
|
+
the shade geometry assigned to the Aperture.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
aperture: A honeybee Aperture for which a RAD representation will be returned.
|
|
104
|
+
blk: Boolean to note whether the "blacked out" version of the Aperture should
|
|
105
|
+
be output, which is useful for direct studies and isolation studies
|
|
106
|
+
to understand the contribution of individual apertures.
|
|
107
|
+
minimal: Boolean to note whether the radiance string should be written
|
|
108
|
+
in a minimal format (with spaces instead of line breaks). Default: False.
|
|
109
|
+
"""
|
|
110
|
+
rad_prop = aperture.properties.radiance
|
|
111
|
+
modifier = rad_prop.modifier_blk if blk else rad_prop.modifier
|
|
112
|
+
rad_poly = Polygon(aperture.identifier, aperture.vertices, modifier)
|
|
113
|
+
ap_strs = [rad_poly.to_radiance(minimal, False, False)]
|
|
114
|
+
for shd in aperture.shades:
|
|
115
|
+
ap_strs.append(shade_to_rad(shd, blk, minimal))
|
|
116
|
+
return '\n\n'.join(ap_strs)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def face_to_rad(face, blk=False, minimal=False, exclude_sub_faces=False):
|
|
120
|
+
"""Get Face as a Radiance string.
|
|
121
|
+
|
|
122
|
+
Note that the resulting string does not include modifier definitions. Nor
|
|
123
|
+
does it include any states for dynamic geometry. However, it does include
|
|
124
|
+
any of the shades assigned to the Face along with the Apertures and Doors
|
|
125
|
+
in the Face.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
face: A honeybee Face for which a RAD representation will be returned.
|
|
129
|
+
blk: Boolean to note whether the "blacked out" version of the Face should
|
|
130
|
+
be output, which is useful for direct studies and isolation studies
|
|
131
|
+
to understand the contribution of individual apertures.
|
|
132
|
+
minimal: Boolean to note whether the radiance string should be written
|
|
133
|
+
in a minimal format (with spaces instead of line breaks). (Default: False).
|
|
134
|
+
exclude_sub_faces:Boolean to note whether Apertures and Doors should
|
|
135
|
+
be excluded from the output string. (Default: False).
|
|
136
|
+
"""
|
|
137
|
+
rad_prop = face.properties.radiance
|
|
138
|
+
modifier = rad_prop.modifier_blk if blk else rad_prop.modifier
|
|
139
|
+
rad_poly = Polygon(face.identifier, face.punched_vertices, modifier)
|
|
140
|
+
face_strs = [rad_poly.to_radiance(minimal, False, False)]
|
|
141
|
+
for shd in face.shades:
|
|
142
|
+
face_strs.append(shade_to_rad(shd, blk, minimal))
|
|
143
|
+
if not exclude_sub_faces:
|
|
144
|
+
for dr in face.doors:
|
|
145
|
+
face_strs.append(door_to_rad(dr, blk, minimal))
|
|
146
|
+
for ap in face.apertures:
|
|
147
|
+
face_strs.append(aperture_to_rad(ap, blk, minimal))
|
|
148
|
+
return '\n\n'.join(face_strs)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def room_to_rad(room, blk=False, minimal=False):
|
|
152
|
+
"""Generate a RAD string representation of a Room.
|
|
153
|
+
|
|
154
|
+
This method will write all geometry associated with a Room including all
|
|
155
|
+
Faces, Apertures, Doors, and Shades. However, it does not include modifiers
|
|
156
|
+
for this geometry. Nor does it include any states for dynamic geometry and
|
|
157
|
+
will only write the default state for each dynamic object.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
room: A honeybee Room for which a RAD representation will be returned.
|
|
161
|
+
blk: Boolean to note whether the "blacked out" version of the geometry
|
|
162
|
+
should be output, which is useful for direct studies and isolation
|
|
163
|
+
studies to understand the contribution of individual apertures.
|
|
164
|
+
minimal: Boolean to note whether the radiance string should be written
|
|
165
|
+
in a minimal format (with spaces instead of line breaks). Default: False.
|
|
166
|
+
"""
|
|
167
|
+
room_strs = []
|
|
168
|
+
for face in room.faces:
|
|
169
|
+
room_strs.append(face_to_rad(face, blk, minimal))
|
|
170
|
+
for shd in room.shades:
|
|
171
|
+
room_strs.append(shade_to_rad(shd, blk, minimal))
|
|
172
|
+
return '\n\n'.join(room_strs)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def model_to_rad(model, blk=False, minimal=False):
|
|
176
|
+
r"""Generate a RAD string representation of a Model.
|
|
177
|
+
|
|
178
|
+
The resulting strings will include all geometry (Rooms, Faces, Shades, Apertures,
|
|
179
|
+
Doors) and all modifiers. However, it does not include any states for dynamic
|
|
180
|
+
geometry and will only write the default state for each dynamic object. To
|
|
181
|
+
correctly account for dynamic objects, the model_to_rad_folder should be used.
|
|
182
|
+
|
|
183
|
+
Note that this method will also ensure that any Faces, Apertures, or Doors
|
|
184
|
+
with Surface boundary condition only have one of such objects in the output
|
|
185
|
+
string.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
model: A honeybee Model for which a RAD representation will be returned.
|
|
189
|
+
blk: Boolean to note whether the "blacked out" version of the geometry
|
|
190
|
+
should be output, which is useful for direct studies and isolation
|
|
191
|
+
studies to understand the contribution of individual apertures.
|
|
192
|
+
minimal: Boolean to note whether the radiance string should be written
|
|
193
|
+
in a minimal format (with spaces instead of line breaks). Default: False.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A tuple of two strings.
|
|
197
|
+
|
|
198
|
+
- model_str: A radiance string that contains all geometry of the Model.
|
|
199
|
+
|
|
200
|
+
- modifier_str: A radiance string that contains all of the modifiers
|
|
201
|
+
in the model. These will be modifier_blk if blk is True.
|
|
202
|
+
"""
|
|
203
|
+
# write all modifiers into the file
|
|
204
|
+
modifier_str = ['# ============== MODIFIERS ==============\n']
|
|
205
|
+
rad_prop = model.properties.radiance
|
|
206
|
+
modifiers = rad_prop.blk_modifiers if blk else rad_prop.modifiers
|
|
207
|
+
if not blk:
|
|
208
|
+
# must be imported here to avoid circular imports
|
|
209
|
+
from .lib.modifiersets import generic_modifier_set_visible
|
|
210
|
+
modifiers = set(modifiers + generic_modifier_set_visible.modifiers_unique)
|
|
211
|
+
for mod in modifiers:
|
|
212
|
+
modifier_str.append(mod.to_radiance(minimal))
|
|
213
|
+
|
|
214
|
+
# write all Faces into the file
|
|
215
|
+
model_str = ['# ================ MODEL ================\n']
|
|
216
|
+
faces = model.faces
|
|
217
|
+
interior_faces, offset = set(), model.tolerance * -2
|
|
218
|
+
if len(faces) != 0:
|
|
219
|
+
model_str.append('# ================ FACES ================\n')
|
|
220
|
+
for face in faces:
|
|
221
|
+
if isinstance(face.boundary_condition, Surface):
|
|
222
|
+
if face.identifier in interior_faces:
|
|
223
|
+
face = face.duplicate()
|
|
224
|
+
face.move(face.normal * offset)
|
|
225
|
+
model_str.append(face_to_rad(face, blk, minimal, True))
|
|
226
|
+
else:
|
|
227
|
+
interior_faces.add(face.boundary_condition.boundary_condition_object)
|
|
228
|
+
model_str.append(face_to_rad(face, blk, minimal))
|
|
229
|
+
else:
|
|
230
|
+
model_str.append(face_to_rad(face, blk, minimal))
|
|
231
|
+
|
|
232
|
+
# write all orphaned Apertures into the file
|
|
233
|
+
apertures = model.orphaned_apertures
|
|
234
|
+
interior_aps = set()
|
|
235
|
+
if len(apertures) != 0:
|
|
236
|
+
model_str.append('# ============== APERTURES ==============\n')
|
|
237
|
+
for ap in apertures:
|
|
238
|
+
if isinstance(ap.boundary_condition, Surface):
|
|
239
|
+
if ap.identifier in interior_aps:
|
|
240
|
+
continue
|
|
241
|
+
interior_aps.add(ap.boundary_condition.boundary_condition_object)
|
|
242
|
+
model_str.append(aperture_to_rad(ap, blk, minimal))
|
|
243
|
+
|
|
244
|
+
# write all orphaned Doors into the file
|
|
245
|
+
doors = model.orphaned_doors
|
|
246
|
+
interior_drs = set()
|
|
247
|
+
if len(doors) != 0:
|
|
248
|
+
model_str.append('# ================ DOORS ================\n')
|
|
249
|
+
for dr in doors:
|
|
250
|
+
if isinstance(dr.boundary_condition, Surface):
|
|
251
|
+
if dr.identifier in interior_drs:
|
|
252
|
+
continue
|
|
253
|
+
interior_drs.add(dr.boundary_condition.boundary_condition_object)
|
|
254
|
+
model_str.append(door_to_rad(dr, blk, minimal))
|
|
255
|
+
|
|
256
|
+
# write all Room shades into the file
|
|
257
|
+
rooms = model.rooms
|
|
258
|
+
if len(rooms) != 0:
|
|
259
|
+
model_str.append('# ============== ROOM SHADES ==============\n')
|
|
260
|
+
for room in rooms:
|
|
261
|
+
for shd in room.shades:
|
|
262
|
+
model_str.append(shade_to_rad(shd, blk, minimal))
|
|
263
|
+
|
|
264
|
+
# write all orphaned Shades into the file
|
|
265
|
+
if len(model.orphaned_shades) != 0 or len(model.shade_meshes):
|
|
266
|
+
model_str.append('# ============= CONTEXT SHADES =============\n')
|
|
267
|
+
for shd in model.orphaned_shades:
|
|
268
|
+
model_str.append(shade_to_rad(shd, blk, minimal))
|
|
269
|
+
for shd_msh in model.shade_meshes:
|
|
270
|
+
model_str.append(shade_mesh_to_rad(shd_msh, blk))
|
|
271
|
+
|
|
272
|
+
return '\n\n'.join(model_str), '\n\n'.join(modifier_str)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def model_to_rad_folder(
|
|
276
|
+
model, folder=None, config_file=None, minimal=False, grids=None, views=None,
|
|
277
|
+
full_match=False
|
|
278
|
+
):
|
|
279
|
+
r"""Write a honeybee model to a rad folder.
|
|
280
|
+
|
|
281
|
+
The rad files in the resulting folders will include all geometry (Rooms, Faces,
|
|
282
|
+
Shades, Apertures, Doors), all modifiers, and all states of dynamic objects.
|
|
283
|
+
It also includes any SensorGrids and Views that are assigned to the model's
|
|
284
|
+
radiance properties.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
model: A honeybee Model for which radiance folder will be written.
|
|
288
|
+
folder: An optional folder to be used as the root of the model's
|
|
289
|
+
Radiance folder. If None, the files will be written into a sub-directory
|
|
290
|
+
of the honeybee-core default_simulation_folder. This sub-directory
|
|
291
|
+
is specifically: default_simulation_folder/[MODEL IDENTIFIER]/Radiance
|
|
292
|
+
config_file: An optional config file path to modify the default folder
|
|
293
|
+
names. If None, ``folder.cfg`` in ``honeybee-radiance-folder``
|
|
294
|
+
will be used. (Default: None).
|
|
295
|
+
minimal: Boolean to note whether the radiance strings should be written
|
|
296
|
+
in a minimal format (with spaces instead of line breaks). Default: False.
|
|
297
|
+
grids: A list of sensor grid names to filter the sensor grids in the
|
|
298
|
+
model. Use this argument to indicate specific sensor grids that should
|
|
299
|
+
be included. By default all the sensor grids will be exported. You can use
|
|
300
|
+
wildcard symbols in names. Use relative path from inside grids folder.
|
|
301
|
+
views: A list of view files that should be exported to folder. Use this argument
|
|
302
|
+
to limit what will be written to the radiance folder. You can use wildcard
|
|
303
|
+
symbols in names. Use relative path from inside views folder.
|
|
304
|
+
full_match: A boolean to filter grids and views by their identifiers as full
|
|
305
|
+
matches. Setting this to True indicates that wildcard symbols will not be
|
|
306
|
+
used in the filtering of grids and views. In this case the names of grids
|
|
307
|
+
and views are filtered as is. (Default: False).
|
|
308
|
+
"""
|
|
309
|
+
# prepare the folder for simulation
|
|
310
|
+
model_id = model.identifier
|
|
311
|
+
if folder is None:
|
|
312
|
+
folder = os.path.join(folders.default_simulation_folder, model_id, 'radiance')
|
|
313
|
+
if not os.path.isdir(folder):
|
|
314
|
+
preparedir(folder) # create the directory if it's not there
|
|
315
|
+
model_folder = ModelFolder(folder, 'model', config_file)
|
|
316
|
+
model_folder.write(folder_type=-1, cfg=folder_config.minimal, overwrite=True)
|
|
317
|
+
|
|
318
|
+
# determine the number of places to which mesh vertices will be rounded
|
|
319
|
+
dec_count = 3 # default value when there is no tolerance
|
|
320
|
+
str_tol = str(model.tolerance).split('.')
|
|
321
|
+
if len(str_tol) == 2 and str_tol[0] == '0':
|
|
322
|
+
str_tol = str_tol[-1]
|
|
323
|
+
dec_count = 0
|
|
324
|
+
for dig in str_tol:
|
|
325
|
+
if dig == '0':
|
|
326
|
+
dec_count += 1
|
|
327
|
+
else:
|
|
328
|
+
dec_count += 1
|
|
329
|
+
break
|
|
330
|
+
|
|
331
|
+
# gather and write static apertures to the folder
|
|
332
|
+
aps, aps_blk = model.properties.radiance.subfaces_by_blk()
|
|
333
|
+
mods, mods_blk, mod_combs, mod_names = _collect_modifiers(aps, aps_blk, True)
|
|
334
|
+
_write_static_files(
|
|
335
|
+
folder, model_folder.aperture_folder(full=True), 'aperture',
|
|
336
|
+
aps, aps_blk, mods, mods_blk, mod_combs, mod_names, 'Face3D', minimal)
|
|
337
|
+
|
|
338
|
+
# gather and write static faces
|
|
339
|
+
faces, faces_blk = model.properties.radiance.faces_by_blk()
|
|
340
|
+
f_mods, f_mods_blk, mod_combs, mod_names = _collect_modifiers(faces, faces_blk)
|
|
341
|
+
_write_static_files(
|
|
342
|
+
folder, model_folder.scene_folder(full=True), 'envelope',
|
|
343
|
+
faces, faces_blk, f_mods, f_mods_blk, mod_combs, mod_names,
|
|
344
|
+
'PunchedFace3D', minimal)
|
|
345
|
+
|
|
346
|
+
# gather and write static shades
|
|
347
|
+
shades, shades_blk = model.properties.radiance.shades_by_blk()
|
|
348
|
+
s_mods, s_mods_blk, mod_combs, mod_names = _collect_modifiers(shades, shades_blk)
|
|
349
|
+
_write_static_files(
|
|
350
|
+
folder, model_folder.scene_folder(full=True), 'shades',
|
|
351
|
+
shades, shades_blk, s_mods, s_mods_blk, mod_combs, mod_names, 'Face3D', minimal)
|
|
352
|
+
|
|
353
|
+
# gather and write static shade meshes
|
|
354
|
+
shade_meshes, shade_meshes_blk = model.properties.radiance.shade_meshes_by_blk()
|
|
355
|
+
sm_mods, sm_mods_blk, mod_combs, mod_names = \
|
|
356
|
+
_collect_modifiers(shade_meshes, shade_meshes_blk)
|
|
357
|
+
_write_static_files(
|
|
358
|
+
folder, model_folder.scene_folder(full=True), 'shade_meshes',
|
|
359
|
+
shade_meshes, shade_meshes_blk, sm_mods, sm_mods_blk,
|
|
360
|
+
mod_combs, mod_names, 'Mesh3D', minimal, dec_count)
|
|
361
|
+
|
|
362
|
+
# write dynamic sub-face groups (apertures and doors)
|
|
363
|
+
ext_dict = {}
|
|
364
|
+
out_subfolder = model_folder.aperture_group_folder(full=True)
|
|
365
|
+
dyn_subface = model.properties.radiance.dynamic_subface_groups
|
|
366
|
+
if len(dyn_subface) != 0:
|
|
367
|
+
preparedir(out_subfolder)
|
|
368
|
+
for group in dyn_subface:
|
|
369
|
+
if group.is_indoor:
|
|
370
|
+
# TODO: Implement dynamic interior apertures once the radiance folder
|
|
371
|
+
# structure is clear about how the "light path" should be input
|
|
372
|
+
raise NotImplementedError('Dynamic interior apertures are not currently'
|
|
373
|
+
' supported by Model.to.rad_folder.')
|
|
374
|
+
else:
|
|
375
|
+
st_d = _write_dynamic_subface_files(
|
|
376
|
+
folder, out_subfolder, group, minimal)
|
|
377
|
+
_write_mtx_files(folder, out_subfolder, group, st_d, minimal)
|
|
378
|
+
|
|
379
|
+
ext_dict[group.identifier] = st_d
|
|
380
|
+
|
|
381
|
+
_write_dynamic_json(folder, out_subfolder, ext_dict)
|
|
382
|
+
|
|
383
|
+
# write dynamic shade groups
|
|
384
|
+
out_dict = {}
|
|
385
|
+
in_dict = {}
|
|
386
|
+
out_subfolder = model_folder.dynamic_scene_folder(full=True, indoor=False)
|
|
387
|
+
in_subfolder = model_folder.dynamic_scene_folder(full=True, indoor=True)
|
|
388
|
+
dyn_shade = model.properties.radiance.dynamic_shade_groups
|
|
389
|
+
if len(dyn_shade) != 0:
|
|
390
|
+
preparedir(out_subfolder)
|
|
391
|
+
indoor_created = False
|
|
392
|
+
for group in dyn_shade:
|
|
393
|
+
if group.is_indoor:
|
|
394
|
+
if not indoor_created:
|
|
395
|
+
preparedir(in_subfolder)
|
|
396
|
+
indoor_created = True
|
|
397
|
+
st_d = _write_dynamic_shade_files(folder, in_subfolder, group, minimal)
|
|
398
|
+
in_dict[group.identifier] = st_d
|
|
399
|
+
else:
|
|
400
|
+
st_d = _write_dynamic_shade_files(folder, out_subfolder, group, minimal)
|
|
401
|
+
out_dict[group.identifier] = st_d
|
|
402
|
+
_write_dynamic_json(folder, out_subfolder, out_dict)
|
|
403
|
+
if indoor_created:
|
|
404
|
+
_write_dynamic_json(folder, in_subfolder, in_dict)
|
|
405
|
+
|
|
406
|
+
# copy all bsdfs into the bsdf folder
|
|
407
|
+
bsdf_folder = model_folder.bsdf_folder(full=True)
|
|
408
|
+
bsdf_mods = model.properties.radiance.bsdf_modifiers
|
|
409
|
+
if len(bsdf_mods) != 0:
|
|
410
|
+
preparedir(bsdf_folder)
|
|
411
|
+
bsdfs_info = []
|
|
412
|
+
for bdf_mod in bsdf_mods:
|
|
413
|
+
bsdf_name = os.path.split(bdf_mod.bsdf_file)[-1]
|
|
414
|
+
new_bsdf_path = os.path.join(bsdf_folder, bsdf_name)
|
|
415
|
+
shutil.copy(bdf_mod.bsdf_file, new_bsdf_path)
|
|
416
|
+
bsdfs_info.append(
|
|
417
|
+
{
|
|
418
|
+
'name': bdf_mod.display_name,
|
|
419
|
+
'identifier': bdf_mod.identifier,
|
|
420
|
+
'path': os.path.join(model_folder.bsdf_folder(full=False), bsdf_name)
|
|
421
|
+
})
|
|
422
|
+
bsdf_info_file = os.path.join(bsdf_folder, '_info.json')
|
|
423
|
+
with open(bsdf_info_file, 'w') as fp:
|
|
424
|
+
json.dump(bsdfs_info, fp, indent=2)
|
|
425
|
+
|
|
426
|
+
# write the assigned sensor grids and views into the correct folder
|
|
427
|
+
grid_dir = model_folder.grid_folder(full=True)
|
|
428
|
+
_write_sensor_grids(grid_dir, model, grids, full_match)
|
|
429
|
+
view_dir = model_folder.view_folder(full=True)
|
|
430
|
+
_write_views(view_dir, model, views, full_match)
|
|
431
|
+
|
|
432
|
+
model_folder.combined_receivers(auto_mtx_path=False)
|
|
433
|
+
return folder
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _write_sensor_grids(folder, model, grids_filter, full_match=False):
|
|
437
|
+
"""Write out the sensor grid files.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
folder: The sensor grids folder.
|
|
441
|
+
model: A Honeybee model.
|
|
442
|
+
grids_filter: A list of sensor grid names to filter the sensor grids in the
|
|
443
|
+
model. Use this argument to indicate specific sensor grids that should
|
|
444
|
+
be included. By default all the sensor grids will be exported. You can use
|
|
445
|
+
wildcard symbols in names. Use relative path from inside grids folder.
|
|
446
|
+
full_match: A boolean to filter grids by their identifiers as full matches.
|
|
447
|
+
(Default: False).
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
A tuple for path to _info.json and _model_grids_info.json. The first file
|
|
451
|
+
includes the information for the sensor grids that are written to the folder and
|
|
452
|
+
the second one is the information for the input sensor grids from the model.
|
|
453
|
+
|
|
454
|
+
Use ``_info.json`` for access the sensor grid information for running the
|
|
455
|
+
commands and ``_model_grids_info`` file for loading the results back to match
|
|
456
|
+
with the model. Model_grids_info has an extra key for `start_ln` which provides
|
|
457
|
+
the start line for where the sensors for this grid starts in a pts file. Unless
|
|
458
|
+
there are grids with same identifier this value will be set to 0.
|
|
459
|
+
|
|
460
|
+
"""
|
|
461
|
+
sensor_grids = model.properties.radiance.sensor_grids
|
|
462
|
+
filtered_grids = _filter_by_pattern(
|
|
463
|
+
sensor_grids, grids_filter, full_match=full_match)
|
|
464
|
+
if len(filtered_grids) != 0:
|
|
465
|
+
grids_info = []
|
|
466
|
+
preparedir(folder)
|
|
467
|
+
# group_by_identifier
|
|
468
|
+
grouped_grids = _group_by_identifier(filtered_grids)
|
|
469
|
+
for grid in grouped_grids:
|
|
470
|
+
grid.to_file(folder)
|
|
471
|
+
grids_info.append(grid.info_dict(model))
|
|
472
|
+
|
|
473
|
+
# write information file for all the grids.
|
|
474
|
+
grids_info_file = os.path.join(folder, '_info.json')
|
|
475
|
+
if (sys.version_info < (3, 0)): # we need to manually encode it as UTF-8
|
|
476
|
+
with open(grids_info_file, 'wb') as fp:
|
|
477
|
+
info_str = json.dumps(grids_info, indent=2, ensure_ascii=False)
|
|
478
|
+
fp.write(info_str.encode('utf-8'))
|
|
479
|
+
else:
|
|
480
|
+
with open(grids_info_file, 'w', encoding='utf-8') as fp:
|
|
481
|
+
json.dump(grids_info, fp, indent=2, ensure_ascii=False)
|
|
482
|
+
|
|
483
|
+
# write input grids info
|
|
484
|
+
model_grids_info = []
|
|
485
|
+
start_line = defaultdict(lambda: 0)
|
|
486
|
+
for grid in filtered_grids:
|
|
487
|
+
identifier = grid.identifier
|
|
488
|
+
grid_info = {
|
|
489
|
+
'name': identifier,
|
|
490
|
+
'identifier': identifier,
|
|
491
|
+
'count': grid.count,
|
|
492
|
+
'group': grid.group_identifier or '',
|
|
493
|
+
'full_id': grid.full_identifier,
|
|
494
|
+
'start_ln': start_line[identifier],
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
start_line[identifier] += grid.count
|
|
498
|
+
|
|
499
|
+
model_grids_info.append(grid_info)
|
|
500
|
+
|
|
501
|
+
model_grids_info_file = os.path.join(folder, '_model_grids_info.json')
|
|
502
|
+
with open(model_grids_info_file, 'w') as fp:
|
|
503
|
+
json.dump(model_grids_info, fp, indent=2)
|
|
504
|
+
|
|
505
|
+
return grids_info_file, model_grids_info_file
|
|
506
|
+
elif len(sensor_grids) != 0:
|
|
507
|
+
raise ValueError('All sensor grids were filtered out of the model folder!')
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _write_views(folder, model, views_filter, full_match=False):
|
|
511
|
+
"""Write out the view files.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
folder: The views folder.
|
|
515
|
+
model: A Honeybee model.
|
|
516
|
+
views_filter: A list of view names to filter the views in the model. Use this
|
|
517
|
+
argument to indicate specific views that should be included. By default,
|
|
518
|
+
all the views will be exported. You can use wildcard symbols in names.
|
|
519
|
+
Use relative path from inside views folder.
|
|
520
|
+
full_match: A boolean to filter views by their identifiers as full matches.
|
|
521
|
+
(Default: False).
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
The path to _info.json, which includes the information for the views that
|
|
525
|
+
are written to the folder.
|
|
526
|
+
"""
|
|
527
|
+
model_views = model.properties.radiance.views
|
|
528
|
+
filtered_views = _filter_by_pattern(model_views, views_filter, full_match=full_match)
|
|
529
|
+
if len(filtered_views) != 0:
|
|
530
|
+
preparedir(folder)
|
|
531
|
+
# group_by_identifier
|
|
532
|
+
views_info = []
|
|
533
|
+
for view in filtered_views:
|
|
534
|
+
view.to_file(folder)
|
|
535
|
+
info_file = os.path.join(folder, '{}.json'.format(view.identifier))
|
|
536
|
+
with open(info_file, 'w') as fp:
|
|
537
|
+
json.dump(view.info_dict(model), fp, indent=4)
|
|
538
|
+
|
|
539
|
+
view_info = {
|
|
540
|
+
'name': view.identifier,
|
|
541
|
+
'identifier': view.identifier,
|
|
542
|
+
'group': view.group_identifier or '',
|
|
543
|
+
'full_id': view.full_identifier
|
|
544
|
+
}
|
|
545
|
+
views_info.append(view_info)
|
|
546
|
+
|
|
547
|
+
# write information file for all the views.
|
|
548
|
+
views_info_file = os.path.join(folder, '_info.json')
|
|
549
|
+
with open(views_info_file, 'w') as fp:
|
|
550
|
+
json.dump(views_info, fp, indent=2)
|
|
551
|
+
|
|
552
|
+
return views_info_file
|
|
553
|
+
elif len(model_views) != 0:
|
|
554
|
+
raise ValueError('All views were filtered out of the model folder!')
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _write_dynamic_shade_files(folder, sub_folder, group, minimal=False):
|
|
558
|
+
"""Write out the files that need to go into any dynamic model folder.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
folder: The model folder location on this machine.
|
|
562
|
+
sub_folder: The sub-folder for the three files (relative to the model folder).
|
|
563
|
+
group: A DynamicShadeGroup object to be written into files.
|
|
564
|
+
minimal: Boolean noting whether radiance strings should be written minimally.
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
A list of dictionaries to be written into the states.json file.
|
|
568
|
+
"""
|
|
569
|
+
# destination folder for all of the radiance files
|
|
570
|
+
dest = os.path.join(folder, sub_folder)
|
|
571
|
+
|
|
572
|
+
# loop through all states and write out the .rad files for them
|
|
573
|
+
states_list = group.states_json_list
|
|
574
|
+
for state_i, file_names in enumerate(states_list):
|
|
575
|
+
default_str = group.to_radiance(state_i, direct=False, minimal=minimal)
|
|
576
|
+
direct_str = group.to_radiance(state_i, direct=True, minimal=minimal)
|
|
577
|
+
write_to_file_by_name(dest, file_names['default'].replace('./', ''), default_str)
|
|
578
|
+
write_to_file_by_name(dest, file_names['direct'].replace('./', ''), direct_str)
|
|
579
|
+
return states_list
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _write_dynamic_subface_files(folder, sub_folder, group, minimal=False):
|
|
583
|
+
"""Write out the files that need to go into any dynamic model folder.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
folder: The model folder location on this machine.
|
|
587
|
+
sub_folder: The sub-folder for the three files (relative to the model folder).
|
|
588
|
+
group: A DynamicSubFaceGroup object to be written into files.
|
|
589
|
+
minimal: Boolean noting whether radiance strings should be written minimally.
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
A list of dictionaries to be written into the states.json file.
|
|
593
|
+
"""
|
|
594
|
+
# destination folder for all of the radiance files
|
|
595
|
+
dest = os.path.join(folder, sub_folder)
|
|
596
|
+
|
|
597
|
+
# loop through all states and write out the .rad files for them
|
|
598
|
+
states_list = group.states_json_list
|
|
599
|
+
for state_i, file_names in enumerate(states_list):
|
|
600
|
+
default_str = group.to_radiance(state_i, direct=False, minimal=minimal)
|
|
601
|
+
direct_str = group.to_radiance(state_i, direct=True, minimal=minimal)
|
|
602
|
+
write_to_file_by_name(dest, file_names['default'].replace('./', ''), default_str)
|
|
603
|
+
write_to_file_by_name(dest, file_names['direct'].replace('./', ''), direct_str)
|
|
604
|
+
|
|
605
|
+
# write out the black representation of the aperture
|
|
606
|
+
black_str = group.blk_to_radiance(minimal)
|
|
607
|
+
write_to_file_by_name(dest, file_names['black'].replace('./', ''), black_str)
|
|
608
|
+
return states_list
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _write_mtx_files(folder, sub_folder, group, states_json_list, minimal=False):
|
|
612
|
+
"""Write out the mtx files needed for 3-phase simulation into a model folder.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
folder: The model folder location on this machine.
|
|
616
|
+
sub_folder: The sub-folder for the three files (relative to the model folder).
|
|
617
|
+
group: A DynamicSubFaceGroup object to be written into files.
|
|
618
|
+
states_json_list: A list to be written into the states.json file.
|
|
619
|
+
minimal: Boolean noting whether radiance strings should be written minimally.
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
A list of dictionaries to be written into the states.json file.
|
|
623
|
+
"""
|
|
624
|
+
dest = os.path.join(folder, sub_folder) # destination folder for radiance files
|
|
625
|
+
|
|
626
|
+
# check if all of the states of all of the vmtx and dmtx geometry are default
|
|
627
|
+
one_mtx = all(st.mtxs_default for obj in group.dynamic_objects
|
|
628
|
+
for st in obj.properties.radiance._states)
|
|
629
|
+
if one_mtx: # if they're all default, we can use one file
|
|
630
|
+
mtx_file = './{}..mtx.rad'.format(group.identifier)
|
|
631
|
+
|
|
632
|
+
# loop through all states and write out the .rad files for them
|
|
633
|
+
tmxt_valid = False
|
|
634
|
+
for state_i, _ in enumerate(states_json_list):
|
|
635
|
+
tmtx_bsdf = group.tmxt_bsdf(state_i)
|
|
636
|
+
if tmtx_bsdf is not None: # it's a valid state for 3-phase
|
|
637
|
+
tmxt_valid = True
|
|
638
|
+
# add the tmxt to the states_json_list
|
|
639
|
+
bsdf_name = os.path.split(tmtx_bsdf.bsdf_file)[-1]
|
|
640
|
+
states_json_list[state_i]['tmtx'] = bsdf_name
|
|
641
|
+
|
|
642
|
+
# add the vmtx and the dmtx to the states_json_list
|
|
643
|
+
if one_mtx:
|
|
644
|
+
states_json_list[state_i]['vmtx'] = mtx_file
|
|
645
|
+
states_json_list[state_i]['dmtx'] = mtx_file
|
|
646
|
+
else:
|
|
647
|
+
states_json_list[state_i]['vmtx'] = \
|
|
648
|
+
'./{}..vmtx..{}.rad'.format(group.identifier, str(state_i))
|
|
649
|
+
states_json_list[state_i]['dmtx'] = \
|
|
650
|
+
'./{}..dmtx..{}.rad'.format(group.identifier, str(state_i))
|
|
651
|
+
vmtx_str = group.vmtx_to_radiance(state_i, minimal)
|
|
652
|
+
dmtx_str = group.dmtx_to_radiance(state_i, minimal)
|
|
653
|
+
write_to_file_by_name(
|
|
654
|
+
dest, states_json_list[state_i]['vmtx'].replace('./', ''), vmtx_str)
|
|
655
|
+
write_to_file_by_name(
|
|
656
|
+
dest, states_json_list[state_i]['dmtx'].replace('./', ''), dmtx_str)
|
|
657
|
+
|
|
658
|
+
# write the single mtx file if everything is default
|
|
659
|
+
if one_mtx and tmxt_valid:
|
|
660
|
+
mtx_str = group.vmtx_to_radiance(state_i, minimal)
|
|
661
|
+
write_to_file_by_name(dest, mtx_file, mtx_str)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def _write_dynamic_json(folder, sub_folder, json_dict):
|
|
665
|
+
"""Write out the files that need to go into any dynamic model folder.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
folder: The model folder location on this machine.
|
|
669
|
+
sub_folder: The sub-folder for the three files (relative to the model folder).
|
|
670
|
+
json_dict: A dictionary to be written into the states.json file.
|
|
671
|
+
"""
|
|
672
|
+
if json_dict != {}:
|
|
673
|
+
dest_file = os.path.join(folder, sub_folder, 'states.json')
|
|
674
|
+
with open(dest_file, 'w') as fp:
|
|
675
|
+
json.dump(json_dict, fp, indent=4)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def _write_static_files(
|
|
679
|
+
folder, sub_folder, file_id, geometry, geometry_blk, modifiers, modifiers_blk,
|
|
680
|
+
mod_combs, mod_names, geo_type='Face3D', minimal=False, decimal_count=3):
|
|
681
|
+
"""Write out the three files that need to go into any static radiance model folder.
|
|
682
|
+
|
|
683
|
+
This includes a .rad, .mat, and .blk file for the folder.
|
|
684
|
+
This method will also catch any cases of BSDF modifiers and copy the XML files
|
|
685
|
+
to the bsdf folder of the model folder.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
folder: The model folder location on this machine.
|
|
689
|
+
sub_folder: The sub-folder for the three files (relative to the model folder).
|
|
690
|
+
file_id: An identifier to be used for the names of each of the files.
|
|
691
|
+
geometry: A list of geometry objects all with default blk modifiers.
|
|
692
|
+
geometry_blk: A list of geometry objects with overridden blk modifiers.
|
|
693
|
+
modifiers: A list of modifiers to write.
|
|
694
|
+
modifiers_blk: A list of modifier_blk to write.
|
|
695
|
+
mod_combs: Dictionary of modifiers from _unique_modifier_blk_combinations method.
|
|
696
|
+
mod_names: Modifier names from the _unique_modifier_blk_combinations method.
|
|
697
|
+
geo_type: Text for the type of static geometry being written (either Face3D,
|
|
698
|
+
PunchedFace3D, or Mesh3D).
|
|
699
|
+
minimal: Boolean noting whether radiance strings should be written minimally.
|
|
700
|
+
decimal_count: Integer for the number of decimal places to round mesh vertices
|
|
701
|
+
"""
|
|
702
|
+
def is_air_boundary(face):
|
|
703
|
+
return isinstance(face, Face) and isinstance(face.type, AirBoundary)
|
|
704
|
+
|
|
705
|
+
if len(geometry) != 0 or len(geometry_blk) != 0:
|
|
706
|
+
# write the strings for the geometry
|
|
707
|
+
face_strs = []
|
|
708
|
+
if geo_type == 'Face3D':
|
|
709
|
+
for face in geometry:
|
|
710
|
+
modifier = face.properties.radiance.modifier
|
|
711
|
+
rad_poly = Polygon(face.identifier, face.vertices, modifier)
|
|
712
|
+
face_strs.append(rad_poly.to_radiance(minimal, False, False))
|
|
713
|
+
for face, mod_name in zip(geometry_blk, mod_names):
|
|
714
|
+
modifier = mod_combs[mod_name][0]
|
|
715
|
+
rad_poly = Polygon(face.identifier, face.vertices, modifier)
|
|
716
|
+
face_strs.append(rad_poly.to_radiance(minimal, False, False))
|
|
717
|
+
elif geo_type == 'Mesh3D':
|
|
718
|
+
tol_f_str = '{:.' + str(decimal_count) + 'f}'
|
|
719
|
+
for shade_mesh in geometry:
|
|
720
|
+
str_vertices = tuple(tuple(tol_f_str.format(v) for v in pt.to_array())
|
|
721
|
+
for pt in shade_mesh.vertices)
|
|
722
|
+
modifier = shade_mesh.properties.radiance.modifier
|
|
723
|
+
base_geo = modifier.identifier + ' polygon {} 0 0 {} {}'
|
|
724
|
+
shd_id = shade_mesh.identifier
|
|
725
|
+
for fi, f_geo in enumerate(shade_mesh.faces):
|
|
726
|
+
coords = tuple(v for pt in f_geo for v in str_vertices[pt])
|
|
727
|
+
poly_id = '{}_{}'.format(shd_id, fi)
|
|
728
|
+
geo_str = base_geo.format(poly_id, len(coords), ' '.join(coords))
|
|
729
|
+
face_strs.append(geo_str)
|
|
730
|
+
for shade_mesh, mod_name in zip(geometry_blk, mod_names):
|
|
731
|
+
str_vertices = tuple(tuple(tol_f_str.format(v) for v in pt.to_array())
|
|
732
|
+
for pt in shade_mesh.vertices)
|
|
733
|
+
modifier = mod_combs[mod_name][0]
|
|
734
|
+
base_geo = modifier.identifier + ' polygon {} 0 0 {} {}'
|
|
735
|
+
shd_id = shade_mesh.identifier
|
|
736
|
+
for fi, f_geo in enumerate(shade_mesh.faces):
|
|
737
|
+
coords = tuple(v for pt in f_geo for v in str_vertices[pt])
|
|
738
|
+
poly_id = '{}_{}'.format(shd_id, fi)
|
|
739
|
+
geo_str = base_geo.format(poly_id, len(coords), ' '.join(coords))
|
|
740
|
+
face_strs.append(geo_str)
|
|
741
|
+
else: # assume that it is punched Face3D
|
|
742
|
+
for face in geometry:
|
|
743
|
+
if not is_air_boundary(face):
|
|
744
|
+
modifier = face.properties.radiance.modifier
|
|
745
|
+
geo = face.punched_vertices if hasattr(face, 'punched_vertices') \
|
|
746
|
+
else face.vertices
|
|
747
|
+
rad_poly = Polygon(face.identifier, geo, modifier)
|
|
748
|
+
face_strs.append(rad_poly.to_radiance(minimal, False, False))
|
|
749
|
+
for face, mod_name in zip(geometry_blk, mod_names):
|
|
750
|
+
if not is_air_boundary(face):
|
|
751
|
+
modifier = mod_combs[mod_name][0]
|
|
752
|
+
geo = face.punched_vertices if hasattr(face, 'punched_vertices') \
|
|
753
|
+
else face.vertices
|
|
754
|
+
rad_poly = Polygon(face.identifier, geo, modifier)
|
|
755
|
+
face_strs.append(rad_poly.to_radiance(minimal, False, False))
|
|
756
|
+
|
|
757
|
+
# write the strings for the modifiers
|
|
758
|
+
mod_strs = []
|
|
759
|
+
mod_blk_strs = []
|
|
760
|
+
for mod in modifiers:
|
|
761
|
+
if isinstance(mod, (aBSDF, BSDF)):
|
|
762
|
+
_process_bsdf_modifier(mod, mod_strs, minimal)
|
|
763
|
+
elif isinstance(mod, Trans):
|
|
764
|
+
r_values = (mod.r_reflectance, mod.g_reflectance, mod.b_reflectance)
|
|
765
|
+
if mod.identifier != 'air_boundary' and not \
|
|
766
|
+
all(v == 1 for v in r_values):
|
|
767
|
+
mod_strs.append(mod.to_radiance(minimal))
|
|
768
|
+
else:
|
|
769
|
+
mod_strs.append(mod.to_radiance(minimal))
|
|
770
|
+
for mod in modifiers_blk:
|
|
771
|
+
if isinstance(mod, (aBSDF, BSDF)):
|
|
772
|
+
_process_bsdf_modifier(mod, mod_blk_strs, minimal)
|
|
773
|
+
elif isinstance(mod, Trans):
|
|
774
|
+
r_values = (mod.r_reflectance, mod.g_reflectance, mod.b_reflectance)
|
|
775
|
+
if mod.identifier != 'air_boundary' and not \
|
|
776
|
+
all(v == 1 for v in r_values):
|
|
777
|
+
mod_blk_strs.append(mod.to_radiance(minimal))
|
|
778
|
+
else:
|
|
779
|
+
mod_blk_strs.append(mod.to_radiance(minimal))
|
|
780
|
+
|
|
781
|
+
# write the three files for the model sub-folder
|
|
782
|
+
dest = os.path.join(folder, sub_folder)
|
|
783
|
+
if geo_type == 'Mesh3D': # write minimum specification to reduce file size
|
|
784
|
+
write_to_file_by_name(dest, '{}.rad'.format(file_id), '\n'.join(face_strs))
|
|
785
|
+
else:
|
|
786
|
+
write_to_file_by_name(dest, '{}.rad'.format(file_id), '\n\n'.join(face_strs))
|
|
787
|
+
write_to_file_by_name(dest, '{}.mat'.format(file_id), '\n\n'.join(mod_strs))
|
|
788
|
+
write_to_file_by_name(dest, '{}.blk'.format(file_id), '\n\n'.join(mod_blk_strs))
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def _unique_modifiers(geometry_objects):
|
|
792
|
+
"""Get a list of unique modifiers across an array of geometry objects.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
geometry_objects: An array of geometry objects (Faces, Apertures,
|
|
796
|
+
Doors, Shades) for which unique modifiers will be determined.
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
A list of all unique modifiers across the input geometry_objects
|
|
800
|
+
"""
|
|
801
|
+
modifiers = []
|
|
802
|
+
for obj in geometry_objects:
|
|
803
|
+
mod = obj.properties.radiance.modifier
|
|
804
|
+
if not _instance_in_array(mod, modifiers):
|
|
805
|
+
modifiers.append(mod)
|
|
806
|
+
return list(set(modifiers))
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def _unique_modifier_blk_combinations(geometry_objects):
|
|
810
|
+
"""Get lists of unique modifier/modifier_blk combinations across geometry objects.
|
|
811
|
+
|
|
812
|
+
Args:
|
|
813
|
+
geometry_objects: An array of geometry objects (Faces, Apertures,
|
|
814
|
+
Doors, Shades) for which unique combinations of modifier and
|
|
815
|
+
modifier_blk will be determined.
|
|
816
|
+
|
|
817
|
+
Returns:
|
|
818
|
+
A tuple with two objects.
|
|
819
|
+
|
|
820
|
+
- modifier_combs: A dictionary of modifiers with identifiers of
|
|
821
|
+
modifiers as keys and tuples with two modifiers as values. Each
|
|
822
|
+
item in the dictionary represents a unique combination of modifier
|
|
823
|
+
and modifier_blk found in the objects. Both modifiers in the pair
|
|
824
|
+
have the same identifier (making them write-able into a radiance
|
|
825
|
+
folder). The first item in the tuple is the true modifier while
|
|
826
|
+
the second one is the modifier_blk.
|
|
827
|
+
|
|
828
|
+
- modifier_names: A list of modifier names with one name for each of
|
|
829
|
+
the input geometry_objects. These names can be looked up in the
|
|
830
|
+
modifier_combs dictionary to get the modifier combination for a
|
|
831
|
+
given geometry object
|
|
832
|
+
"""
|
|
833
|
+
modifier_combs = {}
|
|
834
|
+
modifier_names = []
|
|
835
|
+
for obj in geometry_objects:
|
|
836
|
+
mod = obj.properties.radiance.modifier
|
|
837
|
+
mod_blk = obj.properties.radiance.modifier_blk
|
|
838
|
+
comb_name = '{}_{}'.format(mod.identifier, mod_blk.identifier)
|
|
839
|
+
modifier_names.append(comb_name)
|
|
840
|
+
try: # test to see if the combination already exists
|
|
841
|
+
modifier_combs[comb_name]
|
|
842
|
+
except KeyError: # new combination of modifier and modifier_blk
|
|
843
|
+
new_mod = mod.duplicate()
|
|
844
|
+
new_mod.identifier = comb_name
|
|
845
|
+
new_mod_blk = mod_blk.duplicate()
|
|
846
|
+
new_mod_blk.identifier = comb_name
|
|
847
|
+
modifier_combs[comb_name] = (new_mod, new_mod_blk)
|
|
848
|
+
return modifier_combs, modifier_names
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def _collect_modifiers(geo, geo_blk, aperture=False):
|
|
852
|
+
"""Collect all modifier and modifier_blk across geometry."""
|
|
853
|
+
mods = _unique_modifiers(geo)
|
|
854
|
+
mods_blk = []
|
|
855
|
+
for mod in mods:
|
|
856
|
+
if mod.is_opaque or aperture: # static transparent apertures still use black
|
|
857
|
+
mod_blk = black.duplicate()
|
|
858
|
+
mod_blk.identifier = mod.identifier
|
|
859
|
+
mods_blk.append(mod_blk)
|
|
860
|
+
else:
|
|
861
|
+
mods_blk.append(mod)
|
|
862
|
+
mod_combs, mod_names = _unique_modifier_blk_combinations(geo_blk)
|
|
863
|
+
mods.extend([mod_comb[0] for mod_comb in mod_combs.values()])
|
|
864
|
+
mods_blk.extend([mod_comb[1] for mod_comb in mod_combs.values()])
|
|
865
|
+
return mods, mods_blk, mod_combs, mod_names
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _process_bsdf_modifier(modifier, mod_strs, minimal):
|
|
869
|
+
"""Process a BSDF modifier for a radiance model folder."""
|
|
870
|
+
bsdf_name = os.path.split(modifier.bsdf_file)[-1]
|
|
871
|
+
mod_dup = modifier.duplicate() # duplicate to avoid editing the original
|
|
872
|
+
# we must edit the hidden _bsdf_file property since the file has not yet been copied
|
|
873
|
+
mod_dup._bsdf_file = os.path.join('model', 'bsdf', bsdf_name)
|
|
874
|
+
mod_strs.append(mod_dup.to_radiance(minimal))
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def _instance_in_array(object_instance, object_array):
|
|
878
|
+
"""Check if a specific object instance is already in an array.
|
|
879
|
+
|
|
880
|
+
This can be much faster than `if object_instance in object_array`
|
|
881
|
+
when you expect to be testing a lot of the same instance of an object for
|
|
882
|
+
inclusion in an array since the builtin method uses an == operator to
|
|
883
|
+
test inclusion.
|
|
884
|
+
"""
|
|
885
|
+
for val in object_array:
|
|
886
|
+
if val is object_instance:
|
|
887
|
+
return True
|
|
888
|
+
return False
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _filter_by_pattern(input_objects, filter, full_match=False):
|
|
892
|
+
"""Filter model grids and views based on user input."""
|
|
893
|
+
if not filter or filter == '*':
|
|
894
|
+
return input_objects
|
|
895
|
+
elif len(filter) == 1 and filter[0].replace('"', '').replace("'", '').strip() == '*':
|
|
896
|
+
return input_objects
|
|
897
|
+
|
|
898
|
+
if not isinstance(filter, (list, tuple)):
|
|
899
|
+
filter = [filter]
|
|
900
|
+
if not full_match:
|
|
901
|
+
patterns = [
|
|
902
|
+
re.compile(f.strip().replace('*', '.+').replace('?', '.')) for f in filter
|
|
903
|
+
]
|
|
904
|
+
else:
|
|
905
|
+
patterns = [
|
|
906
|
+
re.compile(f.strip()) if f.startswith('^') and f.endswith('$') else
|
|
907
|
+
re.compile('^%s$' % f.strip()) for f in filter
|
|
908
|
+
]
|
|
909
|
+
indexes = []
|
|
910
|
+
|
|
911
|
+
for count, obj in enumerate(input_objects):
|
|
912
|
+
try:
|
|
913
|
+
id_ = obj.full_identifier
|
|
914
|
+
except AttributeError:
|
|
915
|
+
id_ = obj['full_id']
|
|
916
|
+
for pattern in patterns:
|
|
917
|
+
if re.search(pattern, id_):
|
|
918
|
+
indexes.append(count)
|
|
919
|
+
indexes = list(set(indexes))
|
|
920
|
+
indexes.sort()
|
|
921
|
+
|
|
922
|
+
return [input_objects[i] for i in indexes]
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def _group_by_identifier(sensor_grids):
|
|
926
|
+
"""Group sensor grids or views if they have the same full identifier."""
|
|
927
|
+
group_func = lambda grid: grid.full_identifier # noqa: E731
|
|
928
|
+
|
|
929
|
+
ordered_sensor_grids = sorted(sensor_grids, key=group_func)
|
|
930
|
+
|
|
931
|
+
# check if there is any duplicated identifiers
|
|
932
|
+
ids = {grid.full_identifier for grid in sensor_grids}
|
|
933
|
+
if len(list(ids)) == len(sensor_grids):
|
|
934
|
+
# there is no duplicated identifier - return the original list
|
|
935
|
+
return sensor_grids
|
|
936
|
+
|
|
937
|
+
updated_grids = []
|
|
938
|
+
for group_identifier, grids in itertools.groupby(ordered_sensor_grids, group_func):
|
|
939
|
+
grids = list(grids)
|
|
940
|
+
if len(grids) > 1:
|
|
941
|
+
# merge grids into one
|
|
942
|
+
sensors = []
|
|
943
|
+
for grid in grids:
|
|
944
|
+
sensors.extend(grid.sensors)
|
|
945
|
+
joined_grid = SensorGrid(grids[0].identifier, sensors)
|
|
946
|
+
joined_grid.group_identifier = grids[0].group_identifier
|
|
947
|
+
updated_grids.append(joined_grid)
|
|
948
|
+
else:
|
|
949
|
+
updated_grids.append(grids[0])
|
|
950
|
+
|
|
951
|
+
return updated_grids
|