honeybee-core 1.62.12__tar.gz → 1.63.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.63.0}/PKG-INFO +1 -1
  2. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/aperture.py +0 -7
  3. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/door.py +0 -7
  4. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/face.py +0 -7
  5. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/model.py +123 -8
  6. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/shade.py +2 -9
  7. {honeybee_core-1.62.12 → honeybee_core-1.63.0/honeybee_core.egg-info}/PKG-INFO +1 -1
  8. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/model_test.py +72 -3
  9. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/.github/workflows/ci.yaml +0 -0
  10. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/.github/workflows/dependency-release.yaml +0 -0
  11. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/.gitignore +0 -0
  12. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/.releaserc.json +0 -0
  13. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/CODE_OF_CONDUCT.md +0 -0
  14. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/CONTRIBUTING.md +0 -0
  15. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/LICENSE +0 -0
  16. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/MANIFEST.in +0 -0
  17. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/README.md +0 -0
  18. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/deploy.sh +0 -0
  19. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/dev-requirements.txt +0 -0
  20. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/_build/.nojekyll +0 -0
  21. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/_build/README.md +0 -0
  22. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/_build/docs/README.md +0 -0
  23. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/_static/custom.css +0 -0
  24. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/_templates/layout.html +0 -0
  25. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/cli/index.rst +0 -0
  26. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/conf.py +0 -0
  27. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/docs/index.rst +0 -0
  28. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/__init__.py +0 -0
  29. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/__main__.py +0 -0
  30. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/_base.py +0 -0
  31. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/_basewithshade.py +0 -0
  32. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/_lockable.py +0 -0
  33. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/altnumber.py +0 -0
  34. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/boundarycondition.py +0 -0
  35. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/checkdup.py +0 -0
  36. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/__init__.py +0 -0
  37. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/compare.py +0 -0
  38. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/create.py +0 -0
  39. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/edit.py +0 -0
  40. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/lib.py +0 -0
  41. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/setconfig.py +0 -0
  42. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/cli/validate.py +0 -0
  43. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/colorobj.py +0 -0
  44. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/config.json +0 -0
  45. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/config.py +0 -0
  46. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/dictutil.py +0 -0
  47. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/extensionutil.py +0 -0
  48. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/facetype.py +0 -0
  49. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/logutil.py +0 -0
  50. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/orientation.py +0 -0
  51. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/properties.py +0 -0
  52. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/room.py +0 -0
  53. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/search.py +0 -0
  54. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/shademesh.py +0 -0
  55. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/typing.py +0 -0
  56. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/units.py +0 -0
  57. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/__init__.py +0 -0
  58. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/aperture.py +0 -0
  59. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/door.py +0 -0
  60. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/face.py +0 -0
  61. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/model.py +0 -0
  62. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/room.py +0 -0
  63. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/shade.py +0 -0
  64. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee/writer/shademesh.py +0 -0
  65. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee_core.egg-info/SOURCES.txt +0 -0
  66. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee_core.egg-info/dependency_links.txt +0 -0
  67. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee_core.egg-info/entry_points.txt +0 -0
  68. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee_core.egg-info/requires.txt +0 -0
  69. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/honeybee_core.egg-info/top_level.txt +0 -0
  70. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/requirements.txt +0 -0
  71. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/setup.cfg +0 -0
  72. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/setup.py +0 -0
  73. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/__init__.py +0 -0
  74. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/aperture_test.py +0 -0
  75. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/boundary_condition_test.py +0 -0
  76. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/cli_compare_test.py +0 -0
  77. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/cli_create_test.py +0 -0
  78. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/cli_edit_test.py +0 -0
  79. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/cli_validate_test.py +0 -0
  80. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/colorobj_test.py +0 -0
  81. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/config_test.py +0 -0
  82. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/dictutil_test.py +0 -0
  83. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/door_test.py +0 -0
  84. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/face_test.py +0 -0
  85. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/facetype_test.py +0 -0
  86. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/ShoeBox.json +0 -0
  87. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/bad_geometry_model.hbjson +0 -0
  88. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/colliding_room_volumes.hbjson +0 -0
  89. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/compare_model_1.hbjson +0 -0
  90. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/compare_model_2.hbjson +0 -0
  91. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/complex_polyfaces.json +0 -0
  92. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/minor_geometry/existing_model.hbjson +0 -0
  93. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/minor_geometry/updated_model.hbjson +0 -0
  94. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/mismatched_area_adj.hbjson +0 -0
  95. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/model_with_adiabatic.hbjson +0 -0
  96. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/model_with_holes.hbjson +0 -0
  97. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/model_without_adjacency.hbjson +0 -0
  98. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/nonascii_face.json +0 -0
  99. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/polygons_for_gap_boundary.json +0 -0
  100. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/room_for_window_offset.hbjson +0 -0
  101. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/json/single_family_home.hbjson +0 -0
  102. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/lockable_test.py +0 -0
  103. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/orientation_test.py +0 -0
  104. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/room_test.py +0 -0
  105. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/search_test.py +0 -0
  106. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/shade_test.py +0 -0
  107. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/shademesh_test.py +0 -0
  108. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/stl/cube_ascii.stl +0 -0
  109. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/stl/cube_binary.stl +0 -0
  110. {honeybee_core-1.62.12 → honeybee_core-1.63.0}/tests/typing_test.py +0 -0
  111. {honeybee_core-1.62.12 → honeybee_core-1.63.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.63.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']),
@@ -161,11 +161,16 @@ class Model(_Base):
161
161
  self._properties = ModelProperties(self)
162
162
 
163
163
  @classmethod
164
- def from_dict(cls, data):
164
+ def from_dict(cls, data, cleanup_irrational=False):
165
165
  """Initialize a Model from a dictionary.
166
166
 
167
167
  Args:
168
168
  data: A dictionary representation of a Model object.
169
+ cleanup_irrational: Boolean to note whether common types of irrational
170
+ objects should be cleaned or removed from the dictionary before
171
+ serializing the model to Python. Typical cases that are removed
172
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
173
+ have no Face geometry, etc. (Default: False).
169
174
  """
170
175
  # check the type of dictionary
171
176
  assert data['type'] == 'Model', 'Expected Model dictionary. ' \
@@ -179,6 +184,10 @@ class Model(_Base):
179
184
  angle_tol = 1.0 if 'angle_tolerance' not in data or \
180
185
  data['angle_tolerance'] is None else data['angle_tolerance']
181
186
 
187
+ # clean the irrational objects out if requested
188
+ if cleanup_irrational:
189
+ cls.clean_irrational_geometry(data)
190
+
182
191
  # import all of the geometry
183
192
  rooms = None # import rooms
184
193
  if 'rooms' in data and data['rooms'] is not None:
@@ -244,11 +253,16 @@ class Model(_Base):
244
253
  return model
245
254
 
246
255
  @classmethod
247
- def from_file(cls, hb_file):
256
+ def from_file(cls, hb_file, cleanup_irrational=False):
248
257
  """Initialize a Model from a HBJSON or HBpkl file, auto-sensing the type.
249
258
 
250
259
  Args:
251
260
  hb_file: Path to either a HBJSON or HBpkl file.
261
+ cleanup_irrational: Boolean to note whether common types of irrational
262
+ objects should be cleaned or removed from the dictionary before
263
+ serializing the model to Python. Typical cases that are removed
264
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
265
+ have no Face geometry, etc. (Default: False).
252
266
  """
253
267
  # sense the file type from the first character to avoid maxing memory with JSON
254
268
  # this is needed since queenbee overwrites all file extensions
@@ -258,15 +272,20 @@ class Model(_Base):
258
272
  is_json = True if first_char == '{' or second_char == '{' else False
259
273
  # load the file using either HBJSON pathway or HBpkl
260
274
  if is_json:
261
- return cls.from_hbjson(hb_file)
262
- return cls.from_hbpkl(hb_file)
275
+ return cls.from_hbjson(hb_file, cleanup_irrational)
276
+ return cls.from_hbpkl(hb_file, cleanup_irrational)
263
277
 
264
278
  @classmethod
265
- def from_hbjson(cls, hbjson_file):
279
+ def from_hbjson(cls, hbjson_file, cleanup_irrational=False):
266
280
  """Initialize a Model from a HBJSON file.
267
281
 
268
282
  Args:
269
283
  hbjson_file: Path to HBJSON file.
284
+ cleanup_irrational: Boolean to note whether common types of irrational
285
+ objects should be cleaned or removed from the dictionary before
286
+ serializing the model to Python. Typical cases that are removed
287
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
288
+ have no Face geometry, etc. (Default: False).
270
289
  """
271
290
  assert os.path.isfile(hbjson_file), 'Failed to find %s' % hbjson_file
272
291
  with io.open(hbjson_file, encoding='utf-8') as inf:
@@ -276,19 +295,24 @@ class Model(_Base):
276
295
  if second_char == '{':
277
296
  inf.read(1)
278
297
  data = json.load(inf)
279
- return cls.from_dict(data)
298
+ return cls.from_dict(data, cleanup_irrational)
280
299
 
281
300
  @classmethod
282
- def from_hbpkl(cls, hbpkl_file):
301
+ def from_hbpkl(cls, hbpkl_file, cleanup_irrational=False):
283
302
  """Initialize a Model from a HBpkl file.
284
303
 
285
304
  Args:
286
305
  hbpkl_file: Path to HBpkl file.
306
+ cleanup_irrational: Boolean to note whether common types of irrational
307
+ objects should be cleaned or removed from the dictionary before
308
+ serializing the model to Python. Typical cases that are removed
309
+ this way include Face3Ds with fewer than 3 vertices, Rooms that
310
+ have no Face geometry, etc. (Default: False).
287
311
  """
288
312
  assert os.path.isfile(hbpkl_file), 'Failed to find %s' % hbpkl_file
289
313
  with open(hbpkl_file, 'rb') as inf:
290
314
  data = pickle.load(inf)
291
- return cls.from_dict(data)
315
+ return cls.from_dict(data, cleanup_irrational)
292
316
 
293
317
  @classmethod
294
318
  def from_stl(cls, file_path, geometry_to_faces=False, units='Meters',
@@ -3685,6 +3709,97 @@ class Model(_Base):
3685
3709
  out_dict['valid'] = False
3686
3710
  return json.dumps(out_dict, indent=4)
3687
3711
 
3712
+ @staticmethod
3713
+ def clean_irrational_geometry(model_dict):
3714
+ """Remove irrational geometry objects from a honeybee Model dictionary.
3715
+
3716
+ This can be useful to run prior to serializing the Model object from a
3717
+ dictionary if it was produced from a source other than the Python
3718
+ core libraries, in which case the dictionary is necessarily rational
3719
+ and serializable. This is because not all honeybee-schema bindings
3720
+ enforce fundamental definitions of geometry types upon initialization
3721
+ of the geometry objects, leading to exceptions when an attempt is made
3722
+ to serialize them to Python. Furthermore, it is possible that the honeybee
3723
+ Model dictionary did not originate from any schema bindings at all, in
3724
+ which case it is highly recommended that this method be run.
3725
+
3726
+ Typical irrational geometry cases that are removed by this method include.
3727
+
3728
+ * Apertures/Doors with less than 3 vertices or holes less than 3 vertices.
3729
+ * Faces with less than 3 vertices or holes with less than 3 vertices.
3730
+ * Rooms that have no Face geometry.
3731
+ * Shade Face3Ds with less than 3 vertices or holes with less than 3 vertices.
3732
+ * ShadeMesh Mesh3Ds with no faces or faces with less than 3 vertices.
3733
+ """
3734
+ # clean all of the Room geometry in the model dictionary
3735
+ if 'rooms' in model_dict and model_dict['rooms'] is not None:
3736
+ for ri in range(len(model_dict['rooms']) - 1, -1, -1):
3737
+ r_dict = model_dict['rooms'][ri]
3738
+ # clean all of the Face geometry
3739
+ Model._clean_irrational_geo_with_shade(r_dict['faces'])
3740
+ for f_dict in r_dict['faces']:
3741
+ # clean all of the Aperture/Door geometry
3742
+ if 'apertures' in f_dict and f_dict['apertures'] is not None:
3743
+ Model._clean_irrational_geo_with_shade(f_dict['apertures'])
3744
+ if 'doors' in f_dict and f_dict['doors'] is not None:
3745
+ Model._clean_irrational_geo_with_shade(f_dict['doors'])
3746
+ # clean the assigned shade geometry
3747
+ if 'outdoor_shades' in r_dict and r_dict['outdoor_shades'] is not None:
3748
+ Model._clean_irrational_face3ds(r_dict['outdoor_shades'])
3749
+ if 'indoor_shades' in r_dict and r_dict['indoor_shades'] is not None:
3750
+ Model._clean_irrational_face3ds(r_dict['indoor_shades'])
3751
+ if len(r_dict['faces']) == 0: # the entire Room is irrational
3752
+ model_dict['rooms'].pop(ri)
3753
+ # clean all of the orphaned geometry in the model dictionary
3754
+ if 'orphaned_faces' in model_dict and model_dict['orphaned_faces'] is not None:
3755
+ Model._clean_irrational_geo_with_shade(model_dict['orphaned_faces'])
3756
+ if 'orphaned_apertures' in model_dict and \
3757
+ model_dict['orphaned_apertures'] is not None:
3758
+ Model._clean_irrational_geo_with_shade(model_dict['orphaned_apertures'])
3759
+ if 'orphaned_doors' in model_dict and model_dict['orphaned_doors'] is not None:
3760
+ Model._clean_irrational_geo_with_shade(model_dict['orphaned_doors'])
3761
+ if 'orphaned_shades' in model_dict and model_dict['orphaned_shades'] is not None:
3762
+ Model._clean_irrational_face3ds(model_dict['orphaned_shades'])
3763
+ # clean all of the shade mesh geometry in the model dictionary
3764
+ if 'shade_meshes' in model_dict and model_dict['shade_meshes'] is not None:
3765
+ for smi in range(len(model_dict['shade_meshes']) - 1, -1, -1):
3766
+ sm_dict = model_dict['shade_meshes'][smi]['geometry']
3767
+ for mfi in range(len(sm_dict['faces']) - 1, -1, -1):
3768
+ mf = sm_dict['faces'][mfi]
3769
+ if len(mf) not in (3, 4):
3770
+ sm_dict['faces'].pop(mfi)
3771
+ else:
3772
+ for ind in mf:
3773
+ try:
3774
+ sm_dict['vertices'][ind]
3775
+ except IndexError:
3776
+ sm_dict['faces'].pop(mfi)
3777
+ break
3778
+ if len(sm_dict['faces']) == 0:
3779
+ model_dict['shade_meshes'].pop(smi)
3780
+
3781
+ @staticmethod
3782
+ def _clean_irrational_geo_with_shade(geo_obj_dicts):
3783
+ """Clean irrational Face3Ds out of a list of honeybee geometry objects."""
3784
+ Model._clean_irrational_face3ds(geo_obj_dicts)
3785
+ for f_dict in geo_obj_dicts:
3786
+ if 'outdoor_shades' in f_dict and f_dict['outdoor_shades'] is not None:
3787
+ Model._clean_irrational_face3ds(f_dict['outdoor_shades'])
3788
+ if 'indoor_shades' in f_dict and f_dict['indoor_shades'] is not None:
3789
+ Model._clean_irrational_face3ds(f_dict['indoor_shades'])
3790
+
3791
+ @staticmethod
3792
+ def _clean_irrational_face3ds(geo_obj_dicts):
3793
+ """Clean irrational Face3Ds out of a list of honeybee geometry objects."""
3794
+ for fi in range(len(geo_obj_dicts) - 1, -1, -1):
3795
+ f_dict = geo_obj_dicts[fi]
3796
+ face_3d = f_dict['geometry']
3797
+ if len(face_3d['boundary']) < 3:
3798
+ geo_obj_dicts.pop(fi)
3799
+ continue
3800
+ elif 'holes' in face_3d:
3801
+ face_3d['holes'] = [h for h in face_3d['holes'] if len(h) >= 3]
3802
+
3688
3803
  @staticmethod
3689
3804
  def conversion_factor_to_meters(units):
3690
3805
  """Get the conversion factor to meters based on input units.
@@ -86,16 +86,9 @@ class Shade(_Base):
86
86
  assert data['type'] == 'Shade', 'Expected Shade dictionary. ' \
87
87
  'Got {}.'.format(data['type'])
88
88
 
89
- # remove any invalid holes from the geometry
90
- geo_dict = data['geometry']
91
- if 'holes' in geo_dict and geo_dict['holes'] is not None:
92
- for i, hole_list in enumerate(geo_dict['holes']):
93
- if len(hole_list) < 3:
94
- geo_dict['holes'].pop(i)
95
-
96
89
  # serialize the dictionary to an object
97
- is_detached = data['is_detached'] if 'is_detached' in data else False
98
- shade = cls(data['identifier'], Face3D.from_dict(geo_dict), is_detached)
90
+ is_det = data['is_detached'] if 'is_detached' in data else False
91
+ shade = cls(data['identifier'], Face3D.from_dict(data['geometry']), is_det)
99
92
  if 'display_name' in data and data['display_name'] is not None:
100
93
  shade.display_name = data['display_name']
101
94
  if 'user_data' in data and data['user_data'] is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: honeybee-core
3
- Version: 1.62.12
3
+ Version: 1.63.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
@@ -379,7 +379,7 @@ def test_model_add_prefix():
379
379
  def test_reset_room_ids():
380
380
  """Test the reset_room_ids method."""
381
381
  model_json = './tests/json/model_with_adiabatic.hbjson'
382
- parsed_model = Model.from_hbjson(model_json)
382
+ parsed_model = Model.from_hbjson(model_json, cleanup_irrational=True)
383
383
 
384
384
  new_model = parsed_model.duplicate()
385
385
  new_model.reset_room_ids()
@@ -390,7 +390,7 @@ def test_reset_room_ids():
390
390
  def test_reset_ids():
391
391
  """Test the reset_ids method."""
392
392
  model_json = './tests/json/model_with_adiabatic.hbjson'
393
- parsed_model = Model.from_hbjson(model_json)
393
+ parsed_model = Model.from_hbjson(model_json, cleanup_irrational=True)
394
394
 
395
395
  new_model = parsed_model.duplicate()
396
396
  new_model.reset_ids(True)
@@ -402,7 +402,7 @@ def test_reset_ids():
402
402
  def test_offset_aperture_edges():
403
403
  """Test the Face offset_aperture_edges method."""
404
404
  model_json = './tests/json/room_for_window_offset.hbjson'
405
- parsed_model = Model.from_hbjson(model_json)
405
+ parsed_model = Model.from_hbjson(model_json, cleanup_irrational=True)
406
406
  test_room = parsed_model.rooms[0]
407
407
  test_face = test_room[1]
408
408
 
@@ -1164,6 +1164,75 @@ def test_from_dict_method_extensions():
1164
1164
  assert isinstance(parsed_model, Model)
1165
1165
 
1166
1166
 
1167
+ def test_cleanup_irrational():
1168
+ """Test the cleanup_irrational method during serialization."""
1169
+ room = Room.from_box('TinyHouseZone', 5, 10, 3)
1170
+ south_face = room[3]
1171
+ south_face.apertures_by_ratio(0.4, 0.01)
1172
+ south_face.apertures[0].overhang(0.5, indoor=False)
1173
+ south_face.apertures[0].overhang(0.5, indoor=True)
1174
+ south_face.apertures[0].move_shades(Vector3D(0, 0, -0.5))
1175
+ north_face = room[1]
1176
+ door_verts = [Point3D(2, 10, 0.1), Point3D(1, 10, 0.1),
1177
+ Point3D(1, 10, 2.5), Point3D(2, 10, 2.5)]
1178
+ aperture_verts = [Point3D(4.5, 10, 1), Point3D(2.5, 10, 1),
1179
+ Point3D(2.5, 10, 2.5), Point3D(4.5, 10, 2.5)]
1180
+ door = Door('FrontDoor', Face3D(door_verts))
1181
+ north_face.add_door(door)
1182
+ aperture = Aperture('FrontAperture', Face3D(aperture_verts))
1183
+ north_face.add_aperture(aperture)
1184
+
1185
+ model = Model('TinyHouse', [room])
1186
+ model_dict = model.to_dict()
1187
+
1188
+ irrational_dict = {
1189
+ 'type': 'Model',
1190
+ 'identifier': 'irrational_model',
1191
+ 'units': 'Meters',
1192
+ 'tolerance': 0.001,
1193
+ 'angle_tolerance': 1.0,
1194
+ 'properties': model_dict['properties'],
1195
+ 'rooms': [
1196
+ model_dict['rooms'][0],
1197
+ {
1198
+ 'type': 'Room',
1199
+ 'identifier': 'irrational_room',
1200
+ 'faces': [
1201
+ {
1202
+ 'type': 'Face',
1203
+ 'identifier': 'irrational_face',
1204
+ 'face_type': 'Wall',
1205
+ 'geometry': {
1206
+ 'type': 'Face3D',
1207
+ 'boundary': [
1208
+ (0, 0, 0), (0, 0, 1)
1209
+ ]
1210
+ },
1211
+ 'apertures': [
1212
+ {
1213
+ 'type': 'Aperture',
1214
+ 'identifier': 'irrational_aperture',
1215
+ 'geometry': {
1216
+ 'type': 'Face3D',
1217
+ 'boundary': [
1218
+ (0, 0, 0.2), (0, 0, 0.8)
1219
+ ]
1220
+ }
1221
+ }
1222
+ ]
1223
+ },
1224
+ ]
1225
+ }
1226
+ ]
1227
+ }
1228
+
1229
+ with pytest.raises(ValueError):
1230
+ Model.from_dict(irrational_dict, cleanup_irrational=False)
1231
+
1232
+ clean_model = Model.from_dict(irrational_dict, cleanup_irrational=True)
1233
+ assert len(clean_model.rooms) == 1
1234
+
1235
+
1167
1236
  def test_comparison_report():
1168
1237
  """Test the comparison_report method."""
1169
1238
  room = Room.from_box('TinyHouseZone', 5, 10, 3)
File without changes
File without changes