honeybee-core 1.64.12__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.
- honeybee/__init__.py +23 -0
- honeybee/__main__.py +4 -0
- honeybee/_base.py +331 -0
- honeybee/_basewithshade.py +310 -0
- honeybee/_lockable.py +99 -0
- honeybee/altnumber.py +47 -0
- honeybee/aperture.py +997 -0
- honeybee/boundarycondition.py +358 -0
- honeybee/checkdup.py +173 -0
- honeybee/cli/__init__.py +118 -0
- honeybee/cli/compare.py +132 -0
- honeybee/cli/create.py +265 -0
- honeybee/cli/edit.py +559 -0
- honeybee/cli/lib.py +103 -0
- honeybee/cli/setconfig.py +43 -0
- honeybee/cli/validate.py +224 -0
- honeybee/colorobj.py +363 -0
- honeybee/config.json +5 -0
- honeybee/config.py +347 -0
- honeybee/dictutil.py +54 -0
- honeybee/door.py +746 -0
- honeybee/extensionutil.py +208 -0
- honeybee/face.py +2360 -0
- honeybee/facetype.py +153 -0
- honeybee/logutil.py +79 -0
- honeybee/model.py +4272 -0
- honeybee/orientation.py +132 -0
- honeybee/properties.py +845 -0
- honeybee/room.py +3485 -0
- honeybee/search.py +107 -0
- honeybee/shade.py +514 -0
- honeybee/shademesh.py +362 -0
- honeybee/typing.py +498 -0
- honeybee/units.py +88 -0
- honeybee/writer/__init__.py +7 -0
- honeybee/writer/aperture.py +6 -0
- honeybee/writer/door.py +6 -0
- honeybee/writer/face.py +6 -0
- honeybee/writer/model.py +6 -0
- honeybee/writer/room.py +6 -0
- honeybee/writer/shade.py +6 -0
- honeybee/writer/shademesh.py +6 -0
- honeybee_core-1.64.12.dist-info/METADATA +94 -0
- honeybee_core-1.64.12.dist-info/RECORD +48 -0
- honeybee_core-1.64.12.dist-info/WHEEL +5 -0
- honeybee_core-1.64.12.dist-info/entry_points.txt +2 -0
- honeybee_core-1.64.12.dist-info/licenses/LICENSE +661 -0
- honeybee_core-1.64.12.dist-info/top_level.txt +1 -0
honeybee/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Honeybee core library."""
|
|
2
|
+
import importlib
|
|
3
|
+
import pkgutil
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from honeybee.logutil import get_logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
# find and import honeybee extensions
|
|
12
|
+
# this is a critical step to add additional functionalities to honeybee core library.
|
|
13
|
+
extensions = {}
|
|
14
|
+
for finder, name, ispkg in pkgutil.iter_modules():
|
|
15
|
+
if not name.startswith('honeybee_') or name.count('_') > 1:
|
|
16
|
+
continue
|
|
17
|
+
try:
|
|
18
|
+
extensions[name] = importlib.import_module(name)
|
|
19
|
+
except Exception:
|
|
20
|
+
if (sys.version_info >= (3, 0)):
|
|
21
|
+
logger.exception('Failed to import {0}!'.format(name))
|
|
22
|
+
else:
|
|
23
|
+
logger.info('Successfully imported Honeybee plugin: {}'.format(name))
|
honeybee/__main__.py
ADDED
honeybee/_base.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""Base class for all geometry objects."""
|
|
3
|
+
from ladybug_geometry.geometry3d.pointvector import Point3D
|
|
4
|
+
|
|
5
|
+
from .typing import valid_string
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _Base(object):
|
|
9
|
+
"""A base class for all geometry objects.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
identifier: Text string for a unique object ID. Must be < 100 characters and
|
|
13
|
+
not contain any spaces or special characters.
|
|
14
|
+
|
|
15
|
+
Properties:
|
|
16
|
+
* identifier
|
|
17
|
+
* display_name
|
|
18
|
+
* full_id
|
|
19
|
+
* user_data
|
|
20
|
+
"""
|
|
21
|
+
__slots__ = ('_identifier', '_display_name', '_properties', '_user_data')
|
|
22
|
+
|
|
23
|
+
def __init__(self, identifier):
|
|
24
|
+
"""Initialize base object."""
|
|
25
|
+
self.identifier = identifier
|
|
26
|
+
self._display_name = None
|
|
27
|
+
self._properties = None
|
|
28
|
+
self._user_data = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def identifier(self):
|
|
32
|
+
"""Get or set a text string for the unique object identifier.
|
|
33
|
+
|
|
34
|
+
This identifier remains constant as the object is mutated, copied, and
|
|
35
|
+
serialized to different formats (eg. dict, idf, rad). As such, this
|
|
36
|
+
property is used to reference the object across a Model.
|
|
37
|
+
"""
|
|
38
|
+
return self._identifier
|
|
39
|
+
|
|
40
|
+
@identifier.setter
|
|
41
|
+
def identifier(self, value):
|
|
42
|
+
self._identifier = valid_string(value, 'honeybee object identifier')
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def display_name(self):
|
|
46
|
+
"""Get or set a string for the object name without any character restrictions.
|
|
47
|
+
|
|
48
|
+
If not set, this will be equal to the identifier.
|
|
49
|
+
"""
|
|
50
|
+
if self._display_name is None:
|
|
51
|
+
return self._identifier
|
|
52
|
+
return self._display_name
|
|
53
|
+
|
|
54
|
+
@display_name.setter
|
|
55
|
+
def display_name(self, value):
|
|
56
|
+
if value is not None:
|
|
57
|
+
try:
|
|
58
|
+
value = str(value)
|
|
59
|
+
except UnicodeEncodeError: # Python 2 machine lacking the character set
|
|
60
|
+
pass # keep it as unicode
|
|
61
|
+
self._display_name = value
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def full_id(self):
|
|
65
|
+
"""Get a string with both the object display_name and identifier.
|
|
66
|
+
|
|
67
|
+
This is formatted as display_name[identifier].
|
|
68
|
+
|
|
69
|
+
This is useful in error messages to give users an easy means of finding
|
|
70
|
+
invalid objects within models. If there is no display_name assigned,
|
|
71
|
+
only the identifier will be returned.
|
|
72
|
+
"""
|
|
73
|
+
if self._display_name is None:
|
|
74
|
+
return self._identifier
|
|
75
|
+
else:
|
|
76
|
+
return '{}[{}]'.format(self._display_name, self._identifier)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def properties(self):
|
|
80
|
+
"""Get object properties, including Radiance, Energy and other properties."""
|
|
81
|
+
return self._properties
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def user_data(self):
|
|
85
|
+
"""Get or set an optional dictionary for additional meta data for this object.
|
|
86
|
+
|
|
87
|
+
This will be None until it has been set. All keys and values of this
|
|
88
|
+
dictionary should be of a standard Python type to ensure correct
|
|
89
|
+
serialization of the object to/from JSON (eg. str, float, int, list, dict)
|
|
90
|
+
"""
|
|
91
|
+
return self._user_data
|
|
92
|
+
|
|
93
|
+
@user_data.setter
|
|
94
|
+
def user_data(self, value):
|
|
95
|
+
if value is not None:
|
|
96
|
+
assert isinstance(value, dict), 'Expected dictionary for honeybee ' \
|
|
97
|
+
'object user_data. Got {}.'.format(type(value))
|
|
98
|
+
self._user_data = value
|
|
99
|
+
|
|
100
|
+
def duplicate(self):
|
|
101
|
+
"""Get a copy of this object."""
|
|
102
|
+
return self.__copy__()
|
|
103
|
+
|
|
104
|
+
def display_dict(self):
|
|
105
|
+
"""Get a list of DisplayFace3D dictionaries for visualizing the object."""
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
def _changed_dict(self, other_object, tolerance):
|
|
109
|
+
"""Get a dictionary reporting changes between this object and another.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
other_object: Another object of the same type to be compared to this one.
|
|
113
|
+
tolerance: The tolerance to be used in checking whether the geometry
|
|
114
|
+
has been changed.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
A dictionary with a report of differences. This will be None if there
|
|
118
|
+
are no differences detected between this object and the other object.
|
|
119
|
+
"""
|
|
120
|
+
# check whether each type of property has changed
|
|
121
|
+
geo_changed = not other_object.is_geo_equivalent(self, tolerance)
|
|
122
|
+
meta_changed = other_object.properties.is_equivalent(self.properties)
|
|
123
|
+
if not geo_changed and all(meta_changed.values()):
|
|
124
|
+
return None
|
|
125
|
+
# establish the base dictionary
|
|
126
|
+
base_dict = {
|
|
127
|
+
'type': 'ChangedObject',
|
|
128
|
+
'element_type': self.__class__.__name__,
|
|
129
|
+
'element_id': self.identifier,
|
|
130
|
+
'element_name': self.display_name,
|
|
131
|
+
'geometry_changed': geo_changed
|
|
132
|
+
}
|
|
133
|
+
# add booleans for whether metadata changed
|
|
134
|
+
for atr, equiv in meta_changed.items():
|
|
135
|
+
base_dict['{}_changed'.format(atr)] = not equiv
|
|
136
|
+
# add a representation of the geometry if it has changed
|
|
137
|
+
base_dict['geometry'] = other_object.display_dict()
|
|
138
|
+
if geo_changed:
|
|
139
|
+
base_dict['existing_geometry'] = self.display_dict()
|
|
140
|
+
return base_dict
|
|
141
|
+
|
|
142
|
+
def _base_report_dict(self, dict_type='AddedObject'):
|
|
143
|
+
"""Get a dictionary reporting the object as an addition/deletion to/from a Model.
|
|
144
|
+
"""
|
|
145
|
+
return {
|
|
146
|
+
'type': dict_type,
|
|
147
|
+
'element_type': self.__class__.__name__,
|
|
148
|
+
'element_id': self.identifier,
|
|
149
|
+
'element_name': self.display_name,
|
|
150
|
+
'geometry': self.display_dict()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def _validation_message(
|
|
154
|
+
self, message, raise_exception=True, detailed=False,
|
|
155
|
+
code='000000', extension='Core', error_type='Unknown Error'):
|
|
156
|
+
"""Handle a validation error message given various options.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
message: Text for the error message.
|
|
160
|
+
raise_exception: Boolean to note whether an ValueError should be
|
|
161
|
+
raised with the message. (Default: True).
|
|
162
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
163
|
+
dicts with error info or a string with a message. (Default: False).
|
|
164
|
+
code: Text for the error code. (Default: 000000).
|
|
165
|
+
extension: Text for the name of the Honeybee extension for which duplicate
|
|
166
|
+
identifiers are being evaluated. (Default: Core).
|
|
167
|
+
error_type: Text for the type of error. This should be directly linked
|
|
168
|
+
to the error code and should simply be a human-readable version of
|
|
169
|
+
the error code. (Default: Unknown Error).
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
173
|
+
"""
|
|
174
|
+
# first check whether an exception should be raised or the message returned
|
|
175
|
+
if raise_exception:
|
|
176
|
+
raise ValueError(message)
|
|
177
|
+
if not detailed:
|
|
178
|
+
return message
|
|
179
|
+
# if not, then assemble a dictionary with detailed error information
|
|
180
|
+
error_dict = {
|
|
181
|
+
'type': 'ValidationError',
|
|
182
|
+
'code': code,
|
|
183
|
+
'error_type': error_type,
|
|
184
|
+
'extension_type': extension,
|
|
185
|
+
'element_type': self.__class__.__name__,
|
|
186
|
+
'element_id': [self.identifier],
|
|
187
|
+
'element_name': [self.display_name],
|
|
188
|
+
'message': message
|
|
189
|
+
}
|
|
190
|
+
# add parents to the error dictionary if they exist
|
|
191
|
+
if getattr(self, '_parent', None) is not None:
|
|
192
|
+
parents = []
|
|
193
|
+
rel_obj = self
|
|
194
|
+
while getattr(rel_obj, '_parent', None) is not None:
|
|
195
|
+
rel_obj = getattr(rel_obj, '_parent')
|
|
196
|
+
par_dict = {
|
|
197
|
+
'parent_type': rel_obj.__class__.__name__,
|
|
198
|
+
'id': rel_obj.identifier,
|
|
199
|
+
'name': rel_obj.display_name
|
|
200
|
+
}
|
|
201
|
+
parents.append(par_dict)
|
|
202
|
+
error_dict['parents'] = [parents]
|
|
203
|
+
return [error_dict]
|
|
204
|
+
|
|
205
|
+
def _top_parent(self):
|
|
206
|
+
"""Get the highest parent object that this object is a part of."""
|
|
207
|
+
if getattr(self, '_parent', None) is not None:
|
|
208
|
+
rel_obj = self
|
|
209
|
+
while getattr(rel_obj, '_parent', None) is not None:
|
|
210
|
+
rel_obj = getattr(rel_obj, '_parent')
|
|
211
|
+
return rel_obj
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def _validation_message_child(
|
|
215
|
+
message, child_obj, detailed=False, code='000000', extension='Core',
|
|
216
|
+
error_type='Unknown Error'):
|
|
217
|
+
"""Process a validation error message of a child object.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
message: Text for the error message.
|
|
221
|
+
child_obj: The child object instance for which the error message is for.
|
|
222
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
223
|
+
dicts with error info or a string with a message. (Default: False).
|
|
224
|
+
code: Text for the error code. (Default: 000000).
|
|
225
|
+
extension: Text for the name of the Honeybee extension for which duplicate
|
|
226
|
+
identifiers are being evaluated. (Default: Core).
|
|
227
|
+
error_type: Text for the type of error. This should be directly linked
|
|
228
|
+
to the error code and should simply be a human-readable version of
|
|
229
|
+
the error code. (Default: Unknown Error).
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
A string with the message or a dictionary if detailed is True.
|
|
233
|
+
"""
|
|
234
|
+
# first check whether an exception should be raised or the message returned
|
|
235
|
+
if not detailed:
|
|
236
|
+
return message
|
|
237
|
+
# if not, then assemble a dictionary with detailed error information
|
|
238
|
+
error_dict = {
|
|
239
|
+
'type': 'ValidationError',
|
|
240
|
+
'code': code,
|
|
241
|
+
'error_type': error_type,
|
|
242
|
+
'extension_type': extension,
|
|
243
|
+
'element_type': child_obj.__class__.__name__,
|
|
244
|
+
'element_id': [child_obj.identifier],
|
|
245
|
+
'element_name': [child_obj.display_name],
|
|
246
|
+
'message': message
|
|
247
|
+
}
|
|
248
|
+
# add parents to the error dictionary
|
|
249
|
+
parents = []
|
|
250
|
+
rel_obj = child_obj
|
|
251
|
+
while getattr(rel_obj, '_parent', None) is not None:
|
|
252
|
+
rel_obj = getattr(rel_obj, '_parent')
|
|
253
|
+
par_dict = {
|
|
254
|
+
'parent_type': rel_obj.__class__.__name__,
|
|
255
|
+
'id': rel_obj.identifier,
|
|
256
|
+
'name': rel_obj.display_name
|
|
257
|
+
}
|
|
258
|
+
parents.append(par_dict)
|
|
259
|
+
error_dict['parents'] = [parents]
|
|
260
|
+
return error_dict
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def _from_dict_error_message(obj_dict, exception_obj):
|
|
264
|
+
"""Give an error message when the object serialization from_dict fails.
|
|
265
|
+
|
|
266
|
+
This error message will include the identifier if it exists in the dict.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
obj_dict: The objection dictionary that failed serialization.
|
|
270
|
+
exception_obj: The exception object to be included in the message.
|
|
271
|
+
"""
|
|
272
|
+
obj_name = obj_dict['type'] if 'type' in obj_dict else 'Honeybee object'
|
|
273
|
+
full_id = ''
|
|
274
|
+
if 'identifier' in obj_dict and obj_dict['identifier'] is not None:
|
|
275
|
+
full_id = '{}[{}]'.format(obj_dict['display_name'], obj_dict['identifier']) \
|
|
276
|
+
if 'display_name' in obj_dict and obj_dict['display_name'] is not None \
|
|
277
|
+
else obj_dict['identifier']
|
|
278
|
+
msg = '{} "{}" is not valid and is not following honeybee-schema:\n{}'.format(
|
|
279
|
+
obj_name, full_id, exception_obj)
|
|
280
|
+
raise ValueError(msg)
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def _display_face(face3d, color):
|
|
284
|
+
"""Create a DisplayFace3D dictionary from a Face3D and color."""
|
|
285
|
+
return {
|
|
286
|
+
'type': 'DisplayFace3D',
|
|
287
|
+
'geometry': face3d.to_dict(),
|
|
288
|
+
'color': color.to_dict(),
|
|
289
|
+
'display_mode': 'SurfaceWithEdges'
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def _calculate_min(geometry_objects):
|
|
294
|
+
"""Calculate min Point3D around an array of geometry with min attributes."""
|
|
295
|
+
first_obj = geometry_objects[0]
|
|
296
|
+
min_pt = [first_obj.min.x, first_obj.min.y, first_obj.min.z]
|
|
297
|
+
for obj in geometry_objects[1:]:
|
|
298
|
+
if obj.min.x < min_pt[0]:
|
|
299
|
+
min_pt[0] = obj.min.x
|
|
300
|
+
if obj.min.y < min_pt[1]:
|
|
301
|
+
min_pt[1] = obj.min.y
|
|
302
|
+
if obj.min.z < min_pt[2]:
|
|
303
|
+
min_pt[2] = obj.min.z
|
|
304
|
+
return Point3D(*min_pt)
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def _calculate_max(geometry_objects):
|
|
308
|
+
"""Calculate max Point3D around an array of geometry with max attributes."""
|
|
309
|
+
first_obj = geometry_objects[0]
|
|
310
|
+
max_pt = [first_obj.max.x, first_obj.max.y, first_obj.max.z]
|
|
311
|
+
for obj in geometry_objects[1:]:
|
|
312
|
+
if obj.max.x > max_pt[0]:
|
|
313
|
+
max_pt[0] = obj.max.x
|
|
314
|
+
if obj.max.y > max_pt[1]:
|
|
315
|
+
max_pt[1] = obj.max.y
|
|
316
|
+
if obj.max.z > max_pt[2]:
|
|
317
|
+
max_pt[2] = obj.max.z
|
|
318
|
+
return Point3D(*max_pt)
|
|
319
|
+
|
|
320
|
+
def __copy__(self):
|
|
321
|
+
new_obj = self.__class__(self.identifier)
|
|
322
|
+
new_obj._display_name = self._display_name
|
|
323
|
+
new_obj._user_data = None if self.user_data is None else self.user_data.copy()
|
|
324
|
+
return new_obj
|
|
325
|
+
|
|
326
|
+
def ToString(self):
|
|
327
|
+
"""Overwrite .NET ToString."""
|
|
328
|
+
return self.__repr__()
|
|
329
|
+
|
|
330
|
+
def __repr__(self):
|
|
331
|
+
return 'Honeybee Base Object: %s' % self.display_name
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""Base class for all geometry objects that can have shades as children."""
|
|
3
|
+
from ._base import _Base
|
|
4
|
+
from .shade import Shade
|
|
5
|
+
from .typing import invalid_dict_error
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _BaseWithShade(_Base):
|
|
9
|
+
"""A base class for all objects that can have Shades nested on them.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
identifier: Text string for a unique object ID. Must be < 100 characters and
|
|
13
|
+
not contain any spaces or special characters.
|
|
14
|
+
|
|
15
|
+
Properties:
|
|
16
|
+
* identifier
|
|
17
|
+
* display_name
|
|
18
|
+
* geometry
|
|
19
|
+
* outdoor_shades
|
|
20
|
+
* indoor_shades
|
|
21
|
+
* shades
|
|
22
|
+
"""
|
|
23
|
+
__slots__ = ('_outdoor_shades', '_indoor_shades')
|
|
24
|
+
|
|
25
|
+
def __init__(self, identifier):
|
|
26
|
+
"""Initialize base with shade object."""
|
|
27
|
+
_Base.__init__(self, identifier) # process the identifier
|
|
28
|
+
self._outdoor_shades = []
|
|
29
|
+
self._indoor_shades = []
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def outdoor_shades(self):
|
|
33
|
+
"""Get an array of all outdoor shades assigned to this object."""
|
|
34
|
+
return tuple(self._outdoor_shades)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def indoor_shades(self):
|
|
38
|
+
"""Get an array of all indoor shades assigned to this object."""
|
|
39
|
+
return tuple(self._indoor_shades)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def shades(self):
|
|
43
|
+
"""Get an array of all shades (indoor + outdoor) assigned to this object."""
|
|
44
|
+
return self._outdoor_shades + self._indoor_shades
|
|
45
|
+
|
|
46
|
+
def remove_shades(self):
|
|
47
|
+
"""Remove all indoor and outdoor shades assigned to this object."""
|
|
48
|
+
self.remove_indoor_shades()
|
|
49
|
+
self.remove_outdoor_shades()
|
|
50
|
+
|
|
51
|
+
def remove_outdoor_shades(self):
|
|
52
|
+
"""Remove all outdoor shades assigned to this object."""
|
|
53
|
+
for shade in self._outdoor_shades:
|
|
54
|
+
shade._parent = None
|
|
55
|
+
self._outdoor_shades = []
|
|
56
|
+
|
|
57
|
+
def remove_indoor_shades(self):
|
|
58
|
+
"""Remove all indoor shades assigned to this object."""
|
|
59
|
+
for shade in self._indoor_shades:
|
|
60
|
+
shade._parent = None
|
|
61
|
+
shade._is_indoor = False
|
|
62
|
+
self._indoor_shades = []
|
|
63
|
+
|
|
64
|
+
def add_outdoor_shade(self, shade):
|
|
65
|
+
"""Add a Shade object to the outdoors of this object.
|
|
66
|
+
|
|
67
|
+
Outdoor Shade objects can be used to represent balconies, outdoor furniture,
|
|
68
|
+
overhangs, light shelves, fins, the exterior part of mullions, etc.
|
|
69
|
+
For representing larger shade objects like trees or other buildings,
|
|
70
|
+
it may be more appropriate to add them to the Model as orphaned_shades
|
|
71
|
+
without a specific parent object.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
shade: A Shade object to add to the outdoors of this object.
|
|
75
|
+
"""
|
|
76
|
+
assert isinstance(shade, Shade), \
|
|
77
|
+
'Expected Shade for outdoor_shade. Got {}.'.format(type(shade))
|
|
78
|
+
assert shade.parent is None, 'Shade cannot have more than one parent object.'
|
|
79
|
+
shade._parent = self
|
|
80
|
+
shade._is_detached = False
|
|
81
|
+
self._outdoor_shades.append(shade)
|
|
82
|
+
|
|
83
|
+
def add_indoor_shade(self, shade):
|
|
84
|
+
"""Add a Shade object to be added to the indoors of this object.
|
|
85
|
+
|
|
86
|
+
Indoor Shade objects can be used to represent furniture, the interior
|
|
87
|
+
portion of light shelves, the interior part of mullions, etc.
|
|
88
|
+
For representing finely detailed objects like blinds or roller shades,
|
|
89
|
+
it may be more appropriate to model them as materials assigned to
|
|
90
|
+
Aperture properties (like Radiance materials or Energy constructions).
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
shade: A Shade object to add to the indoors of this object.
|
|
94
|
+
"""
|
|
95
|
+
assert isinstance(shade, Shade), \
|
|
96
|
+
'Expected Shade for indoor_shade. Got {}.'.format(type(shade))
|
|
97
|
+
assert shade.parent is None, 'Shade cannot have more than one parent object.'
|
|
98
|
+
shade._parent = self
|
|
99
|
+
shade._is_detached = False
|
|
100
|
+
shade._is_indoor = True
|
|
101
|
+
self._indoor_shades.append(shade)
|
|
102
|
+
|
|
103
|
+
def add_outdoor_shades(self, shades):
|
|
104
|
+
"""Add a list of Shade objects to the outdoors of this object.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
shades: A list of Shade objects to add to the outdoors of this object.
|
|
108
|
+
"""
|
|
109
|
+
for shade in shades:
|
|
110
|
+
self.add_outdoor_shade(shade)
|
|
111
|
+
|
|
112
|
+
def add_indoor_shades(self, shades):
|
|
113
|
+
"""Add a list of Shade objects to the indoors of this object.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
shades: A list of Shade objects to add to the indoors of this object.
|
|
117
|
+
"""
|
|
118
|
+
for shade in shades:
|
|
119
|
+
self.add_indoor_shade(shade)
|
|
120
|
+
|
|
121
|
+
def move_shades(self, moving_vec):
|
|
122
|
+
"""Move all indoor and outdoor shades assigned to this object along a vector.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
moving_vec: A ladybug_geometry Vector3D with the direction and distance
|
|
126
|
+
to move the shades.
|
|
127
|
+
"""
|
|
128
|
+
for oshd in self._outdoor_shades:
|
|
129
|
+
oshd.move(moving_vec)
|
|
130
|
+
for ishd in self._indoor_shades:
|
|
131
|
+
ishd.move(moving_vec)
|
|
132
|
+
|
|
133
|
+
def rotate_shades(self, axis, angle, origin):
|
|
134
|
+
"""Rotate all indoor and outdoor shades assigned to this object.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
axis: A ladybug_geometry Vector3D axis representing the axis of rotation.
|
|
138
|
+
angle: An angle for rotation in degrees.
|
|
139
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
140
|
+
object will be rotated.
|
|
141
|
+
"""
|
|
142
|
+
for oshd in self._outdoor_shades:
|
|
143
|
+
oshd.rotate(axis, angle, origin)
|
|
144
|
+
for ishd in self._indoor_shades:
|
|
145
|
+
ishd.rotate(axis, angle, origin)
|
|
146
|
+
|
|
147
|
+
def rotate_xy_shades(self, angle, origin):
|
|
148
|
+
"""Rotate all indoor and outdoor shades counterclockwise in the world XY plane.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
angle: An angle in degrees.
|
|
152
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
153
|
+
object will be rotated.
|
|
154
|
+
"""
|
|
155
|
+
for oshd in self._outdoor_shades:
|
|
156
|
+
oshd.rotate_xy(angle, origin)
|
|
157
|
+
for ishd in self._indoor_shades:
|
|
158
|
+
ishd.rotate_xy(angle, origin)
|
|
159
|
+
|
|
160
|
+
def reflect_shades(self, plane):
|
|
161
|
+
"""Reflect all indoor and outdoor shades assigned to this object across a plane.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
plane: A ladybug_geometry Plane across which the object will
|
|
165
|
+
be reflected.
|
|
166
|
+
"""
|
|
167
|
+
for oshd in self._outdoor_shades:
|
|
168
|
+
oshd.reflect(plane)
|
|
169
|
+
for ishd in self._indoor_shades:
|
|
170
|
+
ishd.reflect(plane)
|
|
171
|
+
|
|
172
|
+
def scale_shades(self, factor, origin=None):
|
|
173
|
+
"""Scale all indoor and outdoor shades assigned to this object by a factor.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
factor: A number representing how much the object should be scaled.
|
|
177
|
+
origin: A ladybug_geometry Point3D representing the origin from which
|
|
178
|
+
to scale. If None, it will be scaled from the World origin (0, 0, 0).
|
|
179
|
+
"""
|
|
180
|
+
for oshd in self._outdoor_shades:
|
|
181
|
+
oshd.scale(factor, origin)
|
|
182
|
+
for ishd in self._indoor_shades:
|
|
183
|
+
ishd.scale(factor, origin)
|
|
184
|
+
|
|
185
|
+
def _add_prefix_shades(self, prefix):
|
|
186
|
+
"""Change the name of all child shades by inserting a prefix.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
prefix: Text that will be inserted at the start of this shades' name
|
|
190
|
+
and display_name.
|
|
191
|
+
"""
|
|
192
|
+
for shade in self._outdoor_shades:
|
|
193
|
+
shade.add_prefix(prefix)
|
|
194
|
+
for shade in self._indoor_shades:
|
|
195
|
+
shade.add_prefix(prefix)
|
|
196
|
+
|
|
197
|
+
def _check_planar_shades(self, tolerance, detailed=False):
|
|
198
|
+
"""Check that all of the child shades are planar."""
|
|
199
|
+
msgs = []
|
|
200
|
+
for oshd in self._outdoor_shades:
|
|
201
|
+
msgs.append(oshd.check_planar(tolerance, False, detailed))
|
|
202
|
+
for ishd in self._indoor_shades:
|
|
203
|
+
msgs.append(ishd.check_planar(tolerance, False, detailed))
|
|
204
|
+
flat_msgs = [m for m in msgs if m]
|
|
205
|
+
return flat_msgs if detailed else '\n'.join(flat_msgs)
|
|
206
|
+
|
|
207
|
+
def _check_self_intersecting_shades(self, tolerance, detailed=False):
|
|
208
|
+
"""Check that no edges of the indoor or outdoor shades self-intersect."""
|
|
209
|
+
msgs = []
|
|
210
|
+
for oshd in self._outdoor_shades:
|
|
211
|
+
msgs.append(oshd.check_self_intersecting(tolerance, False, detailed))
|
|
212
|
+
for ishd in self._indoor_shades:
|
|
213
|
+
msgs.append(ishd.check_self_intersecting(tolerance, False, detailed))
|
|
214
|
+
flat_msgs = [m for m in msgs if m]
|
|
215
|
+
return flat_msgs if detailed else '\n'.join(flat_msgs)
|
|
216
|
+
|
|
217
|
+
def _add_shades_to_dict(
|
|
218
|
+
self, base, abridged=False, included_prop=None, include_plane=True):
|
|
219
|
+
"""Method used to add child shades to the parent base dictionary.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
base: The base object dictionary to which the child shades will be added.
|
|
223
|
+
abridged: Boolean to note whether the extension properties of the
|
|
224
|
+
object should be included in detail (False) or just referenced by
|
|
225
|
+
identifier (True). (Default: False).
|
|
226
|
+
included_prop: List of properties to filter keys that must be included in
|
|
227
|
+
output dictionary. For example ['energy'] will include 'energy' key if
|
|
228
|
+
available in properties to_dict. By default all the keys will be
|
|
229
|
+
included. To exclude all the keys from extensions use an empty list.
|
|
230
|
+
include_plane: Boolean to note wether the plane of the Face3D should be
|
|
231
|
+
included in the output. This can preserve the orientation of the
|
|
232
|
+
X/Y axes of the plane but is not required and can be removed to
|
|
233
|
+
keep the dictionary smaller. (Default: True).
|
|
234
|
+
"""
|
|
235
|
+
if self._outdoor_shades != []:
|
|
236
|
+
base['outdoor_shades'] = [shd.to_dict(abridged, included_prop, include_plane)
|
|
237
|
+
for shd in self._outdoor_shades]
|
|
238
|
+
if self._indoor_shades != []:
|
|
239
|
+
base['indoor_shades'] = [shd.to_dict(abridged, included_prop, include_plane)
|
|
240
|
+
for shd in self._indoor_shades]
|
|
241
|
+
|
|
242
|
+
def _recover_shades_from_dict(self, data):
|
|
243
|
+
"""Method used to recover shades from a dictionary.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
data: The dictionary representation of this object to which shades will
|
|
247
|
+
be added from the dictionary.
|
|
248
|
+
"""
|
|
249
|
+
if 'outdoor_shades' in data and data['outdoor_shades'] is not None:
|
|
250
|
+
for sh in data['outdoor_shades']:
|
|
251
|
+
try:
|
|
252
|
+
oshd = Shade.from_dict(sh)
|
|
253
|
+
oshd._parent = self
|
|
254
|
+
self._outdoor_shades.append(oshd)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
invalid_dict_error(sh, e)
|
|
257
|
+
if 'indoor_shades' in data and data['indoor_shades'] is not None:
|
|
258
|
+
for sh in data['indoor_shades']:
|
|
259
|
+
try:
|
|
260
|
+
ishd = Shade.from_dict(sh)
|
|
261
|
+
ishd._parent = self
|
|
262
|
+
ishd._is_indoor = True
|
|
263
|
+
self._indoor_shades.append(ishd)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
invalid_dict_error(sh, e)
|
|
266
|
+
|
|
267
|
+
def _duplicate_child_shades(self, new_object):
|
|
268
|
+
"""Add duplicated child shades to a duplicated new_object."""
|
|
269
|
+
new_object._outdoor_shades = [oshd.duplicate() for oshd in self._outdoor_shades]
|
|
270
|
+
new_object._indoor_shades = [ishd.duplicate() for ishd in self._indoor_shades]
|
|
271
|
+
for oshd in new_object._outdoor_shades:
|
|
272
|
+
oshd._parent = new_object
|
|
273
|
+
for ishd in new_object._indoor_shades:
|
|
274
|
+
ishd._parent = new_object
|
|
275
|
+
ishd._is_indoor = True
|
|
276
|
+
|
|
277
|
+
def _min_with_shades(self, geometry):
|
|
278
|
+
"""Calculate min Point3D around this object's geometry and its shades."""
|
|
279
|
+
all_geo = self._outdoor_shades + self._indoor_shades
|
|
280
|
+
all_geo.append(geometry)
|
|
281
|
+
return self._calculate_min(all_geo)
|
|
282
|
+
|
|
283
|
+
def _max_with_shades(self, geometry):
|
|
284
|
+
"""Calculate max Point3D around this object's geometry and its shades."""
|
|
285
|
+
all_geo = self._outdoor_shades + self._indoor_shades
|
|
286
|
+
all_geo.append(geometry)
|
|
287
|
+
return self._calculate_max(all_geo)
|
|
288
|
+
|
|
289
|
+
def _are_shades_equivalent(self, other, tolerance=0.01):
|
|
290
|
+
"""Get a boolean for whether this object's shades are equivalent to another.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
other: Another object for which shade equivalency will be tested.
|
|
294
|
+
tolerance: The minimum difference between the coordinate values of two
|
|
295
|
+
vertices at which they can be considered geometrically equivalent.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
True if shades are equivalent. False if shades are not equivalent.
|
|
299
|
+
"""
|
|
300
|
+
if len(self._outdoor_shades) != len(other._outdoor_shades):
|
|
301
|
+
return False
|
|
302
|
+
for f1, f2 in zip(self._outdoor_shades, other._outdoor_shades):
|
|
303
|
+
if not f1.is_geo_equivalent(f2, tolerance):
|
|
304
|
+
return False
|
|
305
|
+
if len(self._indoor_shades) != len(other._indoor_shades):
|
|
306
|
+
return False
|
|
307
|
+
for f1, f2 in zip(self._indoor_shades, other._indoor_shades):
|
|
308
|
+
if not f1.is_geo_equivalent(f2, tolerance):
|
|
309
|
+
return False
|
|
310
|
+
return True
|