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.
- {honeybee_core-1.62.12/honeybee_core.egg-info → honeybee_core-1.64.0}/PKG-INFO +1 -1
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/aperture.py +0 -7
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/door.py +0 -7
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/face.py +0 -7
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/model.py +278 -8
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/room.py +192 -84
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/shade.py +2 -9
- {honeybee_core-1.62.12 → honeybee_core-1.64.0/honeybee_core.egg-info}/PKG-INFO +1 -1
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/model_test.py +72 -3
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.github/workflows/ci.yaml +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.github/workflows/dependency-release.yaml +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.gitignore +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/.releaserc.json +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/CODE_OF_CONDUCT.md +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/CONTRIBUTING.md +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/LICENSE +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/MANIFEST.in +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/README.md +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/deploy.sh +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/dev-requirements.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_build/.nojekyll +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_build/README.md +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_build/docs/README.md +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_static/custom.css +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/_templates/layout.html +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/cli/index.rst +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/conf.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/docs/index.rst +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/__init__.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/__main__.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/_base.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/_basewithshade.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/_lockable.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/altnumber.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/boundarycondition.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/checkdup.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/__init__.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/compare.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/create.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/edit.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/lib.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/setconfig.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/cli/validate.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/colorobj.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/config.json +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/config.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/dictutil.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/extensionutil.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/facetype.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/logutil.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/orientation.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/properties.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/search.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/shademesh.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/typing.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/units.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/__init__.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/aperture.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/door.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/face.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/model.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/room.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/shade.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee/writer/shademesh.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/SOURCES.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/dependency_links.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/entry_points.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/requires.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/honeybee_core.egg-info/top_level.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/requirements.txt +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/setup.cfg +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/setup.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/__init__.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/aperture_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/boundary_condition_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_compare_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_create_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_edit_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/cli_validate_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/colorobj_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/config_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/dictutil_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/door_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/face_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/facetype_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/ShoeBox.json +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/bad_geometry_model.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/colliding_room_volumes.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/compare_model_1.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/compare_model_2.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/complex_polyfaces.json +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/minor_geometry/existing_model.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/minor_geometry/updated_model.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/mismatched_area_adj.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/model_with_adiabatic.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/model_with_holes.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/model_without_adjacency.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/nonascii_face.json +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/polygons_for_gap_boundary.json +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/room_for_window_offset.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/json/single_family_home.hbjson +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/lockable_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/orientation_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/room_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/search_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/shade_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/shademesh_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/stl/cube_ascii.stl +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/stl/cube_binary.stl +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/typing_test.py +0 -0
- {honeybee_core-1.62.12 → honeybee_core-1.64.0}/tests/units_test.py +0 -0
|
@@ -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.
|