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,261 @@
|
|
|
1
|
+
"""honeybee radiance view commands."""
|
|
2
|
+
import click
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import json
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
from honeybee_radiance_command.rpict import Rpict, RpictOptions
|
|
11
|
+
from honeybee_radiance_command.pcompos import Pcompos
|
|
12
|
+
from honeybee_radiance_command.pfilt import Pfilt
|
|
13
|
+
from honeybee_radiance_command.getinfo import Getinfo
|
|
14
|
+
|
|
15
|
+
from honeybee_radiance.view import View
|
|
16
|
+
from honeybee_radiance.config import folders
|
|
17
|
+
|
|
18
|
+
_logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group(help='Commands for generating and modifying views.')
|
|
22
|
+
def view():
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@view.command('split-count')
|
|
27
|
+
@click.argument(
|
|
28
|
+
'view-info-file',
|
|
29
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True)
|
|
30
|
+
)
|
|
31
|
+
@click.argument('cpu-count', type=int)
|
|
32
|
+
@click.option(
|
|
33
|
+
'--output-file', '-f', help='Optional file to output the integer for the number '
|
|
34
|
+
'of times to split the view. By default this will be printed to stdout',
|
|
35
|
+
type=click.File('w'), default='-', show_default=True
|
|
36
|
+
)
|
|
37
|
+
def split_count_from_cpu_count(view_info_file, cpu_count, output_file):
|
|
38
|
+
"""Get the number of times to split each view in a model using a CPU count.
|
|
39
|
+
|
|
40
|
+
\b
|
|
41
|
+
Args:
|
|
42
|
+
view_info_file: Input view info file.
|
|
43
|
+
cpu_count: Number of processes that will be used to run
|
|
44
|
+
the simulations in parallel.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
with open(view_info_file) as inf:
|
|
48
|
+
view_count = len(json.load(inf))
|
|
49
|
+
opt_split = int(cpu_count / view_count)
|
|
50
|
+
opt_split = 1 if opt_split == 0 else opt_split
|
|
51
|
+
output_file.write(str(opt_split))
|
|
52
|
+
except Exception:
|
|
53
|
+
_logger.exception('Failed to compute the view split-count from cpu-count.')
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
else:
|
|
56
|
+
sys.exit(0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@view.command('split')
|
|
60
|
+
@click.argument(
|
|
61
|
+
'view', type=click.Path(
|
|
62
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True)
|
|
63
|
+
)
|
|
64
|
+
@click.argument('count', type=int)
|
|
65
|
+
@click.option(
|
|
66
|
+
'--skip-overture/--overture', ' /-o', help='Flag to note whether an ambient '
|
|
67
|
+
'file (.amb) should be generated for an overture calculation before the view is '
|
|
68
|
+
'split into smaller views. The .amb file will have the same name as the view-file. '
|
|
69
|
+
'With an overture calculation, the ambient file (aka ambient cache) is first '
|
|
70
|
+
'populated with values. Thereby ensuring that - when reused to create an image - '
|
|
71
|
+
'Radiance uses interpolation between already calculated values rather than less '
|
|
72
|
+
'reliable extrapolation. The overture calculation has comparatively small '
|
|
73
|
+
'computation time to full rendering but is single core can become time '
|
|
74
|
+
'consuming in situations with very high numbers of rendering multiprocessors.',
|
|
75
|
+
default=True, show_default=True
|
|
76
|
+
)
|
|
77
|
+
@click.option(
|
|
78
|
+
'--resolution', '-r', default=None, type=int, show_default=True,
|
|
79
|
+
help='An optional integer for the maximum dimension of the image in pixels. '
|
|
80
|
+
'Specifying a value here will automatically lower the input --count to ensure '
|
|
81
|
+
'the resulting images can be combined to meet this dimension. If unspecified, '
|
|
82
|
+
'the --count will always be respected and the resulting images might not be '
|
|
83
|
+
'combine-able to meet a specific target dimension.'
|
|
84
|
+
)
|
|
85
|
+
@click.option(
|
|
86
|
+
'--octree', '-oct', help='Octree file for the overture calculation. This must be '
|
|
87
|
+
'specified when the overture is not skipped.', default=None, show_default=True,
|
|
88
|
+
type=click.Path(file_okay=True, dir_okay=False, resolve_path=True)
|
|
89
|
+
)
|
|
90
|
+
@click.option(
|
|
91
|
+
'--rad-params', '-rp', help='Radiance parameters for the overture calculation. '
|
|
92
|
+
'If unspecified, default rpict paramters will be used.'
|
|
93
|
+
)
|
|
94
|
+
@click.option(
|
|
95
|
+
'--folder', '-f', help='Output folder.', default='.', show_default=True,
|
|
96
|
+
type=click.Path(file_okay=False, dir_okay=True, resolve_path=True)
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
'--log-file', '-log', help='Optional log file to output the name of the newly'
|
|
100
|
+
' created views. By default the list will be printed out to stdout',
|
|
101
|
+
type=click.File('w'), default='-'
|
|
102
|
+
)
|
|
103
|
+
def split_view(view, count, resolution, skip_overture, octree, rad_params,
|
|
104
|
+
folder, log_file):
|
|
105
|
+
"""Split a radiance view file into smaller views based on count.
|
|
106
|
+
|
|
107
|
+
\b
|
|
108
|
+
Args:
|
|
109
|
+
view: Full path to input sensor view file.
|
|
110
|
+
count: Maximum number of sensors in new files. The number will be rounded to
|
|
111
|
+
closest round number for each file. For example if the input file has 21
|
|
112
|
+
sensors and input count is set to 5 this command will generate 4 files where
|
|
113
|
+
the first three files will have 5 sensors and the last file will have 6.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# correct the count to meet the target resolution
|
|
117
|
+
if resolution is not None and resolution % count != 0:
|
|
118
|
+
while resolution % count != 0:
|
|
119
|
+
count = count - 1
|
|
120
|
+
|
|
121
|
+
# split the view into smaller views
|
|
122
|
+
view_obj = View.from_file(view)
|
|
123
|
+
views = view_obj.grid(y_div_count=count)
|
|
124
|
+
views_info = []
|
|
125
|
+
for c, v in enumerate(views):
|
|
126
|
+
name = '%s_%04d' % (view_obj.identifier, c)
|
|
127
|
+
path = '%s.vf' % name
|
|
128
|
+
full_path = os.path.join(folder, path)
|
|
129
|
+
v.to_file(folder, path, mkdir=True)
|
|
130
|
+
views_info.append({
|
|
131
|
+
'name': name,
|
|
132
|
+
'path': path,
|
|
133
|
+
'full_path': full_path
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
# create the ambient cache file if specified
|
|
137
|
+
amb_file = os.path.join(folder, os.path.basename(view).replace('.vf', '.amb'))
|
|
138
|
+
if not skip_overture:
|
|
139
|
+
options = RpictOptions()
|
|
140
|
+
if rad_params:
|
|
141
|
+
options.update_from_string(rad_params.strip())
|
|
142
|
+
# overwrite default image size to be small for the ambient cache (64 x 64)
|
|
143
|
+
options.x = 64
|
|
144
|
+
options.y = 64
|
|
145
|
+
options.af = amb_file
|
|
146
|
+
|
|
147
|
+
# create command and run it to get the .amb file
|
|
148
|
+
assert octree is not None, \
|
|
149
|
+
'Octree must be specified for an overture calculation.'
|
|
150
|
+
out_file = os.path.join(
|
|
151
|
+
folder, os.path.basename(view).replace('.vf', '.unf'))
|
|
152
|
+
rpict = Rpict(options=options, output=out_file, octree=octree, view=view)
|
|
153
|
+
env = None
|
|
154
|
+
if folders.env != {}:
|
|
155
|
+
env = folders.env
|
|
156
|
+
env = dict(os.environ, **env) if env else None
|
|
157
|
+
rpict.run(env=env)
|
|
158
|
+
os.remove(out_file)
|
|
159
|
+
|
|
160
|
+
# record all of the view files that were generated
|
|
161
|
+
log_file.write(json.dumps(views_info))
|
|
162
|
+
except Exception:
|
|
163
|
+
_logger.exception('Failed to split view file.')
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
else:
|
|
166
|
+
sys.exit(0)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@view.command('merge')
|
|
170
|
+
@click.argument('input-folder', type=click.Path(
|
|
171
|
+
file_okay=False, dir_okay=True, resolve_path=True))
|
|
172
|
+
@click.argument('base-name', type=str)
|
|
173
|
+
@click.argument('extension', default='.unf', type=str)
|
|
174
|
+
@click.option('--view', '-vf', type=click.Path(
|
|
175
|
+
exists=False, file_okay=True, dir_okay=False, resolve_path=True),
|
|
176
|
+
help='Full path to the original view file.'
|
|
177
|
+
)
|
|
178
|
+
@click.option(
|
|
179
|
+
'--scale-factor', '-s', default=1, type=float, show_default=True,
|
|
180
|
+
help='A number that will be used to scale the dimensions of the output image '
|
|
181
|
+
'as it is filtered for anti-aliasing.'
|
|
182
|
+
)
|
|
183
|
+
@click.option('--folder', '-f', help='Optional output folder.',
|
|
184
|
+
default='.', show_default=True)
|
|
185
|
+
@click.option('--name', '-n', help='Optional output filename. Default is base-name.')
|
|
186
|
+
def merge_view(input_folder, base_name, extension, view, scale_factor, folder, name):
|
|
187
|
+
"""Merge several radiance HDR image files into a single file.
|
|
188
|
+
|
|
189
|
+
This command will also perform an anti-aliasing operation on the output and
|
|
190
|
+
replace the view information in the header of the merged file if a single .vf
|
|
191
|
+
file is found within the root of the input-folder.
|
|
192
|
+
|
|
193
|
+
\b
|
|
194
|
+
Args:
|
|
195
|
+
input_folder: Input folder.
|
|
196
|
+
base_name: File base name. All of the files must start with base name and
|
|
197
|
+
continue with _ and an integer values.
|
|
198
|
+
extension: File extension. [Default: .unf]
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
# view is an optional input. Make sure we can handle the case that the view
|
|
202
|
+
# file doesn't exist
|
|
203
|
+
view = None if (view and not os.path.isfile(view)) else view
|
|
204
|
+
|
|
205
|
+
# get all of the files in the folder with the given extension
|
|
206
|
+
pattern = r'{}_\d+{}'.format(base_name, extension)
|
|
207
|
+
images = sorted(f for f in os.listdir(input_folder) if re.match(pattern, f))
|
|
208
|
+
if len(images) == 0:
|
|
209
|
+
raise ValueError('Found no files to merge.')
|
|
210
|
+
name = name or base_name
|
|
211
|
+
|
|
212
|
+
# get the new dir name as view name might be group/name
|
|
213
|
+
dirname = os.path.dirname(os.path.normpath(os.path.join(folder, name)))
|
|
214
|
+
if dirname and not os.path.exists(dirname):
|
|
215
|
+
os.makedirs(dirname)
|
|
216
|
+
temp_output = os.path.join(dirname, name + '_temp.HDR')
|
|
217
|
+
output_file = os.path.join(dirname, name + '.HDR')
|
|
218
|
+
|
|
219
|
+
# set up the pcompos command
|
|
220
|
+
in_dirname = os.path.normpath(input_folder)
|
|
221
|
+
pcompos = Pcompos(output=temp_output)
|
|
222
|
+
pcompos.input = [os.path.join(in_dirname, img) for img in images]
|
|
223
|
+
pcompos.options.a = 1
|
|
224
|
+
|
|
225
|
+
# setup the pfilt command to perform anti-aliasing on the output
|
|
226
|
+
pfilt = Pfilt(input=temp_output)
|
|
227
|
+
pfilt.options.r = 0.6
|
|
228
|
+
if scale_factor != 1:
|
|
229
|
+
pfilt.options.x = '/{}'.format(scale_factor)
|
|
230
|
+
pfilt.options.y = '/{}'.format(scale_factor)
|
|
231
|
+
|
|
232
|
+
# search for a single .vf in the folder and, if it's found, grab the info
|
|
233
|
+
views = sorted(f for f in os.listdir(input_folder) if f.endswith('.vf'))
|
|
234
|
+
if view and len(views) != 1:
|
|
235
|
+
view_obj = View.from_file(view)
|
|
236
|
+
getinfo = Getinfo(output=output_file)
|
|
237
|
+
getinfo.options.a = 'VIEW= {}'.format(view_obj)
|
|
238
|
+
pfilt.pipe_to = getinfo
|
|
239
|
+
elif len(views) == 1: # replace the header with the info in the view
|
|
240
|
+
view_obj = View.from_file(os.path.join(input_folder, views[0]))
|
|
241
|
+
getinfo = Getinfo(output=output_file)
|
|
242
|
+
getinfo.options.a = 'VIEW= {}'.format(view_obj)
|
|
243
|
+
pfilt.pipe_to = getinfo
|
|
244
|
+
else: # just let the output of pfilt be the final output
|
|
245
|
+
pfilt.output = output_file
|
|
246
|
+
|
|
247
|
+
# run the commands in series
|
|
248
|
+
env = None
|
|
249
|
+
if folders.env != {}:
|
|
250
|
+
env = folders.env
|
|
251
|
+
env = dict(os.environ, **env) if env else None
|
|
252
|
+
pcompos.run(env)
|
|
253
|
+
try:
|
|
254
|
+
pfilt.run(env)
|
|
255
|
+
except RuntimeError: # the image was too bright or dark; just use pcompos output
|
|
256
|
+
shutil.copyfile(temp_output, output_file)
|
|
257
|
+
except Exception:
|
|
258
|
+
_logger.exception('Failed to merge image files.')
|
|
259
|
+
sys.exit(1)
|
|
260
|
+
else:
|
|
261
|
+
sys.exit(0)
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Commands to compute view factors to geometry."""
|
|
2
|
+
import click
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
import math
|
|
7
|
+
from itertools import islice
|
|
8
|
+
|
|
9
|
+
from honeybee_radiance.config import folders
|
|
10
|
+
from honeybee_radiance.geometry import Polygon
|
|
11
|
+
from honeybee_radiance.modifier.material import Plastic
|
|
12
|
+
|
|
13
|
+
from honeybee_radiance_command.oconv import Oconv
|
|
14
|
+
from honeybee_radiance_command.rcontrib import Rcontrib, RcontribOptions
|
|
15
|
+
from honeybee_radiance_command._command_util import run_command
|
|
16
|
+
|
|
17
|
+
from honeybee.model import Model
|
|
18
|
+
from honeybee.facetype import AirBoundary
|
|
19
|
+
from honeybee.boundarycondition import Surface
|
|
20
|
+
from ladybug.futil import preparedir
|
|
21
|
+
|
|
22
|
+
_logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group(help='Commands to compute view factors to geometry.')
|
|
26
|
+
def view_factor():
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@view_factor.command('modifiers')
|
|
31
|
+
@click.argument('model-file', type=click.Path(
|
|
32
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
|
|
33
|
+
@click.option(
|
|
34
|
+
'--exclude-sky/--include-sky', ' /-s',
|
|
35
|
+
help='Flag to note whether a sky dome should be included in the resulting octree. '
|
|
36
|
+
'The inclusion of the sky dome enables the sky view to be computed in the '
|
|
37
|
+
'resulting calculation.', default=True, show_default=True
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
'--exclude-ground/--include-ground', ' /-g',
|
|
41
|
+
help='Flag to note whether a ground dome should be included in the resulting octree.'
|
|
42
|
+
' The inclusion of the ground dome enables the ground view to be computed in the '
|
|
43
|
+
'resulting calculation.', default=True, show_default=True
|
|
44
|
+
)
|
|
45
|
+
@click.option(
|
|
46
|
+
'--individual-shades/--grouped-shades', ' /-shd',
|
|
47
|
+
help='Flag to note whether the shade geometries should be included in the '
|
|
48
|
+
'list of modifiers. Note that they are still included in the resulting octree '
|
|
49
|
+
'but are just excluded from the list of modifiers.', default=True, show_default=True
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
'--triangulate/--skip-triangulate', ' /-t', help='Flag to note whether '
|
|
53
|
+
'the Apertures and Doors of the output model should be triangulated if '
|
|
54
|
+
'they have more than 4 vertices. This triangulation is necessary to '
|
|
55
|
+
'align a model with EnergyPlus results since E+ cannot accept sub-faces '
|
|
56
|
+
'with more than 4 vertices.', default=True
|
|
57
|
+
)
|
|
58
|
+
@click.option(
|
|
59
|
+
'--folder', default='.', help='Output folder into which the modifier and '
|
|
60
|
+
'octree files will be written.'
|
|
61
|
+
)
|
|
62
|
+
@click.option(
|
|
63
|
+
'--name', default='scene', help='File name, which will be used for both the '
|
|
64
|
+
'modifiers and the octree.'
|
|
65
|
+
)
|
|
66
|
+
def create_view_factor_modifiers(
|
|
67
|
+
model_file, exclude_sky, exclude_ground, individual_shades, triangulate,
|
|
68
|
+
folder, name):
|
|
69
|
+
"""Translate a Model into an Octree and corresponding modifier list for view factors.
|
|
70
|
+
|
|
71
|
+
\b
|
|
72
|
+
Args:
|
|
73
|
+
model_file: Full path to a Model JSON file (HBJSON) or a Model pkl (HBpkl) file.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
# create the directory if it's not there
|
|
77
|
+
if not os.path.isdir(folder):
|
|
78
|
+
preparedir(folder)
|
|
79
|
+
|
|
80
|
+
# load the model and ensure the properties align with the energy model
|
|
81
|
+
model = Model.from_file(model_file)
|
|
82
|
+
original_units = None
|
|
83
|
+
if model.units != 'Meters':
|
|
84
|
+
original_units = model.units
|
|
85
|
+
model.convert_to_units('Meters')
|
|
86
|
+
for room in model.rooms:
|
|
87
|
+
room.remove_colinear_vertices_envelope(
|
|
88
|
+
tolerance=0.01, delete_degenerate=True)
|
|
89
|
+
if original_units is not None:
|
|
90
|
+
model.convert_to_units(original_units)
|
|
91
|
+
|
|
92
|
+
# triangulate the sub-faces if requested
|
|
93
|
+
if triangulate:
|
|
94
|
+
apertures, parents_to_edit = model.triangulated_apertures()
|
|
95
|
+
for tri_aps, edit_infos in zip(apertures, parents_to_edit):
|
|
96
|
+
if len(edit_infos) == 3:
|
|
97
|
+
for room in model._rooms:
|
|
98
|
+
if room.identifier == edit_infos[2]:
|
|
99
|
+
break
|
|
100
|
+
for face in room._faces:
|
|
101
|
+
if face.identifier == edit_infos[1]:
|
|
102
|
+
break
|
|
103
|
+
for i, ap in enumerate(face._apertures):
|
|
104
|
+
if ap.identifier == edit_infos[0]:
|
|
105
|
+
break
|
|
106
|
+
face._apertures.pop(i) # remove the aperture to replace
|
|
107
|
+
face._apertures.extend(tri_aps)
|
|
108
|
+
doors, parents_to_edit = model.triangulated_doors()
|
|
109
|
+
for tri_drs, edit_infos in zip(doors, parents_to_edit):
|
|
110
|
+
if len(edit_infos) == 3:
|
|
111
|
+
for room in model._rooms:
|
|
112
|
+
if room.identifier == edit_infos[2]:
|
|
113
|
+
break
|
|
114
|
+
for face in room._faces:
|
|
115
|
+
if face.identifier == edit_infos[1]:
|
|
116
|
+
break
|
|
117
|
+
for i, dr in enumerate(face._doors):
|
|
118
|
+
if dr.identifier == edit_infos[0]:
|
|
119
|
+
break
|
|
120
|
+
face._doors.pop(i) # remove the doors to replace
|
|
121
|
+
face._doors.extend(tri_drs)
|
|
122
|
+
|
|
123
|
+
# set values to be used throughout the modifier assignment
|
|
124
|
+
offset = model.tolerance * -1
|
|
125
|
+
white_plastic = Plastic('white_plastic', 1, 1, 1)
|
|
126
|
+
geo_strs, mod_strs, mod_names = [], [], []
|
|
127
|
+
|
|
128
|
+
def _add_geo_and_modifier(hb_obj):
|
|
129
|
+
"""Add a honeybee object to the geometry and modifier strings."""
|
|
130
|
+
mod_name = '%s_mod' % hb_obj.identifier
|
|
131
|
+
mod_names.append(mod_name)
|
|
132
|
+
white_plastic.identifier = mod_name
|
|
133
|
+
rad_poly = Polygon(hb_obj.identifier, hb_obj.vertices, white_plastic)
|
|
134
|
+
geo_strs.append(rad_poly.to_radiance(False, False, False))
|
|
135
|
+
mod_strs.append(white_plastic.to_radiance(True, False, False))
|
|
136
|
+
|
|
137
|
+
# loop through all geometry in the model and get radiance strings
|
|
138
|
+
for room in model.rooms:
|
|
139
|
+
for face in room.faces:
|
|
140
|
+
if not isinstance(face.type, AirBoundary):
|
|
141
|
+
if isinstance(face.boundary_condition, Surface):
|
|
142
|
+
face.move(face.normal * offset)
|
|
143
|
+
_add_geo_and_modifier(face)
|
|
144
|
+
for ap in face.apertures:
|
|
145
|
+
_add_geo_and_modifier(ap)
|
|
146
|
+
for dr in face.doors:
|
|
147
|
+
_add_geo_and_modifier(dr)
|
|
148
|
+
all_shades = model.shades + model._orphaned_faces + \
|
|
149
|
+
model._orphaned_apertures + model._orphaned_doors
|
|
150
|
+
if individual_shades:
|
|
151
|
+
for shade in all_shades:
|
|
152
|
+
_add_geo_and_modifier(shade)
|
|
153
|
+
for shade_mesh in model.shade_meshes:
|
|
154
|
+
shd_id = shade_mesh.identifier
|
|
155
|
+
str_vertices = tuple(tuple(str(v) for v in pt.to_array())
|
|
156
|
+
for pt in shade_mesh.vertices)
|
|
157
|
+
for fi, f_geo in enumerate(shade_mesh.faces):
|
|
158
|
+
coords = tuple(v for pt in f_geo for v in str_vertices[pt])
|
|
159
|
+
poly_id = '{}_{}'.format(shd_id, fi)
|
|
160
|
+
mod_name = '%s_mod' % poly_id
|
|
161
|
+
mod_names.append(mod_name)
|
|
162
|
+
white_plastic.identifier = mod_name
|
|
163
|
+
base_geo = white_plastic.identifier + ' polygon {} 0 0 {} {}'
|
|
164
|
+
geo_str = base_geo.format(poly_id, len(coords), ' '.join(coords))
|
|
165
|
+
geo_strs.append(geo_str)
|
|
166
|
+
mod_strs.append(white_plastic.to_radiance(True, False, False))
|
|
167
|
+
else:
|
|
168
|
+
white_plastic.identifier = 'shade_plastic_mod'
|
|
169
|
+
mod_names.append(white_plastic.identifier)
|
|
170
|
+
mod_strs.append(white_plastic.to_radiance(True, False, False))
|
|
171
|
+
for shade in all_shades:
|
|
172
|
+
rad_poly = Polygon(shade.identifier, shade.vertices, white_plastic)
|
|
173
|
+
geo_strs.append(rad_poly.to_radiance(False, False, False))
|
|
174
|
+
for shade_mesh in model.shade_meshes:
|
|
175
|
+
shade_mesh.properties.radiance.modifier = white_plastic
|
|
176
|
+
base_geo = white_plastic.identifier + ' polygon {} 0 0 {} {}'
|
|
177
|
+
shd_id = shade_mesh.identifier
|
|
178
|
+
str_vertices = tuple(tuple(str(v) for v in pt.to_array())
|
|
179
|
+
for pt in shade_mesh.vertices)
|
|
180
|
+
for fi, f_geo in enumerate(shade_mesh.faces):
|
|
181
|
+
coords = tuple(v for pt in f_geo for v in str_vertices[pt])
|
|
182
|
+
poly_id = '{}_{}'.format(shd_id, fi)
|
|
183
|
+
geo_str = base_geo.format(poly_id, len(coords), ' '.join(coords))
|
|
184
|
+
geo_strs.append(geo_str)
|
|
185
|
+
|
|
186
|
+
# add the ground and sky domes if requested
|
|
187
|
+
if not exclude_sky:
|
|
188
|
+
mod_names.append('sky_glow_mod')
|
|
189
|
+
mod_strs.append('void glow sky_glow_mod 0 0 4 1 1 1 0')
|
|
190
|
+
geo_strs.append('sky_glow_mod source sky_dome 0 0 4 0 0 1 180')
|
|
191
|
+
if not exclude_ground:
|
|
192
|
+
mod_names.append('ground_glow_mod')
|
|
193
|
+
mod_strs.append('void glow ground_glow_mod 0 0 4 1 1 1 0')
|
|
194
|
+
geo_strs.append('ground_glow_mod source ground_dome 0 0 4 0 0 -1 180')
|
|
195
|
+
|
|
196
|
+
# write the radiance strings to the output folder
|
|
197
|
+
geo_file = os.path.join(folder, '{}.rad'.format(name))
|
|
198
|
+
mod_file = os.path.join(folder, '{}.mod'.format(name))
|
|
199
|
+
oct_file = os.path.join(folder, '{}.oct'.format(name))
|
|
200
|
+
with open(geo_file, 'w') as gf:
|
|
201
|
+
gf.write('\n\n'.join(mod_strs + geo_strs))
|
|
202
|
+
with open(mod_file, 'w') as mf:
|
|
203
|
+
mf.write('\n'.join(mod_names))
|
|
204
|
+
|
|
205
|
+
# use the radiance files to create an octree
|
|
206
|
+
cmd = Oconv(output=oct_file, inputs=[geo_file])
|
|
207
|
+
cmd.options.f = True
|
|
208
|
+
run_command(cmd.to_radiance(), env=folders.env)
|
|
209
|
+
except Exception as e:
|
|
210
|
+
_logger.exception('Model translation failed.\n{}'.format(e))
|
|
211
|
+
sys.exit(1)
|
|
212
|
+
else:
|
|
213
|
+
sys.exit(0)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@view_factor.command('contrib')
|
|
217
|
+
@click.argument(
|
|
218
|
+
'octree', type=click.Path(exists=True, file_okay=True, resolve_path=True)
|
|
219
|
+
)
|
|
220
|
+
@click.argument(
|
|
221
|
+
'sensor-grid', type=click.Path(exists=True, file_okay=True, resolve_path=True)
|
|
222
|
+
)
|
|
223
|
+
@click.argument(
|
|
224
|
+
'modifiers', type=click.Path(exists=True, file_okay=True, resolve_path=True)
|
|
225
|
+
)
|
|
226
|
+
@click.option(
|
|
227
|
+
'--ray-count', type=click.INT, default=6, show_default=True,
|
|
228
|
+
help='The number of rays to be equally distributed over a sphere to compute '
|
|
229
|
+
'the view factor for each of the input sensors.'
|
|
230
|
+
)
|
|
231
|
+
@click.option(
|
|
232
|
+
'--rad-params', show_default=True, help='Radiance parameters.'
|
|
233
|
+
)
|
|
234
|
+
@click.option(
|
|
235
|
+
'--rad-params-locked', show_default=True, help='Protected Radiance parameters. '
|
|
236
|
+
'These values will overwrite user input rad parameters.'
|
|
237
|
+
)
|
|
238
|
+
@click.option(
|
|
239
|
+
'--folder', default='.', help='Output folder into which the modifier and '
|
|
240
|
+
'octree files will be written.'
|
|
241
|
+
)
|
|
242
|
+
@click.option(
|
|
243
|
+
'--name', default='view_factor', help='File name, which will be used for the '
|
|
244
|
+
'rebuilt sensor-grid, the matrix and the resulting CSV with view factors.'
|
|
245
|
+
)
|
|
246
|
+
def rcontrib_command_with_view_postprocess(
|
|
247
|
+
octree, sensor_grid, modifiers, ray_count, rad_params, rad_params_locked,
|
|
248
|
+
folder, name
|
|
249
|
+
):
|
|
250
|
+
"""Run rcontrib to get spherical view factors from a sensor grid.
|
|
251
|
+
|
|
252
|
+
\b
|
|
253
|
+
Args:
|
|
254
|
+
octree: Path to octree file.
|
|
255
|
+
sensor-grid: Path to sensor grid file.
|
|
256
|
+
modifiers: Path to modifiers file.
|
|
257
|
+
"""
|
|
258
|
+
try:
|
|
259
|
+
# create the directory if it's not there
|
|
260
|
+
if not os.path.isdir(folder):
|
|
261
|
+
preparedir(folder)
|
|
262
|
+
|
|
263
|
+
# generate the ray vectors to be used in the view factor calculation
|
|
264
|
+
if ray_count == 6:
|
|
265
|
+
rays = ((1, 0, 0), (0, 1, 0), (0, 0, 1), (-1, 0, 0), (0, -1, 0), (0, 0, -1))
|
|
266
|
+
else:
|
|
267
|
+
rays = _fibonacci_spiral(ray_count)
|
|
268
|
+
ray_str = [' {} {} {}\n'.format(*ray) for ray in rays]
|
|
269
|
+
|
|
270
|
+
# create a new .pts file with the view vectors
|
|
271
|
+
ray_file = os.path.abspath(os.path.join(folder, '{}.pts'.format(name)))
|
|
272
|
+
total_rays = 0
|
|
273
|
+
with open(sensor_grid) as sg_file:
|
|
274
|
+
with open(ray_file, 'w') as r_file:
|
|
275
|
+
for line in sg_file:
|
|
276
|
+
for ray in ray_str:
|
|
277
|
+
try:
|
|
278
|
+
r_file.write(' '.join(line.split()[:3]) + ray)
|
|
279
|
+
total_rays += 1
|
|
280
|
+
except Exception:
|
|
281
|
+
pass # we are at the end of the file
|
|
282
|
+
|
|
283
|
+
# set up the Rcontrib options
|
|
284
|
+
options = RcontribOptions()
|
|
285
|
+
if rad_params: # parse input radiance parameters
|
|
286
|
+
options.update_from_string(rad_params.strip())
|
|
287
|
+
if rad_params_locked: # overwrite input values with protected ones
|
|
288
|
+
options.update_from_string(rad_params_locked.strip())
|
|
289
|
+
# overwrite specific options that would otherwise break the command
|
|
290
|
+
options.M = modifiers
|
|
291
|
+
options.update_from_string('-I -V- -y {}'.format(total_rays))
|
|
292
|
+
|
|
293
|
+
# create the rcontrib command and run it
|
|
294
|
+
mtx_file = os.path.abspath(os.path.join(folder, '{}.mtx'.format(name)))
|
|
295
|
+
rcontrib = Rcontrib(options=options, octree=octree, sensors=ray_file)
|
|
296
|
+
cmd = rcontrib.to_radiance().replace('\\', '/')
|
|
297
|
+
cmd = '{} | rmtxop -fa - -c .333 .333 .334'.format(cmd)
|
|
298
|
+
cmd = '{} | getinfo - > "{}"'.format(cmd, mtx_file.replace('\\', '/'))
|
|
299
|
+
run_command(cmd, env=folders.env)
|
|
300
|
+
|
|
301
|
+
# load the resulting matrix and process the results into view factors
|
|
302
|
+
view_fac_mtx = []
|
|
303
|
+
with open(mtx_file) as mtx_data:
|
|
304
|
+
while True:
|
|
305
|
+
sens_lines = list(islice(mtx_data, ray_count))
|
|
306
|
+
if not sens_lines:
|
|
307
|
+
break
|
|
308
|
+
sens_mtx = ((float(v) for v in ln.strip().split()) for ln in sens_lines)
|
|
309
|
+
s_facs = []
|
|
310
|
+
for sens_facs in zip(*sens_mtx):
|
|
311
|
+
s_facs.append(sum(sens_facs) / (math.pi * ray_count))
|
|
312
|
+
view_fac_mtx.append(s_facs)
|
|
313
|
+
|
|
314
|
+
# write the final view factors into a CSV file
|
|
315
|
+
view_file = os.path.join(folder, '{}.csv'.format(name))
|
|
316
|
+
with open(view_file, 'w') as v_file:
|
|
317
|
+
for facs in view_fac_mtx:
|
|
318
|
+
v_file.write(','.join((str(v) for v in facs)) + '\n')
|
|
319
|
+
except Exception:
|
|
320
|
+
_logger.exception('Failed to compute view factor contributions.')
|
|
321
|
+
sys.exit(1)
|
|
322
|
+
else:
|
|
323
|
+
sys.exit(0)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _fibonacci_spiral(point_count=24):
|
|
327
|
+
"""Get points distributed uniformly across a unit spherical surface.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
point_count: Integer for the number of points to be distributed.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
List of tuple, each with 3 values representing the XYZ coordinates of
|
|
334
|
+
the points that were generated.
|
|
335
|
+
"""
|
|
336
|
+
points = []
|
|
337
|
+
phi = math.pi * (3. - math.sqrt(5.))
|
|
338
|
+
|
|
339
|
+
for i in range(point_count):
|
|
340
|
+
y = 1 - (i / float(point_count - 1)) * 2
|
|
341
|
+
radius = math.sqrt(1 - y * y)
|
|
342
|
+
theta = phi * i
|
|
343
|
+
x = math.cos(theta) * radius
|
|
344
|
+
z = math.sin(theta) * radius
|
|
345
|
+
points.append((x, y, z))
|
|
346
|
+
|
|
347
|
+
return points
|