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
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""Boundary Condition for Face, Aperture, Door."""
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from .typing import float_in_range, tuple_with_length
|
|
5
|
+
from .altnumber import autocalculate
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _BoundaryCondition(object):
|
|
9
|
+
"""Base boundary condition class."""
|
|
10
|
+
|
|
11
|
+
__slots__ = ()
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize Boundary condition."""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def name(self):
|
|
18
|
+
"""Get the name of the boundary condition (ie. 'Outdoors', 'Ground')."""
|
|
19
|
+
return self.__class__.__name__
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def view_factor(self):
|
|
23
|
+
"""Get the view factor to the ground."""
|
|
24
|
+
return 'autocalculate'
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def sun_exposure_idf(self):
|
|
28
|
+
"""Get a text string for sun exposure, which is write-able into an IDF."""
|
|
29
|
+
return 'NoSun'
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def wind_exposure_idf(self):
|
|
33
|
+
""" Get a text string for wind exposure, which is write-able into an IDF."""
|
|
34
|
+
return 'NoWind'
|
|
35
|
+
|
|
36
|
+
def to_dict(self):
|
|
37
|
+
"""Get the boundary condition as a dictionary."""
|
|
38
|
+
return {'type': self.name}
|
|
39
|
+
|
|
40
|
+
def ToString(self):
|
|
41
|
+
"""Overwrite .NET ToString."""
|
|
42
|
+
return self.__repr__()
|
|
43
|
+
|
|
44
|
+
def __repr__(self):
|
|
45
|
+
return self.name
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Outdoors(_BoundaryCondition):
|
|
49
|
+
"""Outdoor boundary condition.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
sun_exposure: A boolean noting whether the boundary is exposed to sun.
|
|
53
|
+
Default: True.
|
|
54
|
+
wind_exposure: A boolean noting whether the boundary is exposed to wind.
|
|
55
|
+
Default: True.
|
|
56
|
+
view_factor: A number between 0 and 1 for the view factor to the ground.
|
|
57
|
+
This input can also be an Autocalculate object to signify that the view
|
|
58
|
+
factor automatically calculated. Default: autocalculate.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
__slots__ = ('_sun_exposure', '_wind_exposure', '_view_factor')
|
|
62
|
+
|
|
63
|
+
def __init__(self, sun_exposure=True, wind_exposure=True,
|
|
64
|
+
view_factor=autocalculate):
|
|
65
|
+
"""Initialize Outdoors boundary condition."""
|
|
66
|
+
assert isinstance(sun_exposure, bool), \
|
|
67
|
+
'Input sun_exposure must be a Boolean. Got {}.'.format(type(sun_exposure))
|
|
68
|
+
self._sun_exposure = sun_exposure
|
|
69
|
+
assert isinstance(wind_exposure, bool), \
|
|
70
|
+
'Input wind_exposure must be a Boolean. Got {}.'.format(type(wind_exposure))
|
|
71
|
+
self._wind_exposure = wind_exposure
|
|
72
|
+
if view_factor == autocalculate:
|
|
73
|
+
self._view_factor = autocalculate
|
|
74
|
+
else:
|
|
75
|
+
self._view_factor = float_in_range(
|
|
76
|
+
view_factor, 0.0, 1.0, 'view factor to ground')
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_dict(cls, data):
|
|
80
|
+
"""Initialize Outdoors BoundaryCondition from a dictionary.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
data: A dictionary representation of the boundary condition.
|
|
84
|
+
"""
|
|
85
|
+
assert data['type'] == 'Outdoors', 'Expected dictionary for Outdoors boundary ' \
|
|
86
|
+
'condition. Got {}.'.format(data['type'])
|
|
87
|
+
sun_exposure = True if 'sun_exposure' not in data else data['sun_exposure']
|
|
88
|
+
wind_exposure = True if 'wind_exposure' not in data else data['wind_exposure']
|
|
89
|
+
view_factor = autocalculate if 'view_factor' not in data or \
|
|
90
|
+
data['view_factor'] == autocalculate.to_dict() else data['view_factor']
|
|
91
|
+
return cls(sun_exposure, wind_exposure, view_factor)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def sun_exposure(self):
|
|
95
|
+
"""Get a boolean noting whether the boundary is exposed to sun."""
|
|
96
|
+
return self._sun_exposure
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def wind_exposure(self):
|
|
100
|
+
"""Get a boolean noting whether the boundary is exposed to wind."""
|
|
101
|
+
return self._wind_exposure
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def view_factor(self):
|
|
105
|
+
"""Get the view factor to the ground as a number or 'autocalculate'."""
|
|
106
|
+
return self._view_factor
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def sun_exposure_idf(self):
|
|
110
|
+
"""Get a text string for sun exposure, which is write-able into an IDF."""
|
|
111
|
+
return 'NoSun' if not self.sun_exposure else 'SunExposed'
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def wind_exposure_idf(self):
|
|
115
|
+
"""Get a text string for wind exposure, which is write-able into an IDF."""
|
|
116
|
+
return 'NoWind' if not self.wind_exposure else 'WindExposed'
|
|
117
|
+
|
|
118
|
+
def to_dict(self, full=False):
|
|
119
|
+
"""Get the boundary condition as a dictionary.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
full: Set to True to get the full dictionary which includes energy
|
|
123
|
+
simulation specific keys such as sun_exposure, wind_exposure and
|
|
124
|
+
view_factor. (Default: False).
|
|
125
|
+
"""
|
|
126
|
+
bc_dict = {'type': self.name}
|
|
127
|
+
if full:
|
|
128
|
+
bc_dict['sun_exposure'] = self.sun_exposure
|
|
129
|
+
bc_dict['wind_exposure'] = self.wind_exposure
|
|
130
|
+
bc_dict['view_factor'] = autocalculate.to_dict() if \
|
|
131
|
+
self.view_factor == autocalculate else self.view_factor
|
|
132
|
+
return bc_dict
|
|
133
|
+
|
|
134
|
+
def __key(self):
|
|
135
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
136
|
+
return (self.sun_exposure, self.wind_exposure, self.view_factor)
|
|
137
|
+
|
|
138
|
+
def __hash__(self):
|
|
139
|
+
return hash(self.__key())
|
|
140
|
+
|
|
141
|
+
def __eq__(self, other):
|
|
142
|
+
return isinstance(other, Outdoors) and self.__key() == other.__key()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Surface(_BoundaryCondition):
|
|
146
|
+
"""Boundary condition when an object is adjacent to another object."""
|
|
147
|
+
|
|
148
|
+
__slots__ = ('_boundary_condition_objects',)
|
|
149
|
+
|
|
150
|
+
def __init__(self, boundary_condition_objects, sub_face=False):
|
|
151
|
+
"""Initialize Surface boundary condition.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
boundary_condition_objects: A list of up to 3 object identifiers that are
|
|
155
|
+
adjacent to this one. The first object is always immediately
|
|
156
|
+
adjacent and is of the same object type (Face, Aperture, Door). When
|
|
157
|
+
this boundary condition is applied to a Face, the second object in the
|
|
158
|
+
tuple will be the parent Room of the adjacent object. When the boundary
|
|
159
|
+
condition is applied to a sub-face (Door or Aperture), the second object
|
|
160
|
+
will be the parent Face of the adjacent sub-face and the third object
|
|
161
|
+
will be the parent Room of the adjacent sub-face.
|
|
162
|
+
sub_face: Boolean to note whether this boundary condition is applied to a
|
|
163
|
+
sub-face (an Aperture or a Door) instead of a Face. (Default: False).
|
|
164
|
+
"""
|
|
165
|
+
if sub_face:
|
|
166
|
+
self._boundary_condition_objects = tuple_with_length(
|
|
167
|
+
boundary_condition_objects, 3, str,
|
|
168
|
+
'boundary_condition_objects for Apertures or Doors')
|
|
169
|
+
else:
|
|
170
|
+
self._boundary_condition_objects = tuple_with_length(
|
|
171
|
+
boundary_condition_objects, 2, str,
|
|
172
|
+
'boundary_condition_objects for Faces')
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def from_dict(cls, data, sub_face=False):
|
|
176
|
+
"""Initialize Surface BoundaryCondition from a dictionary.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
data: A dictionary representation of the boundary condition.
|
|
180
|
+
sub_face: Boolean to note whether this boundary condition is applied to a
|
|
181
|
+
sub-face (an Aperture or a Door) instead of a Face. Default: False.
|
|
182
|
+
"""
|
|
183
|
+
assert data['type'] == 'Surface', 'Expected dictionary for Surface boundary ' \
|
|
184
|
+
'condition. Got {}.'.format(data['type'])
|
|
185
|
+
return cls(data['boundary_condition_objects'], sub_face)
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def from_other_object(cls, other_object, sub_face=False):
|
|
189
|
+
"""Initialize Surface boundary condition from an adjacent other object.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
other_object: Another object (Face, Aperture, Door) of the same type
|
|
193
|
+
that this boundary condition is assigned. This other_object will be
|
|
194
|
+
set as the adjacent object in this boundary condition.
|
|
195
|
+
sub_face: Boolean to note whether this boundary condition is applied to a
|
|
196
|
+
sub-face (an Aperture or a Door) instead of a Face. Default: False.
|
|
197
|
+
"""
|
|
198
|
+
error_msg = 'Surface boundary conditions can only be assigned to objects' \
|
|
199
|
+
' with parent Rooms.'
|
|
200
|
+
bc_objects = [other_object.identifier]
|
|
201
|
+
if other_object.has_parent:
|
|
202
|
+
bc_objects.append(other_object.parent.identifier)
|
|
203
|
+
if sub_face:
|
|
204
|
+
if other_object.parent.has_parent:
|
|
205
|
+
bc_objects.append(other_object.parent.parent.identifier)
|
|
206
|
+
else:
|
|
207
|
+
raise AttributeError(error_msg)
|
|
208
|
+
else:
|
|
209
|
+
raise AttributeError(error_msg)
|
|
210
|
+
return cls(bc_objects, sub_face)
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def boundary_condition_objects(self):
|
|
214
|
+
"""Get a tuple of up to 3 object identifiers that are adjacent to this one.
|
|
215
|
+
|
|
216
|
+
The first object is always the one that is immediately adjacent and is of
|
|
217
|
+
the same object type (Face, Aperture, Door).
|
|
218
|
+
When this boundary condition is applied to a Face, the second object in the
|
|
219
|
+
tuple will be the parent Room of the adjacent object.
|
|
220
|
+
When the boundary condition is applied to a sub-face (Door or Aperture),
|
|
221
|
+
the second object will be the parent Face of the sub-face and the third
|
|
222
|
+
object will be the parent Room of the adjacent sub-face.
|
|
223
|
+
"""
|
|
224
|
+
return self._boundary_condition_objects
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def boundary_condition_object(self):
|
|
228
|
+
"""Get the identifier of the object adjacent to this one."""
|
|
229
|
+
return self._boundary_condition_objects[0]
|
|
230
|
+
|
|
231
|
+
def to_dict(self):
|
|
232
|
+
"""Get the boundary condition as a dictionary.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
full: Set to True to get the full dictionary which includes energy
|
|
236
|
+
simulation specific keys such as sun_exposure, wind_exposure and
|
|
237
|
+
view_factor. Default: False.
|
|
238
|
+
"""
|
|
239
|
+
return {'type': self.name,
|
|
240
|
+
'boundary_condition_objects': self.boundary_condition_objects}
|
|
241
|
+
|
|
242
|
+
def __key(self):
|
|
243
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
244
|
+
return self.boundary_condition_objects
|
|
245
|
+
|
|
246
|
+
def __hash__(self):
|
|
247
|
+
return hash(self.__key())
|
|
248
|
+
|
|
249
|
+
def __eq__(self, other):
|
|
250
|
+
return isinstance(other, Surface) and self.__key() == other.__key()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class Ground(_BoundaryCondition):
|
|
254
|
+
"""Ground boundary condition.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
data: A dictionary representation of the boundary condition.
|
|
258
|
+
"""
|
|
259
|
+
__slots__ = ()
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def from_dict(cls, data):
|
|
263
|
+
"""Initialize Ground BoundaryCondition from a dictionary."""
|
|
264
|
+
assert data['type'] == 'Ground', 'Expected dictionary for Ground boundary ' \
|
|
265
|
+
'condition. Got {}.'.format(data['type'])
|
|
266
|
+
return cls()
|
|
267
|
+
|
|
268
|
+
def __eq__(self, other):
|
|
269
|
+
return isinstance(other, Ground)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class _BoundaryConditions(object):
|
|
273
|
+
"""Boundary conditions."""
|
|
274
|
+
|
|
275
|
+
def __init__(self):
|
|
276
|
+
self._outdoors = Outdoors()
|
|
277
|
+
self._ground = Ground()
|
|
278
|
+
self._bc_name_dict = None
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def outdoors(self):
|
|
282
|
+
"""Default outdoor boundary condition."""
|
|
283
|
+
return self._outdoors
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def ground(self):
|
|
287
|
+
"""Default ground boundary condition."""
|
|
288
|
+
return self._ground
|
|
289
|
+
|
|
290
|
+
def surface(self, other_object, sub_face=False):
|
|
291
|
+
"""Get a Surface boundary condition.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
other_object: The other object that is adjacent to the one that will
|
|
295
|
+
bear this Surface boundary condition.
|
|
296
|
+
sub_face: Boolean to note whether the boundary condition is for a
|
|
297
|
+
sub-face (Aperture or Door) instead of a Face. (Default: False).
|
|
298
|
+
"""
|
|
299
|
+
return Surface.from_other_object(other_object, sub_face)
|
|
300
|
+
|
|
301
|
+
def by_name(self, bc_name):
|
|
302
|
+
"""Get a boundary condition object instance by its name.
|
|
303
|
+
|
|
304
|
+
This method will correct for capitalization as well as the presence of
|
|
305
|
+
spaces and underscores. Note that this method only works for boundary
|
|
306
|
+
conditions with all of their inputs defaulted.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
bc_name: A boundary condition name.
|
|
310
|
+
"""
|
|
311
|
+
if self._bc_name_dict is None:
|
|
312
|
+
self._build_bc_name_dict()
|
|
313
|
+
try:
|
|
314
|
+
return self._bc_name_dict[re.sub(r'[\s_]', '', bc_name.lower())]
|
|
315
|
+
except KeyError:
|
|
316
|
+
raise ValueError(
|
|
317
|
+
'"{}" is not a valid boundary condition name.\nChoose from the '
|
|
318
|
+
'following: {}'.format(bc_name, list(self._bc_name_dict.keys())))
|
|
319
|
+
|
|
320
|
+
def _build_bc_name_dict(self):
|
|
321
|
+
"""Build a dictionary that can be used to lookup boundary conditions by name."""
|
|
322
|
+
attr = [atr for atr in dir(self) if not atr.startswith('_')]
|
|
323
|
+
clean_attr = [re.sub(r'[\s_]', '', atr.lower()) for atr in attr]
|
|
324
|
+
self._bc_name_dict = {}
|
|
325
|
+
for atr_name, atr in zip(clean_attr, attr):
|
|
326
|
+
try:
|
|
327
|
+
full_attr = getattr(self, '_' + atr)
|
|
328
|
+
self._bc_name_dict[atr_name] = full_attr
|
|
329
|
+
except AttributeError:
|
|
330
|
+
pass # callable method that has no static default object
|
|
331
|
+
|
|
332
|
+
def __contains__(self, value):
|
|
333
|
+
return isinstance(value, _BoundaryCondition)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
boundary_conditions = _BoundaryConditions()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def get_bc_from_position(positions, ground_depth=0):
|
|
340
|
+
"""Return a boundary condition based on the relationship to a ground plane.
|
|
341
|
+
|
|
342
|
+
Positions that are entirely at or below the ground_depth will get a Ground
|
|
343
|
+
boundary condition. If there are any positions above the ground_depth, an
|
|
344
|
+
Outdoors boundary condition will be returned.
|
|
345
|
+
|
|
346
|
+
args:
|
|
347
|
+
positions: A list of ladybug_geometry Point3D objects representing the
|
|
348
|
+
vertices of an object.
|
|
349
|
+
ground_depth: The Z value above which positions are considered Outdoors
|
|
350
|
+
instead of Ground.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Face type instance.
|
|
354
|
+
"""
|
|
355
|
+
for position in positions:
|
|
356
|
+
if position.z > ground_depth:
|
|
357
|
+
return boundary_conditions.outdoors
|
|
358
|
+
return boundary_conditions.ground
|
honeybee/checkdup.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Utilities to check whether there are any duplicate values in a list of ids."""
|
|
3
|
+
|
|
4
|
+
import collections
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def check_duplicate_identifiers(
|
|
8
|
+
objects_to_check, raise_exception=True, obj_name='', detailed=False,
|
|
9
|
+
code='000000', extension='Core', error_type='Duplicate Object Identifier'):
|
|
10
|
+
"""Check whether there are duplicated identifiers across a list of objects.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
objects_to_check: A list of honeybee objects across which duplicate
|
|
14
|
+
identifiers will be checked.
|
|
15
|
+
raise_exception: Boolean to note whether an exception should be raised if
|
|
16
|
+
duplicated identifiers are found. (Default: True).
|
|
17
|
+
obj_name: An optional name for the object to be included in the error
|
|
18
|
+
message. Fro example, 'Room', 'Face', 'Aperture'.
|
|
19
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
20
|
+
dicts with error info or a string with a message. (Default: False).
|
|
21
|
+
code: Text for the error code. (Default: 0000).
|
|
22
|
+
extension: Text for the name of the Honeybee extension for which duplicate
|
|
23
|
+
identifiers are being evaluated. (Default: Core).
|
|
24
|
+
error_type: Text for the type of error. This should be directly linked
|
|
25
|
+
to the error code and should simply be a human-readable version of
|
|
26
|
+
the error code. (Default: Unknown Error).
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A message string indicating the duplicated identifiers (if detailed is False)
|
|
30
|
+
or a list of dictionaries with information about the duplicated identifiers
|
|
31
|
+
(if detailed is True). This string (or list) will be empty if no duplicates
|
|
32
|
+
were found.
|
|
33
|
+
"""
|
|
34
|
+
detailed = False if raise_exception else detailed
|
|
35
|
+
obj_id_iter = (obj.identifier for obj in objects_to_check)
|
|
36
|
+
dup = [t for t, c in collections.Counter(obj_id_iter).items() if c > 1]
|
|
37
|
+
if len(dup) != 0:
|
|
38
|
+
if detailed:
|
|
39
|
+
# find the object display names
|
|
40
|
+
dis_names = []
|
|
41
|
+
for obj_id in dup:
|
|
42
|
+
dis_name = None
|
|
43
|
+
for obj in objects_to_check:
|
|
44
|
+
if obj.identifier == obj_id:
|
|
45
|
+
dis_name = obj.display_name
|
|
46
|
+
dis_names.append(dis_name)
|
|
47
|
+
err_list = []
|
|
48
|
+
for dup_id, dis_name in zip(dup, dis_names):
|
|
49
|
+
msg = 'There is a duplicated {} identifier: {}'.format(obj_name, dup_id)
|
|
50
|
+
dup_dict = {
|
|
51
|
+
'type': 'ValidationError',
|
|
52
|
+
'code': code,
|
|
53
|
+
'error_type': error_type,
|
|
54
|
+
'extension_type': extension,
|
|
55
|
+
'element_type': obj_name,
|
|
56
|
+
'element_id': [dup_id],
|
|
57
|
+
'message': msg
|
|
58
|
+
}
|
|
59
|
+
if dis_name is not None:
|
|
60
|
+
dup_dict['element_name'] = [dis_name]
|
|
61
|
+
err_list.append(dup_dict)
|
|
62
|
+
return err_list
|
|
63
|
+
msg = 'The following duplicated {} identifiers were found:\n{}'.format(
|
|
64
|
+
obj_name, '\n'.join(dup))
|
|
65
|
+
if raise_exception:
|
|
66
|
+
raise ValueError(msg)
|
|
67
|
+
return msg
|
|
68
|
+
return [] if detailed else ''
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def check_duplicate_identifiers_parent(
|
|
72
|
+
objects_to_check, raise_exception=True, obj_name='', detailed=False,
|
|
73
|
+
code='000000', extension='Core', error_type='Duplicate Object Identifier'):
|
|
74
|
+
"""Check whether there are duplicated identifiers across a list of objects.
|
|
75
|
+
|
|
76
|
+
The error message will include the identifiers of top-level parents in order
|
|
77
|
+
to make it easier to find the duplicated objects in the model.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
objects_to_check: A list of honeybee objects across which duplicate
|
|
81
|
+
identifiers will be checked. These objects must have the ability to
|
|
82
|
+
have parents for this method to run correctly.
|
|
83
|
+
raise_exception: Boolean to note whether an exception should be raised if
|
|
84
|
+
duplicated identifiers are found. (Default: True).
|
|
85
|
+
obj_name: An optional name for the object to be included in the error
|
|
86
|
+
message. For example, 'Room', 'Face', 'Aperture'.
|
|
87
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
88
|
+
dicts with error info or a string with a message. (Default: False).
|
|
89
|
+
code: Text for the error code. (Default: 0000).
|
|
90
|
+
extension: Text for the name of the Honeybee extension for which duplicate
|
|
91
|
+
identifiers are being evaluated. (Default: Core).
|
|
92
|
+
error_type: Text for the type of error. This should be directly linked
|
|
93
|
+
to the error code and should simply be a human-readable version of
|
|
94
|
+
the error code. (Default: Unknown Error).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
A message string indicating the duplicated identifiers (if detailed is False)
|
|
98
|
+
or a list of dictionaries with information about the duplicated identifiers
|
|
99
|
+
(if detailed is True). This string (or list) will be empty if no duplicates
|
|
100
|
+
were found.
|
|
101
|
+
"""
|
|
102
|
+
detailed = False if raise_exception else detailed
|
|
103
|
+
obj_id_iter = (obj.identifier for obj in objects_to_check)
|
|
104
|
+
dup = [t for t, c in collections.Counter(obj_id_iter).items() if c > 1]
|
|
105
|
+
if len(dup) != 0:
|
|
106
|
+
# find the relevant top-level parents
|
|
107
|
+
top_par, dis_names = [], []
|
|
108
|
+
for obj_id in dup:
|
|
109
|
+
rel_parents, dis_name = [], None
|
|
110
|
+
for obj in objects_to_check:
|
|
111
|
+
if obj.identifier == obj_id:
|
|
112
|
+
dis_name = obj.display_name
|
|
113
|
+
if obj.has_parent:
|
|
114
|
+
try:
|
|
115
|
+
par_obj = obj.top_level_parent
|
|
116
|
+
except AttributeError:
|
|
117
|
+
par_obj = obj.parent
|
|
118
|
+
rel_parents.append(par_obj)
|
|
119
|
+
top_par.append(rel_parents)
|
|
120
|
+
dis_names.append(dis_name)
|
|
121
|
+
# if a detailed dictionary is requested, then create it
|
|
122
|
+
if detailed:
|
|
123
|
+
err_list = []
|
|
124
|
+
for dup_id, dis_name, rel_par in zip(dup, dis_names, top_par):
|
|
125
|
+
dup_dict = {
|
|
126
|
+
'type': 'ValidationError',
|
|
127
|
+
'code': code,
|
|
128
|
+
'error_type': error_type,
|
|
129
|
+
'extension_type': extension,
|
|
130
|
+
'element_type': obj_name,
|
|
131
|
+
'element_id': [dup_id]
|
|
132
|
+
}
|
|
133
|
+
if dis_name is not None:
|
|
134
|
+
dup_dict['element_name'] = [dis_name]
|
|
135
|
+
msg = 'There is a duplicated {} identifier: {}'.format(obj_name, dup_id)
|
|
136
|
+
if len(rel_par) != 0:
|
|
137
|
+
dup_dict['top_parents'] = []
|
|
138
|
+
msg += '\n Relevant Top-Level Parents:\n'
|
|
139
|
+
for par_o in rel_par:
|
|
140
|
+
par_dict = {
|
|
141
|
+
'parent_type': par_o.__class__.__name__,
|
|
142
|
+
'id': par_o.identifier,
|
|
143
|
+
'name': par_o.display_name
|
|
144
|
+
}
|
|
145
|
+
dup_dict['top_parents'].append(par_dict)
|
|
146
|
+
msg += ' {} "{}"\n'.format(
|
|
147
|
+
par_o.__class__.__name__, par_o.full_id)
|
|
148
|
+
dup_dict['message'] = msg
|
|
149
|
+
err_list.append(dup_dict)
|
|
150
|
+
return err_list
|
|
151
|
+
# if just an error message is requested, then build it from the information
|
|
152
|
+
msg = 'The following duplicated {} identifiers were found:\n'.format(obj_name)
|
|
153
|
+
for obj_id, rel_par in zip(dup, top_par):
|
|
154
|
+
obj_msg = obj_id + '\n'
|
|
155
|
+
if len(rel_par) != 0:
|
|
156
|
+
obj_msg += ' Relevant Top-Level Parents:\n'
|
|
157
|
+
for par_o in rel_par:
|
|
158
|
+
obj_msg += ' {} "{}"\n'.format(
|
|
159
|
+
par_o.__class__.__name__, par_o.full_id)
|
|
160
|
+
msg += obj_msg
|
|
161
|
+
msg = msg.strip()
|
|
162
|
+
if raise_exception:
|
|
163
|
+
raise ValueError(msg)
|
|
164
|
+
return msg
|
|
165
|
+
return [] if detailed else ''
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def is_equivalent(object_1, object_2):
|
|
169
|
+
"""Check if two objects are equal with an initial check for the same instance.
|
|
170
|
+
"""
|
|
171
|
+
if object_1 is object_2: # first see if they're the same instance
|
|
172
|
+
return True
|
|
173
|
+
return object_1 == object_2 # two objects that should have == operators
|
honeybee/cli/__init__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Line Interface (CLI) entry point for honeybee and honeybee extensions.
|
|
3
|
+
|
|
4
|
+
Use this file only to add command related to honeybee-core. For adding extra commands
|
|
5
|
+
from each extention see below.
|
|
6
|
+
|
|
7
|
+
Note:
|
|
8
|
+
|
|
9
|
+
Do not import this module in your code directly unless you are extending the command
|
|
10
|
+
line interface. For running the commands execute them from the command line or as a
|
|
11
|
+
subprocess (e.g. ``subprocess.call(['honeybee', 'viz'])``)
|
|
12
|
+
|
|
13
|
+
Honeybee is using click (https://click.palletsprojects.com/en/7.x/) for creating the CLI.
|
|
14
|
+
You can extend the command line interface from inside each extention by following these
|
|
15
|
+
steps:
|
|
16
|
+
|
|
17
|
+
1. Create a ``cli.py`` file in your extension.
|
|
18
|
+
2. Import the ``main`` function from this ``honeybee.cli``.
|
|
19
|
+
3. Add your commands and command groups to main using add_command method.
|
|
20
|
+
4. Add ``import [your-extention].cli`` to ``__init__.py`` file to the commands are added
|
|
21
|
+
to the cli when the module is loaded.
|
|
22
|
+
|
|
23
|
+
The good practice is to group all your extention commands in a command group named after
|
|
24
|
+
the extension. This will make the commands organized under extension namespace. For
|
|
25
|
+
instance commands for `honeybee-radiance` will be called like
|
|
26
|
+
``honeybee radiance [radiance-command]``.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
.. code-block:: python
|
|
30
|
+
|
|
31
|
+
import click
|
|
32
|
+
from honeybee.cli import main
|
|
33
|
+
|
|
34
|
+
@click.group()
|
|
35
|
+
def radiance():
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
# add commands to radiance group
|
|
39
|
+
@radiance.command('daylight-factor')
|
|
40
|
+
# ...
|
|
41
|
+
def daylight_factor():
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# finally add the newly created commands to honeybee cli
|
|
46
|
+
main.add_command(radiance)
|
|
47
|
+
|
|
48
|
+
# do not forget to import this module in __init__.py otherwise it will not be added
|
|
49
|
+
# to honeybee commands.
|
|
50
|
+
|
|
51
|
+
Note:
|
|
52
|
+
|
|
53
|
+
For extension with several commands you can use a folder structure instead
|
|
54
|
+
of a single file. Refer to ``honeybee-radiance`` for an example.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
import click
|
|
58
|
+
import sys
|
|
59
|
+
import logging
|
|
60
|
+
import json
|
|
61
|
+
|
|
62
|
+
from ..config import folders
|
|
63
|
+
from honeybee.cli.setconfig import set_config
|
|
64
|
+
from honeybee.cli.validate import validate
|
|
65
|
+
from honeybee.cli.compare import compare
|
|
66
|
+
from honeybee.cli.create import create
|
|
67
|
+
from honeybee.cli.edit import edit
|
|
68
|
+
from honeybee.cli.lib import lib
|
|
69
|
+
|
|
70
|
+
_logger = logging.getLogger(__name__)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@click.group()
|
|
74
|
+
@click.version_option()
|
|
75
|
+
def main():
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@main.command('config')
|
|
80
|
+
@click.option('--output-file', help='Optional file to output the JSON string of '
|
|
81
|
+
'the config object. By default, it will be printed out to stdout',
|
|
82
|
+
type=click.File('w'), default='-', show_default=True)
|
|
83
|
+
def config(output_file):
|
|
84
|
+
"""Get a JSON object with all configuration information"""
|
|
85
|
+
try:
|
|
86
|
+
config_dict = {
|
|
87
|
+
'default_simulation_folder': folders.default_simulation_folder,
|
|
88
|
+
'honeybee_schema_version': folders.honeybee_schema_version_str,
|
|
89
|
+
'python_package_path': folders.python_package_path,
|
|
90
|
+
'python_scripts_path': folders.python_scripts_path,
|
|
91
|
+
'python_exe_path': folders.python_exe_path,
|
|
92
|
+
'python_version': folders.python_version_str,
|
|
93
|
+
'default_standards_folder': folders.default_standards_folder
|
|
94
|
+
}
|
|
95
|
+
output_file.write(json.dumps(config_dict, indent=4))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
_logger.exception('Failed to retrieve configurations.\n{}'.format(e))
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
else:
|
|
100
|
+
sys.exit(0)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@main.command('viz')
|
|
104
|
+
def viz():
|
|
105
|
+
"""Check if honeybee is flying!"""
|
|
106
|
+
click.echo('viiiiiiiiiiiiizzzzzzzzz!')
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
main.add_command(set_config, name='set-config')
|
|
110
|
+
main.add_command(validate)
|
|
111
|
+
main.add_command(compare)
|
|
112
|
+
main.add_command(create)
|
|
113
|
+
main.add_command(edit)
|
|
114
|
+
main.add_command(lib)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|