honeybee-core 1.61.18__tar.gz → 1.64.7__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 (115) hide show
  1. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/.github/workflows/ci.yaml +1 -1
  2. {honeybee_core-1.61.18/honeybee_core.egg-info → honeybee_core-1.64.7}/PKG-INFO +21 -7
  3. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/deploy.sh +1 -1
  4. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/dev-requirements.txt +5 -4
  5. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/aperture.py +79 -6
  6. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/edit.py +2 -1
  7. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/validate.py +14 -7
  8. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/door.py +70 -5
  9. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/face.py +118 -12
  10. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/model.py +729 -116
  11. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/properties.py +52 -16
  12. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/room.py +483 -67
  13. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/search.py +8 -2
  14. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/shade.py +31 -3
  15. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/shademesh.py +17 -0
  16. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/typing.py +25 -11
  17. {honeybee_core-1.61.18 → honeybee_core-1.64.7/honeybee_core.egg-info}/PKG-INFO +21 -7
  18. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee_core.egg-info/SOURCES.txt +1 -1
  19. honeybee_core-1.64.7/honeybee_core.egg-info/requires.txt +5 -0
  20. honeybee_core-1.64.7/requirements.txt +3 -0
  21. honeybee_core-1.64.7/setup.cfg +4 -0
  22. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/setup.py +6 -2
  23. honeybee_core-1.64.7/tests/json/room_for_envelope_edges.hbjson +1 -0
  24. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/model_test.py +138 -3
  25. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/room_test.py +99 -0
  26. honeybee_core-1.61.18/honeybee_core.egg-info/requires.txt +0 -5
  27. honeybee_core-1.61.18/requirements.txt +0 -3
  28. honeybee_core-1.61.18/setup.cfg +0 -13
  29. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/.github/workflows/dependency-release.yaml +0 -0
  30. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/.gitignore +0 -0
  31. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/.releaserc.json +0 -0
  32. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/CODE_OF_CONDUCT.md +0 -0
  33. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/CONTRIBUTING.md +0 -0
  34. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/LICENSE +0 -0
  35. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/MANIFEST.in +0 -0
  36. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/README.md +0 -0
  37. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/_build/.nojekyll +0 -0
  38. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/_build/README.md +0 -0
  39. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/_build/docs/README.md +0 -0
  40. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/_static/custom.css +0 -0
  41. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/_templates/layout.html +0 -0
  42. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/cli/index.rst +0 -0
  43. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/conf.py +0 -0
  44. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/docs/index.rst +0 -0
  45. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/__init__.py +0 -0
  46. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/__main__.py +0 -0
  47. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/_base.py +0 -0
  48. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/_basewithshade.py +0 -0
  49. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/_lockable.py +0 -0
  50. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/altnumber.py +0 -0
  51. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/boundarycondition.py +0 -0
  52. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/checkdup.py +0 -0
  53. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/__init__.py +0 -0
  54. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/compare.py +0 -0
  55. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/create.py +0 -0
  56. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/lib.py +0 -0
  57. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/cli/setconfig.py +0 -0
  58. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/colorobj.py +0 -0
  59. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/config.json +0 -0
  60. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/config.py +0 -0
  61. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/dictutil.py +0 -0
  62. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/extensionutil.py +0 -0
  63. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/facetype.py +0 -0
  64. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/logutil.py +0 -0
  65. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/orientation.py +0 -0
  66. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/units.py +0 -0
  67. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/__init__.py +0 -0
  68. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/aperture.py +0 -0
  69. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/door.py +0 -0
  70. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/face.py +0 -0
  71. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/model.py +0 -0
  72. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/room.py +0 -0
  73. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/shade.py +0 -0
  74. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee/writer/shademesh.py +0 -0
  75. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee_core.egg-info/dependency_links.txt +0 -0
  76. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee_core.egg-info/entry_points.txt +0 -0
  77. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/honeybee_core.egg-info/top_level.txt +0 -0
  78. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/__init__.py +0 -0
  79. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/aperture_test.py +0 -0
  80. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/boundary_condition_test.py +0 -0
  81. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/cli_compare_test.py +0 -0
  82. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/cli_create_test.py +0 -0
  83. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/cli_edit_test.py +0 -0
  84. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/cli_validate_test.py +0 -0
  85. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/colorobj_test.py +0 -0
  86. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/config_test.py +0 -0
  87. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/dictutil_test.py +0 -0
  88. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/door_test.py +0 -0
  89. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/face_test.py +0 -0
  90. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/facetype_test.py +0 -0
  91. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/ShoeBox.json +0 -0
  92. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/bad_geometry_model.hbjson +0 -0
  93. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/colliding_room_volumes.hbjson +0 -0
  94. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/compare_model_1.hbjson +0 -0
  95. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/compare_model_2.hbjson +0 -0
  96. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/complex_polyfaces.json +0 -0
  97. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/minor_geometry/existing_model.hbjson +0 -0
  98. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/minor_geometry/updated_model.hbjson +0 -0
  99. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/mismatched_area_adj.hbjson +0 -0
  100. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/model_with_adiabatic.hbjson +0 -0
  101. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/model_with_holes.hbjson +0 -0
  102. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/model_without_adjacency.hbjson +0 -0
  103. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/nonascii_face.json +0 -0
  104. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/polygons_for_gap_boundary.json +0 -0
  105. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/room_for_window_offset.hbjson +0 -0
  106. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/json/single_family_home.hbjson +0 -0
  107. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/lockable_test.py +0 -0
  108. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/orientation_test.py +0 -0
  109. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/search_test.py +0 -0
  110. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/shade_test.py +0 -0
  111. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/shademesh_test.py +0 -0
  112. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/stl/cube_ascii.stl +0 -0
  113. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/stl/cube_binary.stl +0 -0
  114. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/typing_test.py +0 -0
  115. {honeybee_core-1.61.18 → honeybee_core-1.64.7}/tests/units_test.py +0 -0
@@ -55,7 +55,7 @@ jobs:
55
55
  run: |
56
56
  nextRelease="`npx semantic-release@^23.1.1 --dryRun | grep -oP 'Published release \K.*? ' || true`"
57
57
  npx semantic-release@^23.1.1
58
- echo "::set-output name=tag::$nextRelease"
58
+ echo "tag=$nextRelease" >> $GITHUB_OUTPUT
59
59
  env:
60
60
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61
61
  PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
@@ -1,22 +1,36 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: honeybee-core
3
- Version: 1.61.18
3
+ Version: 1.64.7
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
7
7
  Author-email: info@ladybug.tools
8
8
  License: AGPL-3.0
9
9
  Classifier: Programming Language :: Python :: 2.7
10
- Classifier: Programming Language :: Python :: 3.6
11
10
  Classifier: Programming Language :: Python :: 3.7
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
12
16
  Classifier: Programming Language :: Python :: Implementation :: CPython
13
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3
17
+ Classifier: Programming Language :: Python :: Implementation :: IronPython
14
18
  Classifier: Operating System :: OS Independent
15
19
  Description-Content-Type: text/markdown
16
20
  License-File: LICENSE
17
- Requires-Dist: ladybug-core==0.44.12
18
- Requires-Dist: ladybug-geometry-polyskel==1.7.25
19
- Requires-Dist: honeybee-schema==1.59.0; python_version >= "3.7"
21
+ Requires-Dist: ladybug-core==0.44.28
22
+ Requires-Dist: ladybug-geometry-polyskel==1.7.35
23
+ Requires-Dist: honeybee-schema==1.59.1; python_version >= "3.7"
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: license
31
+ Dynamic: license-file
32
+ Dynamic: requires-dist
33
+ Dynamic: summary
20
34
 
21
35
  ![Honeybee](https://www.ladybug.tools/assets/img/honeybee.png)
22
36
 
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
 
3
3
  echo "Building distribution"
4
- python setup.py sdist bdist_wheel
4
+ python -m build
5
5
  echo "Pushing new version to PyPi"
6
6
  twine upload dist/* -u $PYPI_USERNAME -p $PYPI_PASSWORD
@@ -4,10 +4,10 @@ sphinx-bootstrap-theme==0.8.1
4
4
  sphinxcontrib-fulltoc==1.2.0
5
5
  sphinxcontrib-websupport==2.0.0;python_version>='3.6'
6
6
  sphinx-click==6.0.0;python_version>='3.6'
7
- twine==5.1.1;python_version>='3.6'
8
- wheel==0.44.0;python_version>='3.6'
9
- setuptools==75.1.0;python_version>='3.6'
10
- importlib-metadata==8.5.0;python_version>='3.6'
7
+ twine==6.1.0;python_version>='3.6'
8
+ wheel==0.45.1;python_version>='3.6'
9
+ setuptools==80.9.0;python_version>='3.6'
10
+ build==1.3.0;python_version>='3.6'
11
11
  pytest==4.6.9;python_version<'3.0'
12
12
  Sphinx==1.8.5;python_version<'3.0'
13
13
  sphinxcontrib-websupport==1.1.2;python_version<'3.0'
@@ -15,4 +15,5 @@ sphinx-click==4.4.0;python_version<'3.0'
15
15
  twine==1.13.0;python_version<'3.0'
16
16
  wheel==0.38.1;python_version<'3.0'
17
17
  setuptools==44.1.0;python_version<'3.0'
18
+ build==0.1.0;python_version<'3.0'
18
19
  importlib-metadata==2.0.0;python_version<'3.0'
@@ -2,16 +2,18 @@
2
2
  """Honeybee Aperture."""
3
3
  from __future__ import division
4
4
  import math
5
+ import re
5
6
 
6
- from ladybug_geometry.geometry2d.pointvector import Vector2D
7
- from ladybug_geometry.geometry3d.pointvector import Point3D
8
- from ladybug_geometry.geometry3d.face import Face3D
7
+ from ladybug_geometry.geometry2d import Vector2D
8
+ from ladybug_geometry.geometry3d import Point3D, Face3D
9
9
  from ladybug.color import Color
10
10
 
11
11
  from ._basewithshade import _BaseWithShade
12
12
  from .typing import clean_string
13
+ from .search import get_attr_nested
13
14
  from .properties import ApertureProperties
14
15
  from .boundarycondition import boundary_conditions, Outdoors, Surface
16
+ from .facetype import RoofCeiling
15
17
  from .shade import Shade
16
18
  import honeybee.writer.aperture as writer
17
19
 
@@ -276,6 +278,23 @@ class Aperture(_BaseWithShade):
276
278
  """
277
279
  return isinstance(self.boundary_condition, Outdoors)
278
280
 
281
+ @property
282
+ def gbxml_type(self):
283
+ """Get text for the type of object this is in gbXML schema.
284
+
285
+ This will always be one of the following.
286
+
287
+ * FixedWindow
288
+ * OperableWindow
289
+ * FixedSkylight
290
+ * OperableSkylight
291
+ """
292
+ base_type = 'Window'
293
+ if self.has_parent and isinstance(self.parent.type, RoofCeiling):
294
+ base_type = 'Skylight'
295
+ win_type = 'Fixed' if not self.is_operable else 'Operable'
296
+ return win_type + base_type
297
+
279
298
  @property
280
299
  def type_color(self):
281
300
  """Get a Color to be used in visualizations by type."""
@@ -298,16 +317,23 @@ class Aperture(_BaseWithShade):
298
317
  return math.degrees(
299
318
  north_vector.angle_clockwise(Vector2D(self.normal.x, self.normal.y)))
300
319
 
301
- def cardinal_direction(self, north_vector=Vector2D(0, 1)):
320
+ def cardinal_direction(self, north_vector=Vector2D(0, 1), angle_tolerance=1):
302
321
  """Get text description for the cardinal direction that the aperture is pointing.
303
322
 
304
323
  Will be one of the following: ('North', 'NorthEast', 'East', 'SouthEast',
305
- 'South', 'SouthWest', 'West', 'NorthWest').
324
+ 'South', 'SouthWest', 'West', 'NorthWest', 'Up', 'Down').
306
325
 
307
326
  Args:
308
327
  north_vector: A ladybug_geometry Vector2D for the north direction.
309
328
  Default is the Y-axis (0, 1).
310
- """
329
+ angle_tolerance: The angle tolerance in degrees used to determine if
330
+ the geometry is perfectly Up or Down. (Default: 1).
331
+ """
332
+ tilt = self.tilt
333
+ if tilt < angle_tolerance:
334
+ return 'Up'
335
+ elif tilt > 180 - angle_tolerance:
336
+ return 'Down'
311
337
  orient = self.horizontal_orientation(north_vector)
312
338
  orient_text = ('North', 'NorthEast', 'East', 'SouthEast', 'South',
313
339
  'SouthWest', 'West', 'NorthWest')
@@ -317,6 +343,31 @@ class Aperture(_BaseWithShade):
317
343
  return orient_text[i]
318
344
  return orient_text[0]
319
345
 
346
+ def cardinal_abbrev(self, north_vector=Vector2D(0, 1), angle_tolerance=1):
347
+ """Get text abbreviation for the cardinal direction that the aperture is pointing.
348
+
349
+ Will be one of the following: ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW',
350
+ 'Up', 'Down').
351
+
352
+ Args:
353
+ north_vector: A ladybug_geometry Vector2D for the north direction.
354
+ Default is the Y-axis (0, 1).
355
+ angle_tolerance: The angle tolerance in degrees used to determine if
356
+ the aperture is perfectly Up or Down. (Default: 1).
357
+ """
358
+ tilt = self.tilt
359
+ if tilt < angle_tolerance:
360
+ return 'Up'
361
+ elif tilt > 180 - angle_tolerance:
362
+ return 'Down'
363
+ orient = self.horizontal_orientation(north_vector)
364
+ orient_text = ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW')
365
+ angles = (22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5)
366
+ for i, ang in enumerate(angles):
367
+ if orient < ang:
368
+ return orient_text[i]
369
+ return orient_text[0]
370
+
320
371
  def add_prefix(self, prefix):
321
372
  """Change the identifier of this object and child objects by inserting a prefix.
322
373
 
@@ -340,6 +391,28 @@ class Aperture(_BaseWithShade):
340
391
  in self._boundary_condition._boundary_condition_objects)
341
392
  self._boundary_condition = Surface(new_bc_objs, True)
342
393
 
394
+ def rename_by_attribute(
395
+ self,
396
+ format_str='{parent.parent.display_name} - {gbxml_type} - {cardinal_direction}'
397
+ ):
398
+ """Set the display name of this Aperture using a format string with attributes.
399
+
400
+ Args:
401
+ format_str: Text string for the pattern with which the Aperture will be
402
+ renamed. Any property on this class may be used (eg. gbxml_str)
403
+ and each property should be put in curly brackets. Nested
404
+ properties can be specified by using "." to denote nesting levels
405
+ (eg. properties.energy.construction.display_name). Functions that
406
+ return string outputs can also be passed here as long as these
407
+ functions defaults specified for all arguments.
408
+ """
409
+ matches = re.findall(r'{([^}]*)}', format_str)
410
+ attributes = [get_attr_nested(self, m, decimal_count=2) for m in matches]
411
+ for attr_name, attr_val in zip(matches, attributes):
412
+ format_str = format_str.replace('{{{}}}'.format(attr_name), attr_val)
413
+ self.display_name = format_str
414
+ return format_str
415
+
343
416
  def set_adjacency(self, other_aperture):
344
417
  """Set this aperture to be adjacent to another.
345
418
 
@@ -104,7 +104,8 @@ def solve_adjacency(model_file, no_merge, no_intersect, no_overwrite,
104
104
  air_boundary = not wall
105
105
  adiabatic = not surface
106
106
  parsed_model.solve_adjacency(
107
- merge_coplanar, intersect, overwrite, air_boundary, adiabatic)
107
+ merge_coplanar, intersect, overwrite,
108
+ air_boundary=air_boundary, adiabatic=adiabatic)
108
109
 
109
110
  # write the new model out to the file or stdout
110
111
  output_file.write(json.dumps(parsed_model.to_dict()))
@@ -21,10 +21,13 @@ def validate():
21
21
  '--extension', '-e', help='Text for the name of the extension to be checked. '
22
22
  'The value input is case-insensitive such that "radiance" and "Radiance" will '
23
23
  'both result in the model being checked for validity with honeybee-radiance. '
24
- 'This value can also be set to "All" in order to run checks for all installed '
25
- 'extensions. Some common honeybee extension names that can be input here include: '
26
- 'Radiance, EnergyPlus, DOE2, IES, IDAICE',
27
- type=str, default='All', show_default=True)
24
+ 'This value can also be set to "Generic" in order to run checks for all installed '
25
+ 'extensions. Using "Generic" will run all except the most limiting of checks '
26
+ '(eg. the DOE2 lack of support for courtyards) with the goal of producing a model '
27
+ 'that can be exported to multiple engines (albeit with a little extra '
28
+ 'postprocessing for particularly limited engines). Some common honeybee extension '
29
+ 'names that can be input here include: Radiance, EnergyPlus, DOE2, IES, IDAICE',
30
+ type=str, default='Generic', show_default=True)
28
31
  @click.option(
29
32
  '--plain-text/--json', ' /-j', help='Flag to note whether the output validation '
30
33
  'report should be formatted as a JSON object instead of plain text. If set to JSON, '
@@ -82,7 +85,7 @@ def validate_model_cli(model_file, extension, plain_text, room_overlaps, output_
82
85
  sys.exit(0)
83
86
 
84
87
 
85
- def validate_model(model_file, extension='All', json=False, output_file=None,
88
+ def validate_model(model_file, extension='Generic', json=False, output_file=None,
86
89
  plain_text=True):
87
90
  """Validate all properties of a Model file against the Honeybee schema.
88
91
 
@@ -95,8 +98,12 @@ def validate_model(model_file, extension='All', json=False, output_file=None,
95
98
  The value input here is case-insensitive such that "radiance"
96
99
  and "Radiance" will both result in the model being checked for
97
100
  validity with honeybee-radiance. This value can also be set to
98
- "All" in order to run checks for all installed extensions. Some
99
- common honeybee extension names that can be input here if they
101
+ "Generic" in order to run checks for all installed extensions.
102
+ Using "Generic" will run all except the most limiting of checks
103
+ (eg. DOE2's lack of support for courtyards) with the goal of
104
+ producing a model that is export-able to multiple engines (albeit
105
+ with a little extra postprocessing for particularly limited engines).
106
+ Some common dragonfly extension names that can be input here if they
100
107
  are installed include:
101
108
 
102
109
  * Radiance
@@ -2,14 +2,15 @@
2
2
  """Honeybee Door."""
3
3
  from __future__ import division
4
4
  import math
5
+ import re
5
6
 
6
- from ladybug_geometry.geometry2d.pointvector import Vector2D
7
- from ladybug_geometry.geometry3d.pointvector import Point3D
8
- from ladybug_geometry.geometry3d.face import Face3D
7
+ from ladybug_geometry.geometry2d import Vector2D
8
+ from ladybug_geometry.geometry3d import Point3D, Face3D
9
9
  from ladybug.color import Color
10
10
 
11
11
  from ._basewithshade import _BaseWithShade
12
12
  from .typing import clean_string
13
+ from .search import get_attr_nested
13
14
  from .properties import DoorProperties
14
15
  from .boundarycondition import boundary_conditions, Outdoors, Surface
15
16
  from .shade import Shade
@@ -278,6 +279,16 @@ class Door(_BaseWithShade):
278
279
  """
279
280
  return isinstance(self.boundary_condition, Outdoors)
280
281
 
282
+ @property
283
+ def gbxml_type(self):
284
+ """Get text for the type of object this is in gbXML schema."""
285
+ return 'NonSlidingDoor'
286
+
287
+ @property
288
+ def energyplus_type(self):
289
+ """Get text for the type of object this is in IDF schema."""
290
+ return 'GlassDoor' if self.is_glass else 'Door'
291
+
281
292
  @property
282
293
  def type_color(self):
283
294
  """Get a Color to be used in visualizations by type."""
@@ -300,16 +311,23 @@ class Door(_BaseWithShade):
300
311
  return math.degrees(
301
312
  north_vector.angle_clockwise(Vector2D(self.normal.x, self.normal.y)))
302
313
 
303
- def cardinal_direction(self, north_vector=Vector2D(0, 1)):
314
+ def cardinal_direction(self, north_vector=Vector2D(0, 1), angle_tolerance=1):
304
315
  """Get text description for the cardinal direction that the door is pointing.
305
316
 
306
317
  Will be one of the following: ('North', 'NorthEast', 'East', 'SouthEast',
307
- 'South', 'SouthWest', 'West', 'NorthWest').
318
+ 'South', 'SouthWest', 'West', 'NorthWest', 'Up', 'Down').
308
319
 
309
320
  Args:
310
321
  north_vector: A ladybug_geometry Vector2D for the north direction.
311
322
  Default is the Y-axis (0, 1).
323
+ angle_tolerance: The angle tolerance in degrees used to determine if
324
+ the geometry is perfectly Up or Down. (Default: 1).
312
325
  """
326
+ tilt = self.tilt
327
+ if tilt < angle_tolerance:
328
+ return 'Up'
329
+ elif tilt > 180 - angle_tolerance:
330
+ return 'Down'
313
331
  orient = self.horizontal_orientation(north_vector)
314
332
  orient_text = ('North', 'NorthEast', 'East', 'SouthEast', 'South',
315
333
  'SouthWest', 'West', 'NorthWest')
@@ -319,6 +337,31 @@ class Door(_BaseWithShade):
319
337
  return orient_text[i]
320
338
  return orient_text[0]
321
339
 
340
+ def cardinal_abbrev(self, north_vector=Vector2D(0, 1), angle_tolerance=1):
341
+ """Get text abbreviation for the cardinal direction that the door is pointing.
342
+
343
+ Will be one of the following: ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW',
344
+ 'Up', 'Down').
345
+
346
+ Args:
347
+ north_vector: A ladybug_geometry Vector2D for the north direction.
348
+ Default is the Y-axis (0, 1).
349
+ angle_tolerance: The angle tolerance in degrees used to determine if
350
+ the door is perfectly Up or Down. (Default: 1).
351
+ """
352
+ tilt = self.tilt
353
+ if tilt < angle_tolerance:
354
+ return 'Up'
355
+ elif tilt > 180 - angle_tolerance:
356
+ return 'Down'
357
+ orient = self.horizontal_orientation(north_vector)
358
+ orient_text = ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW')
359
+ angles = (22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5)
360
+ for i, ang in enumerate(angles):
361
+ if orient < ang:
362
+ return orient_text[i]
363
+ return orient_text[0]
364
+
322
365
  def add_prefix(self, prefix):
323
366
  """Change the identifier of this object and child objects by inserting a prefix.
324
367
 
@@ -342,6 +385,28 @@ class Door(_BaseWithShade):
342
385
  in self._boundary_condition._boundary_condition_objects)
343
386
  self._boundary_condition = Surface(new_bc_objs, True)
344
387
 
388
+ def rename_by_attribute(
389
+ self,
390
+ format_str='{parent.parent.display_name} - {energyplus_type} - {cardinal_direction}'
391
+ ):
392
+ """Set the display name of this Door using a format string with attributes.
393
+
394
+ Args:
395
+ format_str: Text string for the pattern with which the Door will be
396
+ renamed. Any property on this class may be used (eg. energyplus_type)
397
+ and each property should be put in curly brackets. Nested
398
+ properties can be specified by using "." to denote nesting levels
399
+ (eg. properties.energy.construction.display_name). Functions that
400
+ return string outputs can also be passed here as long as these
401
+ functions defaults specified for all arguments.
402
+ """
403
+ matches = re.findall(r'{([^}]*)}', format_str)
404
+ attributes = [get_attr_nested(self, m, decimal_count=2) for m in matches]
405
+ for attr_name, attr_val in zip(matches, attributes):
406
+ format_str = format_str.replace('{{{}}}'.format(attr_name), attr_val)
407
+ self.display_name = format_str
408
+ return format_str
409
+
345
410
  def set_adjacency(self, other_door):
346
411
  """Set this door to be adjacent to another (and vice versa).
347
412
 
@@ -2,6 +2,7 @@
2
2
  """Honeybee Face."""
3
3
  from __future__ import division
4
4
  import math
5
+ import re
5
6
 
6
7
  from ladybug_geometry.geometry2d import Vector2D, Point2D, Polygon2D, Mesh2D
7
8
  from ladybug_geometry.geometry3d import Vector3D, Point3D, Plane, Face3D
@@ -9,8 +10,10 @@ from ladybug.color import Color
9
10
 
10
11
  from ._basewithshade import _BaseWithShade
11
12
  from .typing import clean_string, invalid_dict_error
13
+ from .search import get_attr_nested
12
14
  from .properties import FaceProperties
13
- from .facetype import face_types, get_type_from_normal, AirBoundary, Floor, RoofCeiling
15
+ from .facetype import face_types, get_type_from_normal, AirBoundary, Wall, \
16
+ Floor, RoofCeiling
14
17
  from .boundarycondition import boundary_conditions, get_bc_from_position, \
15
18
  _BoundaryCondition, Outdoors, Surface, Ground
16
19
  from .shade import Shade
@@ -130,13 +133,6 @@ class Face(_BaseWithShade):
130
133
  assert data['type'] == 'Face', 'Expected Face dictionary. ' \
131
134
  'Got {}.'.format(data['type'])
132
135
 
133
- # remove any invalid holes from the geometry
134
- geo_dict = data['geometry']
135
- if 'holes' in geo_dict and geo_dict['holes'] is not None:
136
- for i, hole_list in enumerate(geo_dict['holes']):
137
- if len(hole_list) < 3:
138
- geo_dict['holes'].pop(i)
139
-
140
136
  # first serialize it with an outdoor boundary condition
141
137
  face_type = face_types.by_name(data['face_type'])
142
138
  face = cls(data['identifier'], Face3D.from_dict(data['geometry']),
@@ -398,6 +394,52 @@ class Face(_BaseWithShade):
398
394
  """
399
395
  return isinstance(self.boundary_condition, Outdoors)
400
396
 
397
+ @property
398
+ def gbxml_type(self):
399
+ """Get text for the type of object this is in gbXML schema.
400
+
401
+ This will always be one of the following.
402
+
403
+ * InteriorWall
404
+ * ExteriorWall
405
+ * UndergroundWall
406
+ * Roof
407
+ * Ceiling
408
+ * UndergroundCeiling
409
+ * InteriorFloor
410
+ * ExposedFloor
411
+ * UndergroundSlab
412
+ * SlabOnGrade
413
+ * Air
414
+ """
415
+ if isinstance(self.type, AirBoundary):
416
+ return 'Air'
417
+ elif isinstance(self.type, Wall):
418
+ bc_type = 'Interior'
419
+ if isinstance(self.boundary_condition, Outdoors):
420
+ bc_type = 'Exterior'
421
+ elif isinstance(self.boundary_condition, Ground):
422
+ bc_type = 'Underground'
423
+ return bc_type + 'Wall'
424
+ elif isinstance(self.type, Floor):
425
+ if isinstance(self.boundary_condition, Ground):
426
+ if self.has_parent:
427
+ for f in self.parent.faces:
428
+ if isinstance(f.type, Wall) and \
429
+ isinstance(f.boundary_condition, Outdoors):
430
+ return 'SlabOnGrade'
431
+ return 'UndergroundSlab'
432
+ elif isinstance(self.boundary_condition, Outdoors):
433
+ return 'ExposedFloor'
434
+ else:
435
+ return 'InteriorFloor'
436
+ else:
437
+ if isinstance(self.boundary_condition, Outdoors):
438
+ return 'Roof'
439
+ elif isinstance(self.boundary_condition, Ground):
440
+ return 'UndergroundCeiling'
441
+ return 'Ceiling'
442
+
401
443
  @property
402
444
  def type_color(self):
403
445
  """Get a Color to be used in visualizations by type."""
@@ -425,16 +467,23 @@ class Face(_BaseWithShade):
425
467
  return math.degrees(
426
468
  north_vector.angle_clockwise(Vector2D(self.normal.x, self.normal.y)))
427
469
 
428
- def cardinal_direction(self, north_vector=Vector2D(0, 1)):
470
+ def cardinal_direction(self, north_vector=Vector2D(0, 1), angle_tolerance=1):
429
471
  """Get text description for the cardinal direction that the face is pointing.
430
472
 
431
473
  Will be one of the following: ('North', 'NorthEast', 'East', 'SouthEast',
432
- 'South', 'SouthWest', 'West', 'NorthWest').
474
+ 'South', 'SouthWest', 'West', 'NorthWest', 'Up', 'Down').
433
475
 
434
476
  Args:
435
477
  north_vector: A ladybug_geometry Vector2D for the north direction.
436
478
  Default is the Y-axis (0, 1).
479
+ angle_tolerance: The angle tolerance in degrees used to determine if
480
+ the Face is perfectly Up or Down. (Default: 1).
437
481
  """
482
+ tilt = self.tilt
483
+ if tilt < angle_tolerance:
484
+ return 'Up'
485
+ elif tilt > 180 - angle_tolerance:
486
+ return 'Down'
438
487
  orient = self.horizontal_orientation(north_vector)
439
488
  orient_text = ('North', 'NorthEast', 'East', 'SouthEast', 'South',
440
489
  'SouthWest', 'West', 'NorthWest')
@@ -444,6 +493,31 @@ class Face(_BaseWithShade):
444
493
  return orient_text[i]
445
494
  return orient_text[0]
446
495
 
496
+ def cardinal_abbrev(self, north_vector=Vector2D(0, 1), angle_tolerance=1):
497
+ """Get text abbreviation for the cardinal direction that the face is pointing.
498
+
499
+ Will be one of the following: ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW',
500
+ 'Up', 'Down').
501
+
502
+ Args:
503
+ north_vector: A ladybug_geometry Vector2D for the north direction.
504
+ Default is the Y-axis (0, 1).
505
+ angle_tolerance: The angle tolerance in degrees used to determine if
506
+ the Face is perfectly Up or Down. (Default: 1).
507
+ """
508
+ tilt = self.tilt
509
+ if tilt < angle_tolerance:
510
+ return 'Up'
511
+ elif tilt > 180 - angle_tolerance:
512
+ return 'Down'
513
+ orient = self.horizontal_orientation(north_vector)
514
+ orient_text = ('N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW')
515
+ angles = (22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5)
516
+ for i, ang in enumerate(angles):
517
+ if orient < ang:
518
+ return orient_text[i]
519
+ return orient_text[0]
520
+
447
521
  def add_prefix(self, prefix):
448
522
  """Change the identifier of this object and child objects by inserting a prefix.
449
523
 
@@ -471,6 +545,28 @@ class Face(_BaseWithShade):
471
545
  in self._boundary_condition._boundary_condition_objects)
472
546
  self._boundary_condition = Surface(new_bc_objs, False)
473
547
 
548
+ def rename_by_attribute(
549
+ self,
550
+ format_str='{parent.display_name} - {gbxml_type} - {cardinal_direction}'
551
+ ):
552
+ """Set the display name of this Face using a format string with Face attributes.
553
+
554
+ Args:
555
+ format_str: Text string for the pattern with which the Face will be
556
+ renamed. Any property on this class may be used (eg. gbxml_str)
557
+ and each property should be put in curly brackets. Nested
558
+ properties can be specified by using "." to denote nesting levels
559
+ (eg. properties.energy.construction.display_name). Functions that
560
+ return string outputs can also be passed here as long as these
561
+ functions defaults specified for all arguments.
562
+ """
563
+ matches = re.findall(r'{([^}]*)}', format_str)
564
+ attributes = [get_attr_nested(self, m, decimal_count=2) for m in matches]
565
+ for attr_name, attr_val in zip(matches, attributes):
566
+ format_str = format_str.replace('{{{}}}'.format(attr_name), attr_val)
567
+ self.display_name = format_str
568
+ return format_str
569
+
474
570
  def remove_sub_faces(self):
475
571
  """Remove all apertures and doors from the face."""
476
572
  self.remove_apertures()
@@ -740,6 +836,8 @@ class Face(_BaseWithShade):
740
836
  if len(joined_bounds) == 1: # can be represented with a single Face3D
741
837
  verts3d = tuple(ref_plane.xy_to_xyz(_v) for _v in joined_bounds[0])
742
838
  non_rect_geos = [Face3D(verts3d, plane=ref_plane)]
839
+ elif len(joined_bounds) == 0: # everything was invalid
840
+ non_rect_geos = []
743
841
  else: # need to separate holes from distinct Face3Ds
744
842
  bound_faces = []
745
843
  for poly in joined_bounds:
@@ -1749,16 +1847,24 @@ class Face(_BaseWithShade):
1749
1847
  at which point the vertex is considered colinear. Default: 0.01,
1750
1848
  suitable for objects in meters.
1751
1849
  """
1850
+ # set up lists to track sub-faces to remove
1851
+ del_ap_i, del_dr_i = [], []
1852
+ # remove degenerate apertures
1752
1853
  for i, ap in enumerate(self._apertures):
1753
1854
  try:
1754
1855
  ap.remove_colinear_vertices(tolerance)
1755
1856
  except ValueError:
1756
- self._apertures.pop(i)
1857
+ del_ap_i.append(i)
1858
+ for del_i in reversed(del_ap_i):
1859
+ self._apertures.pop(del_i)
1860
+ # remove degenerate doors
1757
1861
  for i, dr in enumerate(self._doors):
1758
1862
  try:
1759
1863
  dr.remove_colinear_vertices(tolerance)
1760
1864
  except ValueError:
1761
- self._apertures.pop(i)
1865
+ del_dr_i.append(i)
1866
+ for del_i in reversed(del_dr_i):
1867
+ self._doors.pop(del_i)
1762
1868
 
1763
1869
  def is_geo_equivalent(self, face, tolerance=0.01):
1764
1870
  """Get a boolean for whether this object is geometrically equivalent to another.