honeybee-core 1.64.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- honeybee/__init__.py +23 -0
- honeybee/__main__.py +4 -0
- honeybee/_base.py +331 -0
- honeybee/_basewithshade.py +310 -0
- honeybee/_lockable.py +99 -0
- honeybee/altnumber.py +47 -0
- honeybee/aperture.py +997 -0
- honeybee/boundarycondition.py +358 -0
- honeybee/checkdup.py +173 -0
- honeybee/cli/__init__.py +118 -0
- honeybee/cli/compare.py +132 -0
- honeybee/cli/create.py +265 -0
- honeybee/cli/edit.py +559 -0
- honeybee/cli/lib.py +103 -0
- honeybee/cli/setconfig.py +43 -0
- honeybee/cli/validate.py +224 -0
- honeybee/colorobj.py +363 -0
- honeybee/config.json +5 -0
- honeybee/config.py +347 -0
- honeybee/dictutil.py +54 -0
- honeybee/door.py +746 -0
- honeybee/extensionutil.py +208 -0
- honeybee/face.py +2360 -0
- honeybee/facetype.py +153 -0
- honeybee/logutil.py +79 -0
- honeybee/model.py +4272 -0
- honeybee/orientation.py +132 -0
- honeybee/properties.py +845 -0
- honeybee/room.py +3485 -0
- honeybee/search.py +107 -0
- honeybee/shade.py +514 -0
- honeybee/shademesh.py +362 -0
- honeybee/typing.py +498 -0
- honeybee/units.py +88 -0
- honeybee/writer/__init__.py +7 -0
- honeybee/writer/aperture.py +6 -0
- honeybee/writer/door.py +6 -0
- honeybee/writer/face.py +6 -0
- honeybee/writer/model.py +6 -0
- honeybee/writer/room.py +6 -0
- honeybee/writer/shade.py +6 -0
- honeybee/writer/shademesh.py +6 -0
- honeybee_core-1.64.12.dist-info/METADATA +94 -0
- honeybee_core-1.64.12.dist-info/RECORD +48 -0
- honeybee_core-1.64.12.dist-info/WHEEL +5 -0
- honeybee_core-1.64.12.dist-info/entry_points.txt +2 -0
- honeybee_core-1.64.12.dist-info/licenses/LICENSE +661 -0
- honeybee_core-1.64.12.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Commands to set honeybee-core configurations."""
|
|
2
|
+
import click
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from honeybee.config import folders
|
|
8
|
+
|
|
9
|
+
_logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group(help='Commands to set honeybee-core configurations.')
|
|
13
|
+
def set_config():
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@set_config.command('default-simulation-folder')
|
|
18
|
+
@click.argument('folder-path', required=False, type=click.Path(
|
|
19
|
+
exists=True, file_okay=False, dir_okay=True, resolve_path=True))
|
|
20
|
+
def default_simulation_folder(folder_path):
|
|
21
|
+
"""Set the default-simulation-folder configuration variable.
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Args:
|
|
25
|
+
folder_path: Path to a folder to be set as the default-simulation-folder.
|
|
26
|
+
If unspecified, the default-simulation-folder will be set back to
|
|
27
|
+
the default.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
config_file = folders.config_file
|
|
31
|
+
with open(config_file) as inf:
|
|
32
|
+
data = json.load(inf)
|
|
33
|
+
data['default_simulation_folder'] = folder_path if folder_path is not None else ''
|
|
34
|
+
with open(config_file, 'w') as fp:
|
|
35
|
+
json.dump(data, fp, indent=4)
|
|
36
|
+
msg_end = 'reset to default' if folder_path is None \
|
|
37
|
+
else 'set to: {}'.format(folder_path)
|
|
38
|
+
print('default-simulation-folder successfully {}.'.format(msg_end))
|
|
39
|
+
except Exception as e:
|
|
40
|
+
_logger.exception('Failed to set default-simulation-folder.\n{}'.format(e))
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
else:
|
|
43
|
+
sys.exit(0)
|
honeybee/cli/validate.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""honeybee validation commands."""
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from ladybug.commandutil import process_content_to_output
|
|
7
|
+
from honeybee.model import Model
|
|
8
|
+
|
|
9
|
+
_logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group(help='Commands for validating Honeybee objects.')
|
|
13
|
+
def validate():
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@validate.command('model')
|
|
18
|
+
@click.argument('model-file', type=click.Path(
|
|
19
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
|
|
20
|
+
@click.option(
|
|
21
|
+
'--extension', '-e', help='Text for the name of the extension to be checked. '
|
|
22
|
+
'The value input is case-insensitive such that "radiance" and "Radiance" will '
|
|
23
|
+
'both result in the model being checked for validity with honeybee-radiance. '
|
|
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)
|
|
31
|
+
@click.option(
|
|
32
|
+
'--plain-text/--json', ' /-j', help='Flag to note whether the output validation '
|
|
33
|
+
'report should be formatted as a JSON object instead of plain text. If set to JSON, '
|
|
34
|
+
'the output object will contain several attributes. The "honeybee_core" and '
|
|
35
|
+
'"honeybee_schema" attributes will note the versions of these libraries used in '
|
|
36
|
+
'the validation process. An attribute called "fatal_error" is a text string '
|
|
37
|
+
'containing an exception if the Model failed to serialize and will be an empty '
|
|
38
|
+
'string if serialization was successful. An attribute called "errors" will '
|
|
39
|
+
'contain a list of JSON objects for each invalid issue found in the model. A '
|
|
40
|
+
'boolean attribute called "valid" will note whether the Model is valid or not.',
|
|
41
|
+
default=True, show_default=True)
|
|
42
|
+
@click.option(
|
|
43
|
+
'--room-overlaps', 'room_overlaps', flag_value='True',
|
|
44
|
+
help='Deprecated flag used to check room collisions. '
|
|
45
|
+
'Use `honeybee validate room-collisions` instead.')
|
|
46
|
+
@click.option(
|
|
47
|
+
'--output-file', '-f', help='Optional file to output the full report '
|
|
48
|
+
'of the validation. By default it will be printed out to stdout',
|
|
49
|
+
type=click.File('w'), default='-')
|
|
50
|
+
def validate_model_cli(model_file, extension, plain_text, room_overlaps, output_file):
|
|
51
|
+
"""Validate all properties of a Model file against Honeybee schema.
|
|
52
|
+
|
|
53
|
+
This includes checking basic compliance with the 5 rules of honeybee geometry
|
|
54
|
+
as well as checks for all extension attributes. The 5 rules of honeybee geometry
|
|
55
|
+
are as follows.
|
|
56
|
+
|
|
57
|
+
1. All Face3Ds must be planar to within the model tolerance.
|
|
58
|
+
|
|
59
|
+
2. All Face3Ds must NOT be self-intersecting (like a bowtie shape)
|
|
60
|
+
|
|
61
|
+
3. All children sub-faces (Apertures and Doors) must be co-planar with
|
|
62
|
+
their parent Face and lie completely within its boundary.
|
|
63
|
+
|
|
64
|
+
4. All adjacent object pairs (faces and sub-faces with a Surface boundary
|
|
65
|
+
condition) must have matching areas.
|
|
66
|
+
|
|
67
|
+
5. All Room volumes must be closed solids.
|
|
68
|
+
|
|
69
|
+
\b
|
|
70
|
+
Args:
|
|
71
|
+
model_file: Full path to a Model JSON file.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
json = not plain_text
|
|
75
|
+
if room_overlaps == 'True':
|
|
76
|
+
print('--room-overlaps option is deprecated. '
|
|
77
|
+
'Use `honeybee validate room-collisions` instead.')
|
|
78
|
+
validate_room_collisions(model_file, json, output_file)
|
|
79
|
+
else:
|
|
80
|
+
validate_model(model_file, extension, json, output_file)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
_logger.exception('Model validation failed.\n{}'.format(e))
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
else:
|
|
85
|
+
sys.exit(0)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def validate_model(model_file, extension='Generic', json=False, output_file=None,
|
|
89
|
+
plain_text=True):
|
|
90
|
+
"""Validate all properties of a Model file against the Honeybee schema.
|
|
91
|
+
|
|
92
|
+
This includes checking basic compliance with the 5 rules of honeybee geometry
|
|
93
|
+
as well as checks for all extension attributes.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
model_file: Full path to a Honeybee Model file.
|
|
97
|
+
extension_name: Text for the name of the extension to be checked.
|
|
98
|
+
The value input here is case-insensitive such that "radiance"
|
|
99
|
+
and "Radiance" will both result in the model being checked for
|
|
100
|
+
validity with honeybee-radiance. This value can also be set to
|
|
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
|
|
107
|
+
are installed include:
|
|
108
|
+
|
|
109
|
+
* Radiance
|
|
110
|
+
* EnergyPlus
|
|
111
|
+
* DOE2
|
|
112
|
+
* IES
|
|
113
|
+
* IDAICE
|
|
114
|
+
|
|
115
|
+
json: Boolean to note whether the output validation report should be
|
|
116
|
+
formatted as a JSON object instead of plain text. (Default: False).
|
|
117
|
+
output_file: Optional file to output the full report of the validation.
|
|
118
|
+
If None, the string will simply be returned from this method.
|
|
119
|
+
"""
|
|
120
|
+
report = Model.validate(model_file, 'check_for_extension', [extension], json)
|
|
121
|
+
return process_content_to_output(report, output_file)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@validate.command('rooms-solid')
|
|
125
|
+
@click.argument('model-file', type=click.Path(
|
|
126
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
|
|
127
|
+
@click.option(
|
|
128
|
+
'--plain-text/--json', ' /-j', help='Flag to note whether the output validation '
|
|
129
|
+
'report should be formatted as a JSON object instead of plain text. If set to JSON, '
|
|
130
|
+
'the output object will contain several attributes. An attribute called '
|
|
131
|
+
'"fatal_error" is a text string containing an exception if the Model failed to '
|
|
132
|
+
'serialize and will be an empty string if serialization was successful. An '
|
|
133
|
+
'attribute called "errors" will contain a list of JSON objects for each '
|
|
134
|
+
'invalid issue. A boolean attribute called "valid" will note whether the Model '
|
|
135
|
+
'is valid or not.',
|
|
136
|
+
default=True, show_default=True)
|
|
137
|
+
@click.option(
|
|
138
|
+
'--output-file', '-f', help='Optional file to output the full report '
|
|
139
|
+
'of the validation. By default it will be printed out to stdout.',
|
|
140
|
+
type=click.File('w'), default='-')
|
|
141
|
+
def validate_rooms_solid_cli(model_file, plain_text, output_file):
|
|
142
|
+
"""Validate whether all Room volumes in a model are solid.
|
|
143
|
+
|
|
144
|
+
The returned result can include a list of all naked and non-manifold edges
|
|
145
|
+
preventing closed room volumes when --json is used. This is helpful for visually
|
|
146
|
+
identifying issues in geometry that are preventing the room volume from
|
|
147
|
+
validating as closed.
|
|
148
|
+
|
|
149
|
+
\b
|
|
150
|
+
Args:
|
|
151
|
+
model_file: Full path to a Honeybee Model file.
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
json = not plain_text
|
|
155
|
+
validate_rooms_solid(model_file, json, output_file)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
_logger.exception('Model room volume validation failed.\n{}'.format(e))
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
else:
|
|
160
|
+
sys.exit(0)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def validate_rooms_solid(model_file, json=False, output_file=None, plain_text=True):
|
|
164
|
+
"""Get a list of all naked and non-manifold edges preventing closed room volumes.
|
|
165
|
+
|
|
166
|
+
This is helpful for visually identifying issues in geometry that are preventing
|
|
167
|
+
the room volume from reading as closed.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
model_file: Full path to a Honeybee Model file.
|
|
171
|
+
json: Boolean to note whether the output validation report should be
|
|
172
|
+
formatted as a JSON object instead of plain text. (Default: False).
|
|
173
|
+
output_file: Optional file to output the full report of the validation.
|
|
174
|
+
If None, the string will simply be returned from this method.
|
|
175
|
+
"""
|
|
176
|
+
report = Model.validate(model_file, 'check_rooms_solid', json_output=json)
|
|
177
|
+
return process_content_to_output(report, output_file)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@validate.command('room-collisions')
|
|
181
|
+
@click.argument('model-file', type=click.Path(
|
|
182
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
|
|
183
|
+
@click.option(
|
|
184
|
+
'--plain-text/--json', ' /-j', help='Flag to note whether the output validation '
|
|
185
|
+
'report should be formatted as a JSON object instead of plain text. If set to JSON, '
|
|
186
|
+
'the output object will contain several attributes. An attribute called '
|
|
187
|
+
'"fatal_error" is a text string containing an exception if the Model failed to '
|
|
188
|
+
'serialize and will be an empty string if serialization was successful. An '
|
|
189
|
+
'attribute called "errors" will contain a list of JSON objects for each '
|
|
190
|
+
'invalid issue. A boolean attribute called "valid" will note whether the Model '
|
|
191
|
+
'is valid or not.', default=True, show_default=True)
|
|
192
|
+
@click.option(
|
|
193
|
+
'--output-file', '-f', help='Optional file to output the full report '
|
|
194
|
+
'of the validation. By default it will be printed out to stdout.',
|
|
195
|
+
type=click.File('w'), default='-')
|
|
196
|
+
def validate_room_collisions_cli(model_file, plain_text, output_file):
|
|
197
|
+
"""Validate whether Room collide with one another beyond the model tolerance.
|
|
198
|
+
|
|
199
|
+
\b
|
|
200
|
+
Args:
|
|
201
|
+
model_file: Full path to a Honeybee Model file.
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
json = not plain_text
|
|
205
|
+
validate_room_collisions(model_file, json, output_file)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
_logger.exception('Model room volume validation failed.\n{}'.format(e))
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
else:
|
|
210
|
+
sys.exit(0)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def validate_room_collisions(model_file, json=False, output_file=None, plain_text=True):
|
|
214
|
+
"""Validate whether Room collide with one another beyond the model tolerance.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
model_file: Full path to a Honeybee Model file.
|
|
218
|
+
json: Boolean to note whether the output validation report should be
|
|
219
|
+
formatted as a JSON object instead of plain text. (Default: False).
|
|
220
|
+
output_file: Optional file to output the full report of the validation.
|
|
221
|
+
If None, the string will simply be returned from this method.
|
|
222
|
+
"""
|
|
223
|
+
report = Model.validate(model_file, 'check_room_volume_collisions', json_output=json)
|
|
224
|
+
return process_content_to_output(report, output_file)
|
honeybee/colorobj.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Module for coloring geometry with attributes."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
from .shademesh import ShadeMesh
|
|
6
|
+
from .shade import Shade
|
|
7
|
+
from .door import Door
|
|
8
|
+
from .aperture import Aperture
|
|
9
|
+
from .face import Face
|
|
10
|
+
from .room import Room
|
|
11
|
+
from .facetype import Floor
|
|
12
|
+
from .search import get_attr_nested
|
|
13
|
+
|
|
14
|
+
from ladybug.graphic import GraphicContainer
|
|
15
|
+
from ladybug.legend import LegendParameters, LegendParametersCategorized
|
|
16
|
+
from ladybug_geometry.geometry3d.pointvector import Point3D
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _ColorObject(object):
|
|
20
|
+
"""Base class for visualization objects.
|
|
21
|
+
|
|
22
|
+
Properties:
|
|
23
|
+
* legend_parameters
|
|
24
|
+
* attr_name
|
|
25
|
+
* attr_name_end
|
|
26
|
+
* attributes
|
|
27
|
+
* attributes_unique
|
|
28
|
+
* attributes_original
|
|
29
|
+
* min_point
|
|
30
|
+
* max_point
|
|
31
|
+
* graphic_container
|
|
32
|
+
"""
|
|
33
|
+
__slots__ = ('_attr_name', '_legend_parameters', '_attr_name_end',
|
|
34
|
+
'_attributes', '_attributes_unique', '_attributes_original',
|
|
35
|
+
'_min_point', '_max_point')
|
|
36
|
+
|
|
37
|
+
def __init__(self, legend_parameters=None):
|
|
38
|
+
"""Initialize ColorObject."""
|
|
39
|
+
# assign the legend parameters of this object
|
|
40
|
+
self.legend_parameters = legend_parameters
|
|
41
|
+
|
|
42
|
+
self._attr_name = None
|
|
43
|
+
self._attr_name_end = None
|
|
44
|
+
self._attributes = None
|
|
45
|
+
self._attributes_unique = None
|
|
46
|
+
self._attributes_original = None
|
|
47
|
+
self._min_point = None
|
|
48
|
+
self._max_point = None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def legend_parameters(self):
|
|
52
|
+
"""Get or set the legend parameters."""
|
|
53
|
+
return self._legend_parameters
|
|
54
|
+
|
|
55
|
+
@legend_parameters.setter
|
|
56
|
+
def legend_parameters(self, value):
|
|
57
|
+
if value is not None:
|
|
58
|
+
assert isinstance(value, LegendParameters) and not \
|
|
59
|
+
isinstance(value, LegendParametersCategorized), \
|
|
60
|
+
'Expected LegendParameters. Got {}.'.format(type(value))
|
|
61
|
+
self._legend_parameters = value
|
|
62
|
+
else:
|
|
63
|
+
self._legend_parameters = LegendParameters()
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def attr_name(self):
|
|
67
|
+
"""Get a text string of an attribute that the input objects should have."""
|
|
68
|
+
return self._attr_name
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def attr_name_end(self):
|
|
72
|
+
"""Get text for the last attribute in the attr_name.
|
|
73
|
+
|
|
74
|
+
Useful when attr_name is nested.
|
|
75
|
+
"""
|
|
76
|
+
return self._attr_name_end
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def attributes(self):
|
|
80
|
+
"""Get a tuple of text for the attributes assigned to the objects.
|
|
81
|
+
|
|
82
|
+
If the input attr_name is a valid attribute for the object but None is
|
|
83
|
+
assigned, the output will be 'None'. If the input attr_name is not valid
|
|
84
|
+
for the input object, 'N/A' will be returned.
|
|
85
|
+
"""
|
|
86
|
+
return self._attributes
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def attributes_unique(self):
|
|
90
|
+
"""Get a tuple of text for the unique attributes assigned to the objects."""
|
|
91
|
+
return self._attributes_unique
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def attributes_original(self):
|
|
95
|
+
"""Get a tuple of objects for the attributes assigned to the objects.
|
|
96
|
+
|
|
97
|
+
These will follow the original object typing of the attribute and won't
|
|
98
|
+
be strings like the attributes.
|
|
99
|
+
"""
|
|
100
|
+
return self._attributes_original
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def min_point(self):
|
|
104
|
+
"""Get a Point3D for the minimum of the box around the objects."""
|
|
105
|
+
return self._min_point
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def max_point(self):
|
|
109
|
+
"""Get a Point3D for the maximum of the box around the objects."""
|
|
110
|
+
return self._max_point
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def graphic_container(self):
|
|
114
|
+
"""Get a ladybug GraphicContainer that relates to this object.
|
|
115
|
+
|
|
116
|
+
The GraphicContainer possesses almost all things needed to visualize the
|
|
117
|
+
ColorRooms object including the legend, value_colors, etc.
|
|
118
|
+
"""
|
|
119
|
+
# produce a range of values from the collected attributes
|
|
120
|
+
attr_dict = {i: val for i, val in enumerate(self._attributes_unique)}
|
|
121
|
+
attr_dict_rev = {val: i for i, val in attr_dict.items()}
|
|
122
|
+
try:
|
|
123
|
+
values = tuple(attr_dict_rev[r_attr] for r_attr in self._attributes)
|
|
124
|
+
except KeyError: # possibly caused by float cast to -0.0
|
|
125
|
+
values = []
|
|
126
|
+
for r_attr in self._attributes:
|
|
127
|
+
if r_attr == '-0.0':
|
|
128
|
+
values.append(attr_dict_rev['0.0'])
|
|
129
|
+
else:
|
|
130
|
+
values.append(attr_dict_rev[r_attr])
|
|
131
|
+
|
|
132
|
+
# produce legend parameters with an ordinal dict for the attributes
|
|
133
|
+
l_par = self.legend_parameters.duplicate()
|
|
134
|
+
if l_par.is_segment_count_default:
|
|
135
|
+
l_par.segment_count = len(self._attributes_unique)
|
|
136
|
+
l_par.ordinal_dictionary = attr_dict
|
|
137
|
+
if l_par.is_title_default:
|
|
138
|
+
l_par.title = self.attr_name_end.replace('_', ' ').title()
|
|
139
|
+
|
|
140
|
+
return GraphicContainer(values, self.min_point, self.max_point, l_par)
|
|
141
|
+
|
|
142
|
+
def _process_attribute_name(self, attr_name):
|
|
143
|
+
"""Process the attribute name and assign it to this object."""
|
|
144
|
+
self._attr_name = str(attr_name)
|
|
145
|
+
at_split = self._attr_name.split('.')
|
|
146
|
+
if len(at_split) == 1:
|
|
147
|
+
self._attr_name_end = at_split[-1]
|
|
148
|
+
elif at_split[-1] == 'display_name':
|
|
149
|
+
self._attr_name_end = at_split[-2]
|
|
150
|
+
elif at_split[-1] == '__name__' and at_split[-2] == '__class__':
|
|
151
|
+
self._attr_name_end = at_split[-3]
|
|
152
|
+
else:
|
|
153
|
+
self._attr_name_end = at_split[-1]
|
|
154
|
+
|
|
155
|
+
def _process_attributes(self, hb_objs):
|
|
156
|
+
"""Process the attributes of honeybee objects."""
|
|
157
|
+
nd = self.legend_parameters.decimal_count
|
|
158
|
+
attributes = [get_attr_nested(obj, self._attr_name, nd) for obj in hb_objs]
|
|
159
|
+
attributes_unique = set(attributes)
|
|
160
|
+
float_attr = [atr for atr in attributes_unique if isinstance(atr, float)]
|
|
161
|
+
str_attr = [atr for atr in attributes_unique if isinstance(atr, str)]
|
|
162
|
+
float_attr.sort()
|
|
163
|
+
str_attr.sort()
|
|
164
|
+
self._attributes = tuple(str(val) for val in attributes)
|
|
165
|
+
self._attributes_unique = tuple(str_attr) + tuple(str(val) for val in float_attr)
|
|
166
|
+
self._attributes_original = \
|
|
167
|
+
tuple(get_attr_nested(obj, self._attr_name, cast_to_str=False)
|
|
168
|
+
for obj in hb_objs)
|
|
169
|
+
|
|
170
|
+
def _calculate_min_max(self, hb_objs):
|
|
171
|
+
"""Calculate maximum and minimum Point3D for a set of rooms."""
|
|
172
|
+
st_rm_min, st_rm_max = hb_objs[0].geometry.min, hb_objs[0].geometry.max
|
|
173
|
+
min_pt = [st_rm_min.x, st_rm_min.y, st_rm_min.z]
|
|
174
|
+
max_pt = [st_rm_max.x, st_rm_max.y, st_rm_max.z]
|
|
175
|
+
|
|
176
|
+
for room in hb_objs[1:]:
|
|
177
|
+
rm_min, rm_max = room.geometry.min, room.geometry.max
|
|
178
|
+
if rm_min.x < min_pt[0]:
|
|
179
|
+
min_pt[0] = rm_min.x
|
|
180
|
+
if rm_min.y < min_pt[1]:
|
|
181
|
+
min_pt[1] = rm_min.y
|
|
182
|
+
if rm_min.z < min_pt[2]:
|
|
183
|
+
min_pt[2] = rm_min.z
|
|
184
|
+
if rm_max.x > max_pt[0]:
|
|
185
|
+
max_pt[0] = rm_max.x
|
|
186
|
+
if rm_max.y > max_pt[1]:
|
|
187
|
+
max_pt[1] = rm_max.y
|
|
188
|
+
if rm_max.z > max_pt[2]:
|
|
189
|
+
max_pt[2] = rm_max.z
|
|
190
|
+
|
|
191
|
+
self._min_point = Point3D(min_pt[0], min_pt[1], min_pt[2])
|
|
192
|
+
self._max_point = Point3D(max_pt[0], max_pt[1], max_pt[2])
|
|
193
|
+
|
|
194
|
+
def ToString(self):
|
|
195
|
+
"""Overwrite .NET ToString."""
|
|
196
|
+
return self.__repr__()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ColorRoom(_ColorObject):
|
|
200
|
+
"""Object for visualizing room-level attributes.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
rooms: An array of honeybee Rooms, which will be colored with the attribute.
|
|
204
|
+
attr_name: A text string of an attribute that the input rooms should have.
|
|
205
|
+
This can have '.' that separate the nested attributes from one another.
|
|
206
|
+
For example, 'properties.energy.program_type'.
|
|
207
|
+
legend_parameters: An optional LegendParameter object to change the display
|
|
208
|
+
of the ColorRoom (Default: None).
|
|
209
|
+
|
|
210
|
+
Properties:
|
|
211
|
+
* rooms
|
|
212
|
+
* attr_name
|
|
213
|
+
* legend_parameters
|
|
214
|
+
* attr_name_end
|
|
215
|
+
* attributes
|
|
216
|
+
* attributes_unique
|
|
217
|
+
* attributes_original
|
|
218
|
+
* floor_faces
|
|
219
|
+
* graphic_container
|
|
220
|
+
* min_point
|
|
221
|
+
* max_point
|
|
222
|
+
"""
|
|
223
|
+
__slots__ = ('_rooms',)
|
|
224
|
+
|
|
225
|
+
def __init__(self, rooms, attr_name, legend_parameters=None):
|
|
226
|
+
"""Initialize ColorRoom."""
|
|
227
|
+
try: # check the input rooms
|
|
228
|
+
rooms = tuple(rooms)
|
|
229
|
+
except TypeError:
|
|
230
|
+
raise TypeError('Input rooms must be an array. Got {}.'.format(type(rooms)))
|
|
231
|
+
assert len(rooms) > 0, 'ColorRooms must have at least one room.'
|
|
232
|
+
for room in rooms:
|
|
233
|
+
assert isinstance(room, Room), 'Expected honeybee Room for ' \
|
|
234
|
+
'ColorRoom rooms. Got {}.'.format(type(room))
|
|
235
|
+
self._rooms = rooms
|
|
236
|
+
self._calculate_min_max(rooms)
|
|
237
|
+
|
|
238
|
+
# assign the legend parameters of this object
|
|
239
|
+
self.legend_parameters = legend_parameters
|
|
240
|
+
|
|
241
|
+
# get the attributes of the input rooms
|
|
242
|
+
self._process_attribute_name(attr_name)
|
|
243
|
+
self._process_attributes(rooms)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def rooms(self):
|
|
247
|
+
"""Get a tuple of honeybee Rooms assigned to this object."""
|
|
248
|
+
return self._rooms
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def floor_faces(self):
|
|
252
|
+
"""Get a nested array with each sub-array having all floor Face3Ds of each room.
|
|
253
|
+
|
|
254
|
+
This is useful for producing visualizations since coloring floors or rooms
|
|
255
|
+
instead of the entire room solid allows more of the model to be viewed at once.
|
|
256
|
+
"""
|
|
257
|
+
flr_faces = []
|
|
258
|
+
for room in self.rooms:
|
|
259
|
+
flr_faces.append(
|
|
260
|
+
[face.geometry for face in room.faces if isinstance(face.type, Floor)])
|
|
261
|
+
return flr_faces
|
|
262
|
+
|
|
263
|
+
def __repr__(self):
|
|
264
|
+
"""Color Room representation."""
|
|
265
|
+
return 'Color Room:\n{} Rooms\n{}'.format(len(self.rooms), self.attr_name_end)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ColorFace(_ColorObject):
|
|
269
|
+
"""Object for visualizing face and sub-face level attributes.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
faces: An array of honeybee Faces, Apertures, Doors, Shades and/or ShadeMeshes
|
|
273
|
+
which will be colored with their attributes.
|
|
274
|
+
attr_name: A text string of an attribute that the input faces should have.
|
|
275
|
+
This can have '.' that separate the nested attributes from one another.
|
|
276
|
+
For example, 'properties.energy.construction'.
|
|
277
|
+
legend_parameters: An optional LegendParameter object to change the display
|
|
278
|
+
of the ColorFace (Default: None).
|
|
279
|
+
|
|
280
|
+
Properties:
|
|
281
|
+
* faces
|
|
282
|
+
* attr_name
|
|
283
|
+
* legend_parameters
|
|
284
|
+
* flat_faces
|
|
285
|
+
* flat_geometry
|
|
286
|
+
* attr_name_end
|
|
287
|
+
* attributes
|
|
288
|
+
* attributes_unique
|
|
289
|
+
* attributes_original
|
|
290
|
+
* floor_faces
|
|
291
|
+
* graphic_container
|
|
292
|
+
* min_point
|
|
293
|
+
* max_point
|
|
294
|
+
"""
|
|
295
|
+
__slots__ = ('_faces', '_flat_faces', '_flat_geometry')
|
|
296
|
+
|
|
297
|
+
def __init__(self, faces, attr_name, legend_parameters=None):
|
|
298
|
+
"""Initialize ColorFace."""
|
|
299
|
+
try: # check the input faces
|
|
300
|
+
faces = tuple(faces)
|
|
301
|
+
except TypeError:
|
|
302
|
+
raise TypeError('Input faces must be an array. Got {}.'.format(type(faces)))
|
|
303
|
+
assert len(faces) > 0, 'ColorFaces must have at least one face.'
|
|
304
|
+
flat_f = []
|
|
305
|
+
for face in faces:
|
|
306
|
+
if isinstance(face, Face):
|
|
307
|
+
flat_f.append(face)
|
|
308
|
+
flat_f.extend(face.shades)
|
|
309
|
+
for ap in face.apertures:
|
|
310
|
+
flat_f.append(ap)
|
|
311
|
+
flat_f.extend(ap.shades)
|
|
312
|
+
for dr in face.doors:
|
|
313
|
+
flat_f.append(dr)
|
|
314
|
+
flat_f.extend(dr.shades)
|
|
315
|
+
elif isinstance(face, (Aperture, Door)):
|
|
316
|
+
flat_f.append(face)
|
|
317
|
+
flat_f.extend(face.shades)
|
|
318
|
+
elif isinstance(face, Shade):
|
|
319
|
+
flat_f.append(face)
|
|
320
|
+
elif isinstance(face, ShadeMesh):
|
|
321
|
+
flat_f.append(face)
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
'Expected honeybee Face, Aperture, Door, Shade or ShadeMesh '
|
|
325
|
+
'for ColorFaces. Got {}.'.format(type(face)))
|
|
326
|
+
self._faces = faces
|
|
327
|
+
self._flat_faces = tuple(flat_f)
|
|
328
|
+
self._flat_geometry = tuple(face.geometry if not isinstance(face, Face)
|
|
329
|
+
else face.punched_geometry for face in flat_f)
|
|
330
|
+
self._calculate_min_max(faces)
|
|
331
|
+
|
|
332
|
+
# assign the legend parameters of this object
|
|
333
|
+
self.legend_parameters = legend_parameters
|
|
334
|
+
|
|
335
|
+
# get the attributes of the input faces
|
|
336
|
+
self._process_attribute_name(attr_name)
|
|
337
|
+
self._process_attributes(flat_f)
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def faces(self):
|
|
341
|
+
"""Get the honeybee Faces, Apertures, Doors and Shades assigned to this object.
|
|
342
|
+
"""
|
|
343
|
+
return self._faces
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def flat_faces(self):
|
|
347
|
+
"""Get non-nested honeybee Faces, Apertures, Doors and Shades on this object.
|
|
348
|
+
|
|
349
|
+
The objects here align with the attributes and graphic_container colors.
|
|
350
|
+
"""
|
|
351
|
+
return self._flat_faces
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def flat_geometry(self):
|
|
355
|
+
"""Get non-nested array of faces on this object.
|
|
356
|
+
|
|
357
|
+
The geometries here align with the attributes and graphic_container colors.
|
|
358
|
+
"""
|
|
359
|
+
return self._flat_geometry
|
|
360
|
+
|
|
361
|
+
def __repr__(self):
|
|
362
|
+
"""Color Room representation."""
|
|
363
|
+
return 'Color Faces:\n{} Faces\n{}'.format(len(self.faces), self.attr_name_end)
|