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,1101 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
u"""Create a Radiance view."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
from .lightpath import light_path_from_room
|
|
6
|
+
|
|
7
|
+
from honeybee_radiance_command.options import TupleOption, \
|
|
8
|
+
StringOptionJoined, NumericOption
|
|
9
|
+
import honeybee.typing as typing
|
|
10
|
+
import ladybug_geometry.geometry3d.pointvector as pv
|
|
11
|
+
from ladybug_geometry.geometry3d.plane import Plane
|
|
12
|
+
import ladybug.futil as futil
|
|
13
|
+
|
|
14
|
+
import math
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import collections
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class View(object):
|
|
21
|
+
u"""A Radiance view.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
identifier: Text string for a unique View ID. Must not contain spaces
|
|
25
|
+
or special characters. This will be used to identify the object across
|
|
26
|
+
a model and in the exported Radiance files.
|
|
27
|
+
position: Set the view position (-vp) to (x, y, z). This is the focal
|
|
28
|
+
point of a perspective view or the center of a parallel projection.
|
|
29
|
+
Default: (0, 0, 0)
|
|
30
|
+
direction: Set the view direction (-vd) vector to (x, y, z). The
|
|
31
|
+
length of this vector indicates the focal distance as needed by
|
|
32
|
+
the pixel depth of field (-pd) in rpict. Default: (0, 0, 1)
|
|
33
|
+
up_vector: Set the view up (-vu) vector (vertical direction) to
|
|
34
|
+
(x, y, z) default: (0, 1, 0).
|
|
35
|
+
type: A single character for the view type (-vt). Choose from the following.
|
|
36
|
+
|
|
37
|
+
* v - Perspective
|
|
38
|
+
* h - Hemispherical fisheye
|
|
39
|
+
* l - Parallel
|
|
40
|
+
* c - Cylindrical panorama
|
|
41
|
+
* a - Angular fisheye
|
|
42
|
+
* s - Planisphere [stereographic] projection
|
|
43
|
+
|
|
44
|
+
For more detailed description about view types check rpict manual
|
|
45
|
+
page: (http://radsite.lbl.gov/radiance/man_html/rpict.1.html)
|
|
46
|
+
h_size: Set the view horizontal size (-vh). For a perspective
|
|
47
|
+
projection (including fisheye views), val is the horizontal field
|
|
48
|
+
of view (in degrees). For a parallel projection, val is the view
|
|
49
|
+
width in world coordinates.
|
|
50
|
+
v_size: Set the view vertical size (-vv). For a perspective
|
|
51
|
+
projection (including fisheye views), val is the horizontal field
|
|
52
|
+
of view (in degrees). For a parallel projection, val is the view
|
|
53
|
+
width in world coordinates.
|
|
54
|
+
shift: Set the view shift (-vs). This is the amount the actual
|
|
55
|
+
image will be shifted to the right of the specified view. This
|
|
56
|
+
option is useful for generating skewed perspectives or rendering
|
|
57
|
+
an image a piece at a time. A value of 1 means that the rendered
|
|
58
|
+
image starts just to the right of the normal view. A value of -1
|
|
59
|
+
would be to the left. Larger or fractional values are permitted
|
|
60
|
+
as well.
|
|
61
|
+
lift: Set the view lift (-vl) to a value. This is the amount the
|
|
62
|
+
actual image will be lifted up from the specified view.
|
|
63
|
+
|
|
64
|
+
Properties:
|
|
65
|
+
* identifier
|
|
66
|
+
* display_name
|
|
67
|
+
* position
|
|
68
|
+
* direction
|
|
69
|
+
* up_vector
|
|
70
|
+
* type
|
|
71
|
+
* h_size
|
|
72
|
+
* v_size
|
|
73
|
+
* shift
|
|
74
|
+
* lift
|
|
75
|
+
* room_identifier
|
|
76
|
+
* light_path
|
|
77
|
+
* group_identifier
|
|
78
|
+
* full_identifier
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
|
|
82
|
+
.. code-block:: python
|
|
83
|
+
|
|
84
|
+
v = View()
|
|
85
|
+
# add a fore clip
|
|
86
|
+
v.fore_clip = 100
|
|
87
|
+
print(v)
|
|
88
|
+
|
|
89
|
+
> -vtv -vp 0.000 0.000 0.000 -vd 0.000 0.000 1.000 -vu 0.000 1.000
|
|
90
|
+
0.000 -vh 60.000 -vv 60.000 -vo 100.000
|
|
91
|
+
|
|
92
|
+
# split the view into a view grid
|
|
93
|
+
gridViews = v.grid(2, 2, 600, 600)
|
|
94
|
+
for g in gridViews:
|
|
95
|
+
print(g)
|
|
96
|
+
|
|
97
|
+
> -vtv -vp 0.000 0.000 0.000 -vd 0.000 0.000 1.000 -vu 0.000 1.000
|
|
98
|
+
0.000 -vh 29.341 -vv 32.204 -vs -0.500 -vl -0.500 -vo 100.000
|
|
99
|
+
|
|
100
|
+
> -vtv -vp 0.000 0.000 0.000 -vd 0.000 0.000 1.000 -vu 0.000 1.000
|
|
101
|
+
0.000 -vh 29.341 -vv 32.204 -vs 0.500 -vl -0.500 -vo 100.000
|
|
102
|
+
|
|
103
|
+
> -vtv -vp 0.000 0.000 0.000 -vd 0.000 0.000 1.000 -vu 0.000 1.000
|
|
104
|
+
0.000 -vh 29.341 -vv 32.204 -vs -0.500 -vl 0.500 -vo 100.000
|
|
105
|
+
|
|
106
|
+
> -vtv -vp 0.000 0.000 0.000 -vd 0.000 0.000 1.000 -vu 0.000 1.000
|
|
107
|
+
0.000 -vh 29.341 -vv 32.204 -vs 0.500 -vl 0.500 -vo 100.000
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
__slots__ = ('_identifier', '_display_name', '_position', '_direction',
|
|
111
|
+
'_up_vector', '_h_size', '_v_size', '_shift', '_lift',
|
|
112
|
+
'_type', '_fore_clip', '_aft_clip', '_room_identifier',
|
|
113
|
+
'_light_path', '_group_identifier')
|
|
114
|
+
|
|
115
|
+
def __init__(self, identifier, position=None, direction=None, up_vector=None,
|
|
116
|
+
type='v', h_size=60, v_size=60, shift=None, lift=None):
|
|
117
|
+
u"""Create a view."""
|
|
118
|
+
self.identifier = identifier
|
|
119
|
+
self._display_name = None
|
|
120
|
+
self._position = TupleOption(
|
|
121
|
+
'vp', 'view position', position if position is not None else (0, 0, 0)
|
|
122
|
+
)
|
|
123
|
+
self._direction = TupleOption(
|
|
124
|
+
'vd', 'view direction', direction if direction is not None else (0, 0, 1)
|
|
125
|
+
)
|
|
126
|
+
self._up_vector = TupleOption(
|
|
127
|
+
'vu', 'view up vector', up_vector if up_vector is not None else (0, 1, 0)
|
|
128
|
+
)
|
|
129
|
+
self._h_size = NumericOption('vh', 'view horizontal size', h_size, min_value=0)
|
|
130
|
+
self._v_size = NumericOption('vv', 'view vertical size', v_size, min_value=0)
|
|
131
|
+
self._shift = NumericOption('vs', 'view shift', shift)
|
|
132
|
+
self._lift = NumericOption('vl', 'view lift', lift)
|
|
133
|
+
self._type = StringOptionJoined(
|
|
134
|
+
'vt', 'view type', type, valid_values=['v', 'h', 'l', 'c', 'a', 's']
|
|
135
|
+
)
|
|
136
|
+
# set for_clip to None
|
|
137
|
+
self._fore_clip = NumericOption('vo', 'view fore clip')
|
|
138
|
+
self._aft_clip = NumericOption('va', 'view aft clip')
|
|
139
|
+
|
|
140
|
+
self._room_identifier = None
|
|
141
|
+
self._group_identifier = None
|
|
142
|
+
self._light_path = None
|
|
143
|
+
self._check_size_and_type()
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def identifier(self):
|
|
147
|
+
"""Get or set a text string for a unique View identifier."""
|
|
148
|
+
return self._identifier
|
|
149
|
+
|
|
150
|
+
@identifier.setter
|
|
151
|
+
def identifier(self, n):
|
|
152
|
+
self._identifier = typing.valid_rad_string(n, 'view identifier')
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def display_name(self):
|
|
156
|
+
"""Get or set a string for the object name without any character restrictions.
|
|
157
|
+
|
|
158
|
+
If not set, this will be equal to the identifier.
|
|
159
|
+
"""
|
|
160
|
+
if self._display_name is None:
|
|
161
|
+
return self._identifier
|
|
162
|
+
return self._display_name
|
|
163
|
+
|
|
164
|
+
@display_name.setter
|
|
165
|
+
def display_name(self, value):
|
|
166
|
+
try:
|
|
167
|
+
self._display_name = str(value)
|
|
168
|
+
except UnicodeEncodeError: # Python 2 machine lacking the character set
|
|
169
|
+
self._display_name = value # keep it as unicode
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def is_fisheye(self):
|
|
173
|
+
"""Check if the view type is one of the fisheye views."""
|
|
174
|
+
return self.type in ('h', 'a', 's')
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def type(self):
|
|
178
|
+
"""Set and get view type (-vt) to one of the choices below.
|
|
179
|
+
|
|
180
|
+
* v - Perspective (v)
|
|
181
|
+
* h - Hemispherical fisheye (h)
|
|
182
|
+
* l - Parallel (l)
|
|
183
|
+
* c - Cylindrical panorama (c)
|
|
184
|
+
* a - Angular fisheye (a)
|
|
185
|
+
* s - Planisphere [stereographic] projection (s)
|
|
186
|
+
"""
|
|
187
|
+
return self._type.value
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def vt(self):
|
|
191
|
+
"""View type as a string in radiance format."""
|
|
192
|
+
return self._type.to_radiance()
|
|
193
|
+
|
|
194
|
+
@type.setter
|
|
195
|
+
def type(self, value):
|
|
196
|
+
self._type.value = value[-1:] # this will handle both vtv and v inputs
|
|
197
|
+
self._check_size_and_type()
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def position(self):
|
|
201
|
+
"""Set the view position (-vp) to (x, y, z).
|
|
202
|
+
|
|
203
|
+
This is the focal point of a perspective view or the center of a parallel
|
|
204
|
+
projection. Default: (0, 0, 0)
|
|
205
|
+
"""
|
|
206
|
+
return self._position.value
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def vp(self):
|
|
210
|
+
"""View point / position as a string in radiance format."""
|
|
211
|
+
return self._position.to_radiance()
|
|
212
|
+
|
|
213
|
+
@position.setter
|
|
214
|
+
def position(self, value):
|
|
215
|
+
self._position.value = value
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def direction(self):
|
|
219
|
+
"""Set the view direction (-vd) vector to (x, y, z).
|
|
220
|
+
|
|
221
|
+
The length of this vector indicates the focal distance as needed by
|
|
222
|
+
the pixel depth of field (-pd) in rpict. Default: (0, 0, 1)
|
|
223
|
+
"""
|
|
224
|
+
return self._direction.value
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def vd(self):
|
|
228
|
+
"""View direction as a string in radiance format."""
|
|
229
|
+
return self._direction.to_radiance()
|
|
230
|
+
|
|
231
|
+
@direction.setter
|
|
232
|
+
def direction(self, value):
|
|
233
|
+
self._direction.value = value
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def up_vector(self):
|
|
237
|
+
"""Set and get the view up (-vu) vector (vertical direction) to (x, y, z)
|
|
238
|
+
|
|
239
|
+
Default: (0, 1, 0).
|
|
240
|
+
"""
|
|
241
|
+
return self._up_vector.value
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def vu(self):
|
|
245
|
+
"""View up as a string in radiance format."""
|
|
246
|
+
return self._up_vector.to_radiance()
|
|
247
|
+
|
|
248
|
+
@up_vector.setter
|
|
249
|
+
def up_vector(self, value):
|
|
250
|
+
self._up_vector.value = value
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def h_size(self):
|
|
254
|
+
"""Set the view horizontal size (-vh).
|
|
255
|
+
|
|
256
|
+
For a perspective projection (including fisheye views), this is the horizontal
|
|
257
|
+
field of view (in degrees). For a parallel projection, this is the view
|
|
258
|
+
width in world coordinates.
|
|
259
|
+
"""
|
|
260
|
+
return self._h_size.value
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def vh(self):
|
|
264
|
+
"""View horizontal size as a string in radiance format."""
|
|
265
|
+
return self._h_size.to_radiance()
|
|
266
|
+
|
|
267
|
+
@h_size.setter
|
|
268
|
+
def h_size(self, value):
|
|
269
|
+
self._h_size.value = value if value is not None else 60
|
|
270
|
+
self._check_size_and_type()
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def v_size(self):
|
|
274
|
+
"""Set the view vertical size (-vv).
|
|
275
|
+
|
|
276
|
+
For a perspective projection (including fisheye views), this is the horizontal
|
|
277
|
+
field of view (in degrees). For a parallel projection, this is the view width in
|
|
278
|
+
world coordinates.
|
|
279
|
+
"""
|
|
280
|
+
return self._v_size.value
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def vv(self):
|
|
284
|
+
"""View vertical size as a string in radiance format."""
|
|
285
|
+
return self._v_size.to_radiance()
|
|
286
|
+
|
|
287
|
+
@v_size.setter
|
|
288
|
+
def v_size(self, value):
|
|
289
|
+
self._v_size.value = value if value is not None else 60
|
|
290
|
+
self._check_size_and_type()
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def shift(self):
|
|
294
|
+
"""Set the view shift (-vs).
|
|
295
|
+
|
|
296
|
+
This is the amount the actual image will be shifted to the right of the specified
|
|
297
|
+
view. This option is useful for generating skewed perspectives or rendering an
|
|
298
|
+
image a piece at a time. A value of 1 means that the rendered image starts just
|
|
299
|
+
to the right of the normal view. A value of -1 would be to the left. Larger or
|
|
300
|
+
fractional values are permitted as well.
|
|
301
|
+
"""
|
|
302
|
+
return self._shift.value
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def vs(self):
|
|
306
|
+
"""View shift as a string in radiance format."""
|
|
307
|
+
return self._shift.to_radiance()
|
|
308
|
+
|
|
309
|
+
@shift.setter
|
|
310
|
+
def shift(self, value):
|
|
311
|
+
self._shift.value = value
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def lift(self):
|
|
315
|
+
"""Set the view lift (-vl) to a value.
|
|
316
|
+
|
|
317
|
+
This is the amount the actual image will be lifted up from the specified view.
|
|
318
|
+
"""
|
|
319
|
+
return self._lift.value
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def vl(self):
|
|
323
|
+
"""View lift as a string in radiance format."""
|
|
324
|
+
return self._lift.to_radiance()
|
|
325
|
+
|
|
326
|
+
@lift.setter
|
|
327
|
+
def lift(self, value):
|
|
328
|
+
self._lift.value = value
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def fore_clip(self):
|
|
332
|
+
"""View fore clip (-vo) at a distance from the view point.
|
|
333
|
+
|
|
334
|
+
The plane will be perpendicular to the view direction for perspective
|
|
335
|
+
and parallel view types. For fisheye view types, the clipping plane is
|
|
336
|
+
actually a clipping sphere, centered on the view point with radius val.
|
|
337
|
+
Objects in front of this imaginary surface will not be visible. This may
|
|
338
|
+
be useful for seeing through walls (to get a longer perspective from an
|
|
339
|
+
exterior view point) or for incremental rendering. A value of zero implies
|
|
340
|
+
no foreground clipping. A negative value produces some interesting effects,
|
|
341
|
+
since it creates an inverted image for objects behind the viewpoint.
|
|
342
|
+
"""
|
|
343
|
+
return self._fore_clip.value
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def vo(self):
|
|
347
|
+
"""View fore clip as a string in radiance format."""
|
|
348
|
+
return self._fore_clip.to_radiance()
|
|
349
|
+
|
|
350
|
+
@fore_clip.setter
|
|
351
|
+
def fore_clip(self, distance):
|
|
352
|
+
self._fore_clip.value = distance
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def aft_clip(self):
|
|
356
|
+
"""View aft clip (-va) at a distance from the view point.
|
|
357
|
+
|
|
358
|
+
Set the view aft clipping plane at a distance of val from the view point. Like
|
|
359
|
+
the view fore plane, it will be perpendicular to the view direction for
|
|
360
|
+
perspective and parallel view types. For fisheye view types, the clipping plane
|
|
361
|
+
is actually a clipping sphere, centered on the view point with radius val.
|
|
362
|
+
Objects behind this imaginary surface will not be visible. A value of zero means
|
|
363
|
+
no aft clipping, and is the only way to see infinitely distant objects such as
|
|
364
|
+
the sky.
|
|
365
|
+
"""
|
|
366
|
+
return self._aft_clip.value
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def va(self):
|
|
370
|
+
"""View aft clip as a string in radiance format."""
|
|
371
|
+
return self._aft_clip.to_radiance()
|
|
372
|
+
|
|
373
|
+
@aft_clip.setter
|
|
374
|
+
def aft_clip(self, distance):
|
|
375
|
+
self._aft_clip.value = distance
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def room_identifier(self):
|
|
379
|
+
"""Get or set text for the Room identifier to which this View belongs.
|
|
380
|
+
|
|
381
|
+
This will be used in the info_dict method to narrow down the
|
|
382
|
+
number of aperture groups that have to be run with this view. If None,
|
|
383
|
+
the view will be run with all aperture groups in the model.
|
|
384
|
+
"""
|
|
385
|
+
return self._room_identifier
|
|
386
|
+
|
|
387
|
+
@room_identifier.setter
|
|
388
|
+
def room_identifier(self, n):
|
|
389
|
+
self._room_identifier = typing.valid_string(n)
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def group_identifier(self):
|
|
393
|
+
"""Get or set text for the group identifier to which this View belongs.
|
|
394
|
+
|
|
395
|
+
This will be used in the write to radiance folder method to write all the views
|
|
396
|
+
with the same group identifier under the same subfolder.
|
|
397
|
+
|
|
398
|
+
You may use / in name to identify nested view groups. For example
|
|
399
|
+
floor_1/living_room create a view under living_room/floor_1 subfolder.
|
|
400
|
+
|
|
401
|
+
If None, the view will be written to the root of views folder.
|
|
402
|
+
"""
|
|
403
|
+
return self._group_identifier
|
|
404
|
+
|
|
405
|
+
@group_identifier.setter
|
|
406
|
+
def group_identifier(self, identifier_key):
|
|
407
|
+
if identifier_key is not None:
|
|
408
|
+
identifier_key = \
|
|
409
|
+
'/'.join(
|
|
410
|
+
typing.valid_rad_string(key, 'view group identifier')
|
|
411
|
+
for key in identifier_key.split('/')
|
|
412
|
+
)
|
|
413
|
+
self._group_identifier = identifier_key
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def full_identifier(self):
|
|
417
|
+
"""Get full identifier for view.
|
|
418
|
+
|
|
419
|
+
For a view with group identifier it will be group_identifier/identifier
|
|
420
|
+
"""
|
|
421
|
+
return self.identifier if not self.group_identifier \
|
|
422
|
+
else '%s/%s' % (self.group_identifier, self.identifier)
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def light_path(self):
|
|
426
|
+
"""Get or set list of lists for the light path from the view to the sky.
|
|
427
|
+
|
|
428
|
+
Each sub-list contains identifiers of aperture groups through which light
|
|
429
|
+
passes. (eg. [['SouthWindow1'], ['__static_apertures__', 'NorthWindow2']]).
|
|
430
|
+
Setting this property will override any auto-calculation of the light
|
|
431
|
+
path from the model upon export to the simulation.
|
|
432
|
+
"""
|
|
433
|
+
return self._light_path
|
|
434
|
+
|
|
435
|
+
@light_path.setter
|
|
436
|
+
def light_path(self, l_path):
|
|
437
|
+
if l_path is not None:
|
|
438
|
+
assert isinstance(l_path, (tuple, list)), 'Expected list or tuple for ' \
|
|
439
|
+
'light_path. Got {}.'.format(type(l_path))
|
|
440
|
+
for ap_list in l_path:
|
|
441
|
+
assert isinstance(ap_list, (tuple, list)), 'Expected list or tuple ' \
|
|
442
|
+
'for light_path sub-list. Got {}.'.format(type(ap_list))
|
|
443
|
+
for ap in ap_list:
|
|
444
|
+
assert isinstance(ap, str), 'Expected text for light_path ' \
|
|
445
|
+
'aperture group identifier. Got {}.'.format(type(ap))
|
|
446
|
+
self._light_path = l_path
|
|
447
|
+
|
|
448
|
+
def standardize_fisheye(self):
|
|
449
|
+
"""Automatically set view size to 180 degrees if the view type is a fisheye.
|
|
450
|
+
|
|
451
|
+
Alternatively it sets the view size to 360 degrees if both the view type is
|
|
452
|
+
angular fisheye and either the horizontal or vertical view size is 360 degrees.
|
|
453
|
+
"""
|
|
454
|
+
if self.type in ('h', 's'):
|
|
455
|
+
if self.h_size != 180:
|
|
456
|
+
self.h_size = 180
|
|
457
|
+
if self.v_size != 180:
|
|
458
|
+
self.v_size = 180
|
|
459
|
+
if self.type in ('a'):
|
|
460
|
+
if self.h_size == 360 or self.v_size == 360:
|
|
461
|
+
self.h_size = self.v_size = 360
|
|
462
|
+
else:
|
|
463
|
+
if self.h_size != 180:
|
|
464
|
+
self.h_size = 180
|
|
465
|
+
if self.v_size != 180:
|
|
466
|
+
self.v_size = 180
|
|
467
|
+
|
|
468
|
+
def _check_size_and_type(self):
|
|
469
|
+
"""Check to be sure the view size and type are compatible."""
|
|
470
|
+
if self.type == 'v':
|
|
471
|
+
assert self.h_size < 180, \
|
|
472
|
+
'\n{} is an invalid horizontal view size for Perspective view.\n' \
|
|
473
|
+
'The size should be smaller than 180.'.format(self.h_size)
|
|
474
|
+
assert self.v_size < 180, \
|
|
475
|
+
'\n{} is an invalid vertical view size for Perspective view.\n' \
|
|
476
|
+
'The size should be smaller than 180.'.format(self.v_size)
|
|
477
|
+
|
|
478
|
+
@classmethod
|
|
479
|
+
def from_dict(cls, view_dict):
|
|
480
|
+
"""Create a view from a dictionary in the following format.
|
|
481
|
+
|
|
482
|
+
.. code-block:: python
|
|
483
|
+
|
|
484
|
+
{
|
|
485
|
+
'type': 'View',
|
|
486
|
+
'identifier': str, # View identifier
|
|
487
|
+
"display_name": str, # View display name
|
|
488
|
+
'position': [], # list with position value
|
|
489
|
+
'direction': [], # list with direction value
|
|
490
|
+
'up_vector': [], # list with up_vector value
|
|
491
|
+
'h_size': number, # h_size.value
|
|
492
|
+
'v_size': number, # v_size value
|
|
493
|
+
'shift': number, # shift value
|
|
494
|
+
'lift': number, # lift value
|
|
495
|
+
'view_type': number, # view_type value
|
|
496
|
+
'fore_clip': number, # fore_clip value
|
|
497
|
+
'aft_clip': number, # aft_clip value
|
|
498
|
+
'room_identifier': str, # optional room identifier
|
|
499
|
+
'light_path': [] # optional list of lists for light path
|
|
500
|
+
}
|
|
501
|
+
"""
|
|
502
|
+
assert view_dict['type'] == 'View', \
|
|
503
|
+
'Expected View dictionary. Got {}.'.format(view_dict['type'])
|
|
504
|
+
|
|
505
|
+
view_type = view_dict['view_type'][-1:] if 'view_type' in view_dict else 'v'
|
|
506
|
+
view = cls(
|
|
507
|
+
identifier=view_dict['identifier'],
|
|
508
|
+
position=view_dict['position'],
|
|
509
|
+
direction=view_dict['direction'],
|
|
510
|
+
up_vector=view_dict['up_vector'],
|
|
511
|
+
type=view_type,
|
|
512
|
+
h_size=view_dict['h_size'],
|
|
513
|
+
v_size=view_dict['v_size'],
|
|
514
|
+
shift=view_dict['shift'],
|
|
515
|
+
lift=view_dict['lift'],
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
if 'fore_clip' in view_dict:
|
|
519
|
+
view.fore_clip = view_dict['fore_clip']
|
|
520
|
+
if 'aft_clip' in view_dict:
|
|
521
|
+
view.aft_clip = view_dict['aft_clip']
|
|
522
|
+
if 'display_name' in view_dict and view_dict['display_name'] is not None:
|
|
523
|
+
view.display_name = view_dict['display_name']
|
|
524
|
+
if 'room_identifier' in view_dict and view_dict['room_identifier'] is not None:
|
|
525
|
+
view.room_identifier = view_dict['room_identifier']
|
|
526
|
+
if 'light_path' in view_dict and view_dict['light_path'] is not None:
|
|
527
|
+
view.light_path = view_dict['light_path']
|
|
528
|
+
if 'group_identifier' in view_dict and view_dict['group_identifier'] is not None:
|
|
529
|
+
view_dict.group_identifier = view_dict['group_identifier']
|
|
530
|
+
return view
|
|
531
|
+
|
|
532
|
+
@classmethod
|
|
533
|
+
def from_string(cls, identifier, view_string):
|
|
534
|
+
"""Create a view object from a string.
|
|
535
|
+
|
|
536
|
+
This method is similar to from_string method for radiance parameters with the
|
|
537
|
+
difference that all the parameters that are not related to view will be ignored.
|
|
538
|
+
"""
|
|
539
|
+
mapper = {
|
|
540
|
+
'identifier': identifier, 'vp': 'position', 'vd': 'direction',
|
|
541
|
+
'vu': 'up_vector', 'vh': 'h_size', 'vv': 'v_size', 'vs': 'shift',
|
|
542
|
+
'vl': 'lift', 'vo': 'fore_clip', 'va': 'aft_clip'
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
base = {
|
|
546
|
+
'type': 'View',
|
|
547
|
+
'identifier': identifier,
|
|
548
|
+
'position': None,
|
|
549
|
+
'direction': None,
|
|
550
|
+
'up_vector': None,
|
|
551
|
+
'h_size': None,
|
|
552
|
+
'v_size': None,
|
|
553
|
+
'shift': None,
|
|
554
|
+
'lift': None,
|
|
555
|
+
'view_type': None,
|
|
556
|
+
'fore_clip': None,
|
|
557
|
+
'aft_clip': None
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
# parse the string here
|
|
561
|
+
options = cls._parse_radiance_options(view_string)
|
|
562
|
+
|
|
563
|
+
for opt, value in options.items():
|
|
564
|
+
if opt in mapper:
|
|
565
|
+
base[mapper[opt]] = value
|
|
566
|
+
elif opt[:2] == 'vt':
|
|
567
|
+
base['view_type'] = opt
|
|
568
|
+
else:
|
|
569
|
+
print('%s is not a view parameter and is ignored.' % opt)
|
|
570
|
+
|
|
571
|
+
return cls.from_dict(base)
|
|
572
|
+
|
|
573
|
+
@classmethod
|
|
574
|
+
def from_file(cls, file_path, identifier=None):
|
|
575
|
+
"""Create view from a view file.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
file_path: Full path to view file.
|
|
579
|
+
identifier: Optional ext string for a unique View ID. Must not contain spaces
|
|
580
|
+
or special characters. This will be used to identify the object across
|
|
581
|
+
a model and in the exported Radiance files. If None, this will be set
|
|
582
|
+
to file name. (Default: None)
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
if not os.path.isfile(file_path):
|
|
586
|
+
raise IOError("Can't find {}.".format(file_path))
|
|
587
|
+
identifier = identifier or os.path.split(os.path.splitext(file_path)[0])[-1]
|
|
588
|
+
|
|
589
|
+
with open(file_path, 'r') as input_data:
|
|
590
|
+
view_string = str(input_data.read()).rstrip()
|
|
591
|
+
|
|
592
|
+
assert view_string[:3] == 'rvu', \
|
|
593
|
+
'View file must start with rvu not %s' % view_string[:3]
|
|
594
|
+
return cls.from_string(identifier, view_string)
|
|
595
|
+
|
|
596
|
+
@classmethod
|
|
597
|
+
def from_grid(cls, grid, identifier='from_grid'):
|
|
598
|
+
"""Create view from a grid of views.
|
|
599
|
+
Generally the grid argument should be the views generated by the grid method.
|
|
600
|
+
Args:
|
|
601
|
+
grid: A list of subviews. If only a single view is given, this view will be
|
|
602
|
+
returned. The views can be either class instances of View, strings or
|
|
603
|
+
.unf files. If strings are used, the views will be created by the
|
|
604
|
+
from_string method. If .unf files are used, the views will be created by
|
|
605
|
+
the from_string method using the view found in the Radiance header.
|
|
606
|
+
identifier: Text string for a unique View ID. Must not contain spaces
|
|
607
|
+
or special characters. This will be used to identify the object across
|
|
608
|
+
a model and in the exported Radiance files. If None, this will be set
|
|
609
|
+
to 'from_grid'. (Default: 'from_grid')
|
|
610
|
+
"""
|
|
611
|
+
if not isinstance(grid, (list, tuple)):
|
|
612
|
+
grid = [grid]
|
|
613
|
+
|
|
614
|
+
views = []
|
|
615
|
+
# check if grid argument views are valid
|
|
616
|
+
for c, view in enumerate(grid):
|
|
617
|
+
if isinstance(view, View):
|
|
618
|
+
views.append(view)
|
|
619
|
+
elif view.endswith('.unf'):
|
|
620
|
+
try:
|
|
621
|
+
f = open(view, 'r', encoding='utf-8', errors='ignore')
|
|
622
|
+
except Exception:
|
|
623
|
+
f = open(view, 'r')
|
|
624
|
+
try:
|
|
625
|
+
for line in f:
|
|
626
|
+
if not line.strip():
|
|
627
|
+
break
|
|
628
|
+
else:
|
|
629
|
+
low_line = line.strip().lower()
|
|
630
|
+
if low_line.startswith('view='):
|
|
631
|
+
print(low_line)
|
|
632
|
+
views.append(cls.from_string('view_%04d' % c, low_line))
|
|
633
|
+
except Exception:
|
|
634
|
+
raise ValueError('Failed to find view in Radiance header.')
|
|
635
|
+
finally:
|
|
636
|
+
f.close()
|
|
637
|
+
elif isinstance(view, str):
|
|
638
|
+
views.append(cls.from_string('view_%04d' % c, view))
|
|
639
|
+
else:
|
|
640
|
+
raise ValueError(
|
|
641
|
+
'Expected Honeybee Radiance View, string or .unf file.'
|
|
642
|
+
'Got: {}'.format(type(view))
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
# if only a single (valid) view is given, then return the view
|
|
646
|
+
if len(views) == 1:
|
|
647
|
+
return views[0]
|
|
648
|
+
|
|
649
|
+
_type = set()
|
|
650
|
+
_view_point = set()
|
|
651
|
+
_view_direction = set()
|
|
652
|
+
_up_direction = set()
|
|
653
|
+
_vh = set()
|
|
654
|
+
_vv = set()
|
|
655
|
+
_x_div_count = set()
|
|
656
|
+
_y_div_count = set()
|
|
657
|
+
# check if type, view point, view direction, and up direction are equal in views
|
|
658
|
+
# all unique values are collected, all except -vh and -vv must be the same
|
|
659
|
+
for _view in views:
|
|
660
|
+
_type.add(_view.type)
|
|
661
|
+
_view_point.add(_view.position)
|
|
662
|
+
_view_direction.add(_view.direction)
|
|
663
|
+
_up_direction.add(_view.up_vector)
|
|
664
|
+
_vh.add(_view.h_size)
|
|
665
|
+
_vv.add(_view.v_size)
|
|
666
|
+
_x_div_count.add(_view.shift)
|
|
667
|
+
_y_div_count.add(_view.lift)
|
|
668
|
+
|
|
669
|
+
if len(_type) > 1:
|
|
670
|
+
raise ValueError('All subviews must have the same view type.')
|
|
671
|
+
if len(_view_point) > 1:
|
|
672
|
+
raise ValueError('All subviews must have the same view point.')
|
|
673
|
+
if len(_view_direction) > 1:
|
|
674
|
+
raise ValueError('All subviews must have the same view direction.')
|
|
675
|
+
if len(_up_direction) > 1:
|
|
676
|
+
raise ValueError('All subviews must have the same up direction.')
|
|
677
|
+
if len(_vh) > 1:
|
|
678
|
+
raise ValueError('All subviews must have the same horizontal view size.')
|
|
679
|
+
if len(_vv) > 1:
|
|
680
|
+
raise ValueError('All subviews must have the same vertical view size.')
|
|
681
|
+
|
|
682
|
+
# find the grid dimensions (x_count, y_count)
|
|
683
|
+
x_div_count = len(_x_div_count)
|
|
684
|
+
y_div_count = len(_y_div_count)
|
|
685
|
+
|
|
686
|
+
# get the horizontal and vertical view size from the subviews
|
|
687
|
+
_vh = list(_vh)[0]
|
|
688
|
+
_vv = list(_vv)[0]
|
|
689
|
+
|
|
690
|
+
# create instance of view
|
|
691
|
+
view = cls(
|
|
692
|
+
identifier=identifier,
|
|
693
|
+
position=list(_view_point)[0],
|
|
694
|
+
direction=list(_view_direction)[0],
|
|
695
|
+
up_vector=list(_up_direction)[0],
|
|
696
|
+
type=list(_type)[0])
|
|
697
|
+
|
|
698
|
+
PI = math.pi
|
|
699
|
+
# find the horizontal and vertical size
|
|
700
|
+
if view.type == 'l' or view.type == 'a':
|
|
701
|
+
# parallel view (vtl) or angular fisheye (vta)
|
|
702
|
+
h_size = _vh * x_div_count
|
|
703
|
+
v_size = _vv * y_div_count
|
|
704
|
+
|
|
705
|
+
elif view.type == 'v':
|
|
706
|
+
# perspective (vtv)
|
|
707
|
+
pi2 = (2. * 180. / PI)
|
|
708
|
+
h_size = pi2 * math.tan(_vh / (2. * 180. / PI)) * x_div_count
|
|
709
|
+
v_size = pi2 * math.atan(math.tan(_vv / (2. * 180. / PI)) * y_div_count)
|
|
710
|
+
|
|
711
|
+
elif view.type == 'h':
|
|
712
|
+
# hemispherical fisheye (vth)
|
|
713
|
+
pi2 = (2. * 180. / PI)
|
|
714
|
+
h_size = pi2 * math.asin(math.sin(_vh / (2. * 180. / PI)) * x_div_count)
|
|
715
|
+
v_size = pi2 * math.asin(math.sin(_vv / (2. * 180. / PI)) * y_div_count)
|
|
716
|
+
|
|
717
|
+
else:
|
|
718
|
+
raise ValueError(
|
|
719
|
+
'Grid views are not supported for %s.' % view.type)
|
|
720
|
+
|
|
721
|
+
# round the number to avoid cases like 59.99999999999999 when should be 60
|
|
722
|
+
h_size = round(h_size, 10)
|
|
723
|
+
v_size = round(v_size, 10)
|
|
724
|
+
|
|
725
|
+
# update horizontal and vertical view size
|
|
726
|
+
view.h_size = h_size
|
|
727
|
+
view.v_size = v_size
|
|
728
|
+
|
|
729
|
+
return view
|
|
730
|
+
|
|
731
|
+
def dimension(self, x_res=None, y_res=None):
|
|
732
|
+
"""Get dimensions for this view as '-x %d -y %d [-ld-]'.
|
|
733
|
+
|
|
734
|
+
This method is same as vwrays -d. Default values for x_res and y_res are set to
|
|
735
|
+
match Radiance defaults.
|
|
736
|
+
"""
|
|
737
|
+
x, y = self.dimension_x_y(x_res, y_res)
|
|
738
|
+
return '-x %d -y %d -ld%s' % (x, y, '-' if (self.vo + self.va == '') else '+')
|
|
739
|
+
|
|
740
|
+
def dimension_x_y(self, x_res=None, y_res=None):
|
|
741
|
+
"""Get dimensions for this view as x, y.
|
|
742
|
+
|
|
743
|
+
Default values for x_res and y_res are set to match Radiance defaults.
|
|
744
|
+
"""
|
|
745
|
+
# radiance default is 512
|
|
746
|
+
x_res = int(x_res) if x_res is not None else 512
|
|
747
|
+
y_res = int(y_res) if y_res is not None else 512
|
|
748
|
+
|
|
749
|
+
if self.is_fisheye:
|
|
750
|
+
return min(x_res, y_res), min(x_res, y_res)
|
|
751
|
+
|
|
752
|
+
vh = self.h_size
|
|
753
|
+
vv = self.v_size
|
|
754
|
+
|
|
755
|
+
if self.type == 'v':
|
|
756
|
+
hv_ratio = math.tan(math.radians(vh) / 2.0) / \
|
|
757
|
+
math.tan(math.radians(vv) / 2.0)
|
|
758
|
+
else:
|
|
759
|
+
hv_ratio = vh / vv
|
|
760
|
+
|
|
761
|
+
# radiance keeps the largest max size and tries to scale the other size
|
|
762
|
+
# to fit the aspect ratio. In case the size doesn't match it reverses
|
|
763
|
+
# the process.
|
|
764
|
+
if y_res <= x_res:
|
|
765
|
+
new_x = int(round(hv_ratio * y_res))
|
|
766
|
+
if new_x <= x_res:
|
|
767
|
+
return new_x, y_res
|
|
768
|
+
else:
|
|
769
|
+
new_y = int(round(x_res / hv_ratio))
|
|
770
|
+
return x_res, new_y
|
|
771
|
+
else:
|
|
772
|
+
new_y = int(round(x_res / hv_ratio))
|
|
773
|
+
if new_y <= y_res:
|
|
774
|
+
return x_res, new_y
|
|
775
|
+
else:
|
|
776
|
+
new_x = int(round(hv_ratio * y_res))
|
|
777
|
+
return new_x, y_res
|
|
778
|
+
|
|
779
|
+
def grid(self, x_div_count=1, y_div_count=1):
|
|
780
|
+
"""Break-down the view into a grid of views based on x and y grid count.
|
|
781
|
+
|
|
782
|
+
Views will be returned row by row from right to left.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
x_div_count: Set number of divisions in x direction (Default: 1).
|
|
786
|
+
y_div_count: Set number of divisions in y direction (Default: 1).
|
|
787
|
+
|
|
788
|
+
Returns:
|
|
789
|
+
A tuple of views. Views are sorted row by row from right to left.
|
|
790
|
+
"""
|
|
791
|
+
PI = math.pi
|
|
792
|
+
try:
|
|
793
|
+
x_div_count = abs(x_div_count)
|
|
794
|
+
y_div_count = abs(y_div_count)
|
|
795
|
+
except TypeError as e:
|
|
796
|
+
raise ValueError("Division count should be a number.\n%s" % str(e))
|
|
797
|
+
|
|
798
|
+
assert x_div_count * y_div_count != 0, "Division count should be larger than 0."
|
|
799
|
+
|
|
800
|
+
if x_div_count == y_div_count == 1:
|
|
801
|
+
return [self]
|
|
802
|
+
|
|
803
|
+
_views = list(range(x_div_count * y_div_count))
|
|
804
|
+
|
|
805
|
+
if self.type in ('l', 'a', 'c'):
|
|
806
|
+
# parallel view (vtl) or angular fisheye (vta)
|
|
807
|
+
_vh = self.h_size / x_div_count
|
|
808
|
+
_vv = self.v_size / y_div_count
|
|
809
|
+
|
|
810
|
+
elif self.type == 'v':
|
|
811
|
+
# perspective (vtv)
|
|
812
|
+
pi2 = (2. * 180. / PI)
|
|
813
|
+
_vh = pi2 * math.atan(((PI / 180. / 2.) * self.h_size) / x_div_count)
|
|
814
|
+
_vv = pi2 * math.atan(math.tan((PI / 180. / 2.) * self.v_size) / y_div_count)
|
|
815
|
+
|
|
816
|
+
elif self.type == 's':
|
|
817
|
+
# planisphere (stereographic)
|
|
818
|
+
pi2 = (2. * 180. / PI * 2)
|
|
819
|
+
_vh = pi2 * math.atan(math.sin((PI / 180. / 2.) * self.h_size) / x_div_count)
|
|
820
|
+
_vv = pi2 * math.atan(math.sin((PI / 180. / 2.) * self.v_size) / y_div_count)
|
|
821
|
+
|
|
822
|
+
elif self.type in ('h'):
|
|
823
|
+
# hemispherical fish eye
|
|
824
|
+
pi2 = (2. * 180. / PI)
|
|
825
|
+
_vh = pi2 * math.asin(math.sin((PI / 180. / 2.) * self.h_size) / x_div_count)
|
|
826
|
+
_vv = pi2 * math.asin(math.sin((PI / 180. / 2.) * self.v_size) / y_div_count)
|
|
827
|
+
|
|
828
|
+
else:
|
|
829
|
+
print("Grid views are not supported for %s." % self.type)
|
|
830
|
+
return [self]
|
|
831
|
+
|
|
832
|
+
# create a set of new views
|
|
833
|
+
for view_count in range(len(_views)):
|
|
834
|
+
# calculate view shift and view lift
|
|
835
|
+
if x_div_count == 1:
|
|
836
|
+
_vs = 0
|
|
837
|
+
else:
|
|
838
|
+
_vs = (((view_count % x_div_count) / (x_div_count - 1)) - 0.5) \
|
|
839
|
+
* (x_div_count - 1)
|
|
840
|
+
|
|
841
|
+
if y_div_count == 1:
|
|
842
|
+
_vl = 0
|
|
843
|
+
else:
|
|
844
|
+
_vl = ((int(view_count % y_div_count) / (y_div_count - 1)) - 0.5) \
|
|
845
|
+
* (y_div_count - 1)
|
|
846
|
+
|
|
847
|
+
# create a copy from the current view
|
|
848
|
+
_n_view = self.duplicate()
|
|
849
|
+
_n_view.identifier = '%s_%d' % (self.identifier, view_count)
|
|
850
|
+
# update parameters
|
|
851
|
+
_n_view.h_size = _vh
|
|
852
|
+
_n_view.v_size = _vv
|
|
853
|
+
_n_view.shift = _vs
|
|
854
|
+
_n_view.lift = _vl
|
|
855
|
+
_n_view._fore_clip = self._fore_clip
|
|
856
|
+
_n_view._aft_clip = self._aft_clip
|
|
857
|
+
try:
|
|
858
|
+
_n_view.display_name = '%s_%d' % (self.display_name, view_count)
|
|
859
|
+
except UnicodeEncodeError: # character no found on machine
|
|
860
|
+
pass
|
|
861
|
+
|
|
862
|
+
# add the new view to views list
|
|
863
|
+
_views[view_count] = _n_view
|
|
864
|
+
|
|
865
|
+
return _views
|
|
866
|
+
|
|
867
|
+
def to_radiance(self):
|
|
868
|
+
"""Return full Radiance definition as a string."""
|
|
869
|
+
# create base information of view
|
|
870
|
+
view_options = ' '.join((
|
|
871
|
+
self.vt, self.vp, self.vd, self.vu,
|
|
872
|
+
self.vh, self.vv, self.vs, self.vl,
|
|
873
|
+
self.vo, self.va
|
|
874
|
+
))
|
|
875
|
+
|
|
876
|
+
return ' '.join(view_options.split()) # remove white spaces
|
|
877
|
+
|
|
878
|
+
def info_dict(self, model=None):
|
|
879
|
+
"""Get a dictionary with information about the View.
|
|
880
|
+
|
|
881
|
+
This can be written as a JSON into a model radiance folder to narrow
|
|
882
|
+
down the number of aperture groups that have to be run with this view.
|
|
883
|
+
|
|
884
|
+
Args:
|
|
885
|
+
model: A honeybee Model object which will be used to identify
|
|
886
|
+
the aperture groups that will be run with this view. Default: None.
|
|
887
|
+
"""
|
|
888
|
+
base = {}
|
|
889
|
+
if self._light_path:
|
|
890
|
+
base['light_path'] = self._light_path
|
|
891
|
+
elif model and self._room_identifier: # auto-calculate the light path
|
|
892
|
+
base['light_path'] = light_path_from_room(model, self._room_identifier)
|
|
893
|
+
|
|
894
|
+
if self._group_identifier:
|
|
895
|
+
base['group_identifier'] = self._group_identifier
|
|
896
|
+
|
|
897
|
+
return base
|
|
898
|
+
|
|
899
|
+
def to_dict(self):
|
|
900
|
+
"""Translate view to a dictionary."""
|
|
901
|
+
base = {
|
|
902
|
+
'type': 'View',
|
|
903
|
+
'identifier': self.identifier,
|
|
904
|
+
'position': self.position,
|
|
905
|
+
'direction': self.direction,
|
|
906
|
+
'up_vector': self.up_vector,
|
|
907
|
+
'h_size': self.h_size,
|
|
908
|
+
'v_size': self.v_size,
|
|
909
|
+
'shift': self.shift,
|
|
910
|
+
'lift': self.lift,
|
|
911
|
+
'view_type': self.type,
|
|
912
|
+
'fore_clip': self.fore_clip,
|
|
913
|
+
'aft_clip': self.aft_clip
|
|
914
|
+
}
|
|
915
|
+
if self._display_name is not None:
|
|
916
|
+
base['display_name'] = self.display_name
|
|
917
|
+
if self._room_identifier is not None:
|
|
918
|
+
base['room_identifier'] = self.room_identifier
|
|
919
|
+
if self._light_path is not None:
|
|
920
|
+
base['light_path'] = self.light_path
|
|
921
|
+
if self._group_identifier is not None:
|
|
922
|
+
base['group_identifier'] = self.group_identifier
|
|
923
|
+
return base
|
|
924
|
+
|
|
925
|
+
def to_file(self, folder, file_name=None, mkdir=False):
|
|
926
|
+
"""Save view to a file.
|
|
927
|
+
|
|
928
|
+
Args:
|
|
929
|
+
folder: Target folder.
|
|
930
|
+
file_name: Optional file name without extension (Default: self.identifier).
|
|
931
|
+
mkdir: A boolean to indicate if the folder should be created in case it
|
|
932
|
+
doesn't exist already (Default: False).
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
Full path to newly created file.
|
|
936
|
+
"""
|
|
937
|
+
|
|
938
|
+
identifier = file_name or self.identifier + '.vf'
|
|
939
|
+
if not (identifier.endswith('.vf') or identifier.endswith('.unf')):
|
|
940
|
+
identifier += '.vf'
|
|
941
|
+
# add rvu before the view itself
|
|
942
|
+
content = 'rvu ' + self.to_radiance()
|
|
943
|
+
return futil.write_to_file_by_name(folder, identifier, content, mkdir)
|
|
944
|
+
|
|
945
|
+
def move(self, moving_vec):
|
|
946
|
+
"""Move this view along a vector.
|
|
947
|
+
|
|
948
|
+
Args:
|
|
949
|
+
moving_vec: A ladybug_geometry Vector3D with the direction and distance
|
|
950
|
+
to move the view.
|
|
951
|
+
"""
|
|
952
|
+
position = pv.Point3D(*self.position)
|
|
953
|
+
self.position = tuple(position.move(moving_vec))
|
|
954
|
+
|
|
955
|
+
def rotate(self, axis, angle, origin=None):
|
|
956
|
+
"""Rotate this view by a certain angle around an axis and origin.
|
|
957
|
+
|
|
958
|
+
Args:
|
|
959
|
+
axis: Rotation axis as a Vector3D. If None, self.up_vector will be used.
|
|
960
|
+
angle: An angle for rotation in degrees.
|
|
961
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
962
|
+
object will be rotated. If None, self.position is used. (Default: None).
|
|
963
|
+
"""
|
|
964
|
+
view_up_vector = pv.Vector3D(*self.up_vector)
|
|
965
|
+
view_position = pv.Point3D(*self.position)
|
|
966
|
+
view_direction = pv.Vector3D(*self.direction)
|
|
967
|
+
view_plane = Plane(n=view_up_vector, o=view_position, x=view_direction)
|
|
968
|
+
axis = axis if axis is not None else view_up_vector
|
|
969
|
+
position = origin if origin is not None else view_position
|
|
970
|
+
|
|
971
|
+
rotated_plane = view_plane.rotate(axis, math.radians(angle), position)
|
|
972
|
+
self._apply_plane_properties(rotated_plane, view_direction, view_up_vector)
|
|
973
|
+
|
|
974
|
+
def rotate_xy(self, angle, origin=None):
|
|
975
|
+
"""Rotate this view counterclockwise in the world XY plane by a certain angle.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
angle: An angle in degrees.
|
|
979
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
980
|
+
object will be rotated. If None, self.position is used. (Default: None).
|
|
981
|
+
"""
|
|
982
|
+
view_up_vector = pv.Vector3D(*self.up_vector)
|
|
983
|
+
view_position = pv.Point3D(*self.position)
|
|
984
|
+
view_direction = pv.Vector3D(*self.direction)
|
|
985
|
+
view_plane = Plane(n=view_up_vector, o=view_position, x=view_direction)
|
|
986
|
+
position = origin if origin is not None else view_position
|
|
987
|
+
|
|
988
|
+
rotated_plane = view_plane.rotate_xy(math.radians(angle), position)
|
|
989
|
+
self._apply_plane_properties(rotated_plane, view_direction, view_up_vector)
|
|
990
|
+
|
|
991
|
+
def reflect(self, plane):
|
|
992
|
+
"""Reflect this view across a plane.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
plane: A ladybug_geometry Plane across which the object will
|
|
996
|
+
be reflected.
|
|
997
|
+
"""
|
|
998
|
+
view_up_vector = pv.Vector3D(*self.up_vector)
|
|
999
|
+
view_position = pv.Point3D(*self.position)
|
|
1000
|
+
view_direction = pv.Vector3D(*self.direction)
|
|
1001
|
+
view_plane = Plane(n=view_up_vector, o=view_position, x=view_direction)
|
|
1002
|
+
|
|
1003
|
+
ref_plane = view_plane.reflect(plane.n, plane.o)
|
|
1004
|
+
self._apply_plane_properties(ref_plane, view_direction, view_up_vector)
|
|
1005
|
+
|
|
1006
|
+
def scale(self, factor, origin=None):
|
|
1007
|
+
"""Scale this view by a factor from an origin point.
|
|
1008
|
+
|
|
1009
|
+
Args:
|
|
1010
|
+
factor: A number representing how much the object should be scaled.
|
|
1011
|
+
origin: A ladybug_geometry Point3D representing the origin from which
|
|
1012
|
+
to scale. If None, it will be scaled from the World origin (0, 0, 0).
|
|
1013
|
+
"""
|
|
1014
|
+
view_position = pv.Point3D(*self.position)
|
|
1015
|
+
self.position = view_position.scale(factor, origin)
|
|
1016
|
+
self.direction = pv.Vector3D(*self.direction) * factor
|
|
1017
|
+
self.up_vector = pv.Vector3D(*self.up_vector) * factor
|
|
1018
|
+
|
|
1019
|
+
def _apply_plane_properties(self, plane, view_direction, view_up_vector):
|
|
1020
|
+
"""Re-set the position, direction and up_vector from a Plane.
|
|
1021
|
+
|
|
1022
|
+
This method also ensures that the magnitude of the vectors is unchanged
|
|
1023
|
+
(since all Plane objects will have unitized vectors).
|
|
1024
|
+
"""
|
|
1025
|
+
self.position = plane.o
|
|
1026
|
+
self.direction = plane.x * view_direction.magnitude
|
|
1027
|
+
self.up_vector = plane.n * view_up_vector.magnitude
|
|
1028
|
+
|
|
1029
|
+
@staticmethod
|
|
1030
|
+
def _parse_radiance_options(string):
|
|
1031
|
+
"""Parse a radiance option string (e.g. '-ab 4 -ad 256').
|
|
1032
|
+
|
|
1033
|
+
The string should start with a '-' otherwise it will be trimmed to the
|
|
1034
|
+
first '-' in the string.
|
|
1035
|
+
"""
|
|
1036
|
+
try:
|
|
1037
|
+
index = string.index('-')
|
|
1038
|
+
except ValueError:
|
|
1039
|
+
if not ' '.join(string.split()).replace('"', '').replace("'", '').strip():
|
|
1040
|
+
return {}
|
|
1041
|
+
raise ValueError(
|
|
1042
|
+
'Invalid Radiance options string input. '
|
|
1043
|
+
'Failed to find - in input string.'
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
_rad_opt_pattern = r'-[a-zA-Z]+'
|
|
1047
|
+
_rad_opt_compiled_pattern = re.compile(_rad_opt_pattern)
|
|
1048
|
+
sub_string = ' '.join(string[index:].split())
|
|
1049
|
+
value = re.split(_rad_opt_compiled_pattern, sub_string)[1:]
|
|
1050
|
+
key = re.findall(_rad_opt_pattern, sub_string)
|
|
1051
|
+
|
|
1052
|
+
options = collections.OrderedDict()
|
|
1053
|
+
for k, v in zip(key, value):
|
|
1054
|
+
values = v.split()
|
|
1055
|
+
count = len(values)
|
|
1056
|
+
if count == 0:
|
|
1057
|
+
values = ''
|
|
1058
|
+
elif count == 1:
|
|
1059
|
+
values = values[0]
|
|
1060
|
+
options[k[1:]] = values
|
|
1061
|
+
|
|
1062
|
+
return options
|
|
1063
|
+
|
|
1064
|
+
def duplicate(self):
|
|
1065
|
+
"""Get a copy of this object."""
|
|
1066
|
+
return self.__copy__()
|
|
1067
|
+
|
|
1068
|
+
def __copy__(self):
|
|
1069
|
+
new_obj = View(
|
|
1070
|
+
self.identifier, position=self.position, direction=self.direction,
|
|
1071
|
+
up_vector=self.up_vector, type=self.type, h_size=self.h_size,
|
|
1072
|
+
v_size=self.v_size, shift=self.shift, lift=self.lift)
|
|
1073
|
+
new_obj._display_name = self._display_name
|
|
1074
|
+
new_obj._room_identifier = self._room_identifier
|
|
1075
|
+
new_obj._light_path = self._light_path
|
|
1076
|
+
return new_obj
|
|
1077
|
+
|
|
1078
|
+
def ToString(self):
|
|
1079
|
+
"""Overwrite .NET ToString."""
|
|
1080
|
+
return self.__repr__()
|
|
1081
|
+
|
|
1082
|
+
def __key(self):
|
|
1083
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
1084
|
+
return (self.identifier, hash(self.position), hash(self.direction),
|
|
1085
|
+
hash(self.up_vector), self.type, self.h_size,
|
|
1086
|
+
self.v_size, self.shift, self.lift, self._display_name,
|
|
1087
|
+
self._room_identifier)
|
|
1088
|
+
|
|
1089
|
+
def __hash__(self):
|
|
1090
|
+
return hash(self.__key())
|
|
1091
|
+
|
|
1092
|
+
def __eq__(self, other):
|
|
1093
|
+
return isinstance(other, View) and self.__key() == other.__key() and \
|
|
1094
|
+
self.light_path == other.light_path
|
|
1095
|
+
|
|
1096
|
+
def __ne__(self, other):
|
|
1097
|
+
return not self.__eq__(other)
|
|
1098
|
+
|
|
1099
|
+
def __repr__(self):
|
|
1100
|
+
"""View representation."""
|
|
1101
|
+
return self.to_radiance()
|