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/shademesh.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""Honeybee ShadeMesh."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
from ladybug_geometry.geometry3d import Mesh3D, Face3D
|
|
7
|
+
from ladybug.color import Color
|
|
8
|
+
|
|
9
|
+
from ._base import _Base
|
|
10
|
+
from .typing import clean_string
|
|
11
|
+
from .properties import ShadeMeshProperties
|
|
12
|
+
import honeybee.writer.shademesh as writer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ShadeMesh(_Base):
|
|
16
|
+
"""A single planar shade.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
identifier: Text string for a unique Shade ID. Must be < 100 characters and
|
|
20
|
+
not contain any spaces or special characters.
|
|
21
|
+
geometry: A ladybug-geometry Mesh3D.
|
|
22
|
+
is_detached: Boolean to note whether this object is detached from other
|
|
23
|
+
geometry. Cases where this should be True include shade representing
|
|
24
|
+
surrounding buildings or context. (Default: True).
|
|
25
|
+
|
|
26
|
+
Properties:
|
|
27
|
+
* identifier
|
|
28
|
+
* display_name
|
|
29
|
+
* is_detached
|
|
30
|
+
* geometry
|
|
31
|
+
* vertices
|
|
32
|
+
* faces
|
|
33
|
+
* center
|
|
34
|
+
* area
|
|
35
|
+
* min
|
|
36
|
+
* max
|
|
37
|
+
* type_color
|
|
38
|
+
* bc_color
|
|
39
|
+
* user_data
|
|
40
|
+
"""
|
|
41
|
+
__slots__ = ('_geometry', '_is_detached')
|
|
42
|
+
TYPE_COLORS = {
|
|
43
|
+
False: Color(120, 75, 190),
|
|
44
|
+
True: Color(80, 50, 128)
|
|
45
|
+
}
|
|
46
|
+
BC_COLOR = Color(120, 75, 190)
|
|
47
|
+
|
|
48
|
+
def __init__(self, identifier, geometry, is_detached=True):
|
|
49
|
+
"""A single planar shade."""
|
|
50
|
+
_Base.__init__(self, identifier) # process the identifier
|
|
51
|
+
|
|
52
|
+
# process the geometry and basic properties
|
|
53
|
+
assert isinstance(geometry, Mesh3D), \
|
|
54
|
+
'Expected ladybug_geometry Mesh3D. Got {}'.format(type(geometry))
|
|
55
|
+
self._geometry = geometry
|
|
56
|
+
self.is_detached = is_detached
|
|
57
|
+
|
|
58
|
+
# initialize properties for extensions
|
|
59
|
+
self._properties = ShadeMeshProperties(self)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_dict(cls, data):
|
|
63
|
+
"""Initialize an ShadeMesh from a dictionary.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
data: A dictionary representation of an ShadeMesh object.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
# check the type of dictionary
|
|
70
|
+
assert data['type'] == 'ShadeMesh', 'Expected ShadeMesh dictionary. ' \
|
|
71
|
+
'Got {}.'.format(data['type'])
|
|
72
|
+
|
|
73
|
+
is_detached = data['is_detached'] if 'is_detached' in data else True
|
|
74
|
+
shade = cls(
|
|
75
|
+
data['identifier'], Mesh3D.from_dict(data['geometry']), is_detached)
|
|
76
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
77
|
+
shade.display_name = data['display_name']
|
|
78
|
+
if 'user_data' in data and data['user_data'] is not None:
|
|
79
|
+
shade.user_data = data['user_data']
|
|
80
|
+
|
|
81
|
+
if data['properties']['type'] == 'ShadeMeshProperties':
|
|
82
|
+
shade.properties._load_extension_attr_from_dict(data['properties'])
|
|
83
|
+
return shade
|
|
84
|
+
except Exception as e:
|
|
85
|
+
cls._from_dict_error_message(data, e)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def is_detached(self):
|
|
89
|
+
"""Get or set a boolean for whether this object is detached from other geometry.
|
|
90
|
+
"""
|
|
91
|
+
return self._is_detached
|
|
92
|
+
|
|
93
|
+
@is_detached.setter
|
|
94
|
+
def is_detached(self, value):
|
|
95
|
+
try:
|
|
96
|
+
self._is_detached = bool(value)
|
|
97
|
+
except TypeError:
|
|
98
|
+
raise TypeError(
|
|
99
|
+
'Expected boolean for ShadeMesh.is_detached. Got {}.'.format(value))
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def geometry(self):
|
|
103
|
+
"""Get a ladybug_geometry Mesh3D object representing the Shade."""
|
|
104
|
+
return self._geometry
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def vertices(self):
|
|
108
|
+
"""Get a tuple of ladybug_geometry Point3D for the vertices of the mesh."""
|
|
109
|
+
return self._geometry.vertices
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def faces(self):
|
|
113
|
+
"""Get a tuple of tuples for the faces of the mesh."""
|
|
114
|
+
return self._geometry.faces
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def center(self):
|
|
118
|
+
"""Get a ladybug_geometry Point3D for the center of the shade.
|
|
119
|
+
|
|
120
|
+
Note that this is the center of the bounding box around this geometry
|
|
121
|
+
and not the area or volume centroid.
|
|
122
|
+
"""
|
|
123
|
+
return self._geometry.center
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def area(self):
|
|
127
|
+
"""Get the surface area of the shade mesh."""
|
|
128
|
+
return self._geometry.area
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def min(self):
|
|
132
|
+
"""Get a Point3D for the minimum of the bounding box around the object."""
|
|
133
|
+
return self._geometry.min
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def max(self):
|
|
137
|
+
"""Get a Point3D for the maximum of the bounding box around the object."""
|
|
138
|
+
return self._geometry.max
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def type_color(self):
|
|
142
|
+
"""Get a Color to be used in visualizations by type."""
|
|
143
|
+
return self.TYPE_COLORS[self.is_detached]
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def bc_color(self):
|
|
147
|
+
"""Get a Color to be used in visualizations by boundary condition."""
|
|
148
|
+
return self.BC_COLOR
|
|
149
|
+
|
|
150
|
+
def add_prefix(self, prefix):
|
|
151
|
+
"""Change the identifier of this object by inserting a prefix.
|
|
152
|
+
|
|
153
|
+
This is particularly useful in workflows where you duplicate and edit
|
|
154
|
+
a starting object and then want to combine it with the original object
|
|
155
|
+
into one Model (like making a model of repeated rooms) since all objects
|
|
156
|
+
within a Model must have unique identifiers.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
prefix: Text that will be inserted at the start of this object's identifier
|
|
160
|
+
and display_name. It is recommended that this prefix be short to
|
|
161
|
+
avoid maxing out the 100 allowable characters for honeybee identifiers.
|
|
162
|
+
"""
|
|
163
|
+
self._identifier = clean_string('{}_{}'.format(prefix, self.identifier))
|
|
164
|
+
self.display_name = '{}_{}'.format(prefix, self.display_name)
|
|
165
|
+
self.properties.add_prefix(prefix)
|
|
166
|
+
|
|
167
|
+
def move(self, moving_vec):
|
|
168
|
+
"""Move this Shade along a vector.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
moving_vec: A ladybug_geometry Vector3D with the direction and distance
|
|
172
|
+
to move the face.
|
|
173
|
+
"""
|
|
174
|
+
self._geometry = self.geometry.move(moving_vec)
|
|
175
|
+
self.properties.move(moving_vec)
|
|
176
|
+
|
|
177
|
+
def rotate(self, axis, angle, origin):
|
|
178
|
+
"""Rotate this Shade by a certain angle around an axis and origin.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
axis: A ladybug_geometry Vector3D axis representing the axis of rotation.
|
|
182
|
+
angle: An angle for rotation in degrees.
|
|
183
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
184
|
+
object will be rotated.
|
|
185
|
+
"""
|
|
186
|
+
self._geometry = self.geometry.rotate(axis, math.radians(angle), origin)
|
|
187
|
+
self.properties.rotate(axis, angle, origin)
|
|
188
|
+
|
|
189
|
+
def rotate_xy(self, angle, origin):
|
|
190
|
+
"""Rotate this Shade counterclockwise in the world XY plane by a certain angle.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
angle: An angle in degrees.
|
|
194
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
195
|
+
object will be rotated.
|
|
196
|
+
"""
|
|
197
|
+
self._geometry = self.geometry.rotate_xy(math.radians(angle), origin)
|
|
198
|
+
self.properties.rotate_xy(angle, origin)
|
|
199
|
+
|
|
200
|
+
def reflect(self, plane):
|
|
201
|
+
"""Reflect this Shade across a plane.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
plane: A ladybug_geometry Plane across which the object will
|
|
205
|
+
be reflected.
|
|
206
|
+
"""
|
|
207
|
+
self._geometry = self.geometry.reflect(plane.n, plane.o)
|
|
208
|
+
self.properties.reflect(plane)
|
|
209
|
+
|
|
210
|
+
def scale(self, factor, origin=None):
|
|
211
|
+
"""Scale this Shade by a factor from an origin point.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
factor: A number representing how much the object should be scaled.
|
|
215
|
+
origin: A ladybug_geometry Point3D representing the origin from which
|
|
216
|
+
to scale. If None, it will be scaled from the World origin (0, 0, 0).
|
|
217
|
+
"""
|
|
218
|
+
self._geometry = self.geometry.scale(factor, origin)
|
|
219
|
+
self.properties.scale(factor, origin)
|
|
220
|
+
|
|
221
|
+
def triangulate_and_remove_degenerate_faces(self, tolerance=0.01):
|
|
222
|
+
"""Triangulate non-planar faces in the mesh and remove all degenerate faces.
|
|
223
|
+
|
|
224
|
+
This is helpful for certain geometry interfaces that require perfectly
|
|
225
|
+
planar geometry without duplicate or colinear vertices.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
tolerance: The minimum distance between a vertex and the boundary segments
|
|
229
|
+
at which point the vertex is considered colinear. Default: 0.01,
|
|
230
|
+
suitable for objects in meters.
|
|
231
|
+
"""
|
|
232
|
+
new_faces, verts = [], self.geometry.vertices
|
|
233
|
+
for shd in self.faces:
|
|
234
|
+
shd_verts = [verts[v] for v in shd]
|
|
235
|
+
shf = Face3D(shd_verts)
|
|
236
|
+
if len(shd_verts) == 4 and not \
|
|
237
|
+
shf.check_planar(tolerance, raise_exception=False):
|
|
238
|
+
shades = ((shd[0], shd[1], shd[2]), (shd[2], shd[3], shd[0]))
|
|
239
|
+
for shade in shades:
|
|
240
|
+
shd_verts = [verts[v] for v in shade]
|
|
241
|
+
shade_face = Face3D(shd_verts)
|
|
242
|
+
try:
|
|
243
|
+
shade_face.remove_colinear_vertices(tolerance)
|
|
244
|
+
except AssertionError:
|
|
245
|
+
continue # degenerate face to remove
|
|
246
|
+
new_faces.append(shade)
|
|
247
|
+
else:
|
|
248
|
+
try:
|
|
249
|
+
new_face = shf.remove_colinear_vertices(tolerance)
|
|
250
|
+
except AssertionError:
|
|
251
|
+
continue # degenerate face to remove
|
|
252
|
+
if len(new_face.vertices) == len(shd):
|
|
253
|
+
new_faces.append(shd)
|
|
254
|
+
else: # quad face with duplicate or colinear verts
|
|
255
|
+
new_sh = tuple(shd[shd_verts.index(v)] for v in new_face.vertices)
|
|
256
|
+
new_faces.append(new_sh)
|
|
257
|
+
self._geometry = Mesh3D(verts, new_faces)
|
|
258
|
+
|
|
259
|
+
def is_geo_equivalent(self, shade_mesh, tolerance=0.01):
|
|
260
|
+
"""Get a boolean for whether this object is geometrically equivalent to another.
|
|
261
|
+
|
|
262
|
+
The total number of vertices and the ordering of these vertices can be
|
|
263
|
+
different but the geometries must share the same center point and be
|
|
264
|
+
next to one another to within the tolerance.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
shade_mesh: Another ShadeMesh for which geometric equivalency will be tested.
|
|
268
|
+
tolerance: The minimum difference between the coordinate values of two
|
|
269
|
+
vertices at which they can be considered geometrically equivalent.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
True if geometrically equivalent. False if not geometrically equivalent.
|
|
273
|
+
"""
|
|
274
|
+
meta_1 = (self.display_name, self.is_detached)
|
|
275
|
+
meta_2 = (shade_mesh.display_name, shade_mesh.is_detached)
|
|
276
|
+
if meta_1 != meta_2:
|
|
277
|
+
return False
|
|
278
|
+
if len(self.geometry.vertices) != len(shade_mesh.geometry.vertices):
|
|
279
|
+
return False
|
|
280
|
+
if len(self.geometry.faces) != len(shade_mesh.geometry.faces):
|
|
281
|
+
return False
|
|
282
|
+
return all(pt.is_equivalent(o_pt, tolerance) for pt, o_pt in
|
|
283
|
+
zip(self.geometry.vertices, shade_mesh.geometry.vertices))
|
|
284
|
+
|
|
285
|
+
def display_dict(self):
|
|
286
|
+
"""Get a list of DisplayMesh3D dictionaries for visualizing the object."""
|
|
287
|
+
return [self._display_mesh(self.geometry, self.type_color)]
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def to(self):
|
|
291
|
+
"""ShadeMesh writer object.
|
|
292
|
+
|
|
293
|
+
Use this method to access Writer class to write the shade in different formats.
|
|
294
|
+
|
|
295
|
+
Usage:
|
|
296
|
+
|
|
297
|
+
.. code-block:: python
|
|
298
|
+
|
|
299
|
+
shade_mesh.to.idf(shade) -> idf string.
|
|
300
|
+
shade_mesh.to.radiance(shade) -> Radiance string.
|
|
301
|
+
"""
|
|
302
|
+
return writer
|
|
303
|
+
|
|
304
|
+
def to_dict(self, abridged=False, included_prop=None):
|
|
305
|
+
"""Return Shade as a dictionary.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
abridged: Boolean to note whether the extension properties of the
|
|
309
|
+
object (ie. modifiers, transmittance schedule) should be included in
|
|
310
|
+
detail (False) or just referenced by identifier (True). Default: False.
|
|
311
|
+
included_prop: List of properties to filter keys that must be included in
|
|
312
|
+
output dictionary. For example ['energy'] will include 'energy' key if
|
|
313
|
+
available in properties to_dict. By default all the keys will be
|
|
314
|
+
included. To exclude all the keys from extensions use an empty list.
|
|
315
|
+
"""
|
|
316
|
+
base = {'type': 'ShadeMesh'}
|
|
317
|
+
base['identifier'] = self.identifier
|
|
318
|
+
base['display_name'] = self.display_name
|
|
319
|
+
base['properties'] = self.properties.to_dict(abridged, included_prop)
|
|
320
|
+
base['geometry'] = self._geometry.to_dict()
|
|
321
|
+
if not self.is_detached:
|
|
322
|
+
base['is_detached'] = self.is_detached
|
|
323
|
+
if self.user_data is not None:
|
|
324
|
+
base['user_data'] = self.user_data
|
|
325
|
+
return base
|
|
326
|
+
|
|
327
|
+
def to_shades(self):
|
|
328
|
+
"""Return a list of Honeybee Shade objects derived from this ShadeMesh.
|
|
329
|
+
|
|
330
|
+
Note that the resulting Shades may be degenerate or non-planar so it
|
|
331
|
+
may be useful to call triangulate_and_remove_degenerate_faces on this
|
|
332
|
+
object before converting to Shades.
|
|
333
|
+
"""
|
|
334
|
+
from honeybee.shade import Shade # imported here to avoid circular import
|
|
335
|
+
shades = []
|
|
336
|
+
for i, shade_geo in enumerate(self.geometry.face_vertices):
|
|
337
|
+
shade_id = '{}_{}'.format(self.identifier, i)
|
|
338
|
+
shade = Shade(shade_id, Face3D(shade_geo), self.is_detached)
|
|
339
|
+
if self._display_name is not None:
|
|
340
|
+
shade.display_name = '{} {}'.format(self.display_name, i)
|
|
341
|
+
shades.append(shade)
|
|
342
|
+
return shades
|
|
343
|
+
|
|
344
|
+
@staticmethod
|
|
345
|
+
def _display_mesh(mesh3d, color):
|
|
346
|
+
"""Create a DisplayMesh3D dictionary from a Mesh3D and color."""
|
|
347
|
+
return {
|
|
348
|
+
'type': 'DisplayMesh3D',
|
|
349
|
+
'geometry': mesh3d.to_dict(),
|
|
350
|
+
'color': color.to_dict(),
|
|
351
|
+
'display_mode': 'SurfaceWithEdges'
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
def __copy__(self):
|
|
355
|
+
new_shade = ShadeMesh(self.identifier, self.geometry, self.is_detached)
|
|
356
|
+
new_shade._display_name = self._display_name
|
|
357
|
+
new_shade._user_data = None if self.user_data is None else self.user_data.copy()
|
|
358
|
+
new_shade._properties._duplicate_extension_attr(self._properties)
|
|
359
|
+
return new_shade
|
|
360
|
+
|
|
361
|
+
def __repr__(self):
|
|
362
|
+
return 'ShadeMesh: %s' % self.display_name
|