honeybee-core 1.62.12__tar.gz → 1.64.0__tar.gz

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 (111) hide show
  1. {honeybee_core-1.62.12/honeybee_core.egg-info → honeybee_core-1.64.0}/PKG-INFO +1 -1
  2. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/aperture.py +0 -7
  3. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/door.py +0 -7
  4. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/face.py +0 -7
  5. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/model.py +278 -8
  6. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/room.py +192 -84
  7. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/shade.py +2 -9
  8. {honeybee_core-1.62.12 → honeybee_core-1.64.0/honeybee_core.egg-info}/PKG-INFO +1 -1
  9. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/model_test.py +72 -3
  10. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.github/workflows/ci.yaml +0 -0
  11. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.github/workflows/dependency-release.yaml +0 -0
  12. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.gitignore +0 -0
  13. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.releaserc.json +0 -0
  14. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/CODE_OF_CONDUCT.md +0 -0
  15. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/CONTRIBUTING.md +0 -0
  16. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/LICENSE +0 -0
  17. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/MANIFEST.in +0 -0
  18. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/README.md +0 -0
  19. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/deploy.sh +0 -0
  20. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/dev-requirements.txt +0 -0
  21. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_build/.nojekyll +0 -0
  22. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_build/README.md +0 -0
  23. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_build/docs/README.md +0 -0
  24. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_static/custom.css +0 -0
  25. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_templates/layout.html +0 -0
  26. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/cli/index.rst +0 -0
  27. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/conf.py +0 -0
  28. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/index.rst +0 -0
  29. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/__init__.py +0 -0
  30. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/__main__.py +0 -0
  31. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/_base.py +0 -0
  32. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/_basewithshade.py +0 -0
  33. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/_lockable.py +0 -0
  34. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/altnumber.py +0 -0
  35. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/boundarycondition.py +0 -0
  36. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/checkdup.py +0 -0
  37. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/__init__.py +0 -0
  38. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/compare.py +0 -0
  39. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/create.py +0 -0
  40. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/edit.py +0 -0
  41. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/lib.py +0 -0
  42. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/setconfig.py +0 -0
  43. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/validate.py +0 -0
  44. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/colorobj.py +0 -0
  45. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/config.json +0 -0
  46. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/config.py +0 -0
  47. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/dictutil.py +0 -0
  48. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/extensionutil.py +0 -0
  49. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/facetype.py +0 -0
  50. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/logutil.py +0 -0
  51. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/orientation.py +0 -0
  52. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/properties.py +0 -0
  53. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/search.py +0 -0
  54. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/shademesh.py +0 -0
  55. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/typing.py +0 -0
  56. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/units.py +0 -0
  57. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/__init__.py +0 -0
  58. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/aperture.py +0 -0
  59. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/door.py +0 -0
  60. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/face.py +0 -0
  61. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/model.py +0 -0
  62. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/room.py +0 -0
  63. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/shade.py +0 -0
  64. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/shademesh.py +0 -0
  65. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/SOURCES.txt +0 -0
  66. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/dependency_links.txt +0 -0
  67. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/entry_points.txt +0 -0
  68. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/requires.txt +0 -0
  69. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/top_level.txt +0 -0
  70. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/requirements.txt +0 -0
  71. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/setup.cfg +0 -0
  72. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/setup.py +0 -0
  73. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/__init__.py +0 -0
  74. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/aperture_test.py +0 -0
  75. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/boundary_condition_test.py +0 -0
  76. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_compare_test.py +0 -0
  77. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_create_test.py +0 -0
  78. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_edit_test.py +0 -0
  79. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_validate_test.py +0 -0
  80. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/colorobj_test.py +0 -0
  81. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/config_test.py +0 -0
  82. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/dictutil_test.py +0 -0
  83. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/door_test.py +0 -0
  84. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/face_test.py +0 -0
  85. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/facetype_test.py +0 -0
  86. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/ShoeBox.json +0 -0
  87. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/bad_geometry_model.hbjson +0 -0
  88. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/colliding_room_volumes.hbjson +0 -0
  89. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/compare_model_1.hbjson +0 -0
  90. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/compare_model_2.hbjson +0 -0
  91. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/complex_polyfaces.json +0 -0
  92. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/minor_geometry/existing_model.hbjson +0 -0
  93. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/minor_geometry/updated_model.hbjson +0 -0
  94. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/mismatched_area_adj.hbjson +0 -0
  95. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/model_with_adiabatic.hbjson +0 -0
  96. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/model_with_holes.hbjson +0 -0
  97. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/model_without_adjacency.hbjson +0 -0
  98. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/nonascii_face.json +0 -0
  99. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/polygons_for_gap_boundary.json +0 -0
  100. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/room_for_window_offset.hbjson +0 -0
  101. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/single_family_home.hbjson +0 -0
  102. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/lockable_test.py +0 -0
  103. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/orientation_test.py +0 -0
  104. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/room_test.py +0 -0
  105. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/search_test.py +0 -0
  106. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/shade_test.py +0 -0
  107. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/shademesh_test.py +0 -0
  108. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/stl/cube_ascii.stl +0 -0
  109. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/stl/cube_binary.stl +0 -0
  110. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/typing_test.py +0 -0
  111. {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/units_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: honeybee-core
3
- Version: 1.62.12
3
+ Version: 1.64.0
4
4
  Summary: A library to create 3D building geometry for various types of environmental simulation.
5
5
  Home-page: https://github.com/ladybug-tools/honeybee-core
6
6
  Author: Ladybug Tools
@@ -94,13 +94,6 @@ class Aperture(_BaseWithShade):
94
94
  assert data['type'] == 'Aperture', 'Expected Aperture dictionary. ' \
95
95
  'Got {}.'.format(data['type'])
96
96
 
97
- # remove any invalid holes from the geometry
98
- geo_dict = data['geometry']
99
- if 'holes' in geo_dict and geo_dict['holes'] is not None:
100
- for i, hole_list in enumerate(geo_dict['holes']):
101
- if len(hole_list) < 3:
102
- geo_dict['holes'].pop(i)
103
-
104
97
  # serialize the aperture
105
98
  is_operable = data['is_operable'] if 'is_operable' in data else False
106
99
  if data['boundary_condition']['type'] == 'Outdoors':
@@ -96,13 +96,6 @@ class Door(_BaseWithShade):
96
96
  assert data['type'] == 'Door', 'Expected Door dictionary. ' \
97
97
  'Got {}.'.format(data['type'])
98
98
 
99
- # remove any invalid holes from the geometry
100
- geo_dict = data['geometry']
101
- if 'holes' in geo_dict and geo_dict['holes'] is not None:
102
- for i, hole_list in enumerate(geo_dict['holes']):
103
- if len(hole_list) < 3:
104
- geo_dict['holes'].pop(i)
105
-
106
99
  # serialize the door
107
100
  is_glass = data['is_glass'] if 'is_glass' in data else False
108
101
  if data['boundary_condition']['type'] == 'Outdoors':
@@ -133,13 +133,6 @@ class Face(_BaseWithShade):
133
133
  assert data['type'] == 'Face', 'Expected Face dictionary. ' \
134
134
  'Got {}.'.format(data['type'])
135
135
 
136
- # remove any invalid holes from the geometry
137
- geo_dict = data['geometry']
138
- if 'holes' in geo_dict and geo_dict['holes'] is not None:
139
- for i, hole_list in enumerate(geo_dict['holes']):
140
- if len(hole_list) < 3:
141
- geo_dict['holes'].pop(i)
142
-
143
136
  # first serialize it with an outdoor boundary condition
144
137
  face_type = face_types.by_name(data['face_type'])
145
138
  face = cls(data['identifier'], Face3D.from_dict(data['geometry']),
@@ -107,6 +107,18 @@ class Model(_Base):
107
107
  * exterior_skylight_aperture_area
108
108
  * min
109
109
  * max
110
+ * roof_to_exterior_edges
111
+ * slab_to_exterior_edges
112
+ * exposed_floor_to_exterior_wall_edges
113
+ * exterior_wall_to_wall_edges
114
+ * roof_ridge_edges
115
+ * exposed_floor_to_floor_edges
116
+ * underground_edges
117
+ * interior_edges
118
+ * exterior_aperture_edges
119
+ * exterior_door_edges
120
+ * exterior_aperture_edges
121
+ * exterior_door_edges
110
122
  * top_level_dict
111
123
  * user_data
112
124
  """
@@ -161,11 +173,16 @@ class Model(_Base):
161
173
  self._properties = ModelProperties(self)
162
174
 
163
175
  @classmethod
164
- def from_dict(cls, data):
176
+ def from_dict(cls, data, cleanup_irrational=False):
165
177
  """Initialize a Model from a dictionary.
166
178
 
167
179
  Args:
168
180
  data: A dictionary representation of a Model object.
181
+ cleanup_irrational: Boolean to note whether common types of irrational
182
+ objects should be cleaned or removed from the dictionary before
183
+ serializing the model to Python. Typical cases that are removed
184
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
185
+ have no Face geometry, etc. (Default: False).
169
186
  """
170
187
  # check the type of dictionary
171
188
  assert data['type'] == 'Model', 'Expected Model dictionary. ' \
@@ -179,6 +196,10 @@ class Model(_Base):
179
196
  angle_tol = 1.0 if 'angle_tolerance' not in data or \
180
197
  data['angle_tolerance'] is None else data['angle_tolerance']
181
198
 
199
+ # clean the irrational objects out if requested
200
+ if cleanup_irrational:
201
+ cls.clean_irrational_geometry(data)
202
+
182
203
  # import all of the geometry
183
204
  rooms = None # import rooms
184
205
  if 'rooms' in data and data['rooms'] is not None:
@@ -244,11 +265,16 @@ class Model(_Base):
244
265
  return model
245
266
 
246
267
  @classmethod
247
- def from_file(cls, hb_file):
268
+ def from_file(cls, hb_file, cleanup_irrational=False):
248
269
  """Initialize a Model from a HBJSON or HBpkl file, auto-sensing the type.
249
270
 
250
271
  Args:
251
272
  hb_file: Path to either a HBJSON or HBpkl file.
273
+ cleanup_irrational: Boolean to note whether common types of irrational
274
+ objects should be cleaned or removed from the dictionary before
275
+ serializing the model to Python. Typical cases that are removed
276
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
277
+ have no Face geometry, etc. (Default: False).
252
278
  """
253
279
  # sense the file type from the first character to avoid maxing memory with JSON
254
280
  # this is needed since queenbee overwrites all file extensions
@@ -258,15 +284,20 @@ class Model(_Base):
258
284
  is_json = True if first_char == '{' or second_char == '{' else False
259
285
  # load the file using either HBJSON pathway or HBpkl
260
286
  if is_json:
261
- return cls.from_hbjson(hb_file)
262
- return cls.from_hbpkl(hb_file)
287
+ return cls.from_hbjson(hb_file, cleanup_irrational)
288
+ return cls.from_hbpkl(hb_file, cleanup_irrational)
263
289
 
264
290
  @classmethod
265
- def from_hbjson(cls, hbjson_file):
291
+ def from_hbjson(cls, hbjson_file, cleanup_irrational=False):
266
292
  """Initialize a Model from a HBJSON file.
267
293
 
268
294
  Args:
269
295
  hbjson_file: Path to HBJSON file.
296
+ cleanup_irrational: Boolean to note whether common types of irrational
297
+ objects should be cleaned or removed from the dictionary before
298
+ serializing the model to Python. Typical cases that are removed
299
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
300
+ have no Face geometry, etc. (Default: False).
270
301
  """
271
302
  assert os.path.isfile(hbjson_file), 'Failed to find %s' % hbjson_file
272
303
  with io.open(hbjson_file, encoding='utf-8') as inf:
@@ -276,19 +307,24 @@ class Model(_Base):
276
307
  if second_char == '{':
277
308
  inf.read(1)
278
309
  data = json.load(inf)
279
- return cls.from_dict(data)
310
+ return cls.from_dict(data, cleanup_irrational)
280
311
 
281
312
  @classmethod
282
- def from_hbpkl(cls, hbpkl_file):
313
+ def from_hbpkl(cls, hbpkl_file, cleanup_irrational=False):
283
314
  """Initialize a Model from a HBpkl file.
284
315
 
285
316
  Args:
286
317
  hbpkl_file: Path to HBpkl file.
318
+ cleanup_irrational: Boolean to note whether common types of irrational
319
+ objects should be cleaned or removed from the dictionary before
320
+ serializing the model to Python. Typical cases that are removed
321
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
322
+ have no Face geometry, etc. (Default: False).
287
323
  """
288
324
  assert os.path.isfile(hbpkl_file), 'Failed to find %s' % hbpkl_file
289
325
  with open(hbpkl_file, 'rb') as inf:
290
326
  data = pickle.load(inf)
291
- return cls.from_dict(data)
327
+ return cls.from_dict(data, cleanup_irrational)
292
328
 
293
329
  @classmethod
294
330
  def from_stl(cls, file_path, geometry_to_faces=False, units='Meters',
@@ -990,6 +1026,92 @@ class Model(_Base):
990
1026
  """Get a Point3D for the max bounding box vertex in the XY plane."""
991
1027
  return self._calculate_max(self._all_objects())
992
1028
 
1029
+ @property
1030
+ def roof_to_exterior_edges(self):
1031
+ """Get LineSegment3Ds where roofs meet exterior walls (or floors).
1032
+
1033
+ Note that both the roof Face and the wall/floor Face must be next to one
1034
+ another in the model's outer envelope and have outdoor boundary conditions for
1035
+ the edge to show up in this list.
1036
+ """
1037
+ return self.classified_envelope_edges()[0]
1038
+
1039
+ @property
1040
+ def slab_to_exterior_edges(self):
1041
+ """Get LineSegment3Ds where ground floor slabs meet exterior walls or roofs.
1042
+
1043
+ Note that the floor Face must have a ground boundary condition and the wall or
1044
+ roof Face must have an outdoor boundary condition for the edge between the
1045
+ two Faces to show up in this list.
1046
+ """
1047
+ return self.classified_envelope_edges()[1]
1048
+
1049
+ @property
1050
+ def exposed_floor_to_exterior_wall_edges(self):
1051
+ """Get LineSegment3Ds where exposed floors meet exterior walls.
1052
+
1053
+ Note that both the wall Face and the floor Face must be next to one
1054
+ another in the model's outer envelope and have outdoor boundary conditions for
1055
+ the edge to show up in this list.
1056
+ """
1057
+ return self.classified_envelope_edges()[2]
1058
+
1059
+ @property
1060
+ def exterior_wall_to_wall_edges(self):
1061
+ """Get LineSegment3Ds where exterior walls meet one another.
1062
+
1063
+ Note that both wall Faces must be next to one another in the model's
1064
+ outer envelope and have outdoor boundary conditions for the edge to
1065
+ show up in this list.
1066
+ """
1067
+ return self.classified_envelope_edges()[3]
1068
+
1069
+ @property
1070
+ def roof_ridge_edges(self):
1071
+ """Get a list of LineSegment3D where exterior roofs meet one another.
1072
+
1073
+ Note that both roof Faces must be next to one another in the model's
1074
+ outer envelope and have outdoor boundary conditions for the edge to
1075
+ show up in this list.
1076
+ """
1077
+ return self.classified_envelope_edges()[4]
1078
+
1079
+ @property
1080
+ def exposed_floor_to_floor_edges(self):
1081
+ """Get LineSegment3Ds where exposed floors meet one another.
1082
+
1083
+ Note that both floor Faces must be next to one another in the model's
1084
+ outer envelope and have outdoor boundary conditions for the edge to
1085
+ show up in this list.
1086
+ """
1087
+ return self.classified_envelope_edges()[5]
1088
+
1089
+ @property
1090
+ def underground_edges(self):
1091
+ """Get a list of LineSegment3D where underground Faces meet one another.
1092
+
1093
+ Note that both Faces must be next to one another in the model's outer envelope
1094
+ and have ground boundary conditions for the edge to show up in this list.
1095
+ """
1096
+ return self.classified_envelope_edges()[6]
1097
+
1098
+ @property
1099
+ def exterior_aperture_edges(self):
1100
+ """Get a list of LineSegment3D for the borders around room exterior apertures.
1101
+ """
1102
+ edges = []
1103
+ for room in self.rooms:
1104
+ edges.extend(room.exterior_aperture_edges)
1105
+ return edges
1106
+
1107
+ @property
1108
+ def exterior_door_edges(self):
1109
+ """Get a list of LineSegment3D for the borders around room exterior doors."""
1110
+ edges = []
1111
+ for room in self.rooms:
1112
+ edges.extend(room.exterior_door_edges)
1113
+ return edges
1114
+
993
1115
  @property
994
1116
  def top_level_dict(self):
995
1117
  """Get dictionary of top-level model objects with identifiers as the keys.
@@ -1354,6 +1476,63 @@ class Model(_Base):
1354
1476
  )
1355
1477
  return shades
1356
1478
 
1479
+ def classified_envelope_edges(self, tolerance=None, exclude_coplanar=False):
1480
+ """Get classified edges of this Model's envelope based on Faces they adjoin.
1481
+
1482
+ The edges returned by this method will only exist along the exterior
1483
+ envelope of the Model's Rooms as defined by the contiguous volume across
1484
+ all Room interior adjacencies.
1485
+
1486
+ Args:
1487
+ tolerance: The maximum difference between point values for them to be
1488
+ considered equivalent. If None, the Model's tolerance will be used.
1489
+ exclude_coplanar: Boolean to note whether edges falling between two
1490
+ coplanar Faces in the building envelope should be included
1491
+ in the result (False) or excluded from it (True). (Default: False).
1492
+
1493
+ Returns:
1494
+ A tuple with eight items where each item is a list containing
1495
+ LineSegment3D adjoining different types of Faces.
1496
+
1497
+ - roof_to_exterior - Roofs meet exterior walls or floors.
1498
+
1499
+ - slab_to_exterior - Ground floor slabs meet exterior walls or roofs.
1500
+
1501
+ - exposed_floor_to_exterior_wall - Exposed floors meet exterior walls.
1502
+
1503
+ - exterior_wall_to_wall - Exterior walls meet.
1504
+
1505
+ - roof_ridge - Exterior roofs meet.
1506
+
1507
+ - exposed_floor_to_floor - Exposed floors meet.
1508
+
1509
+ - underground - Underground faces meet.
1510
+ """
1511
+ # set up lists to be populated
1512
+ roof_to_exterior, slab_to_exterior, exposed_floor_to_exterior_wall = [], [], []
1513
+ exterior_wall_to_wall, roof_ridge, exposed_floor_to_floor = [], [], []
1514
+ underground, interior = [], []
1515
+ tol = tolerance if tolerance else self.tolerance
1516
+ ang_tol = self.angle_tolerance if exclude_coplanar else None
1517
+
1518
+ # join all of the rooms in the model across their adjacencies
1519
+ merged_rooms = Room.join_adjacent_rooms(self.rooms, tol)
1520
+ for room in merged_rooms:
1521
+ rf_to_ext, slb_to_ext, ex_flr_to_ext, ext_wl_to_wl, rf_ridge, \
1522
+ ex_flr_to_flr, under_gnd, inter = room.classified_edges(tol, ang_tol)
1523
+ roof_to_exterior.extend(rf_to_ext)
1524
+ slab_to_exterior.extend(slb_to_ext)
1525
+ exposed_floor_to_exterior_wall.extend(ex_flr_to_ext)
1526
+ exterior_wall_to_wall.extend(ext_wl_to_wl)
1527
+ roof_ridge.extend(rf_ridge)
1528
+ exposed_floor_to_floor.extend(ex_flr_to_flr)
1529
+ underground.extend(under_gnd)
1530
+ interior.extend(inter)
1531
+
1532
+ # return the classified edges
1533
+ return roof_to_exterior, slab_to_exterior, exposed_floor_to_exterior_wall, \
1534
+ exterior_wall_to_wall, roof_ridge, exposed_floor_to_floor, underground
1535
+
1357
1536
  def add_prefix(self, prefix):
1358
1537
  """Change the identifier of this object and child objects by inserting a prefix.
1359
1538
 
@@ -3685,6 +3864,97 @@ class Model(_Base):
3685
3864
  out_dict['valid'] = False
3686
3865
  return json.dumps(out_dict, indent=4)
3687
3866
 
3867
+ @staticmethod
3868
+ def clean_irrational_geometry(model_dict):
3869
+ """Remove irrational geometry objects from a honeybee Model dictionary.
3870
+
3871
+ This can be useful to run prior to serializing the Model object from a
3872
+ dictionary if it was produced from a source other than the Python
3873
+ core libraries, in which case the dictionary is necessarily rational
3874
+ and serializable. This is because not all honeybee-schema bindings
3875
+ enforce fundamental definitions of geometry types upon initialization
3876
+ of the geometry objects, leading to exceptions when an attempt is made
3877
+ to serialize them to Python. Furthermore, it is possible that the honeybee
3878
+ Model dictionary did not originate from any schema bindings at all, in
3879
+ which case it is highly recommended that this method be run.
3880
+
3881
+ Typical irrational geometry cases that are removed by this method include.
3882
+
3883
+ * Apertures/Doors with less than 3 vertices or holes less than 3 vertices.
3884
+ * Faces with less than 3 vertices or holes with less than 3 vertices.
3885
+ * Rooms that have no Face geometry.
3886
+ * Shade Face3Ds with less than 3 vertices or holes with less than 3 vertices.
3887
+ * ShadeMesh Mesh3Ds with no faces or faces with less than 3 vertices.
3888
+ """
3889
+ # clean all of the Room geometry in the model dictionary
3890
+ if 'rooms' in model_dict and model_dict['rooms'] is not None:
3891
+ for ri in range(len(model_dict['rooms']) - 1, -1, -1):
3892
+ r_dict = model_dict['rooms'][ri]
3893
+ # clean all of the Face geometry
3894
+ Model._clean_irrational_geo_with_shade(r_dict['faces'])
3895
+ for f_dict in r_dict['faces']:
3896
+ # clean all of the Aperture/Door geometry
3897
+ if 'apertures' in f_dict and f_dict['apertures'] is not None:
3898
+ Model._clean_irrational_geo_with_shade(f_dict['apertures'])
3899
+ if 'doors' in f_dict and f_dict['doors'] is not None:
3900
+ Model._clean_irrational_geo_with_shade(f_dict['doors'])
3901
+ # clean the assigned shade geometry
3902
+ if 'outdoor_shades' in r_dict and r_dict['outdoor_shades'] is not None:
3903
+ Model._clean_irrational_face3ds(r_dict['outdoor_shades'])
3904
+ if 'indoor_shades' in r_dict and r_dict['indoor_shades'] is not None:
3905
+ Model._clean_irrational_face3ds(r_dict['indoor_shades'])
3906
+ if len(r_dict['faces']) == 0: # the entire Room is irrational
3907
+ model_dict['rooms'].pop(ri)
3908
+ # clean all of the orphaned geometry in the model dictionary
3909
+ if 'orphaned_faces' in model_dict and model_dict['orphaned_faces'] is not None:
3910
+ Model._clean_irrational_geo_with_shade(model_dict['orphaned_faces'])
3911
+ if 'orphaned_apertures' in model_dict and \
3912
+ model_dict['orphaned_apertures'] is not None:
3913
+ Model._clean_irrational_geo_with_shade(model_dict['orphaned_apertures'])
3914
+ if 'orphaned_doors' in model_dict and model_dict['orphaned_doors'] is not None:
3915
+ Model._clean_irrational_geo_with_shade(model_dict['orphaned_doors'])
3916
+ if 'orphaned_shades' in model_dict and model_dict['orphaned_shades'] is not None:
3917
+ Model._clean_irrational_face3ds(model_dict['orphaned_shades'])
3918
+ # clean all of the shade mesh geometry in the model dictionary
3919
+ if 'shade_meshes' in model_dict and model_dict['shade_meshes'] is not None:
3920
+ for smi in range(len(model_dict['shade_meshes']) - 1, -1, -1):
3921
+ sm_dict = model_dict['shade_meshes'][smi]['geometry']
3922
+ for mfi in range(len(sm_dict['faces']) - 1, -1, -1):
3923
+ mf = sm_dict['faces'][mfi]
3924
+ if len(mf) not in (3, 4):
3925
+ sm_dict['faces'].pop(mfi)
3926
+ else:
3927
+ for ind in mf:
3928
+ try:
3929
+ sm_dict['vertices'][ind]
3930
+ except IndexError:
3931
+ sm_dict['faces'].pop(mfi)
3932
+ break
3933
+ if len(sm_dict['faces']) == 0:
3934
+ model_dict['shade_meshes'].pop(smi)
3935
+
3936
+ @staticmethod
3937
+ def _clean_irrational_geo_with_shade(geo_obj_dicts):
3938
+ """Clean irrational Face3Ds out of a list of honeybee geometry objects."""
3939
+ Model._clean_irrational_face3ds(geo_obj_dicts)
3940
+ for f_dict in geo_obj_dicts:
3941
+ if 'outdoor_shades' in f_dict and f_dict['outdoor_shades'] is not None:
3942
+ Model._clean_irrational_face3ds(f_dict['outdoor_shades'])
3943
+ if 'indoor_shades' in f_dict and f_dict['indoor_shades'] is not None:
3944
+ Model._clean_irrational_face3ds(f_dict['indoor_shades'])
3945
+
3946
+ @staticmethod
3947
+ def _clean_irrational_face3ds(geo_obj_dicts):
3948
+ """Clean irrational Face3Ds out of a list of honeybee geometry objects."""
3949
+ for fi in range(len(geo_obj_dicts) - 1, -1, -1):
3950
+ f_dict = geo_obj_dicts[fi]
3951
+ face_3d = f_dict['geometry']
3952
+ if len(face_3d['boundary']) < 3:
3953
+ geo_obj_dicts.pop(fi)
3954
+ continue
3955
+ elif 'holes' in face_3d:
3956
+ face_3d['holes'] = [h for h in face_3d['holes'] if len(h) >= 3]
3957
+
3688
3958
  @staticmethod
3689
3959
  def conversion_factor_to_meters(units):
3690
3960
  """Get the conversion factor to meters based on input units.