honeybee-radiance 1.66.190__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (152) hide show
  1. honeybee_radiance/__init__.py +11 -0
  2. honeybee_radiance/__main__.py +4 -0
  3. honeybee_radiance/_extend_honeybee.py +93 -0
  4. honeybee_radiance/cli/__init__.py +88 -0
  5. honeybee_radiance/cli/dc.py +400 -0
  6. honeybee_radiance/cli/edit.py +529 -0
  7. honeybee_radiance/cli/glare.py +118 -0
  8. honeybee_radiance/cli/grid.py +859 -0
  9. honeybee_radiance/cli/lib.py +458 -0
  10. honeybee_radiance/cli/modifier.py +133 -0
  11. honeybee_radiance/cli/mtx.py +226 -0
  12. honeybee_radiance/cli/multiphase.py +1034 -0
  13. honeybee_radiance/cli/octree.py +640 -0
  14. honeybee_radiance/cli/postprocess.py +1186 -0
  15. honeybee_radiance/cli/raytrace.py +219 -0
  16. honeybee_radiance/cli/rpict.py +125 -0
  17. honeybee_radiance/cli/schedule.py +56 -0
  18. honeybee_radiance/cli/setconfig.py +63 -0
  19. honeybee_radiance/cli/sky.py +545 -0
  20. honeybee_radiance/cli/study.py +66 -0
  21. honeybee_radiance/cli/sunpath.py +331 -0
  22. honeybee_radiance/cli/threephase.py +255 -0
  23. honeybee_radiance/cli/translate.py +400 -0
  24. honeybee_radiance/cli/util.py +121 -0
  25. honeybee_radiance/cli/view.py +261 -0
  26. honeybee_radiance/cli/viewfactor.py +347 -0
  27. honeybee_radiance/config.json +6 -0
  28. honeybee_radiance/config.py +427 -0
  29. honeybee_radiance/dictutil.py +50 -0
  30. honeybee_radiance/dynamic/__init__.py +5 -0
  31. honeybee_radiance/dynamic/group.py +479 -0
  32. honeybee_radiance/dynamic/multiphase.py +557 -0
  33. honeybee_radiance/dynamic/state.py +718 -0
  34. honeybee_radiance/dynamic/stategeo.py +352 -0
  35. honeybee_radiance/geometry/__init__.py +13 -0
  36. honeybee_radiance/geometry/bubble.py +42 -0
  37. honeybee_radiance/geometry/cone.py +215 -0
  38. honeybee_radiance/geometry/cup.py +54 -0
  39. honeybee_radiance/geometry/cylinder.py +197 -0
  40. honeybee_radiance/geometry/geometrybase.py +37 -0
  41. honeybee_radiance/geometry/instance.py +40 -0
  42. honeybee_radiance/geometry/mesh.py +38 -0
  43. honeybee_radiance/geometry/polygon.py +174 -0
  44. honeybee_radiance/geometry/ring.py +214 -0
  45. honeybee_radiance/geometry/source.py +182 -0
  46. honeybee_radiance/geometry/sphere.py +178 -0
  47. honeybee_radiance/geometry/tube.py +46 -0
  48. honeybee_radiance/lib/__init__.py +1 -0
  49. honeybee_radiance/lib/_loadmodifiers.py +72 -0
  50. honeybee_radiance/lib/_loadmodifiersets.py +69 -0
  51. honeybee_radiance/lib/modifiers.py +58 -0
  52. honeybee_radiance/lib/modifiersets.py +63 -0
  53. honeybee_radiance/lightpath.py +204 -0
  54. honeybee_radiance/lightsource/__init__.py +1 -0
  55. honeybee_radiance/lightsource/_gendaylit.py +479 -0
  56. honeybee_radiance/lightsource/dictutil.py +49 -0
  57. honeybee_radiance/lightsource/ground.py +160 -0
  58. honeybee_radiance/lightsource/sky/__init__.py +7 -0
  59. honeybee_radiance/lightsource/sky/_skybase.py +177 -0
  60. honeybee_radiance/lightsource/sky/certainirradiance.py +232 -0
  61. honeybee_radiance/lightsource/sky/cie.py +378 -0
  62. honeybee_radiance/lightsource/sky/climatebased.py +501 -0
  63. honeybee_radiance/lightsource/sky/hemisphere.py +160 -0
  64. honeybee_radiance/lightsource/sky/skydome.py +113 -0
  65. honeybee_radiance/lightsource/sky/skymatrix.py +163 -0
  66. honeybee_radiance/lightsource/sky/strutil.py +34 -0
  67. honeybee_radiance/lightsource/sky/sunmatrix.py +212 -0
  68. honeybee_radiance/lightsource/sunpath.py +247 -0
  69. honeybee_radiance/modifier/__init__.py +3 -0
  70. honeybee_radiance/modifier/material/__init__.py +30 -0
  71. honeybee_radiance/modifier/material/absdf.py +477 -0
  72. honeybee_radiance/modifier/material/antimatter.py +54 -0
  73. honeybee_radiance/modifier/material/ashik2.py +51 -0
  74. honeybee_radiance/modifier/material/brtdfunc.py +81 -0
  75. honeybee_radiance/modifier/material/bsdf.py +292 -0
  76. honeybee_radiance/modifier/material/dielectric.py +53 -0
  77. honeybee_radiance/modifier/material/glass.py +431 -0
  78. honeybee_radiance/modifier/material/glow.py +246 -0
  79. honeybee_radiance/modifier/material/illum.py +51 -0
  80. honeybee_radiance/modifier/material/interface.py +49 -0
  81. honeybee_radiance/modifier/material/light.py +206 -0
  82. honeybee_radiance/modifier/material/materialbase.py +36 -0
  83. honeybee_radiance/modifier/material/metal.py +167 -0
  84. honeybee_radiance/modifier/material/metal2.py +41 -0
  85. honeybee_radiance/modifier/material/metdata.py +41 -0
  86. honeybee_radiance/modifier/material/metfunc.py +41 -0
  87. honeybee_radiance/modifier/material/mirror.py +340 -0
  88. honeybee_radiance/modifier/material/mist.py +86 -0
  89. honeybee_radiance/modifier/material/plasdata.py +58 -0
  90. honeybee_radiance/modifier/material/plasfunc.py +59 -0
  91. honeybee_radiance/modifier/material/plastic.py +354 -0
  92. honeybee_radiance/modifier/material/plastic2.py +58 -0
  93. honeybee_radiance/modifier/material/prism1.py +57 -0
  94. honeybee_radiance/modifier/material/prism2.py +48 -0
  95. honeybee_radiance/modifier/material/spotlight.py +50 -0
  96. honeybee_radiance/modifier/material/trans.py +518 -0
  97. honeybee_radiance/modifier/material/trans2.py +49 -0
  98. honeybee_radiance/modifier/material/transdata.py +50 -0
  99. honeybee_radiance/modifier/material/transfunc.py +53 -0
  100. honeybee_radiance/modifier/mixture/__init__.py +6 -0
  101. honeybee_radiance/modifier/mixture/mixdata.py +49 -0
  102. honeybee_radiance/modifier/mixture/mixfunc.py +54 -0
  103. honeybee_radiance/modifier/mixture/mixpict.py +52 -0
  104. honeybee_radiance/modifier/mixture/mixtext.py +66 -0
  105. honeybee_radiance/modifier/mixture/mixturebase.py +28 -0
  106. honeybee_radiance/modifier/modifierbase.py +40 -0
  107. honeybee_radiance/modifier/pattern/__init__.py +9 -0
  108. honeybee_radiance/modifier/pattern/brightdata.py +49 -0
  109. honeybee_radiance/modifier/pattern/brightfunc.py +47 -0
  110. honeybee_radiance/modifier/pattern/brighttext.py +81 -0
  111. honeybee_radiance/modifier/pattern/colordata.py +56 -0
  112. honeybee_radiance/modifier/pattern/colorfunc.py +47 -0
  113. honeybee_radiance/modifier/pattern/colorpict.py +54 -0
  114. honeybee_radiance/modifier/pattern/colortext.py +73 -0
  115. honeybee_radiance/modifier/pattern/patternbase.py +34 -0
  116. honeybee_radiance/modifier/texture/__init__.py +4 -0
  117. honeybee_radiance/modifier/texture/texdata.py +29 -0
  118. honeybee_radiance/modifier/texture/texfunc.py +26 -0
  119. honeybee_radiance/modifier/texture/texturebase.py +27 -0
  120. honeybee_radiance/modifierset.py +1091 -0
  121. honeybee_radiance/mutil.py +60 -0
  122. honeybee_radiance/postprocess/__init__.py +1 -0
  123. honeybee_radiance/postprocess/annual.py +108 -0
  124. honeybee_radiance/postprocess/annualdaylight.py +425 -0
  125. honeybee_radiance/postprocess/annualglare.py +201 -0
  126. honeybee_radiance/postprocess/annualirradiance.py +187 -0
  127. honeybee_radiance/postprocess/electriclight.py +119 -0
  128. honeybee_radiance/postprocess/en17037.py +261 -0
  129. honeybee_radiance/postprocess/leed.py +304 -0
  130. honeybee_radiance/postprocess/solartracking.py +90 -0
  131. honeybee_radiance/primitive.py +554 -0
  132. honeybee_radiance/properties/__init__.py +1 -0
  133. honeybee_radiance/properties/_base.py +390 -0
  134. honeybee_radiance/properties/aperture.py +197 -0
  135. honeybee_radiance/properties/door.py +198 -0
  136. honeybee_radiance/properties/face.py +123 -0
  137. honeybee_radiance/properties/model.py +1291 -0
  138. honeybee_radiance/properties/room.py +490 -0
  139. honeybee_radiance/properties/shade.py +186 -0
  140. honeybee_radiance/properties/shademesh.py +116 -0
  141. honeybee_radiance/putil.py +44 -0
  142. honeybee_radiance/reader.py +214 -0
  143. honeybee_radiance/sensor.py +166 -0
  144. honeybee_radiance/sensorgrid.py +1008 -0
  145. honeybee_radiance/view.py +1101 -0
  146. honeybee_radiance/writer.py +951 -0
  147. honeybee_radiance-1.66.190.dist-info/METADATA +89 -0
  148. honeybee_radiance-1.66.190.dist-info/RECORD +152 -0
  149. honeybee_radiance-1.66.190.dist-info/WHEEL +5 -0
  150. honeybee_radiance-1.66.190.dist-info/entry_points.txt +2 -0
  151. honeybee_radiance-1.66.190.dist-info/licenses/LICENSE +661 -0
  152. honeybee_radiance-1.66.190.dist-info/top_level.txt +1 -0
@@ -0,0 +1,859 @@
1
+ """honeybee radiance grid 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 ladybug_geometry.geometry3d import Vector3D, Face3D
11
+ from ladybug.futil import preparedir
12
+ from honeybee.model import Model
13
+ from honeybee.units import parse_distance_string
14
+ from honeybee.typing import clean_rad_string, clean_and_id_rad_string
15
+
16
+ from honeybee_radiance.sensorgrid import SensorGrid
17
+ from honeybee_radiance_folder.gridutil import redistribute_sensors, \
18
+ restore_original_distribution
19
+
20
+ _logger = logging.getLogger(__name__)
21
+
22
+
23
+ @click.group(help='Commands for generating and modifying sensor grids.')
24
+ def grid():
25
+ pass
26
+
27
+
28
+ @grid.command('split')
29
+ @click.argument('grid-file', type=click.Path(
30
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
31
+ @click.argument('count', type=int)
32
+ @click.option('--folder', help='Output folder.', default='.', show_default=True,
33
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
34
+ @click.option('--log-file', help='Optional log file to output the name of the newly'
35
+ ' created grids. By default the list will be printed out to stdout',
36
+ type=click.File('w'), default='-')
37
+ def split_grid(grid_file, count, folder, log_file):
38
+ """Split a radiance grid file into smaller grids based on maximum sensor count.
39
+
40
+ \b
41
+ Args:
42
+ grid-file: Full path to input sensor grid file.
43
+ count: Maximum number of sensors in new files. The number will be rounded to
44
+ closest round number for each file. For example if the input file has 21
45
+ sensors and input count is set to 5 this command will generate 4 files where
46
+ the first three files will have 5 sensors and the last file will have 6.
47
+ """
48
+ try:
49
+ grid = SensorGrid.from_file(grid_file)
50
+ file_count = max(1, int(round(grid.count / count)))
51
+ files = grid.to_files(folder, file_count, mkdir=True)
52
+
53
+ log_file.write(json.dumps(files))
54
+ except Exception:
55
+ _logger.exception('Failed to split grid file.')
56
+ sys.exit(1)
57
+ else:
58
+ sys.exit(0)
59
+
60
+
61
+ @grid.command('merge')
62
+ @click.argument('input-folder', type=click.Path(
63
+ file_okay=False, dir_okay=True, resolve_path=True))
64
+ @click.argument('base-name', type=str)
65
+ @click.argument('extension', default='.pts', type=str)
66
+ @click.option('--folder', help='Optional output folder.', default='.', show_default=True)
67
+ @click.option('--name', help='Optional output filename. Default is base-name.')
68
+ def merge_grid(input_folder, base_name, extension, folder, name):
69
+ """Merge several radiance files into a single file.
70
+
71
+ This command removes headers from file if it exist.
72
+
73
+ \b
74
+ Args:
75
+ input_folder: Input folder.
76
+ base_name: File base name. All of the files must start with base name and
77
+ continue with _ and an integer values.
78
+ extension: File extension. [Default: .pts]
79
+ """
80
+ try:
81
+ pattern = r'{}_\d+{}'.format(base_name, extension)
82
+ grids = sorted(f for f in os.listdir(input_folder) if re.match(pattern, f))
83
+ if len(grids) == 0:
84
+ raise ValueError('Found no file to merge.')
85
+ name = name or base_name
86
+ output_file = os.path.normpath(os.path.join(folder, name + extension))
87
+ # get the new dir name as grid name might be group/name
88
+ dirname = os.path.dirname(output_file)
89
+ if dirname and not os.path.exists(dirname):
90
+ os.makedirs(dirname)
91
+
92
+ with open(output_file, 'w') as outf:
93
+ for f in grids:
94
+ with open(os.path.join(input_folder, f)) as inf:
95
+ first_line = next(inf)
96
+ if first_line[:10] == '#?RADIANCE':
97
+ for line in inf:
98
+ if line[:7] == 'FORMAT=':
99
+ # pass next empty line
100
+ next(inf)
101
+ break
102
+ continue
103
+ else:
104
+ outf.write(first_line)
105
+ # add rest of the file to outfile
106
+ for line in inf:
107
+ outf.write(line)
108
+ except Exception:
109
+ _logger.exception('Failed to merge grid files.')
110
+ sys.exit(1)
111
+ else:
112
+ sys.exit(0)
113
+
114
+
115
+ @grid.command('split-folder')
116
+ @click.argument(
117
+ 'input-folder',
118
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
119
+ @click.argument(
120
+ 'output-folder',
121
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
122
+ @click.argument('grid-count', type=int)
123
+ @click.argument('extension', default='.pts', type=str)
124
+ @click.option(
125
+ '--grid-divisor', '-d', help='An optional integer to be divided by the grid-count '
126
+ 'to yield a final number of grids to generate. This is useful in workflows where '
127
+ 'the grid-count is being interpreted as a cpu-count but there are multiple '
128
+ 'processors acting on a single grid. To ignore this limitation set the value '
129
+ 'to 1. Default: 1.', type=int, default=1)
130
+ @click.option(
131
+ '--min-sensor-count', '-msc', help='Minimum number of sensors in each output grid. '
132
+ 'Use this number to ensure the number of sensors in output grids never gets very '
133
+ 'small. This input will override the input grid-count when specified. '
134
+ 'To ignore this limitation, set the value to 1. Default: 1.', type=int,
135
+ default=1)
136
+ @click.option(
137
+ '--grid-info-file', help='Optional input JSON file containing information about '
138
+ 'the sensor grids to be split. If unspecified, it will be assumed that this '
139
+ 'JSON already exists in the input-folder with the name _info.json', default=None,
140
+ type=click.Path(file_okay=True, dir_okay=False, resolve_path=True))
141
+ def split_grid_folder(
142
+ input_folder, output_folder, grid_count, extension,
143
+ grid_divisor, min_sensor_count, grid_info_file
144
+ ):
145
+ """Create new sensor grids folder with evenly distribute sensors.
146
+
147
+ This function creates a new folder with evenly distributed sensor grids. The folder
148
+ will include a ``_dist_info.json`` file which has the information to recreate the
149
+ original input files from this folder and the results generated based on the grids
150
+ in this folder.
151
+
152
+ ``_dist_info.json`` file includes an array of JSON objects. Each object has the
153
+ ``id`` or the original file and the distribution information. The distribution
154
+ information includes the id of the new files that the sensors has been distributed
155
+ to and the start and end line in the target file.
156
+
157
+ This file is being used to restructure the data that is generated based on the newly
158
+ created sensor grids.
159
+
160
+ .. code-block:: python
161
+
162
+ [
163
+ {
164
+ "id": "room_1",
165
+ "dist_info": [
166
+ {"id": 0, "st_ln": 0, "end_ln": 175},
167
+ {"id": 1, "st_ln": 0, "end_ln": 21}
168
+ ]
169
+ },
170
+ {
171
+ "id": "room_2",
172
+ "dist_info": [
173
+ {"id": 1, "st_ln": 22, "end_ln": 135}
174
+ ]
175
+ }
176
+ ]
177
+
178
+ \b
179
+ Args:
180
+ input_folder: Input sensor grids folder.
181
+ output_folder: A new folder to write the newly created files.
182
+ grid_count: Number of output sensor grids to be created. This number
183
+ is usually equivalent to the number of processes that will be used to run
184
+ the simulations in parallel.
185
+ extension: Extension of the files to split. The default is ``pts`` for
186
+ sensor files. Another common extension is ``csv`` for data aligned with
187
+ the sensor grids.
188
+ """
189
+ try:
190
+ if os.path.isdir(input_folder):
191
+ if grid_info_file is not None and os.path.isfile(grid_info_file):
192
+ info_file = os.path.join(input_folder, '_info.json')
193
+ shutil.copyfile(grid_info_file, info_file)
194
+ grid_count = int(grid_count / grid_divisor)
195
+ grid_count = 1 if grid_count < 1 else grid_count
196
+ redistribute_sensors(
197
+ input_folder, output_folder, grid_count, min_sensor_count,
198
+ extension=extension.replace('.', '')
199
+ )
200
+ except Exception:
201
+ _logger.exception('Failed to distribute sensor grids in folder.')
202
+ sys.exit(1)
203
+ else:
204
+ sys.exit(0)
205
+
206
+
207
+ @grid.command('merge-folder')
208
+ @click.argument(
209
+ 'input-folder',
210
+ type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True))
211
+ @click.argument(
212
+ 'output-folder',
213
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
214
+ @click.argument('extension', type=str)
215
+ @click.option(
216
+ '--dist-info', '-di',
217
+ help='An optional input for distribution information to put the grids back together '
218
+ '. Alternatively, the command will look for a _redist_info.json file inside the '
219
+ 'folder.', type=click.Path(file_okay=True, dir_okay=False, resolve_path=True)
220
+ )
221
+ def merge_grid_folder(input_folder, output_folder, extension, dist_info):
222
+ """Restructure files in a distributed folder.
223
+
224
+ \b
225
+ Args:
226
+ input_folder: Path to input folder.
227
+ output_folder: Path to the new restructured folder
228
+ extension: Extension of the files to collect data from. It will be ``pts`` for
229
+ sensor files. Another common extension is ``ill`` for the results of daylight
230
+ studies.
231
+ """
232
+ try:
233
+ # handle optional case for Functions input
234
+ if dist_info and not os.path.isfile(dist_info):
235
+ dist_info = None
236
+ restore_original_distribution(input_folder, output_folder, extension, dist_info)
237
+ except Exception:
238
+ _logger.exception('Failed to restructure data from folder.')
239
+ sys.exit(1)
240
+ else:
241
+ sys.exit(0)
242
+
243
+
244
+ @grid.command('mirror')
245
+ @click.argument('grid-file', type=click.Path(
246
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
247
+ @click.option(
248
+ '--vector', '-v', default=None, help='An optional list of three values '
249
+ '(separated by spaces) to standardize the direction of all rays in the output '
250
+ 'files. For example, inputting "0 0 1" will ensure that the output sensor files '
251
+ 'all have vectors pointing up in the base file and down in the mirrored file. If '
252
+ 'unspecified, the direction of sensors in the input file will be used.'
253
+ )
254
+ @click.option(
255
+ '--name', '-n', default='grid', help='File name, which will be incorporated into '
256
+ 'both the base grid and the mirrored grid.'
257
+ )
258
+ @click.option(
259
+ '--suffix', '-s', default='ref', show_default=True,
260
+ help='Text for the suffix to be applied to the mirrored grid file.',
261
+ )
262
+ @click.option(
263
+ '--folder', '-f', default='.', help='Output folder into which the base grid '
264
+ 'and mirrored grid files will be written.'
265
+ )
266
+ @click.option(
267
+ '--log-file', '-log', help='Optional log file to output the list of generated '
268
+ 'radiant enclosure JSONs. By default this will be printed to stdout.',
269
+ type=click.File('w'), default='-'
270
+ )
271
+ def mirror_grid(grid_file, vector, name, suffix, folder, log_file):
272
+ """Mirror a honeybee Model's SensorGrids and format them for thermal mapping.
273
+
274
+ This involves setting the direction of every sensor to point up (0, 0, 1) and
275
+ then adding a mirrored sensor grid with the same sensor positions that all
276
+ point downward. In thermal mapping workflows, the upward-pointing grids can
277
+ be used to account for direct and diffuse shortwave irradiance while the
278
+ downward pointing grids account for ground-reflected shortwave irradiance.
279
+
280
+ \b
281
+ Args:
282
+ model_json: Full path to a Model JSON file.
283
+ """
284
+ try:
285
+ # create the directory if it's not there and set up output paths
286
+ if not os.path.isdir(folder):
287
+ preparedir(folder)
288
+ base_file = os.path.join(folder, '{}.pts'.format(name))
289
+ rev_file = os.path.join(folder, '{}_{}.pts'.format(name, suffix))
290
+
291
+ # loop through the lines of the grid_file and mirror the sensors
292
+ if vector is not None and vector != '':
293
+ # process the vector if it exists
294
+ vec = [float(v) for v in vector.split()]
295
+ assert len(vec) == 3, \
296
+ 'Vector "{}" must have 3 values. Got {}.'.format(vector, len(vec))
297
+ vec_str = ' {} {} {}\n'.format(*vec)
298
+ rev_vec = [-v for v in vec]
299
+ rev_vec_str = ' {} {} {}\n'.format(*rev_vec)
300
+ # get the lines from the grid file
301
+ with open(grid_file) as sg_file:
302
+ with open(base_file, 'w') as b_file, open(rev_file, 'w') as r_file:
303
+ for line in sg_file:
304
+ origin_str = ' '.join(line.split()[:3])
305
+ b_file.write(origin_str + vec_str)
306
+ r_file.write(origin_str + rev_vec_str)
307
+ else:
308
+ # loop through each sensor and reverse the vector
309
+ with open(grid_file) as sg_file:
310
+ with open(rev_file, 'w') as r_file:
311
+ for line in sg_file:
312
+ ray_vals = line.strip().split()
313
+ origin_str = ' '.join(ray_vals[:3])
314
+ vec_vals = (-float(v) for v in ray_vals[3:])
315
+ rev_vec_str = ' {} {} {}\n'.format(*vec_vals)
316
+ r_file.write(origin_str + rev_vec_str)
317
+ # copy the input grid file to the base file location
318
+ shutil.copyfile(grid_file, base_file)
319
+
320
+ # write the resulting file paths to the log file
321
+ log_file.write(json.dumps([base_file, rev_file], indent=4))
322
+ except Exception as e:
323
+ _logger.exception('Sensor grid mirroring failed.\n{}'.format(e))
324
+ sys.exit(1)
325
+ else:
326
+ sys.exit(0)
327
+
328
+
329
+ @grid.command('from-rooms')
330
+ @click.argument('model-file', type=click.Path(
331
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
332
+ @click.option('--grid-size', '-s', help='A number for the dimension of the mesh grid '
333
+ 'cells. This can include the units of the distance (eg. 1ft) '
334
+ 'or, if no units are provided, the value will be interpreted in the '
335
+ 'honeybee model units.', type=str, default='0.5m', show_default=True)
336
+ @click.option('--offset', '-o', help='A number for the distance at which the '
337
+ 'the sensor grid should be offset from the floor. This can include the '
338
+ 'units of the distance (eg. 3ft) or, if no units are provided, the '
339
+ 'value will be interpreted in the honeybee model units.',
340
+ type=str, default='0.8m', show_default=True)
341
+ @click.option('--include-mesh/--exclude-mesh', ' /-xm', help='Flag to note whether to '
342
+ 'include a Mesh3D object that aligns with the grid positions under the '
343
+ '"mesh" property of each grid. Excluding the mesh can reduce size but '
344
+ 'will mean Radiance results cannot be visualized as colored meshes.',
345
+ default=True, show_default=True)
346
+ @click.option('--keep-out/--remove-out', ' /-out', help='Flag to note whether an extra '
347
+ 'check should be run to remove sensor points that lie outside the Room '
348
+ 'volume. Note that this can add significantly to the runtime and this '
349
+ 'check is not necessary in the case that all walls are vertical '
350
+ 'and all floors are horizontal.', default=True, show_default=True)
351
+ @click.option('--wall-offset', '-w', help='A number for the distance at which sensors '
352
+ 'close to walls should be removed. This can include the units of the '
353
+ 'distance (eg. 3ft) or, if no units are provided, the value will be '
354
+ 'interpreted in the honeybee model units. Note that this option has '
355
+ 'no effect unless the value is more than half of the grid-size.',
356
+ type=str, default='0m', show_default=True)
357
+ @click.option('--room', '-r', multiple=True, help='Room identifier to specify the '
358
+ 'room for which sensor grids should be generated. You can pass multiple '
359
+ 'rooms (each preceded by -r). By default, all rooms get sensor grids.')
360
+ @click.option('--write-json/--write-pts', ' /-pts', help='Flag to note whether output '
361
+ 'data collection should be in JSON format or the typical CSV-style format '
362
+ 'of the Radiance .pts files.', default=True, show_default=True)
363
+ @click.option('--folder', help='Optional output folder. If specified, the --output-file '
364
+ 'will be ignored and each sensor grid will be written into its own '
365
+ '.json or .pts file within the folder.', default=None,
366
+ type=click.Path(exists=True, file_okay=False,
367
+ dir_okay=True, resolve_path=True))
368
+ @click.option('--output-file', '-f', help='Optional file to output the JSON or CSV '
369
+ 'string of the sensor grids. By default this will be printed '
370
+ 'to stdout', type=click.File('w'), default='-', show_default=True)
371
+ def from_rooms(model_file, grid_size, offset, include_mesh, keep_out, wall_offset,
372
+ room, write_json, folder, output_file):
373
+ """Generate SensorGrids from the Room floors of a honeybee model.
374
+
375
+ \b
376
+ Args:
377
+ model_file: Full path to a HBJSON or HBPkl Model file.
378
+ """
379
+ try:
380
+ # re-serialize the Model and extract rooms and units
381
+ model = Model.from_file(model_file)
382
+ rooms = model.rooms if room is None or len(room) == 0 else \
383
+ [r for r in model.rooms if r.identifier in room]
384
+ grid_size = parse_distance_string(grid_size, model.units)
385
+ offset = parse_distance_string(offset, model.units)
386
+ wall_offset = parse_distance_string(wall_offset, model.units)
387
+
388
+ # loop through the rooms and generate sensor grids
389
+ sensor_grids = []
390
+ remove_out = not keep_out
391
+ for room in rooms:
392
+ sg = room.properties.radiance.generate_sensor_grid(
393
+ grid_size, offset=offset, remove_out=remove_out, wall_offset=wall_offset)
394
+ if sg is not None:
395
+ sensor_grids.append(sg)
396
+ if not include_mesh:
397
+ for sg in sensor_grids:
398
+ sg.mesh = None
399
+
400
+ # write the sensor grids to the output file or folder
401
+ if folder is None:
402
+ if write_json:
403
+ output_file.write(json.dumps([sg.to_dict() for sg in sensor_grids]))
404
+ else:
405
+ output_file.write('\n'.join([sg.to_radiance() for sg in sensor_grids]))
406
+ else:
407
+ if write_json:
408
+ for sg in sensor_grids:
409
+ sg.to_json(folder)
410
+ else:
411
+ for sg in sensor_grids:
412
+ sg.to_file(folder)
413
+ except Exception as e:
414
+ _logger.exception('Grid generation failed.\n{}'.format(e))
415
+ sys.exit(1)
416
+ else:
417
+ sys.exit(0)
418
+
419
+
420
+ @grid.command('from-rooms-radial')
421
+ @click.argument('model-file', type=click.Path(
422
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
423
+ @click.option('--grid-size', '-s', help='A number for the dimension of the '
424
+ 'radial grid. This can include the units of the distance (eg. 1ft) '
425
+ 'or, if no units are provided, the value will be interpreted in the '
426
+ 'honeybee model units.', type=str, default='0.5m', show_default=True)
427
+ @click.option('--offset', '-o', help='A number for the distance at which the '
428
+ 'the sensor grid should be offset from the floor. This can include the '
429
+ 'units of the distance (eg. 3ft) or, if no units are provided, the '
430
+ 'value will be interpreted in the honeybee model units.',
431
+ type=str, default='1.2m', show_default=True)
432
+ @click.option('--include-mesh/--exclude-mesh', ' /-xm', help='Flag to note whether to '
433
+ 'include a Mesh3D object that aligns with the grid positions under the '
434
+ '"mesh" property of each grid. Excluding the mesh can reduce size but '
435
+ 'will mean Radiance results cannot be visualized as colored meshes.',
436
+ default=True, show_default=True)
437
+ @click.option('--keep-out/--remove-out', ' /-out', help='Flag to note whether an extra '
438
+ 'check should be run to remove sensor points that lie outside the Room '
439
+ 'volume. Note that this can add significantly to the runtime and this '
440
+ 'check is not necessary in the case that all walls are vertical '
441
+ 'and all floors are horizontal.', default=True, show_default=True)
442
+ @click.option('--wall-offset', '-w', help='A number for the distance at which sensors '
443
+ 'close to walls should be removed. This can include the units of the '
444
+ 'distance (eg. 3ft) or, if no units are provided, the value will be '
445
+ 'interpreted in the honeybee model units. Note that this option has '
446
+ 'no effect unless the value is more than half of the grid-size.',
447
+ type=str, default='0m', show_default=True)
448
+ @click.option('--dir-count', '-d', help='A positive integer for the number of '
449
+ 'radial directions to be generated around each position.',
450
+ type=click.INT, default=8, show_default=True)
451
+ @click.option('--start-vector', '-v', help='An optional list of three values '
452
+ '(separated by spaces) set the start direction of the generated '
453
+ 'directions. This can be used to orient the resulting sensors to '
454
+ 'specific parts of the scene. It can also change the elevation of the '
455
+ 'resulting directions since this start vector will always be rotated in '
456
+ 'the XY plane to generate the resulting directions.',
457
+ type=str, default='0 -1 0', show_default=True)
458
+ @click.option('--mesh-radius', '-m', help='An optional number to override the radius '
459
+ 'of the meshes generated around each sensor. If unspecified, it will be '
460
+ 'equal to 45 percent of the grid-size. Set to zero to ensure no mesh is '
461
+ 'added to the resulting sensor grids.', type=float, default=None)
462
+ @click.option('--room', '-r', multiple=True, help='Room identifier to specify the '
463
+ 'room for which sensor grids should be generated. You can pass multiple '
464
+ 'rooms (each preceded by -r). By default, all rooms get sensor grids.')
465
+ @click.option('--write-json/--write-pts', ' /-pts', help='Flag to note whether output '
466
+ 'data collection should be in JSON format or the typical CSV-style format '
467
+ 'of the Radiance .pts files.', default=True, show_default=True)
468
+ @click.option('--folder', help='Optional output folder. If specified, the --output-file '
469
+ 'will be ignored and each sensor grid will be written into its own '
470
+ '.json or .pts file within the folder.', default=None,
471
+ type=click.Path(exists=True, file_okay=False,
472
+ dir_okay=True, resolve_path=True))
473
+ @click.option('--output-file', '-f', help='Optional file to output the JSON or CSV '
474
+ 'string of the sensor grids. By default this will be printed '
475
+ 'to stdout', type=click.File('w'), default='-', show_default=True)
476
+ def from_rooms_radial(
477
+ model_file, grid_size, offset, include_mesh, keep_out, wall_offset,
478
+ dir_count, start_vector, mesh_radius, room, write_json, folder, output_file):
479
+ """Generate SensorGrids of radial directions around positions from room floors.
480
+
481
+ \b
482
+ Args:
483
+ model_file: Full path to a HBJSON or HBPkl Model file.
484
+ """
485
+ try:
486
+ # re-serialize the Model and extract rooms and units
487
+ model = Model.from_file(model_file)
488
+ rooms = model.rooms if room is None or len(room) == 0 else \
489
+ [r for r in model.rooms if r.identifier in room]
490
+ grid_size = parse_distance_string(grid_size, model.units)
491
+ offset = parse_distance_string(offset, model.units)
492
+ wall_offset = parse_distance_string(wall_offset, model.units)
493
+ vec = [float(v) for v in start_vector.split()]
494
+ st_vec = Vector3D(*vec)
495
+
496
+ # loop through the rooms and generate sensor grids
497
+ sensor_grids = []
498
+ remove_out = not keep_out
499
+ for room in rooms:
500
+ sg = room.properties.radiance.generate_sensor_grid_radial(
501
+ grid_size, offset=offset, remove_out=remove_out, wall_offset=wall_offset,
502
+ dir_count=dir_count, start_vector=st_vec, mesh_radius=mesh_radius)
503
+ if sg is not None:
504
+ sensor_grids.append(sg)
505
+ if not include_mesh:
506
+ for sg in sensor_grids:
507
+ sg.mesh = None
508
+
509
+ # write the sensor grids to the output file or folder
510
+ if folder is None:
511
+ if write_json:
512
+ output_file.write(json.dumps([sg.to_dict() for sg in sensor_grids]))
513
+ else:
514
+ output_file.write('\n'.join([sg.to_radiance() for sg in sensor_grids]))
515
+ else:
516
+ if write_json:
517
+ for sg in sensor_grids:
518
+ sg.to_json(folder)
519
+ else:
520
+ for sg in sensor_grids:
521
+ sg.to_file(folder)
522
+ except Exception as e:
523
+ _logger.exception('Grid generation failed.\n{}'.format(e))
524
+ sys.exit(1)
525
+ else:
526
+ sys.exit(0)
527
+
528
+
529
+ @grid.command('from-exterior-faces')
530
+ @click.argument('model-file', type=click.Path(
531
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
532
+ @click.option('--grid-size', '-s', help='A number for the dimension of the mesh grid '
533
+ 'cells. This can include the units of the distance (eg. 1ft) '
534
+ 'or, if no units are provided, the value will be interpreted in the '
535
+ 'honeybee model units.', type=str, default='0.5m', show_default=True)
536
+ @click.option('--offset', '-o', help='A number for the distance at which the '
537
+ 'the sensor grid should be offset from the faces. This can include the '
538
+ 'units of the distance (eg. 3ft) or, if no units are provided, the '
539
+ 'value will be interpreted in the honeybee model units.',
540
+ type=str, default='0.1m', show_default=True)
541
+ @click.option('--face-type', '-t', help='Text to specify the type of face that will be '
542
+ 'used to generate grids. Note that only Faces with Outdoors boundary '
543
+ 'conditions will be used, meaning that most Floors will typically '
544
+ 'be excluded unless they represent the underside of a cantilever. '
545
+ 'Choose from Wall, Roof, Floor, All.',
546
+ type=str, default='Wall', show_default=True)
547
+ @click.option('--full-geometry/--punched-geometry', ' /-p', help='Flag to note whether '
548
+ 'the punched_geometry of the faces should be used with the areas '
549
+ 'of sub-faces removed from the grid or the full geometry should be used.',
550
+ default=True)
551
+ @click.option('--include-mesh/--exclude-mesh', ' /-xm', help='Flag to note whether to '
552
+ 'include a Mesh3D object that aligns with the grid positions under the '
553
+ '"mesh" property of each grid. Excluding the mesh can reduce size but '
554
+ 'will mean Radiance results cannot be visualized as colored meshes.',
555
+ default=True, show_default=True)
556
+ @click.option('--room', '-r', multiple=True, help='Room identifier to specify the '
557
+ 'room for which sensor grids should be generated. You can pass multiple '
558
+ 'rooms (each preceded by -r). By default, all rooms get sensor grids '
559
+ 'joined into a single grid.')
560
+ @click.option('--write-json/--write-pts', ' /-pts', help='Flag to note whether output '
561
+ 'data collection should be in JSON format or the typical CSV-style format '
562
+ 'of the Radiance .pts files.', default=True, show_default=True)
563
+ @click.option('--folder', help='Optional output folder. If specified, the --output-file '
564
+ 'will be ignored and each sensor grid will be written into its own '
565
+ '.json or .pts file within the folder.', default=None,
566
+ type=click.Path(exists=True, file_okay=False,
567
+ dir_okay=True, resolve_path=True))
568
+ @click.option('--output-file', '-f', help='Optional file to output the JSON or CSV '
569
+ 'string of the sensor grids. By default this will be printed '
570
+ 'to stdout', type=click.File('w'), default='-', show_default=True)
571
+ def from_exterior_faces(
572
+ model_file, grid_size, offset, face_type, full_geometry, include_mesh,
573
+ room, write_json, folder, output_file):
574
+ """Generate SensorGrids from the exterior Faces of a honeybee model.
575
+
576
+ \b
577
+ Args:
578
+ model_file: Full path to a HBJSON or HBPkl Model file.
579
+ """
580
+ try:
581
+ # re-serialize the Model and extract rooms and units
582
+ model = Model.from_file(model_file)
583
+ rooms = None if room is None or len(room) == 0 else \
584
+ [r for r in model.rooms if r.identifier in room]
585
+ grid_size = parse_distance_string(grid_size, model.units)
586
+ offset = parse_distance_string(offset, model.units)
587
+ punched_geometry = not full_geometry
588
+
589
+ # loop through the rooms and generate sensor grids
590
+ sensor_grids = []
591
+ if rooms is None:
592
+ sg = model.properties.radiance.generate_exterior_face_sensor_grid(
593
+ grid_size, offset=offset, face_type=face_type,
594
+ punched_geometry=punched_geometry)
595
+ sensor_grids.append(sg)
596
+ else:
597
+ for room in rooms:
598
+ sg = room.properties.radiance.generate_exterior_face_sensor_grid(
599
+ grid_size, offset=offset, face_type=face_type,
600
+ punched_geometry=punched_geometry)
601
+ if sg is not None:
602
+ sensor_grids.append(sg)
603
+ if not include_mesh:
604
+ for sg in sensor_grids:
605
+ sg.mesh = None
606
+
607
+ # write the sensor grids to the output file or folder
608
+ if folder is None:
609
+ if write_json:
610
+ output_file.write(json.dumps([sg.to_dict() for sg in sensor_grids]))
611
+ else:
612
+ output_file.write('\n'.join([sg.to_radiance() for sg in sensor_grids]))
613
+ else:
614
+ if write_json:
615
+ for sg in sensor_grids:
616
+ sg.to_json(folder)
617
+ else:
618
+ for sg in sensor_grids:
619
+ sg.to_file(folder)
620
+ except Exception as e:
621
+ _logger.exception('Grid generation failed.\n{}'.format(e))
622
+ sys.exit(1)
623
+ else:
624
+ sys.exit(0)
625
+
626
+
627
+ @grid.command('from-exterior-apertures')
628
+ @click.argument('model-file', type=click.Path(
629
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
630
+ @click.option('--grid-size', '-s', help='A number for the dimension of the mesh grid '
631
+ 'cells. This can include the units of the distance (eg. 1ft) '
632
+ 'or, if no units are provided, the value will be interpreted in the '
633
+ 'honeybee model units.', type=str, default='0.5m', show_default=True)
634
+ @click.option('--offset', '-o', help='A number for the distance at which the '
635
+ 'the sensor grid should be offset from the apertures. This can include the'
636
+ ' units of the distance (eg. 3ft) or, if no units are provided, the '
637
+ 'value will be interpreted in the honeybee model units.',
638
+ type=str, default='0.1m', show_default=True)
639
+ @click.option('--aperture-type', '-t', help='Text to specify the type of aperture that '
640
+ 'will be used to generate grids. Note that only Faces with Outdoors '
641
+ 'boundary conditions will be used, meaning that most Floors will typically'
642
+ ' be excluded unless they represent the underside of a cantilever. '
643
+ 'Choose from Window, Skylight, All.',
644
+ type=str, default='All', show_default=True)
645
+ @click.option('--include-mesh/--exclude-mesh', ' /-xm', help='Flag to note whether to '
646
+ 'include a Mesh3D object that aligns with the grid positions under the '
647
+ '"mesh" property of each grid. Excluding the mesh can reduce size but '
648
+ 'will mean Radiance results cannot be visualized as colored meshes.',
649
+ default=True, show_default=True)
650
+ @click.option('--room', '-r', multiple=True, help='Room identifier to specify the '
651
+ 'room for which sensor grids should be generated. You can pass multiple '
652
+ 'rooms (each preceded by -r). By default, all rooms get sensor grids '
653
+ 'joined into a single grid.')
654
+ @click.option('--write-json/--write-pts', ' /-pts', help='Flag to note whether output '
655
+ 'data collection should be in JSON format or the typical CSV-style format '
656
+ 'of the Radiance .pts files.', default=True, show_default=True)
657
+ @click.option('--folder', help='Optional output folder. If specified, the --output-file '
658
+ 'will be ignored and each sensor grid will be written into its own '
659
+ '.json or .pts file within the folder.', default=None,
660
+ type=click.Path(exists=True, file_okay=False,
661
+ dir_okay=True, resolve_path=True))
662
+ @click.option('--output-file', '-f', help='Optional file to output the JSON or CSV '
663
+ 'string of the sensor grids. By default this will be printed '
664
+ 'to stdout', type=click.File('w'), default='-', show_default=True)
665
+ def from_exterior_apertures(
666
+ model_file, grid_size, offset, aperture_type, include_mesh,
667
+ room, write_json, folder, output_file):
668
+ """Generate SensorGrids from the exterior Faces of a honeybee model.
669
+
670
+ \b
671
+ Args:
672
+ model_file: Full path to a HBJSON or HBPkl Model file.
673
+ """
674
+ try:
675
+ # re-serialize the Model and extract rooms and units
676
+ model = Model.from_file(model_file)
677
+ rooms = None if room is None or len(room) == 0 else \
678
+ [r for r in model.rooms if r.identifier in room]
679
+ grid_size = parse_distance_string(grid_size, model.units)
680
+ offset = parse_distance_string(offset, model.units)
681
+
682
+ # loop through the rooms and generate sensor grids
683
+ sensor_grids = []
684
+ if rooms is None:
685
+ sg = model.properties.radiance.generate_exterior_aperture_sensor_grid(
686
+ grid_size, offset=offset, aperture_type=aperture_type)
687
+ sensor_grids.append(sg)
688
+ else:
689
+ for room in rooms:
690
+ sg = room.properties.radiance.generate_exterior_aperture_sensor_grid(
691
+ grid_size, offset=offset, aperture_type=aperture_type)
692
+ if sg is not None:
693
+ sensor_grids.append(sg)
694
+ if not include_mesh:
695
+ for sg in sensor_grids:
696
+ sg.mesh = None
697
+
698
+ # write the sensor grids to the output file or folder
699
+ if folder is None:
700
+ if write_json:
701
+ output_file.write(json.dumps([sg.to_dict() for sg in sensor_grids]))
702
+ else:
703
+ output_file.write('\n'.join([sg.to_radiance() for sg in sensor_grids]))
704
+ else:
705
+ if write_json:
706
+ for sg in sensor_grids:
707
+ sg.to_json(folder)
708
+ else:
709
+ for sg in sensor_grids:
710
+ sg.to_file(folder)
711
+ except Exception as e:
712
+ _logger.exception('Grid generation failed.\n{}'.format(e))
713
+ sys.exit(1)
714
+ else:
715
+ sys.exit(0)
716
+
717
+
718
+ @grid.command('from-face3ds')
719
+ @click.argument('face3d-file', type=click.Path(
720
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
721
+ @click.option('--grid-name', '-n', help='Text string for the name of the SensorGrid, '
722
+ 'which will also be used to assign SensorGrid ID. This will be used to '
723
+ 'identify the object across a model and in the exported Radiance files '
724
+ 'so it is recommended that it be relatively unique. If unspecified, '
725
+ 'a random name will be generated.', type=str, default=None)
726
+ @click.option('--grid-size', '-s', help='A number for the dimension of the mesh grid '
727
+ 'cells. This can include the units of the distance (eg. 1ft) '
728
+ 'or, if no units are provided, the value will be interpreted in the '
729
+ '--units.', type=str, default='0.5m', show_default=True)
730
+ @click.option('--offset', '-o', help='A number for the distance at which the the sensor '
731
+ 'grid should be offset from the base geometry. This can include the'
732
+ ' units of the distance (eg. 3ft) or, if no units are provided, the '
733
+ 'value will be interpreted in the --units.',
734
+ type=str, default='0', show_default=True)
735
+ @click.option('--units', '-u', help=' Text for the units system in which the Face3D '
736
+ 'geometry exists, which will be used to interpret the --grid-size and '
737
+ '--offset inputs. Must be (Meters, Millimeters, Feet, Inches, '
738
+ 'Centimeters).', type=str, default='Meters', show_default=True)
739
+ @click.option('--no-flip/--flip', ' /-fl', help='Flag to note whether the mesh '
740
+ 'normals should be reversed from the direction of the face geometries'
741
+ 'and the --offset move the sensors in the opposite direction from the '
742
+ 'face normals.', default=True, show_default=True)
743
+ @click.option('--include-mesh/--exclude-mesh', ' /-xm', help='Flag to note whether to '
744
+ 'include a Mesh3D object that aligns with the grid positions under the '
745
+ '"mesh" property of each grid. Excluding the mesh can reduce size but '
746
+ 'will mean Radiance results cannot be visualized as colored meshes.',
747
+ default=True, show_default=True)
748
+ @click.option('--write-json/--write-pts', ' /-pts', help='Flag to note whether output '
749
+ 'data collection should be in JSON format or the typical CSV-style format '
750
+ 'of the Radiance .pts files.', default=True, show_default=True)
751
+ @click.option('--folder', help='Optional output folder. If specified, the --output-file '
752
+ 'will be ignored and each sensor grid will be written into its own '
753
+ '.json or .pts file within the folder.', default=None,
754
+ type=click.Path(exists=True, file_okay=False,
755
+ dir_okay=True, resolve_path=True))
756
+ @click.option('--output-file', '-f', help='Optional file to output the JSON or CSV '
757
+ 'string of the sensor grids. By default this will be printed '
758
+ 'to stdout', type=click.File('w'), default='-', show_default=True)
759
+ def from_face3ds(
760
+ face3d_file, grid_name, grid_size, offset, units, no_flip,
761
+ include_mesh, write_json, folder, output_file):
762
+ """Generate a SensorGrid from a JSON array of Face3D objects.
763
+
764
+ \b
765
+ Args:
766
+ face3d_file: Full path to a JSON file containing an array of Face3D objects
767
+ that will be used to generate the sensor grid. This could also be a
768
+ nested array (list of lists of Face3Ds), in which case a separate
769
+ SensorGrid will be computed for each sub-list.
770
+ """
771
+ try:
772
+ # re-serialize the Face3Ds
773
+ with open(face3d_file) as inf:
774
+ data = json.load(inf)
775
+ face_arrays = []
776
+ for obj in data:
777
+ if isinstance(obj, list):
778
+ face_arrays.append([Face3D.from_dict(f) for f in obj])
779
+ else: # assume that it is a single Face3D
780
+ face_arrays.append([Face3D.from_dict(obj)])
781
+
782
+ # process all of the other inputs
783
+ grid_size = parse_distance_string(grid_size, units)
784
+ offset = parse_distance_string(offset, units)
785
+ base_id = clean_rad_string(grid_name) if grid_name is not None \
786
+ else clean_and_id_rad_string('SensorGrid')
787
+ flip = not no_flip
788
+
789
+ # loop through the face3ds and generate sensor grids
790
+ sensor_grids = []
791
+ for i, faces in enumerate(face_arrays):
792
+ grid_id = base_id if len(face_arrays) == 1 else '{}_{}'.format(base_id, i)
793
+ try:
794
+ sensor_grids.append(
795
+ SensorGrid.from_face3d(grid_id, faces, grid_size, None, offset, flip)
796
+ )
797
+ except AssertionError: # none of the Face3Ds make a valid grid
798
+ continue
799
+ if not include_mesh:
800
+ for sg in sensor_grids:
801
+ sg.mesh = None
802
+
803
+ # write the sensor grids to the output file or folder
804
+ if folder is None:
805
+ if write_json:
806
+ output_file.write(json.dumps([sg.to_dict() for sg in sensor_grids]))
807
+ else:
808
+ output_file.write('\n'.join([sg.to_radiance() for sg in sensor_grids]))
809
+ else:
810
+ if write_json:
811
+ for sg in sensor_grids:
812
+ sg.to_json(folder)
813
+ else:
814
+ for sg in sensor_grids:
815
+ sg.to_file(folder)
816
+ except Exception as e:
817
+ _logger.exception('Grid generation failed.\n{}'.format(e))
818
+ sys.exit(1)
819
+ else:
820
+ sys.exit(0)
821
+
822
+
823
+ @grid.command('enclosure-info')
824
+ @click.argument('model-file', type=click.Path(
825
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
826
+ @click.argument('grid-file', type=click.Path(
827
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
828
+ @click.option(
829
+ '--air-boundary-distance', '-d', help='A number to set the distance from air '
830
+ 'boundaries over which values should be interpolated. Using 0 will assume a '
831
+ 'hard edge between Rooms of the same radiant enclosures. This can include the '
832
+ 'units of the distance (eg. 3ft) or, if no units are provided the value will '
833
+ 'be interpreted in the honeybee model units.',
834
+ type=str, default='2m', show_default=True)
835
+ @click.option(
836
+ '--output-file', '-f', help='Optional output file for the generated radiant '
837
+ 'enclosure JSON. By default this will be printed to stdout',
838
+ type=click.File('w'), default='-'
839
+ )
840
+ def enclosure_info_grid(model_file, grid_file, air_boundary_distance, output_file):
841
+ """Get a JSON of radiant enclosure information from a .pts file of a sensor grid.
842
+
843
+ \b
844
+ Args:
845
+ model_file: Full path to a Model JSON file (HBJSON) or a Model pkl (HBpkl) file.
846
+ grid-file: Full path to a sensor grid file (.pts).
847
+ """
848
+ try:
849
+ # re-serialize the Model
850
+ model = Model.from_file(model_file)
851
+ grid = SensorGrid.from_file(grid_file)
852
+ ab_distance = parse_distance_string(air_boundary_distance, model.units)
853
+ # write out the list of radiant enclosure JSON info
854
+ output_file.write(json.dumps(grid.enclosure_info_dict(model, ab_distance)))
855
+ except Exception as e:
856
+ _logger.exception('Creation of radiant enclosure info failed.\n{}'.format(e))
857
+ sys.exit(1)
858
+ else:
859
+ sys.exit(0)