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.
Files changed (48) hide show
  1. honeybee/__init__.py +23 -0
  2. honeybee/__main__.py +4 -0
  3. honeybee/_base.py +331 -0
  4. honeybee/_basewithshade.py +310 -0
  5. honeybee/_lockable.py +99 -0
  6. honeybee/altnumber.py +47 -0
  7. honeybee/aperture.py +997 -0
  8. honeybee/boundarycondition.py +358 -0
  9. honeybee/checkdup.py +173 -0
  10. honeybee/cli/__init__.py +118 -0
  11. honeybee/cli/compare.py +132 -0
  12. honeybee/cli/create.py +265 -0
  13. honeybee/cli/edit.py +559 -0
  14. honeybee/cli/lib.py +103 -0
  15. honeybee/cli/setconfig.py +43 -0
  16. honeybee/cli/validate.py +224 -0
  17. honeybee/colorobj.py +363 -0
  18. honeybee/config.json +5 -0
  19. honeybee/config.py +347 -0
  20. honeybee/dictutil.py +54 -0
  21. honeybee/door.py +746 -0
  22. honeybee/extensionutil.py +208 -0
  23. honeybee/face.py +2360 -0
  24. honeybee/facetype.py +153 -0
  25. honeybee/logutil.py +79 -0
  26. honeybee/model.py +4272 -0
  27. honeybee/orientation.py +132 -0
  28. honeybee/properties.py +845 -0
  29. honeybee/room.py +3485 -0
  30. honeybee/search.py +107 -0
  31. honeybee/shade.py +514 -0
  32. honeybee/shademesh.py +362 -0
  33. honeybee/typing.py +498 -0
  34. honeybee/units.py +88 -0
  35. honeybee/writer/__init__.py +7 -0
  36. honeybee/writer/aperture.py +6 -0
  37. honeybee/writer/door.py +6 -0
  38. honeybee/writer/face.py +6 -0
  39. honeybee/writer/model.py +6 -0
  40. honeybee/writer/room.py +6 -0
  41. honeybee/writer/shade.py +6 -0
  42. honeybee/writer/shademesh.py +6 -0
  43. honeybee_core-1.64.12.dist-info/METADATA +94 -0
  44. honeybee_core-1.64.12.dist-info/RECORD +48 -0
  45. honeybee_core-1.64.12.dist-info/WHEEL +5 -0
  46. honeybee_core-1.64.12.dist-info/entry_points.txt +2 -0
  47. honeybee_core-1.64.12.dist-info/licenses/LICENSE +661 -0
  48. 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