honeybee-energy 1.116.106__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_energy/__init__.py +24 -0
- honeybee_energy/__main__.py +4 -0
- honeybee_energy/_extend_honeybee.py +145 -0
- honeybee_energy/altnumber.py +21 -0
- honeybee_energy/baseline/__init__.py +2 -0
- honeybee_energy/baseline/create.py +608 -0
- honeybee_energy/baseline/data/__init__.py +1 -0
- honeybee_energy/baseline/data/constructions.csv +64 -0
- honeybee_energy/baseline/data/fen_ratios.csv +15 -0
- honeybee_energy/baseline/data/lpd_building.csv +21 -0
- honeybee_energy/baseline/data/pci_2016.csv +22 -0
- honeybee_energy/baseline/data/pci_2019.csv +22 -0
- honeybee_energy/baseline/data/pci_2022.csv +22 -0
- honeybee_energy/baseline/data/shw.csv +21 -0
- honeybee_energy/baseline/pci.py +512 -0
- honeybee_energy/baseline/result.py +371 -0
- honeybee_energy/boundarycondition.py +128 -0
- honeybee_energy/cli/__init__.py +69 -0
- honeybee_energy/cli/baseline.py +475 -0
- honeybee_energy/cli/edit.py +327 -0
- honeybee_energy/cli/lib.py +1154 -0
- honeybee_energy/cli/result.py +810 -0
- honeybee_energy/cli/setconfig.py +124 -0
- honeybee_energy/cli/settings.py +569 -0
- honeybee_energy/cli/simulate.py +380 -0
- honeybee_energy/cli/translate.py +1714 -0
- honeybee_energy/cli/validate.py +224 -0
- honeybee_energy/config.json +11 -0
- honeybee_energy/config.py +842 -0
- honeybee_energy/construction/__init__.py +1 -0
- honeybee_energy/construction/_base.py +374 -0
- honeybee_energy/construction/air.py +325 -0
- honeybee_energy/construction/dictutil.py +89 -0
- honeybee_energy/construction/dynamic.py +607 -0
- honeybee_energy/construction/opaque.py +460 -0
- honeybee_energy/construction/shade.py +319 -0
- honeybee_energy/construction/window.py +1096 -0
- honeybee_energy/construction/windowshade.py +847 -0
- honeybee_energy/constructionset.py +1655 -0
- honeybee_energy/dictutil.py +56 -0
- honeybee_energy/generator/__init__.py +5 -0
- honeybee_energy/generator/loadcenter.py +204 -0
- honeybee_energy/generator/pv.py +535 -0
- honeybee_energy/hvac/__init__.py +21 -0
- honeybee_energy/hvac/_base.py +124 -0
- honeybee_energy/hvac/_template.py +270 -0
- honeybee_energy/hvac/allair/__init__.py +22 -0
- honeybee_energy/hvac/allair/_base.py +349 -0
- honeybee_energy/hvac/allair/furnace.py +168 -0
- honeybee_energy/hvac/allair/psz.py +131 -0
- honeybee_energy/hvac/allair/ptac.py +163 -0
- honeybee_energy/hvac/allair/pvav.py +109 -0
- honeybee_energy/hvac/allair/vav.py +128 -0
- honeybee_energy/hvac/detailed.py +337 -0
- honeybee_energy/hvac/doas/__init__.py +28 -0
- honeybee_energy/hvac/doas/_base.py +345 -0
- honeybee_energy/hvac/doas/fcu.py +127 -0
- honeybee_energy/hvac/doas/radiant.py +329 -0
- honeybee_energy/hvac/doas/vrf.py +81 -0
- honeybee_energy/hvac/doas/wshp.py +91 -0
- honeybee_energy/hvac/heatcool/__init__.py +23 -0
- honeybee_energy/hvac/heatcool/_base.py +177 -0
- honeybee_energy/hvac/heatcool/baseboard.py +61 -0
- honeybee_energy/hvac/heatcool/evapcool.py +72 -0
- honeybee_energy/hvac/heatcool/fcu.py +92 -0
- honeybee_energy/hvac/heatcool/gasunit.py +53 -0
- honeybee_energy/hvac/heatcool/radiant.py +269 -0
- honeybee_energy/hvac/heatcool/residential.py +77 -0
- honeybee_energy/hvac/heatcool/vrf.py +54 -0
- honeybee_energy/hvac/heatcool/windowac.py +70 -0
- honeybee_energy/hvac/heatcool/wshp.py +62 -0
- honeybee_energy/hvac/idealair.py +699 -0
- honeybee_energy/internalmass.py +310 -0
- honeybee_energy/lib/__init__.py +1 -0
- honeybee_energy/lib/_loadconstructions.py +194 -0
- honeybee_energy/lib/_loadconstructionsets.py +117 -0
- honeybee_energy/lib/_loadmaterials.py +83 -0
- honeybee_energy/lib/_loadprogramtypes.py +125 -0
- honeybee_energy/lib/_loadschedules.py +87 -0
- honeybee_energy/lib/_loadtypelimits.py +64 -0
- honeybee_energy/lib/constructions.py +207 -0
- honeybee_energy/lib/constructionsets.py +95 -0
- honeybee_energy/lib/materials.py +67 -0
- honeybee_energy/lib/programtypes.py +125 -0
- honeybee_energy/lib/schedules.py +61 -0
- honeybee_energy/lib/scheduletypelimits.py +31 -0
- honeybee_energy/load/__init__.py +1 -0
- honeybee_energy/load/_base.py +190 -0
- honeybee_energy/load/daylight.py +397 -0
- honeybee_energy/load/dictutil.py +47 -0
- honeybee_energy/load/equipment.py +771 -0
- honeybee_energy/load/hotwater.py +543 -0
- honeybee_energy/load/infiltration.py +460 -0
- honeybee_energy/load/lighting.py +480 -0
- honeybee_energy/load/people.py +497 -0
- honeybee_energy/load/process.py +472 -0
- honeybee_energy/load/setpoint.py +816 -0
- honeybee_energy/load/ventilation.py +550 -0
- honeybee_energy/material/__init__.py +1 -0
- honeybee_energy/material/_base.py +166 -0
- honeybee_energy/material/dictutil.py +59 -0
- honeybee_energy/material/frame.py +367 -0
- honeybee_energy/material/gas.py +1087 -0
- honeybee_energy/material/glazing.py +854 -0
- honeybee_energy/material/opaque.py +1351 -0
- honeybee_energy/material/shade.py +1360 -0
- honeybee_energy/measure.py +472 -0
- honeybee_energy/programtype.py +723 -0
- honeybee_energy/properties/__init__.py +1 -0
- honeybee_energy/properties/aperture.py +333 -0
- honeybee_energy/properties/door.py +342 -0
- honeybee_energy/properties/extension.py +244 -0
- honeybee_energy/properties/face.py +274 -0
- honeybee_energy/properties/model.py +2640 -0
- honeybee_energy/properties/room.py +1747 -0
- honeybee_energy/properties/shade.py +314 -0
- honeybee_energy/properties/shademesh.py +262 -0
- honeybee_energy/reader.py +48 -0
- honeybee_energy/result/__init__.py +1 -0
- honeybee_energy/result/colorobj.py +648 -0
- honeybee_energy/result/emissions.py +290 -0
- honeybee_energy/result/err.py +101 -0
- honeybee_energy/result/eui.py +100 -0
- honeybee_energy/result/generation.py +160 -0
- honeybee_energy/result/loadbalance.py +890 -0
- honeybee_energy/result/match.py +202 -0
- honeybee_energy/result/osw.py +90 -0
- honeybee_energy/result/rdd.py +59 -0
- honeybee_energy/result/zsz.py +190 -0
- honeybee_energy/run.py +1577 -0
- honeybee_energy/schedule/__init__.py +1 -0
- honeybee_energy/schedule/day.py +626 -0
- honeybee_energy/schedule/dictutil.py +59 -0
- honeybee_energy/schedule/fixedinterval.py +1012 -0
- honeybee_energy/schedule/rule.py +619 -0
- honeybee_energy/schedule/ruleset.py +1867 -0
- honeybee_energy/schedule/typelimit.py +310 -0
- honeybee_energy/shw.py +315 -0
- honeybee_energy/simulation/__init__.py +1 -0
- honeybee_energy/simulation/control.py +214 -0
- honeybee_energy/simulation/daylightsaving.py +185 -0
- honeybee_energy/simulation/dictutil.py +51 -0
- honeybee_energy/simulation/output.py +646 -0
- honeybee_energy/simulation/parameter.py +606 -0
- honeybee_energy/simulation/runperiod.py +443 -0
- honeybee_energy/simulation/shadowcalculation.py +295 -0
- honeybee_energy/simulation/sizing.py +546 -0
- honeybee_energy/ventcool/__init__.py +5 -0
- honeybee_energy/ventcool/_crack_data.py +91 -0
- honeybee_energy/ventcool/afn.py +289 -0
- honeybee_energy/ventcool/control.py +269 -0
- honeybee_energy/ventcool/crack.py +126 -0
- honeybee_energy/ventcool/fan.py +493 -0
- honeybee_energy/ventcool/opening.py +365 -0
- honeybee_energy/ventcool/simulation.py +314 -0
- honeybee_energy/writer.py +1078 -0
- honeybee_energy-1.116.106.dist-info/METADATA +113 -0
- honeybee_energy-1.116.106.dist-info/RECORD +162 -0
- honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
- honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
- honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
- honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2640 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Model Energy Properties."""
|
|
3
|
+
try:
|
|
4
|
+
from itertools import izip as zip # python 2
|
|
5
|
+
except ImportError:
|
|
6
|
+
pass # python 3
|
|
7
|
+
|
|
8
|
+
from ladybug_geometry.geometry2d import Vector2D
|
|
9
|
+
from ladybug_geometry.geometry3d import Point3D
|
|
10
|
+
from honeybee.boundarycondition import Outdoors, Surface, boundary_conditions
|
|
11
|
+
from honeybee.facetype import AirBoundary, face_types
|
|
12
|
+
from honeybee.extensionutil import model_extension_dicts
|
|
13
|
+
from honeybee.checkdup import check_duplicate_identifiers
|
|
14
|
+
from honeybee.units import conversion_factor_to_meters, parse_distance_string
|
|
15
|
+
from honeybee.typing import invalid_dict_error, clean_ep_string, \
|
|
16
|
+
clean_and_id_ep_string, clean_and_number_ep_string
|
|
17
|
+
from honeybee.face import Face
|
|
18
|
+
from honeybee.room import Room
|
|
19
|
+
from honeybee.model import Model
|
|
20
|
+
|
|
21
|
+
from ..material.dictutil import dict_to_material
|
|
22
|
+
from ..construction.dictutil import CONSTRUCTION_TYPES, dict_to_construction, \
|
|
23
|
+
dict_abridged_to_construction
|
|
24
|
+
from ..construction.opaque import OpaqueConstruction
|
|
25
|
+
from ..construction.window import WindowConstruction
|
|
26
|
+
from ..construction.windowshade import WindowConstructionShade
|
|
27
|
+
from ..construction.dynamic import WindowConstructionDynamic
|
|
28
|
+
from ..construction.air import AirBoundaryConstruction
|
|
29
|
+
from ..constructionset import ConstructionSet
|
|
30
|
+
from ..material.opaque import EnergyMaterialVegetation
|
|
31
|
+
from ..schedule.typelimit import ScheduleTypeLimit
|
|
32
|
+
from ..schedule.ruleset import ScheduleRuleset
|
|
33
|
+
from ..schedule.dictutil import SCHEDULE_TYPES, dict_to_schedule, \
|
|
34
|
+
dict_abridged_to_schedule
|
|
35
|
+
from ..programtype import ProgramType
|
|
36
|
+
from ..hvac.detailed import DetailedHVAC
|
|
37
|
+
from ..hvac import HVAC_TYPES_DICT
|
|
38
|
+
from ..shw import SHWSystem
|
|
39
|
+
from ..ventcool.simulation import VentilationSimulationControl
|
|
40
|
+
from ..generator.loadcenter import ElectricLoadCenter
|
|
41
|
+
|
|
42
|
+
from ..config import folders
|
|
43
|
+
from ..lib.constructions import generic_context
|
|
44
|
+
from ..lib.constructionsets import generic_construction_set
|
|
45
|
+
from ..lib.schedules import always_on
|
|
46
|
+
from ..lib.scheduletypelimits import fractional
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ModelEnergyProperties(object):
|
|
50
|
+
"""Energy Properties for Honeybee Model.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
host: A honeybee_core Model object that hosts these properties.
|
|
54
|
+
ventilation_simulation_control: A VentilationSimulationControl object that
|
|
55
|
+
defines global parameters for ventilation simulation.
|
|
56
|
+
electric_load_center: A ElectricLoadCenter object that defines the properties
|
|
57
|
+
of the model's electric loads center.
|
|
58
|
+
|
|
59
|
+
Properties:
|
|
60
|
+
* host
|
|
61
|
+
* materials
|
|
62
|
+
* constructions
|
|
63
|
+
* room_constructions
|
|
64
|
+
* face_constructions
|
|
65
|
+
* shade_constructions
|
|
66
|
+
* construction_sets
|
|
67
|
+
* global_construction_set
|
|
68
|
+
* schedule_type_limits
|
|
69
|
+
* schedules
|
|
70
|
+
* shade_schedules
|
|
71
|
+
* room_schedules
|
|
72
|
+
* program_type_schedules
|
|
73
|
+
* hvac_schedules
|
|
74
|
+
* orphaned_trans_schedules
|
|
75
|
+
* program_types
|
|
76
|
+
* hvacs
|
|
77
|
+
* shws
|
|
78
|
+
* ventilation_simulation_control
|
|
79
|
+
* electric_load_center
|
|
80
|
+
"""
|
|
81
|
+
# dictionary mapping validation error codes to a corresponding check function
|
|
82
|
+
ERROR_MAP = {
|
|
83
|
+
'020001': 'check_duplicate_material_identifiers',
|
|
84
|
+
'020002': 'check_duplicate_construction_identifiers',
|
|
85
|
+
'020003': 'check_duplicate_construction_set_identifiers',
|
|
86
|
+
'020004': 'check_duplicate_schedule_type_limit_identifiers',
|
|
87
|
+
'020005': 'check_duplicate_schedule_identifiers',
|
|
88
|
+
'020006': 'check_duplicate_program_type_identifiers',
|
|
89
|
+
'020007': 'check_duplicate_hvac_identifiers',
|
|
90
|
+
'020008': 'check_duplicate_shw_identifiers',
|
|
91
|
+
'020009': 'check_shw_rooms_in_model',
|
|
92
|
+
'020010': 'check_one_vegetation_material',
|
|
93
|
+
'020011': 'check_detailed_hvac_rooms',
|
|
94
|
+
'020012': 'check_detailed_hvac_rooms',
|
|
95
|
+
'020013': 'check_detailed_hvac_rooms',
|
|
96
|
+
'020014': 'check_all_zones_have_one_hvac',
|
|
97
|
+
'020101': 'check_maximum_elevation',
|
|
98
|
+
'020201': 'check_interior_constructions_reversed'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self, host, ventilation_simulation_control=None, electric_load_center=None):
|
|
103
|
+
"""Initialize Model energy properties."""
|
|
104
|
+
self._host = host
|
|
105
|
+
self.ventilation_simulation_control = ventilation_simulation_control
|
|
106
|
+
self.electric_load_center = electric_load_center
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def host(self):
|
|
110
|
+
"""Get the Model object hosting these properties."""
|
|
111
|
+
return self._host
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def materials(self):
|
|
115
|
+
"""Get a list of all unique materials contained within the model.
|
|
116
|
+
|
|
117
|
+
This includes materials across all Faces, Apertures, Doors and Room
|
|
118
|
+
ConstructionSets but it does NOT include the Honeybee generic default
|
|
119
|
+
construction set.
|
|
120
|
+
"""
|
|
121
|
+
materials = []
|
|
122
|
+
for constr in self.constructions:
|
|
123
|
+
try:
|
|
124
|
+
materials.extend(constr.materials)
|
|
125
|
+
if constr.has_frame:
|
|
126
|
+
materials.append(constr.frame)
|
|
127
|
+
if isinstance(constr, WindowConstructionShade):
|
|
128
|
+
if constr.is_switchable_glazing:
|
|
129
|
+
materials.append(constr.switched_glass_material)
|
|
130
|
+
if constr.shade_location == 'Between':
|
|
131
|
+
materials.append(constr.window_construction.materials[-2])
|
|
132
|
+
except AttributeError:
|
|
133
|
+
pass # ShadeConstruction or AirBoundaryConstruction
|
|
134
|
+
return list(set(materials))
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def constructions(self):
|
|
138
|
+
"""Get a list of all unique constructions in the model.
|
|
139
|
+
|
|
140
|
+
This includes constructions across all Faces, Apertures, Doors, Shades,
|
|
141
|
+
and Room ConstructionSets but it does NOT include the Honeybee generic
|
|
142
|
+
default construction set.
|
|
143
|
+
"""
|
|
144
|
+
all_constrs = self.room_constructions + self.face_constructions + \
|
|
145
|
+
self.shade_constructions
|
|
146
|
+
return list(set(all_constrs))
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def room_constructions(self):
|
|
150
|
+
"""Get a list of all unique constructions assigned to Room ConstructionSets.
|
|
151
|
+
|
|
152
|
+
This also includes the constructions assigned to Room InternalMasses.
|
|
153
|
+
"""
|
|
154
|
+
room_constrs = []
|
|
155
|
+
for cnstr_set in self.construction_sets:
|
|
156
|
+
room_constrs.extend(cnstr_set.modified_constructions_unique)
|
|
157
|
+
for room in self.host.rooms:
|
|
158
|
+
for int_mass in room.properties.energy._internal_masses:
|
|
159
|
+
constr = int_mass.construction
|
|
160
|
+
if not self._instance_in_array(constr, room_constrs):
|
|
161
|
+
room_constrs.append(constr)
|
|
162
|
+
return list(set(room_constrs))
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def face_constructions(self):
|
|
166
|
+
"""Get a list of all unique constructions assigned to Faces, Apertures and Doors.
|
|
167
|
+
"""
|
|
168
|
+
constructions = []
|
|
169
|
+
for face in self.host.faces:
|
|
170
|
+
self._check_and_add_obj_construction(face, constructions)
|
|
171
|
+
for ap in face.apertures:
|
|
172
|
+
self._check_and_add_obj_construction(ap, constructions)
|
|
173
|
+
for dr in face.doors:
|
|
174
|
+
self._check_and_add_obj_construction(dr, constructions)
|
|
175
|
+
for ap in self.host.orphaned_apertures:
|
|
176
|
+
self._check_and_add_obj_construction(ap, constructions)
|
|
177
|
+
for dr in self.host.orphaned_doors:
|
|
178
|
+
self._check_and_add_obj_construction(dr, constructions)
|
|
179
|
+
return list(set(constructions))
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def shade_constructions(self):
|
|
183
|
+
"""Get a list of all unique constructions assigned to Shades in the model."""
|
|
184
|
+
constructions = []
|
|
185
|
+
for shade in self.host.shades:
|
|
186
|
+
self._check_and_add_obj_construction(shade, constructions)
|
|
187
|
+
for sm in self.host.shade_meshes: # check all ShadeMesh modifiers
|
|
188
|
+
self._check_and_add_obj_construction(sm, constructions)
|
|
189
|
+
return list(set(constructions))
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def construction_sets(self):
|
|
193
|
+
"""Get a list of all unique Room-Assigned ConstructionSets in the Model."""
|
|
194
|
+
construction_sets = []
|
|
195
|
+
for room in self.host.rooms:
|
|
196
|
+
if room.properties.energy._construction_set is not None:
|
|
197
|
+
if not self._instance_in_array(room.properties.energy._construction_set,
|
|
198
|
+
construction_sets):
|
|
199
|
+
construction_sets.append(room.properties.energy._construction_set)
|
|
200
|
+
return list(set(construction_sets)) # catch equivalent construction sets
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def global_construction_set(self):
|
|
204
|
+
"""The global energy construction set.
|
|
205
|
+
|
|
206
|
+
This is what is used whenever no construction has been assigned to a given
|
|
207
|
+
Face/Aperture/Door/Shade and there is no construction_set assigned to the
|
|
208
|
+
parent Room.
|
|
209
|
+
"""
|
|
210
|
+
return generic_construction_set
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def schedule_type_limits(self):
|
|
214
|
+
"""Get a list of all unique schedule type limits contained within the model.
|
|
215
|
+
|
|
216
|
+
This includes schedules across all Shades and Rooms.
|
|
217
|
+
"""
|
|
218
|
+
type_limits = []
|
|
219
|
+
for sched in self.schedules:
|
|
220
|
+
t_lim = sched.schedule_type_limit
|
|
221
|
+
if t_lim is not None and not self._instance_in_array(t_lim, type_limits):
|
|
222
|
+
type_limits.append(t_lim)
|
|
223
|
+
return list(set(type_limits))
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def schedules(self):
|
|
227
|
+
"""Get a list of all unique schedules directly assigned to objects in the model.
|
|
228
|
+
|
|
229
|
+
This includes schedules across all ProgramTypes, HVACs, Rooms and Shades.
|
|
230
|
+
However, it does not include any of the orphaned_trans_schedules as these
|
|
231
|
+
are not directly assigned to objects but rather generated from their
|
|
232
|
+
constructions.
|
|
233
|
+
"""
|
|
234
|
+
all_scheds = self.program_type_schedules + self.hvac_schedules + \
|
|
235
|
+
self.room_schedules + self.shade_schedules + self.construction_schedules
|
|
236
|
+
return list(set(all_scheds))
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def construction_schedules(self):
|
|
240
|
+
"""Get a list of all unique schedules assigned to constructions in the model.
|
|
241
|
+
|
|
242
|
+
This includes schedules on al AirBoundaryConstructions.
|
|
243
|
+
"""
|
|
244
|
+
schedules = []
|
|
245
|
+
for constr in self.constructions:
|
|
246
|
+
if isinstance(constr, AirBoundaryConstruction):
|
|
247
|
+
self._check_and_add_schedule(constr.air_mixing_schedule, schedules)
|
|
248
|
+
elif isinstance(constr, WindowConstructionShade):
|
|
249
|
+
if constr.schedule is not None:
|
|
250
|
+
self._check_and_add_schedule(constr.schedule, schedules)
|
|
251
|
+
elif isinstance(constr, WindowConstructionDynamic):
|
|
252
|
+
self._check_and_add_schedule(constr.schedule, schedules)
|
|
253
|
+
return list(set(schedules))
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def shade_schedules(self):
|
|
257
|
+
"""Get a list of unique transmittance schedules assigned to Shades in the model.
|
|
258
|
+
"""
|
|
259
|
+
schedules = []
|
|
260
|
+
for shade in self.host.orphaned_shades:
|
|
261
|
+
self._check_and_add_shade_schedule(shade, schedules)
|
|
262
|
+
for room in self.host.rooms: # check all Room Shade schedules
|
|
263
|
+
for shade in room.shades:
|
|
264
|
+
self._check_and_add_shade_schedule(shade, schedules)
|
|
265
|
+
for face in room.faces: # check all Face Shade schedules
|
|
266
|
+
for shade in face.shades:
|
|
267
|
+
self._check_and_add_shade_schedule(shade, schedules)
|
|
268
|
+
for ap in face.apertures: # check all Aperture Shade schedules
|
|
269
|
+
for shade in ap.shades:
|
|
270
|
+
self._check_and_add_shade_schedule(shade, schedules)
|
|
271
|
+
for dr in face.doors: # check all Door Shade schedules
|
|
272
|
+
for shade in dr.shades:
|
|
273
|
+
self._check_and_add_shade_schedule(shade, schedules)
|
|
274
|
+
for sm in self.host.shade_meshes:
|
|
275
|
+
self._check_and_add_shade_schedule(sm, schedules)
|
|
276
|
+
return list(set(schedules))
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def room_schedules(self):
|
|
280
|
+
"""Get a list of all unique schedules assigned directly to Rooms in the model.
|
|
281
|
+
|
|
282
|
+
Note that this does not include schedules from ProgramTypes assigned to the
|
|
283
|
+
rooms. For this, use the program_type_schedules property.
|
|
284
|
+
"""
|
|
285
|
+
scheds = []
|
|
286
|
+
for room in self.host.rooms:
|
|
287
|
+
people = room.properties.energy._people
|
|
288
|
+
lighting = room.properties.energy._lighting
|
|
289
|
+
electric_equipment = room.properties.energy._electric_equipment
|
|
290
|
+
gas_equipment = room.properties.energy._gas_equipment
|
|
291
|
+
shw = room.properties.energy._service_hot_water
|
|
292
|
+
infiltration = room.properties.energy._infiltration
|
|
293
|
+
ventilation = room.properties.energy._ventilation
|
|
294
|
+
setpoint = room.properties.energy._setpoint
|
|
295
|
+
window_vent = room.properties.energy._window_vent_control
|
|
296
|
+
processes = room.properties.energy._process_loads
|
|
297
|
+
fans = room.properties.energy._fans
|
|
298
|
+
if people is not None:
|
|
299
|
+
self._check_and_add_schedule(people.occupancy_schedule, scheds)
|
|
300
|
+
self._check_and_add_schedule(people.activity_schedule, scheds)
|
|
301
|
+
if lighting is not None:
|
|
302
|
+
self._check_and_add_schedule(lighting.schedule, scheds)
|
|
303
|
+
if electric_equipment is not None:
|
|
304
|
+
self._check_and_add_schedule(electric_equipment.schedule, scheds)
|
|
305
|
+
if gas_equipment is not None:
|
|
306
|
+
self._check_and_add_schedule(gas_equipment.schedule, scheds)
|
|
307
|
+
if shw is not None:
|
|
308
|
+
self._check_and_add_schedule(shw.schedule, scheds)
|
|
309
|
+
if infiltration is not None:
|
|
310
|
+
self._check_and_add_schedule(infiltration.schedule, scheds)
|
|
311
|
+
if ventilation is not None and ventilation.schedule is not None:
|
|
312
|
+
self._check_and_add_schedule(ventilation.schedule, scheds)
|
|
313
|
+
if setpoint is not None:
|
|
314
|
+
self._check_and_add_schedule(setpoint.heating_schedule, scheds)
|
|
315
|
+
self._check_and_add_schedule(setpoint.cooling_schedule, scheds)
|
|
316
|
+
if setpoint.humidifying_schedule is not None:
|
|
317
|
+
self._check_and_add_schedule(
|
|
318
|
+
setpoint.humidifying_schedule, scheds)
|
|
319
|
+
self._check_and_add_schedule(
|
|
320
|
+
setpoint.dehumidifying_schedule, scheds)
|
|
321
|
+
if window_vent is not None:
|
|
322
|
+
self._check_and_add_schedule(window_vent.schedule, scheds)
|
|
323
|
+
if len(processes) != 0:
|
|
324
|
+
for process in processes:
|
|
325
|
+
self._check_and_add_schedule(process.schedule, scheds)
|
|
326
|
+
if len(fans) != 0:
|
|
327
|
+
for fan in fans:
|
|
328
|
+
self._check_and_add_schedule(fan.control.schedule, scheds)
|
|
329
|
+
return list(set(scheds))
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def program_type_schedules(self):
|
|
333
|
+
"""Get a list of all unique schedules assigned to ProgramTypes in the model."""
|
|
334
|
+
schedules = []
|
|
335
|
+
for p_type in self.program_types:
|
|
336
|
+
for sched in p_type.schedules:
|
|
337
|
+
self._check_and_add_schedule(sched, schedules)
|
|
338
|
+
return list(set(schedules))
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def hvac_schedules(self):
|
|
342
|
+
"""Get a list of all unique HVAC-assigned schedules in the model."""
|
|
343
|
+
schedules = []
|
|
344
|
+
for hvac in self.hvacs:
|
|
345
|
+
for sched in hvac.schedules:
|
|
346
|
+
self._check_and_add_schedule(sched, schedules)
|
|
347
|
+
return list(set(schedules))
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def orphaned_trans_schedules(self):
|
|
351
|
+
"""Get a list of constant transmittance schedules for transparent orphaned objs.
|
|
352
|
+
|
|
353
|
+
These schedules are not directly assigned to any honeybee objects but
|
|
354
|
+
they are automatically generated from the constructions of orphaned objects.
|
|
355
|
+
They are intended to be assigned to shade representations of the orphaned
|
|
356
|
+
objects in the simulation in order to account for their transparency.
|
|
357
|
+
"""
|
|
358
|
+
# collect all unique transmittances
|
|
359
|
+
transmittances = set()
|
|
360
|
+
for face in self.host.orphaned_faces:
|
|
361
|
+
for ap in face.apertures:
|
|
362
|
+
self._check_and_add_obj_transmit(ap, transmittances)
|
|
363
|
+
for dr in face.doors:
|
|
364
|
+
self._check_and_add_obj_transmit(dr, transmittances)
|
|
365
|
+
for ap in self.host.orphaned_apertures:
|
|
366
|
+
self._check_and_add_obj_transmit(ap, transmittances)
|
|
367
|
+
for dr in self.host.orphaned_doors:
|
|
368
|
+
if dr.is_glass:
|
|
369
|
+
self._check_and_add_obj_transmit(dr, transmittances)
|
|
370
|
+
# create the schedules from the transmittances
|
|
371
|
+
schedules = []
|
|
372
|
+
for trans in transmittances:
|
|
373
|
+
sch_name = 'Constant %.3f Transmittance' % trans
|
|
374
|
+
sch = ScheduleRuleset.from_constant_value(sch_name, trans, fractional)
|
|
375
|
+
schedules.append(sch)
|
|
376
|
+
return schedules
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def program_types(self):
|
|
380
|
+
"""Get a list of all unique ProgramTypes in the Model."""
|
|
381
|
+
program_types = []
|
|
382
|
+
for room in self.host.rooms:
|
|
383
|
+
if room.properties.energy._program_type is not None:
|
|
384
|
+
if not self._instance_in_array(room.properties.energy._program_type,
|
|
385
|
+
program_types):
|
|
386
|
+
program_types.append(room.properties.energy._program_type)
|
|
387
|
+
return list(set(program_types)) # catch equivalent program types
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def hvacs(self):
|
|
391
|
+
"""Get a list of all unique HVAC systems in the Model."""
|
|
392
|
+
hvacs = []
|
|
393
|
+
for room in self.host.rooms:
|
|
394
|
+
if room.properties.energy._hvac is not None:
|
|
395
|
+
if not self._instance_in_array(room.properties.energy._hvac, hvacs):
|
|
396
|
+
hvacs.append(room.properties.energy._hvac)
|
|
397
|
+
return hvacs
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def shws(self):
|
|
401
|
+
"""Get a list of all unique SHW systems in the Model."""
|
|
402
|
+
shws = []
|
|
403
|
+
for room in self.host.rooms:
|
|
404
|
+
if room.properties.energy._shw is not None:
|
|
405
|
+
if not self._instance_in_array(room.properties.energy._shw, shws):
|
|
406
|
+
shws.append(room.properties.energy._shw)
|
|
407
|
+
return shws
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def electric_load_center(self):
|
|
411
|
+
"""Get or set global parameters for ventilation cooling simulation."""
|
|
412
|
+
return self._electric_load_center
|
|
413
|
+
|
|
414
|
+
@electric_load_center.setter
|
|
415
|
+
def electric_load_center(self, value):
|
|
416
|
+
if value is None:
|
|
417
|
+
value = ElectricLoadCenter()
|
|
418
|
+
else:
|
|
419
|
+
assert isinstance(value, ElectricLoadCenter), \
|
|
420
|
+
'electric_load_center must be a ' \
|
|
421
|
+
'ElectricLoadCenter object. Got: {}.'.format(value)
|
|
422
|
+
self._electric_load_center = value
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def ventilation_simulation_control(self):
|
|
426
|
+
"""Get or set global parameters for ventilation cooling simulation."""
|
|
427
|
+
return self._ventilation_simulation_control
|
|
428
|
+
|
|
429
|
+
@ventilation_simulation_control.setter
|
|
430
|
+
def ventilation_simulation_control(self, value):
|
|
431
|
+
if value is None:
|
|
432
|
+
value = VentilationSimulationControl()
|
|
433
|
+
else:
|
|
434
|
+
assert isinstance(value, VentilationSimulationControl), \
|
|
435
|
+
'ventilation_simulation_control must be a ' \
|
|
436
|
+
'VentilationSimulationControl object. Got: {}.'.format(value)
|
|
437
|
+
self._ventilation_simulation_control = value
|
|
438
|
+
|
|
439
|
+
def aperture_constructions(self, room_assigned_only=True):
|
|
440
|
+
"""Get only the constructions assigned to Apertures in the Model.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
room_assigned_only: Boolean to note whether only the constructions that
|
|
444
|
+
are a part of Room-assigned Apertures should be returned (True) or
|
|
445
|
+
constructions assigned to all Apertures should be included (False).
|
|
446
|
+
"""
|
|
447
|
+
if room_assigned_only:
|
|
448
|
+
aps_to_search = []
|
|
449
|
+
for room in self.host.rooms:
|
|
450
|
+
for face in room.faces:
|
|
451
|
+
aps_to_search.extend(face.apertures)
|
|
452
|
+
else:
|
|
453
|
+
aps_to_search = self.host.apertures
|
|
454
|
+
constructions = []
|
|
455
|
+
for ap in aps_to_search:
|
|
456
|
+
self._check_and_add_obj_construction_inc_parent(ap, constructions)
|
|
457
|
+
return list(set(constructions))
|
|
458
|
+
|
|
459
|
+
def door_constructions(self, room_assigned_only=True):
|
|
460
|
+
"""Get only the constructions assigned to Doors in the Model.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
room_assigned_only: Boolean to note whether only the constructions that
|
|
464
|
+
are a part of Room-assigned Doors should be returned (True) or
|
|
465
|
+
constructions assigned to all Doors should be included (False).
|
|
466
|
+
"""
|
|
467
|
+
if room_assigned_only:
|
|
468
|
+
doors_to_search = []
|
|
469
|
+
for room in self.host.rooms:
|
|
470
|
+
for face in room.faces:
|
|
471
|
+
doors_to_search.extend(face.doors)
|
|
472
|
+
else:
|
|
473
|
+
doors_to_search = self.host.doors
|
|
474
|
+
constructions = []
|
|
475
|
+
for dr in doors_to_search:
|
|
476
|
+
self._check_and_add_obj_construction_inc_parent(dr, constructions)
|
|
477
|
+
return list(set(constructions))
|
|
478
|
+
|
|
479
|
+
def autocalculate_ventilation_simulation_control(self):
|
|
480
|
+
"""Set geometry properties of ventilation_simulation_control with Model's rooms.
|
|
481
|
+
|
|
482
|
+
The room geometry of the host Model will be used to assign the aspect_ratio,
|
|
483
|
+
long_axis_angle, and the building_type. Note that these properties are only
|
|
484
|
+
meaningful for simulations using the AirflowNetwork.
|
|
485
|
+
"""
|
|
486
|
+
self.ventilation_simulation_control.assign_geometry_properties_from_rooms(
|
|
487
|
+
self.host.rooms)
|
|
488
|
+
|
|
489
|
+
def remove_child_constructions(self):
|
|
490
|
+
"""Remove constructions assigned to Faces, Apertures, Doors and Shades.
|
|
491
|
+
|
|
492
|
+
This means that all constructions of the Mode's rooms will be assigned
|
|
493
|
+
by the Rooms' construction_set (or the Honeybee default ConstructionSet
|
|
494
|
+
if Rooms have no construction set).
|
|
495
|
+
"""
|
|
496
|
+
for room in self._host.rooms:
|
|
497
|
+
room.properties.energy.remove_child_constructions()
|
|
498
|
+
|
|
499
|
+
def window_construction_by_orientation(
|
|
500
|
+
self, construction, orientation=0, offset=45, north_vector=Vector2D(0, 1)):
|
|
501
|
+
"""Set the construction of exterior Apertures facing a given orientation.
|
|
502
|
+
|
|
503
|
+
This is useful for testing orientation-specific energy conservation
|
|
504
|
+
strategies or creating ASHRAE baseline buildings.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
construction: A WindowConstruction that will be assigned to all of the
|
|
508
|
+
room's Apertures in Walls that are facing a certain orientation.
|
|
509
|
+
orientation: A number between 0 and 360 that represents the orientation
|
|
510
|
+
in degrees to which the construction will be assigned. 0 = North,
|
|
511
|
+
90 = East, 180 = South, 270 = West. (Default: 0 for North).
|
|
512
|
+
offset: A number between 0 and 180 that represents the offset from the
|
|
513
|
+
orientation in degrees for which the construction will be assigned.
|
|
514
|
+
For example, a value of 45 indicates that any Apertures falling
|
|
515
|
+
in the 90 degree range around the orientation will get the input
|
|
516
|
+
construction. (Default: 45).
|
|
517
|
+
north_vector: A ladybug_geometry Vector3D for the north direction.
|
|
518
|
+
Default is the Y-axis (0, 1).
|
|
519
|
+
"""
|
|
520
|
+
for room in self._host.rooms:
|
|
521
|
+
room.properties.energy.window_construction_by_orientation(
|
|
522
|
+
construction, orientation, offset, north_vector)
|
|
523
|
+
|
|
524
|
+
def remove_hvac_from_no_setpoints(self):
|
|
525
|
+
"""Remove any HVAC systems assigned to Rooms that have no thermostat setpoints.
|
|
526
|
+
|
|
527
|
+
This will ensure that EnergyPlus does not fail when it tries to simulate
|
|
528
|
+
a HVAC for which there are no criteria to meet.
|
|
529
|
+
|
|
530
|
+
Returns:
|
|
531
|
+
A list of text strings for each room that had an HVAC removed because
|
|
532
|
+
of a lack of setpoints. This can be printed to logs or registered as
|
|
533
|
+
a warning in the interface.
|
|
534
|
+
"""
|
|
535
|
+
removed_msgs = []
|
|
536
|
+
for room in self._host.rooms:
|
|
537
|
+
if room.properties.energy.hvac is not None \
|
|
538
|
+
and room.properties.energy.setpoint is None:
|
|
539
|
+
hvac_name = room.properties.energy.hvac.display_name
|
|
540
|
+
room.properties.energy.hvac = None
|
|
541
|
+
msg = 'Room "{}" has an HVAC assigned to it but does not have any ' \
|
|
542
|
+
'thermostat setpoints.\nThe HVAC "{}" will not be translated ' \
|
|
543
|
+
'for this room.'.format(room.display_name, hvac_name)
|
|
544
|
+
removed_msgs.append(msg)
|
|
545
|
+
return removed_msgs
|
|
546
|
+
|
|
547
|
+
def missing_adjacencies_to_adiabatic(self):
|
|
548
|
+
"""Set any Faces with missing adjacencies in the model to adiabatic.
|
|
549
|
+
|
|
550
|
+
If any of the Faces with missing adjacencies have sub-faces, these will be
|
|
551
|
+
removed in order to accommodate the adiabatic condition. Similarly, if the
|
|
552
|
+
Face is an AirBoundary, the type will be set to a Wall.
|
|
553
|
+
|
|
554
|
+
Note that this method assumes all of the Surface boundary conditions
|
|
555
|
+
are set up correctly with the last boundary_condition_object being
|
|
556
|
+
the adjacent room.
|
|
557
|
+
"""
|
|
558
|
+
room_ids = set()
|
|
559
|
+
for room in self.host._rooms:
|
|
560
|
+
room_ids.add(room.identifier)
|
|
561
|
+
for room in self.host._rooms:
|
|
562
|
+
for face in room._faces:
|
|
563
|
+
if isinstance(face.boundary_condition, Surface):
|
|
564
|
+
bc_room = face.boundary_condition.boundary_condition_objects[-1]
|
|
565
|
+
if bc_room not in room_ids:
|
|
566
|
+
face.remove_sub_faces()
|
|
567
|
+
if isinstance(face.type, AirBoundary):
|
|
568
|
+
face.type = face_types.wall
|
|
569
|
+
face.boundary_condition = boundary_conditions.adiabatic
|
|
570
|
+
elif isinstance(face.type, AirBoundary): # assume it's Surface
|
|
571
|
+
face.type = face_types.wall
|
|
572
|
+
face.boundary_condition = boundary_conditions.adiabatic
|
|
573
|
+
|
|
574
|
+
def assign_radiance_solar_interior(self):
|
|
575
|
+
"""Assign honeybee Radiance modifiers based on interior solar properties."""
|
|
576
|
+
mod_sets = {}
|
|
577
|
+
for constr_set in self.construction_sets + [generic_construction_set]:
|
|
578
|
+
mod_sets[constr_set.identifier] = constr_set.to_radiance_solar_interior()
|
|
579
|
+
self._assign_room_modifier_sets(mod_sets)
|
|
580
|
+
mods = {}
|
|
581
|
+
for con in self.face_constructions + self.shade_constructions:
|
|
582
|
+
mods[con.identifier] = con.to_radiance_solar_interior() \
|
|
583
|
+
if isinstance(con, OpaqueConstruction) else con.to_radiance_solar()
|
|
584
|
+
self._assign_face_modifiers(mods)
|
|
585
|
+
|
|
586
|
+
def assign_radiance_visible_interior(self):
|
|
587
|
+
"""Assign honeybee Radiance modifiers based on interior visible properties."""
|
|
588
|
+
mod_sets = {}
|
|
589
|
+
for constr_set in self.construction_sets + [generic_construction_set]:
|
|
590
|
+
mod_sets[constr_set.identifier] = constr_set.to_radiance_visible_interior()
|
|
591
|
+
self._assign_room_modifier_sets(mod_sets)
|
|
592
|
+
mods = {}
|
|
593
|
+
for con in self.face_constructions + self.shade_constructions:
|
|
594
|
+
mods[con.identifier] = con.to_radiance_visible_interior() \
|
|
595
|
+
if isinstance(con, OpaqueConstruction) else con.to_radiance_visible()
|
|
596
|
+
self._assign_face_modifiers(mods)
|
|
597
|
+
|
|
598
|
+
def assign_radiance_solar_exterior(self):
|
|
599
|
+
"""Assign honeybee Radiance modifiers based on exterior solar properties."""
|
|
600
|
+
mod_sets = {}
|
|
601
|
+
for constr_set in self.construction_sets + [generic_construction_set]:
|
|
602
|
+
mod_sets[constr_set.identifier] = constr_set.to_radiance_solar_exterior()
|
|
603
|
+
self._assign_room_modifier_sets(mod_sets)
|
|
604
|
+
mods = {}
|
|
605
|
+
for con in self.face_constructions + self.shade_constructions:
|
|
606
|
+
mods[con.identifier] = con.to_radiance_solar_exterior() \
|
|
607
|
+
if isinstance(con, OpaqueConstruction) else con.to_radiance_solar()
|
|
608
|
+
self._assign_face_modifiers(mods)
|
|
609
|
+
|
|
610
|
+
def assign_radiance_visible_exterior(self):
|
|
611
|
+
"""Assign honeybee Radiance modifiers based on exterior visible properties."""
|
|
612
|
+
mod_sets = {}
|
|
613
|
+
for constr_set in self.construction_sets + [generic_construction_set]:
|
|
614
|
+
mod_sets[constr_set.identifier] = constr_set.to_radiance_visible_exterior()
|
|
615
|
+
self._assign_room_modifier_sets(mod_sets)
|
|
616
|
+
mods = {}
|
|
617
|
+
for con in self.face_constructions + self.shade_constructions:
|
|
618
|
+
mods[con.identifier] = con.to_radiance_visible_exterior() \
|
|
619
|
+
if isinstance(con, OpaqueConstruction) else con.to_radiance_visible()
|
|
620
|
+
self._assign_face_modifiers(mods)
|
|
621
|
+
|
|
622
|
+
def offset_and_assign_exterior_face_modifiers(
|
|
623
|
+
self, reflectance_type='Solar', offset=0.02):
|
|
624
|
+
"""Offset all exterior Faces and assign them a modifier based on exterior layer.
|
|
625
|
+
|
|
626
|
+
This is often useful in conjunction with the assign_radiance_solar_interior
|
|
627
|
+
or the assign_radiance_visible_interior to make a radiance model that
|
|
628
|
+
accounts for both the interior and exterior material layers.
|
|
629
|
+
|
|
630
|
+
Note that this method will add the offset faces as orphaned faces and so the
|
|
631
|
+
model will not be simulate-able in EnergyPlus after running this method
|
|
632
|
+
(it is only intended to be simulated within Radiance).
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
reflectance_type: Text for the type of reflectance to be used in the
|
|
636
|
+
assigned modifier. Must be either Solar or Visible. (Default: Solar).
|
|
637
|
+
offset: A number for the distance at which the exterior Faces should
|
|
638
|
+
be offset. (Default: 0.02, suitable for models in meters).
|
|
639
|
+
"""
|
|
640
|
+
# collect all of the unique exterior face constructions
|
|
641
|
+
constructions = []
|
|
642
|
+
for room in self.host.rooms:
|
|
643
|
+
for face in room.faces: # check all Face constructions
|
|
644
|
+
if isinstance(face.boundary_condition, Outdoors):
|
|
645
|
+
constr = face.properties.energy.construction
|
|
646
|
+
if not self._instance_in_array(constr, constructions):
|
|
647
|
+
constructions.append(constr)
|
|
648
|
+
constructions = set(constructions)
|
|
649
|
+
# convert constructions into modifiers
|
|
650
|
+
mods = {}
|
|
651
|
+
for con in constructions:
|
|
652
|
+
mods[con.identifier] = con.to_radiance_visible_exterior() \
|
|
653
|
+
if reflectance_type == 'Visible' else con.to_radiance_solar_exterior()
|
|
654
|
+
# loop through the faces and create new offset exterior ones
|
|
655
|
+
new_faces = []
|
|
656
|
+
for room in self._host.rooms:
|
|
657
|
+
for face in room.faces:
|
|
658
|
+
if isinstance(face.boundary_condition, Outdoors):
|
|
659
|
+
new_geo = face.punched_geometry.move(face.normal * offset)
|
|
660
|
+
new_id = '{}_ext'.format(face.identifier)
|
|
661
|
+
new_face = Face(
|
|
662
|
+
new_id, new_geo, face.type, face.boundary_condition)
|
|
663
|
+
new_face.properties.radiance.modifier = \
|
|
664
|
+
mods[face.properties.energy.construction.identifier]
|
|
665
|
+
new_faces.append(new_face)
|
|
666
|
+
# add the new faces to the host model
|
|
667
|
+
for face in new_faces:
|
|
668
|
+
self._host.add_face(face)
|
|
669
|
+
|
|
670
|
+
def assign_dynamic_aperture_groups(self):
|
|
671
|
+
"""Assign aperture groups to all Apertures with dynamic and shaded constructions.
|
|
672
|
+
|
|
673
|
+
Note that this method will only add two groups for each dynamic aperture.
|
|
674
|
+
The first group will be completely transparent while the second group
|
|
675
|
+
will be a 100% transmittance perfectly diffusing modifier. This is done
|
|
676
|
+
with the assumption that EnergyPlus transmittance results will be used to
|
|
677
|
+
appropriately account for the transmittance of states in the results.
|
|
678
|
+
"""
|
|
679
|
+
# import dependencies and set up reused variables
|
|
680
|
+
try:
|
|
681
|
+
from honeybee_radiance.modifier.material import Trans
|
|
682
|
+
from honeybee_radiance.dynamic.state import RadianceSubFaceState
|
|
683
|
+
except ImportError as e:
|
|
684
|
+
raise ImportError('honeybee_radiance library must be installed to use '
|
|
685
|
+
'assign_dynamic_aperture_groups() method. {}'.format(e))
|
|
686
|
+
all_spec = Trans('complete_spec', 1, 1, 1, 0, 0, 1, 1)
|
|
687
|
+
all_diff = Trans('complete_diff', 1, 1, 1, 0, 0, 1, 0)
|
|
688
|
+
|
|
689
|
+
# establish groups based on similar constructions, orientations and controls
|
|
690
|
+
group_dict = {}
|
|
691
|
+
for room in self.host.rooms:
|
|
692
|
+
for face in room.faces:
|
|
693
|
+
for ap in face.apertures:
|
|
694
|
+
u_id = None
|
|
695
|
+
con = ap.properties.energy.construction
|
|
696
|
+
if isinstance(con, WindowConstructionDynamic):
|
|
697
|
+
orient = '{}_{}'.format(int(ap.azimuth), int(ap.altitude))
|
|
698
|
+
u_id = '{}_{}'.format(con.identifier, orient)
|
|
699
|
+
elif isinstance(con, WindowConstructionShade):
|
|
700
|
+
orient = '{}_{}'.format(int(ap.azimuth), int(ap.altitude))
|
|
701
|
+
if con.is_groupable:
|
|
702
|
+
u_id = '{}_{}'.format(con.identifier, orient)
|
|
703
|
+
elif con.is_room_groupable:
|
|
704
|
+
u_id = '{}_{}_{}'.format(
|
|
705
|
+
con.identifier, room.identifier, orient)
|
|
706
|
+
else:
|
|
707
|
+
u_id = ap.identifier
|
|
708
|
+
if u_id is not None:
|
|
709
|
+
try:
|
|
710
|
+
group_dict[u_id].append(ap)
|
|
711
|
+
except KeyError:
|
|
712
|
+
group_dict[u_id] = [ap]
|
|
713
|
+
|
|
714
|
+
# create the actual aperture groups and assign states
|
|
715
|
+
for group in group_dict.values():
|
|
716
|
+
grp_name = group[0].identifier
|
|
717
|
+
for ap in group:
|
|
718
|
+
ap.properties.radiance.dynamic_group_identifier = grp_name
|
|
719
|
+
spec_state = RadianceSubFaceState(all_spec)
|
|
720
|
+
diff_state = RadianceSubFaceState(all_diff)
|
|
721
|
+
ap.properties.radiance.states = [spec_state, diff_state]
|
|
722
|
+
|
|
723
|
+
def generate_ground_room(self, soil_construction):
|
|
724
|
+
"""Generate and add a Room to the Model that represents the ground.
|
|
725
|
+
|
|
726
|
+
The Room will be added such that it exists below all of the other geometry
|
|
727
|
+
of the model and covers the full XY extents of the model.
|
|
728
|
+
|
|
729
|
+
This is useful when it is desirable to track the ground surface temperature
|
|
730
|
+
or when the model needs a simple Room to be able to simulate in EnergyPlus.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
soil_construction: An OpaqueConstruction that reflects the soil type of
|
|
734
|
+
the ground. If a multi-layered construction is input, the multiple
|
|
735
|
+
layers will only be used for the roof Face of the Room and all other
|
|
736
|
+
Faces will get a construction with the inner-most layer assigned.
|
|
737
|
+
If the outer-most material is an EnergyMaterialVegetation and there
|
|
738
|
+
are no other layers in the construction, the vegetation's soil
|
|
739
|
+
material will be used for all other Faces.
|
|
740
|
+
"""
|
|
741
|
+
# create the room geometry from the min and max points
|
|
742
|
+
min_pt, max_pt = self.host.min, self.host.max
|
|
743
|
+
room_height = 1 / conversion_factor_to_meters(self.host.units)
|
|
744
|
+
rm_origin = Point3D(min_pt.x, min_pt.y, min_pt.z - room_height)
|
|
745
|
+
ground = Room.from_box(
|
|
746
|
+
'Ground_Room', max_pt.x - min_pt.x, max_pt.y - min_pt.y, room_height,
|
|
747
|
+
origin=rm_origin)
|
|
748
|
+
# turn the room into a ground with an appropriate construction
|
|
749
|
+
ground.properties.energy.make_ground(soil_construction)
|
|
750
|
+
self.host.add_room(ground)
|
|
751
|
+
return ground
|
|
752
|
+
|
|
753
|
+
def check_for_extension(self, raise_exception=True, detailed=False):
|
|
754
|
+
"""Check that the Model is valid for EnergyPlus simulation.
|
|
755
|
+
|
|
756
|
+
This process includes all relevant honeybee-core checks as well as checks
|
|
757
|
+
that apply only for EnergyPlus.
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
761
|
+
if any errors are found. If False, this method will simply
|
|
762
|
+
return a text string with all errors that were found. (Default: True).
|
|
763
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
764
|
+
dicts with error info or a string with a message. (Default: False).
|
|
765
|
+
|
|
766
|
+
Returns:
|
|
767
|
+
A text string with all errors that were found or a list if detailed is True.
|
|
768
|
+
This string (or list) will be empty if no errors were found.
|
|
769
|
+
"""
|
|
770
|
+
# set up defaults to ensure the method runs correctly
|
|
771
|
+
detailed = False if raise_exception else detailed
|
|
772
|
+
msgs = []
|
|
773
|
+
tol = self.host.tolerance
|
|
774
|
+
ang_tol = self.host.angle_tolerance
|
|
775
|
+
e_tol = parse_distance_string('1cm', self.host.units)
|
|
776
|
+
|
|
777
|
+
# perform checks for duplicate identifiers, which might mess with other checks
|
|
778
|
+
msgs.append(self.host.check_all_duplicate_identifiers(False, detailed))
|
|
779
|
+
|
|
780
|
+
# perform several checks for the Honeybee schema geometry rules
|
|
781
|
+
msgs.append(self.host.check_planar(tol, False, detailed))
|
|
782
|
+
msgs.append(self.host.check_self_intersecting(tol, False, detailed))
|
|
783
|
+
msgs.append(self.host.check_degenerate_rooms(e_tol, False, detailed))
|
|
784
|
+
|
|
785
|
+
# perform geometry checks related to parent-child relationships
|
|
786
|
+
msgs.append(self.host.check_sub_faces_valid(tol, ang_tol, False, detailed))
|
|
787
|
+
msgs.append(self.host.check_sub_faces_overlapping(tol, False, detailed))
|
|
788
|
+
msgs.append(self.host.check_rooms_solid(tol, ang_tol, False, detailed))
|
|
789
|
+
msgs.append(self.host.check_upside_down_faces(ang_tol, False, detailed))
|
|
790
|
+
|
|
791
|
+
# perform checks related to adjacency relationships
|
|
792
|
+
msgs.append(self.host.check_room_volume_collisions(tol, False, detailed))
|
|
793
|
+
msgs.append(self.host.check_missing_adjacencies(False, detailed))
|
|
794
|
+
msgs.append(self.host.check_matching_adjacent_areas(tol, False, detailed))
|
|
795
|
+
msgs.append(self.host.check_all_air_boundaries_adjacent(False, detailed))
|
|
796
|
+
|
|
797
|
+
# perform checks for specific energy simulation rules
|
|
798
|
+
msgs.append(self.check_all_zones_have_one_hvac(False, detailed))
|
|
799
|
+
msgs.append(self.check_detailed_hvac_rooms(False, detailed))
|
|
800
|
+
msgs.append(self.check_shw_rooms_in_model(False, detailed))
|
|
801
|
+
msgs.append(self.check_maximum_elevation(1000, False, detailed))
|
|
802
|
+
msgs.append(self.check_one_vegetation_material(False, detailed))
|
|
803
|
+
msgs.append(self.check_interior_constructions_reversed(False, detailed))
|
|
804
|
+
|
|
805
|
+
# output a final report of errors or raise an exception
|
|
806
|
+
full_msgs = [msg for msg in msgs if msg]
|
|
807
|
+
if detailed:
|
|
808
|
+
return [m for msg in full_msgs for m in msg]
|
|
809
|
+
full_msg = '\n'.join(full_msgs)
|
|
810
|
+
if raise_exception and len(full_msgs) != 0:
|
|
811
|
+
raise ValueError(full_msg)
|
|
812
|
+
return full_msg
|
|
813
|
+
|
|
814
|
+
def check_generic(self, raise_exception=True, detailed=False):
|
|
815
|
+
"""Check generic of the aspects of the Model energy properties.
|
|
816
|
+
|
|
817
|
+
This includes checks for everything except duplicate identifiers for
|
|
818
|
+
constructions, schedules, etc. Typically, these checks just add to the
|
|
819
|
+
validation time without providing useful information since extension
|
|
820
|
+
objects with duplicate IDs are lost during HBJSON serialization.
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
824
|
+
if any errors are found. If False, this method will simply
|
|
825
|
+
return a text string with all errors that were found.
|
|
826
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
827
|
+
dicts with error info or a string with a message. (Default: False).
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
A text string with all errors that were found or a list if detailed is True.
|
|
831
|
+
This string (or list) will be empty if no errors were found.
|
|
832
|
+
"""
|
|
833
|
+
# set up defaults to ensure the method runs correctly
|
|
834
|
+
detailed = False if raise_exception else detailed
|
|
835
|
+
msgs = []
|
|
836
|
+
# perform checks for specific energy simulation rules
|
|
837
|
+
msgs.append(self.check_all_zones_have_one_hvac(False, detailed))
|
|
838
|
+
msgs.append(self.check_detailed_hvac_rooms(False, detailed))
|
|
839
|
+
msgs.append(self.check_shw_rooms_in_model(False, detailed))
|
|
840
|
+
msgs.append(self.check_maximum_elevation(1000, False, detailed))
|
|
841
|
+
msgs.append(self.check_one_vegetation_material(False, detailed))
|
|
842
|
+
msgs.append(self.check_interior_constructions_reversed(False, detailed))
|
|
843
|
+
# output a final report of errors or raise an exception
|
|
844
|
+
full_msgs = [msg for msg in msgs if msg]
|
|
845
|
+
if detailed:
|
|
846
|
+
return [m for msg in full_msgs for m in msg]
|
|
847
|
+
full_msg = '\n'.join(full_msgs)
|
|
848
|
+
if raise_exception and len(full_msgs) != 0:
|
|
849
|
+
raise ValueError(full_msg)
|
|
850
|
+
return full_msg
|
|
851
|
+
|
|
852
|
+
def check_all(self, raise_exception=True, detailed=False):
|
|
853
|
+
"""Check all of the aspects of the Model energy properties.
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
857
|
+
if any errors are found. If False, this method will simply
|
|
858
|
+
return a text string with all errors that were found. (Default: True).
|
|
859
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
860
|
+
dicts with error info or a string with a message. (Default: False).
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
A text string with all errors that were found or a list if detailed is True.
|
|
864
|
+
This string (or list) will be empty if no errors were found.
|
|
865
|
+
"""
|
|
866
|
+
# set up defaults to ensure the method runs correctly
|
|
867
|
+
detailed = False if raise_exception else detailed
|
|
868
|
+
msgs = []
|
|
869
|
+
# perform checks for duplicate identifiers
|
|
870
|
+
msgs.append(self.check_all_duplicate_identifiers(False, detailed))
|
|
871
|
+
# perform checks for specific energy simulation rules
|
|
872
|
+
msgs.append(self.check_all_zones_have_one_hvac(False, detailed))
|
|
873
|
+
msgs.append(self.check_detailed_hvac_rooms(False, detailed))
|
|
874
|
+
msgs.append(self.check_shw_rooms_in_model(False, detailed))
|
|
875
|
+
msgs.append(self.check_maximum_elevation(1000, False, detailed))
|
|
876
|
+
msgs.append(self.check_one_vegetation_material(False, detailed))
|
|
877
|
+
msgs.append(self.check_interior_constructions_reversed(False, detailed))
|
|
878
|
+
# output a final report of errors or raise an exception
|
|
879
|
+
full_msgs = [msg for msg in msgs if msg]
|
|
880
|
+
if detailed:
|
|
881
|
+
return [m for msg in full_msgs for m in msg]
|
|
882
|
+
full_msg = '\n'.join(full_msgs)
|
|
883
|
+
if raise_exception and len(full_msgs) != 0:
|
|
884
|
+
raise ValueError(full_msg)
|
|
885
|
+
return full_msg
|
|
886
|
+
|
|
887
|
+
def check_all_duplicate_identifiers(self, raise_exception=True, detailed=False):
|
|
888
|
+
"""Check that there are no duplicate identifiers for any energy objects.
|
|
889
|
+
|
|
890
|
+
This includes Materials, Constructions, ConstructionSets, Schedules,
|
|
891
|
+
Programs, HVACs, and SHWs.
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
895
|
+
if any duplicate identifiers are found. If False, this method will simply
|
|
896
|
+
return a text string with all errors that were found. (Default: True).
|
|
897
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
898
|
+
dicts with error info or a string with a message. (Default: False).
|
|
899
|
+
|
|
900
|
+
Returns:
|
|
901
|
+
A text string with all errors that were found or a list if detailed is True.
|
|
902
|
+
This string (or list) will be empty if no errors were found.
|
|
903
|
+
"""
|
|
904
|
+
# set up defaults to ensure the method runs correctly
|
|
905
|
+
detailed = False if raise_exception else detailed
|
|
906
|
+
msgs = []
|
|
907
|
+
# perform checks for duplicate identifiers
|
|
908
|
+
msgs.append(self.check_duplicate_material_identifiers(False, detailed))
|
|
909
|
+
msgs.append(self.check_duplicate_construction_identifiers(False, detailed))
|
|
910
|
+
msgs.append(self.check_duplicate_construction_set_identifiers(False, detailed))
|
|
911
|
+
stl_msgs = self.check_duplicate_schedule_type_limit_identifiers(False, detailed)
|
|
912
|
+
msgs.append(stl_msgs)
|
|
913
|
+
msgs.append(self.check_duplicate_schedule_identifiers(False, detailed))
|
|
914
|
+
msgs.append(self.check_duplicate_program_type_identifiers(False, detailed))
|
|
915
|
+
msgs.append(self.check_duplicate_hvac_identifiers(False, detailed))
|
|
916
|
+
msgs.append(self.check_duplicate_shw_identifiers(False, detailed))
|
|
917
|
+
# output a final report of errors or raise an exception
|
|
918
|
+
full_msgs = [msg for msg in msgs if msg]
|
|
919
|
+
if detailed:
|
|
920
|
+
return [m for msg in full_msgs for m in msg]
|
|
921
|
+
full_msg = '\n'.join(full_msgs)
|
|
922
|
+
if raise_exception and len(full_msgs) != 0:
|
|
923
|
+
raise ValueError(full_msg)
|
|
924
|
+
return full_msg
|
|
925
|
+
|
|
926
|
+
def check_duplicate_material_identifiers(self, raise_exception=True, detailed=False):
|
|
927
|
+
"""Check that there are no duplicate Material identifiers in the model.
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
931
|
+
if duplicate identifiers are found. (Default: True).
|
|
932
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
933
|
+
dicts with error info or a string with a message. (Default: False).
|
|
934
|
+
|
|
935
|
+
Returns:
|
|
936
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
937
|
+
"""
|
|
938
|
+
return check_duplicate_identifiers(
|
|
939
|
+
self.materials, raise_exception, 'Material',
|
|
940
|
+
detailed, '020001', 'Energy', error_type='Duplicate Material Identifier')
|
|
941
|
+
|
|
942
|
+
def check_duplicate_construction_identifiers(
|
|
943
|
+
self, raise_exception=True, detailed=False):
|
|
944
|
+
"""Check that there are no duplicate Construction identifiers in the model.
|
|
945
|
+
|
|
946
|
+
Args:
|
|
947
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
948
|
+
if duplicate identifiers are found. (Default: True).
|
|
949
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
950
|
+
dicts with error info or a string with a message. (Default: False).
|
|
951
|
+
|
|
952
|
+
Returns:
|
|
953
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
954
|
+
"""
|
|
955
|
+
return check_duplicate_identifiers(
|
|
956
|
+
self.constructions, raise_exception, 'Construction',
|
|
957
|
+
detailed, '020002', 'Energy', error_type='Duplicate Construction Identifier')
|
|
958
|
+
|
|
959
|
+
def check_duplicate_construction_set_identifiers(
|
|
960
|
+
self, raise_exception=True, detailed=False):
|
|
961
|
+
"""Check that there are no duplicate ConstructionSet identifiers in the model.
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
965
|
+
if duplicate identifiers are found. (Default: True).
|
|
966
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
967
|
+
dicts with error info or a string with a message. (Default: False).
|
|
968
|
+
|
|
969
|
+
Returns:
|
|
970
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
971
|
+
"""
|
|
972
|
+
return check_duplicate_identifiers(
|
|
973
|
+
self.construction_sets, raise_exception, 'ConstructionSet',
|
|
974
|
+
detailed, '020003', 'Energy',
|
|
975
|
+
error_type='Duplicate ConstructionSet Identifier')
|
|
976
|
+
|
|
977
|
+
def check_duplicate_schedule_type_limit_identifiers(
|
|
978
|
+
self, raise_exception=True, detailed=False):
|
|
979
|
+
"""Check that there are no duplicate ScheduleTypeLimit identifiers in the model.
|
|
980
|
+
|
|
981
|
+
Args:
|
|
982
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
983
|
+
if duplicate identifiers are found. (Default: True).
|
|
984
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
985
|
+
dicts with error info or a string with a message. (Default: False).
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
989
|
+
"""
|
|
990
|
+
return check_duplicate_identifiers(
|
|
991
|
+
self.schedule_type_limits, raise_exception, 'ScheduleTypeLimit',
|
|
992
|
+
detailed, '020004', 'Energy',
|
|
993
|
+
error_type='Duplicate ScheduleTypeLimit Identifier')
|
|
994
|
+
|
|
995
|
+
def check_duplicate_schedule_identifiers(self, raise_exception=True, detailed=False):
|
|
996
|
+
"""Check that there are no duplicate Schedule identifiers in the model.
|
|
997
|
+
|
|
998
|
+
Args:
|
|
999
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
1000
|
+
if duplicate identifiers are found. (Default: True).
|
|
1001
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1002
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1003
|
+
|
|
1004
|
+
Returns:
|
|
1005
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1006
|
+
"""
|
|
1007
|
+
return check_duplicate_identifiers(
|
|
1008
|
+
self.schedules, raise_exception, 'Schedule', detailed, '020005', 'Energy',
|
|
1009
|
+
error_type='Duplicate Schedule Identifier')
|
|
1010
|
+
|
|
1011
|
+
def check_duplicate_program_type_identifiers(
|
|
1012
|
+
self, raise_exception=True, detailed=False):
|
|
1013
|
+
"""Check that there are no duplicate ProgramType identifiers in the model.
|
|
1014
|
+
|
|
1015
|
+
Args:
|
|
1016
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
1017
|
+
if duplicate identifiers are found. (Default: True).
|
|
1018
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1019
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1020
|
+
|
|
1021
|
+
Returns:
|
|
1022
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1023
|
+
"""
|
|
1024
|
+
return check_duplicate_identifiers(
|
|
1025
|
+
self.program_types, raise_exception, 'ProgramType',
|
|
1026
|
+
detailed, '020006', 'Energy', error_type='Duplicate ProgramType Identifier')
|
|
1027
|
+
|
|
1028
|
+
def check_duplicate_hvac_identifiers(self, raise_exception=True, detailed=False):
|
|
1029
|
+
"""Check that there are no duplicate HVAC identifiers in the model.
|
|
1030
|
+
|
|
1031
|
+
Args:
|
|
1032
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
1033
|
+
if duplicate identifiers are found. (Default: True).
|
|
1034
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1035
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1036
|
+
|
|
1037
|
+
Returns:
|
|
1038
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1039
|
+
"""
|
|
1040
|
+
return check_duplicate_identifiers(
|
|
1041
|
+
self.hvacs, raise_exception, 'HVAC', detailed, '020007', 'Energy',
|
|
1042
|
+
error_type='Duplicate HVAC Identifier')
|
|
1043
|
+
|
|
1044
|
+
def check_duplicate_shw_identifiers(self, raise_exception=True, detailed=False):
|
|
1045
|
+
"""Check that there are no duplicate SHW identifiers in the model.
|
|
1046
|
+
|
|
1047
|
+
Args:
|
|
1048
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
1049
|
+
if duplicate identifiers are found. (Default: True).
|
|
1050
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1051
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1052
|
+
|
|
1053
|
+
Returns:
|
|
1054
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1055
|
+
"""
|
|
1056
|
+
return check_duplicate_identifiers(
|
|
1057
|
+
self.shws, raise_exception, 'SHW', detailed, '020008', 'Energy',
|
|
1058
|
+
error_type='Duplicate SHW Identifier')
|
|
1059
|
+
|
|
1060
|
+
def check_all_zones_have_one_hvac(self, raise_exception=True, detailed=False):
|
|
1061
|
+
"""Check that all rooms within each zone have only one HVAC assigned to them.
|
|
1062
|
+
|
|
1063
|
+
Multiple HVAC systems serving one zone typically causes EnergyPlus simulation
|
|
1064
|
+
failures and is often a mistake that results from changing zoning strategies
|
|
1065
|
+
without changing the HVAC to be coordinated with the new zones.
|
|
1066
|
+
|
|
1067
|
+
Note that having some Rooms in the zone referencing the HVAC and others
|
|
1068
|
+
with no HVAC is considered permissible since this just implies that the
|
|
1069
|
+
thermostat or zone equipment may be in one of the rooms but the whole
|
|
1070
|
+
zone is conditioned by this equipment.
|
|
1071
|
+
|
|
1072
|
+
Args:
|
|
1073
|
+
raise_exception: Boolean to note whether a ValueError should be raised if
|
|
1074
|
+
there are rooms within zones that have different HVAC systems
|
|
1075
|
+
assigned to them. (Default: True).
|
|
1076
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1077
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1078
|
+
|
|
1079
|
+
Returns:
|
|
1080
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1081
|
+
"""
|
|
1082
|
+
detailed = False if raise_exception else detailed
|
|
1083
|
+
# gather a list of all the zones with multiple HVACs
|
|
1084
|
+
invalid_zones = {}
|
|
1085
|
+
for zone, rooms in self.host.zone_dict.items():
|
|
1086
|
+
if len(rooms) == 1:
|
|
1087
|
+
continue
|
|
1088
|
+
hvacs = [r.properties.energy.hvac for r in rooms
|
|
1089
|
+
if r.properties.energy.hvac is not None]
|
|
1090
|
+
if len(hvacs) > 1:
|
|
1091
|
+
invalid_zones[zone] = [rooms, hvacs]
|
|
1092
|
+
|
|
1093
|
+
# if multiple HVACs in the same zone were found, then report the issue
|
|
1094
|
+
if len(invalid_zones) != 0:
|
|
1095
|
+
if detailed:
|
|
1096
|
+
all_err = []
|
|
1097
|
+
for zone, data in invalid_zones.items():
|
|
1098
|
+
rooms, hvacs = data
|
|
1099
|
+
hvac_names = [h.display_name for h in hvacs]
|
|
1100
|
+
msg = 'Zone "{}" is served by the following different HVAC ' \
|
|
1101
|
+
'systems:\n{}'.format(zone, '\n'.join(hvac_names))
|
|
1102
|
+
error_dict = {
|
|
1103
|
+
'type': 'ValidationError',
|
|
1104
|
+
'code': '020014',
|
|
1105
|
+
'error_type': 'Zone with Different Room HVACs',
|
|
1106
|
+
'extension_type': 'Energy',
|
|
1107
|
+
'element_type': 'Room',
|
|
1108
|
+
'element_id': [r.identifier for r in rooms],
|
|
1109
|
+
'element_name': [r.display_name for r in rooms],
|
|
1110
|
+
'message': msg
|
|
1111
|
+
}
|
|
1112
|
+
all_err.append(error_dict)
|
|
1113
|
+
return all_err
|
|
1114
|
+
else:
|
|
1115
|
+
err_zones = []
|
|
1116
|
+
for zone, data in invalid_zones.items():
|
|
1117
|
+
rooms, hvacs = data
|
|
1118
|
+
hvac_names = [h.display_name for h in hvacs]
|
|
1119
|
+
err_zn = ' {} - [HVACS: {}]'.format(zone, ', '.join(hvac_names))
|
|
1120
|
+
err_zones.append(err_zn)
|
|
1121
|
+
msg = 'The model has the following invalid zones served by different ' \
|
|
1122
|
+
'HVAC systems:\n{}'.format('\n'.join(err_zones))
|
|
1123
|
+
if raise_exception:
|
|
1124
|
+
raise ValueError(msg)
|
|
1125
|
+
return msg
|
|
1126
|
+
return [] if detailed else ''
|
|
1127
|
+
|
|
1128
|
+
def check_shw_rooms_in_model(self, raise_exception=True, detailed=False):
|
|
1129
|
+
"""Check that the room_identifiers of SHWSystems are in the model.
|
|
1130
|
+
|
|
1131
|
+
Args:
|
|
1132
|
+
raise_exception: Boolean to note whether a ValueError should be raised if
|
|
1133
|
+
SHWSystems reference Rooms that are not in the Model. (Default: True).
|
|
1134
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1135
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1136
|
+
|
|
1137
|
+
Returns:
|
|
1138
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1139
|
+
"""
|
|
1140
|
+
detailed = False if raise_exception else detailed
|
|
1141
|
+
# gather a list of all the missing rooms
|
|
1142
|
+
shw_ids = [(shw_sys, shw_sys.ambient_condition) for shw_sys in self.shws
|
|
1143
|
+
if isinstance(shw_sys.ambient_condition, str)]
|
|
1144
|
+
room_ids = set(room.identifier for room in self.host.rooms)
|
|
1145
|
+
missing_rooms = [] if detailed else set()
|
|
1146
|
+
for shw_sys in shw_ids:
|
|
1147
|
+
if shw_sys[1] not in room_ids:
|
|
1148
|
+
if detailed:
|
|
1149
|
+
missing_rooms.append(shw_sys[0])
|
|
1150
|
+
else:
|
|
1151
|
+
missing_rooms.add(shw_sys[1])
|
|
1152
|
+
# if missing rooms were found, then report the issue
|
|
1153
|
+
if len(missing_rooms) != 0:
|
|
1154
|
+
if detailed:
|
|
1155
|
+
all_err = []
|
|
1156
|
+
for shw_sys in missing_rooms:
|
|
1157
|
+
msg = 'SHWSystem "{}" has a ambient_condition referencing ' \
|
|
1158
|
+
'a Room that is not in the ' \
|
|
1159
|
+
'Model: "{}"'.format(shw_sys.identifier, shw_sys.room_identifier)
|
|
1160
|
+
error_dict = {
|
|
1161
|
+
'type': 'ValidationError',
|
|
1162
|
+
'code': '020009',
|
|
1163
|
+
'error_type': 'SHWSystem Room Not In Model',
|
|
1164
|
+
'extension_type': 'Energy',
|
|
1165
|
+
'element_type': 'SHW',
|
|
1166
|
+
'element_id': [shw_sys.identifier],
|
|
1167
|
+
'element_name': [shw_sys.display_name],
|
|
1168
|
+
'message': msg
|
|
1169
|
+
}
|
|
1170
|
+
all_err.append(error_dict)
|
|
1171
|
+
return all_err
|
|
1172
|
+
else:
|
|
1173
|
+
msg = 'The model has the following missing rooms referenced by SHW ' \
|
|
1174
|
+
'Systems:\n{}'.format('\n'.join(missing_rooms))
|
|
1175
|
+
if raise_exception:
|
|
1176
|
+
raise ValueError(msg)
|
|
1177
|
+
return msg
|
|
1178
|
+
return [] if detailed else ''
|
|
1179
|
+
|
|
1180
|
+
def check_detailed_hvac_rooms(self, raise_exception=True, detailed=False):
|
|
1181
|
+
"""Check that any rooms referenced within a DetailedHVAC exist in the model.
|
|
1182
|
+
|
|
1183
|
+
This method will also check to make sure that two detailed HVACs do not
|
|
1184
|
+
reference the same room and that all rooms referencing a detailed HVAC
|
|
1185
|
+
have setpoints.
|
|
1186
|
+
|
|
1187
|
+
Args:
|
|
1188
|
+
raise_exception: Boolean to note whether a ValueError should be raised if
|
|
1189
|
+
DetailedHVAC reference Rooms that are not in the Model. (Default: True).
|
|
1190
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1191
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1192
|
+
|
|
1193
|
+
Returns:
|
|
1194
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1195
|
+
"""
|
|
1196
|
+
detailed = False if raise_exception else detailed
|
|
1197
|
+
all_err = []
|
|
1198
|
+
|
|
1199
|
+
# get all of the HVACs and check Rooms for setpoints in the process
|
|
1200
|
+
hvacs = []
|
|
1201
|
+
for room in self.host.rooms:
|
|
1202
|
+
hvac = room.properties.energy._hvac
|
|
1203
|
+
if hvac is not None and isinstance(hvac, DetailedHVAC):
|
|
1204
|
+
if not self._instance_in_array(hvac, hvacs):
|
|
1205
|
+
hvacs.append(hvac)
|
|
1206
|
+
if room.properties.energy.setpoint is None:
|
|
1207
|
+
msg = 'Detailed HVAC "{}" is assigned to Room {}, which lacks a ' \
|
|
1208
|
+
'thermostat setpoint specification.\nThis makes the model ' \
|
|
1209
|
+
'un-simulate-able in EnergyPlus/OpenStudio.'.format(
|
|
1210
|
+
hvac.display_name, room.full_id)
|
|
1211
|
+
if detailed:
|
|
1212
|
+
error_dict = {
|
|
1213
|
+
'type': 'ValidationError',
|
|
1214
|
+
'code': '020011',
|
|
1215
|
+
'error_type': 'Room With HVAC Lacks Setpoint',
|
|
1216
|
+
'extension_type': 'Energy',
|
|
1217
|
+
'element_type': 'Room',
|
|
1218
|
+
'element_id': [room.identifier],
|
|
1219
|
+
'element_name': [room.display_name],
|
|
1220
|
+
'message': msg
|
|
1221
|
+
}
|
|
1222
|
+
all_err.append(error_dict)
|
|
1223
|
+
else:
|
|
1224
|
+
all_err.append(msg)
|
|
1225
|
+
|
|
1226
|
+
# gather a list of all the rooms and evaluate it against the HVACs
|
|
1227
|
+
room_ids = set(room.zone for room in self.host.rooms)
|
|
1228
|
+
rooms_with_hvac = set()
|
|
1229
|
+
problem_hvacs, problem_rooms = [], []
|
|
1230
|
+
for hvac in hvacs:
|
|
1231
|
+
missing_rooms = []
|
|
1232
|
+
for zone in hvac.thermal_zones:
|
|
1233
|
+
if zone not in room_ids:
|
|
1234
|
+
missing_rooms.append(zone)
|
|
1235
|
+
if zone in rooms_with_hvac:
|
|
1236
|
+
problem_rooms.append(zone)
|
|
1237
|
+
rooms_with_hvac.add(zone)
|
|
1238
|
+
if len(missing_rooms) != 0:
|
|
1239
|
+
problem_hvacs.append((hvac, missing_rooms))
|
|
1240
|
+
|
|
1241
|
+
# if missing room references were found, report them
|
|
1242
|
+
if len(problem_hvacs) != 0:
|
|
1243
|
+
for bad_hvac, missing_rooms in problem_hvacs:
|
|
1244
|
+
msg = 'DetailedHVAC "{}" is referencing the following Rooms that ' \
|
|
1245
|
+
'are not in the Model:\n{}'.format(
|
|
1246
|
+
bad_hvac.display_name, '\n'.join(missing_rooms))
|
|
1247
|
+
if detailed:
|
|
1248
|
+
error_dict = {
|
|
1249
|
+
'type': 'ValidationError',
|
|
1250
|
+
'code': '020012',
|
|
1251
|
+
'error_type': 'DetailedHVAC Rooms Not In Model',
|
|
1252
|
+
'extension_type': 'Energy',
|
|
1253
|
+
'element_type': 'HVAC',
|
|
1254
|
+
'element_id': [bad_hvac.identifier],
|
|
1255
|
+
'element_name': [bad_hvac.display_name],
|
|
1256
|
+
'message': msg
|
|
1257
|
+
}
|
|
1258
|
+
all_err.append(error_dict)
|
|
1259
|
+
else:
|
|
1260
|
+
all_err.append(msg)
|
|
1261
|
+
|
|
1262
|
+
# if rooms were found with multiple HVAC references, report them
|
|
1263
|
+
if len(problem_rooms) != 0:
|
|
1264
|
+
room_objs = [room for room in self.host.rooms
|
|
1265
|
+
if room.identifier in problem_rooms]
|
|
1266
|
+
for mult_hvac_room in room_objs:
|
|
1267
|
+
msg = 'Room {} is referenced by more than one detailed HVAC.'.format(
|
|
1268
|
+
mult_hvac_room.full_id)
|
|
1269
|
+
if detailed:
|
|
1270
|
+
error_dict = {
|
|
1271
|
+
'type': 'ValidationError',
|
|
1272
|
+
'code': '020013',
|
|
1273
|
+
'error_type': 'Room Referenced by Multiple Detailed HVAC',
|
|
1274
|
+
'extension_type': 'Energy',
|
|
1275
|
+
'element_type': 'Room',
|
|
1276
|
+
'element_id': [mult_hvac_room.identifier],
|
|
1277
|
+
'element_name': [mult_hvac_room.display_name],
|
|
1278
|
+
'message': msg
|
|
1279
|
+
}
|
|
1280
|
+
all_err.append(error_dict)
|
|
1281
|
+
else:
|
|
1282
|
+
all_err.append(msg)
|
|
1283
|
+
|
|
1284
|
+
# return any of the errors that were discovered
|
|
1285
|
+
if len(all_err) != 0:
|
|
1286
|
+
if raise_exception:
|
|
1287
|
+
raise ValueError('\n'.join(all_err))
|
|
1288
|
+
return all_err if detailed else '\n'.join(all_err)
|
|
1289
|
+
return [] if detailed else ''
|
|
1290
|
+
|
|
1291
|
+
def check_one_vegetation_material(self, raise_exception=True, detailed=False):
|
|
1292
|
+
"""Check that there no more than one EnergyMaterialVegetation in the model.
|
|
1293
|
+
|
|
1294
|
+
It is a limitation of EnergyPlus that it can only simulate a single
|
|
1295
|
+
eco roof per model. This should probably be addressed at some point
|
|
1296
|
+
so that we don't always have to check for it.
|
|
1297
|
+
|
|
1298
|
+
Args:
|
|
1299
|
+
raise_exception: Boolean for whether a ValueError should be raised if there's
|
|
1300
|
+
more than one EnergyMaterialVegetation in the Model. (Default: True).
|
|
1301
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1302
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1303
|
+
|
|
1304
|
+
Returns:
|
|
1305
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1306
|
+
"""
|
|
1307
|
+
detailed = False if raise_exception else detailed
|
|
1308
|
+
# first see if there's more than one vegetation material
|
|
1309
|
+
all_constrs = self.room_constructions + self.face_constructions
|
|
1310
|
+
materials = []
|
|
1311
|
+
for constr in all_constrs:
|
|
1312
|
+
try:
|
|
1313
|
+
materials.extend(constr.materials)
|
|
1314
|
+
except AttributeError:
|
|
1315
|
+
pass # ShadeConstruction
|
|
1316
|
+
all_mats = list(set(materials))
|
|
1317
|
+
veg_mats = [m for m in all_mats if isinstance(m, EnergyMaterialVegetation)]
|
|
1318
|
+
|
|
1319
|
+
# if more than one vegetation material was found, then report the issue
|
|
1320
|
+
if len(veg_mats) > 1:
|
|
1321
|
+
if detailed:
|
|
1322
|
+
all_err = []
|
|
1323
|
+
for v_mat in veg_mats:
|
|
1324
|
+
msg = 'EnergyMaterialVegetation "{}" is one of several vegetation ' \
|
|
1325
|
+
'materials in the model.\nThis is not allowed by ' \
|
|
1326
|
+
'EnergyPlus.'.format(v_mat.identifier)
|
|
1327
|
+
error_dict = {
|
|
1328
|
+
'type': 'ValidationError',
|
|
1329
|
+
'code': '020010',
|
|
1330
|
+
'error_type': 'Multiple Vegetation Materials',
|
|
1331
|
+
'extension_type': 'Energy',
|
|
1332
|
+
'element_type': 'Material',
|
|
1333
|
+
'element_id': [v_mat.identifier],
|
|
1334
|
+
'element_name': [v_mat.display_name],
|
|
1335
|
+
'message': msg
|
|
1336
|
+
}
|
|
1337
|
+
all_err.append(error_dict)
|
|
1338
|
+
return all_err
|
|
1339
|
+
else:
|
|
1340
|
+
veg_mats_ids = [v_mat.identifier for v_mat in veg_mats]
|
|
1341
|
+
msg = 'The model has multiple vegetation materials. This is not ' \
|
|
1342
|
+
'allowed by EnergyPlus:\n{}'.format('\n'.join(veg_mats_ids))
|
|
1343
|
+
if raise_exception:
|
|
1344
|
+
raise ValueError(msg)
|
|
1345
|
+
return msg
|
|
1346
|
+
return [] if detailed else ''
|
|
1347
|
+
|
|
1348
|
+
def check_maximum_elevation(self, max_elevation=1000, raise_exception=True,
|
|
1349
|
+
detailed=False):
|
|
1350
|
+
"""Check that no Rooms of the model are above a certain elevation.
|
|
1351
|
+
|
|
1352
|
+
EnergyPlus computes wind speeds, air pressures, and adjusts outdoor
|
|
1353
|
+
temperatures to account for the height above the ground using the Z values
|
|
1354
|
+
of the geometry coordinates. This is an important consideration when modeling
|
|
1355
|
+
skyscrapers but it can be detrimental when a building has been modeled
|
|
1356
|
+
with its coordinates at the height above sea level and the location
|
|
1357
|
+
is significantly above sea level (eg. Denver, Colorado).
|
|
1358
|
+
|
|
1359
|
+
This validation check is intended to catch such cases and make the user
|
|
1360
|
+
aware of the repercussions.
|
|
1361
|
+
|
|
1362
|
+
Args:
|
|
1363
|
+
max_elevation: A number for the maximum elevation in Meters that the
|
|
1364
|
+
model's rooms are permitted to be at before a ValidationError is
|
|
1365
|
+
reported. While EnergyPlus technically still simulates with models that
|
|
1366
|
+
are 12 kilometers above the origin, better practice is to set this
|
|
1367
|
+
value at the maximum height above the ground that any human-made
|
|
1368
|
+
structure can reasonably obtain. For this reason, the default is
|
|
1369
|
+
set to 1000 meters or roughly the height of the Burj tower.
|
|
1370
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
1371
|
+
if a Room composed entirely of AirBoundaries is found. (Default: True).
|
|
1372
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1373
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1374
|
+
|
|
1375
|
+
Returns:
|
|
1376
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1377
|
+
"""
|
|
1378
|
+
detailed = False if raise_exception else detailed
|
|
1379
|
+
# get the maximum elevation of all the rooms
|
|
1380
|
+
conv_fac = conversion_factor_to_meters(self.host.units)
|
|
1381
|
+
max_elev_model = max_elevation / conv_fac
|
|
1382
|
+
room_elevations = tuple(room.max.z for room in self.host.rooms)
|
|
1383
|
+
max_bldg_elev = max(room_elevations) if len(room_elevations) != 0 else 0
|
|
1384
|
+
|
|
1385
|
+
# if the maximum elevation was exceeded, then report the issue
|
|
1386
|
+
if max_bldg_elev > max_elev_model:
|
|
1387
|
+
msg = 'The building height is currently {} meters above the ground ' \
|
|
1388
|
+
'given the Z values of the coordinates.\nThis is above the ' \
|
|
1389
|
+
'maximum recommended height of {} meters.'.format(
|
|
1390
|
+
int(max_bldg_elev * conv_fac), max_elevation)
|
|
1391
|
+
if detailed:
|
|
1392
|
+
error_dict = {
|
|
1393
|
+
'type': 'ValidationError',
|
|
1394
|
+
'code': '020101',
|
|
1395
|
+
'error_type': 'Building Height Exceeds Max Elevation',
|
|
1396
|
+
'extension_type': 'Energy',
|
|
1397
|
+
'element_type': 'Building',
|
|
1398
|
+
'element_id': [self.host.identifier],
|
|
1399
|
+
'element_name': [self.host.display_name],
|
|
1400
|
+
'message': msg
|
|
1401
|
+
}
|
|
1402
|
+
return [error_dict]
|
|
1403
|
+
else:
|
|
1404
|
+
if raise_exception:
|
|
1405
|
+
raise ValueError(msg)
|
|
1406
|
+
return msg
|
|
1407
|
+
return [] if detailed else ''
|
|
1408
|
+
|
|
1409
|
+
def check_interior_constructions_reversed(
|
|
1410
|
+
self, raise_exception=True, detailed=False):
|
|
1411
|
+
"""Check that all interior constructions are in reversed order for paired faces.
|
|
1412
|
+
|
|
1413
|
+
Note that, if there are missing adjacencies in the model, the message from
|
|
1414
|
+
this method will simply note this fact without reporting on mis-matched layers.
|
|
1415
|
+
|
|
1416
|
+
Args:
|
|
1417
|
+
raise_exception: Boolean to note whether a ValueError should be raised
|
|
1418
|
+
if mis-matched interior construction layers are found. (Default: True).
|
|
1419
|
+
detailed: Boolean for whether the returned object is a detailed list of
|
|
1420
|
+
dicts with error info or a string with a message. (Default: False).
|
|
1421
|
+
|
|
1422
|
+
Returns:
|
|
1423
|
+
A string with the message or a list with a dictionary if detailed is True.
|
|
1424
|
+
"""
|
|
1425
|
+
detailed = False if raise_exception else detailed
|
|
1426
|
+
# first gather all interior faces in the model and their adjacent object
|
|
1427
|
+
adj_constr, base_objs, adj_ids = [], [], []
|
|
1428
|
+
for face in self.host.faces:
|
|
1429
|
+
if isinstance(face.boundary_condition, Surface):
|
|
1430
|
+
const = face.properties.energy.construction
|
|
1431
|
+
if not isinstance(const, AirBoundaryConstruction):
|
|
1432
|
+
adj_constr.append(face.properties.energy.construction)
|
|
1433
|
+
base_objs.append(face)
|
|
1434
|
+
adj_ids.append(face.boundary_condition.boundary_condition_object)
|
|
1435
|
+
# next, get the adjacent objects
|
|
1436
|
+
try:
|
|
1437
|
+
adj_faces = self.host.faces_by_identifier(adj_ids)
|
|
1438
|
+
except ValueError as e: # the model has missing adjacencies
|
|
1439
|
+
if detailed: # the user will get a more detailed error in honeybee-core
|
|
1440
|
+
return []
|
|
1441
|
+
else:
|
|
1442
|
+
msg = 'Matching adjacent constructions could not be verified because ' \
|
|
1443
|
+
'of missing adjacencies in the model. \n{}'.format(e)
|
|
1444
|
+
if raise_exception:
|
|
1445
|
+
raise ValueError(msg)
|
|
1446
|
+
return msg
|
|
1447
|
+
# loop through the adjacent face pairs and report if materials are not matched
|
|
1448
|
+
full_msgs, reported_items = [], set()
|
|
1449
|
+
for adj_c, base_f, adj_f in zip(adj_constr, base_objs, adj_faces):
|
|
1450
|
+
if (base_f.identifier, adj_f.identifier) in reported_items:
|
|
1451
|
+
continue
|
|
1452
|
+
try:
|
|
1453
|
+
rev_mat = tuple(reversed(adj_f.properties.energy.construction.materials))
|
|
1454
|
+
except AttributeError:
|
|
1455
|
+
rev_mat = None
|
|
1456
|
+
if not adj_c.materials == rev_mat:
|
|
1457
|
+
f_msg = 'Face "{}" with construction "{}" does not have material ' \
|
|
1458
|
+
'layers matching in reversed order with its adjacent pair "{}" '\
|
|
1459
|
+
'with construction "{}".'.format(
|
|
1460
|
+
base_f.full_id,
|
|
1461
|
+
base_f.properties.energy.construction.identifier,
|
|
1462
|
+
adj_f.full_id,
|
|
1463
|
+
adj_f.properties.energy.construction.identifier
|
|
1464
|
+
)
|
|
1465
|
+
f_msg = self.host._validation_message_child(
|
|
1466
|
+
f_msg, base_f, detailed, '020201', 'Energy',
|
|
1467
|
+
error_type='Mismatched Adjacent Constructions')
|
|
1468
|
+
if detailed:
|
|
1469
|
+
f_msg['element_id'].append(adj_f.identifier)
|
|
1470
|
+
f_msg['element_name'].append(adj_f.display_name)
|
|
1471
|
+
parents = []
|
|
1472
|
+
rel_obj = adj_f
|
|
1473
|
+
while getattr(rel_obj, '_parent', None) is not None:
|
|
1474
|
+
rel_obj = getattr(rel_obj, '_parent')
|
|
1475
|
+
par_dict = {
|
|
1476
|
+
'parent_type': rel_obj.__class__.__name__,
|
|
1477
|
+
'id': rel_obj.identifier,
|
|
1478
|
+
'name': rel_obj.display_name
|
|
1479
|
+
}
|
|
1480
|
+
parents.append(par_dict)
|
|
1481
|
+
f_msg['parents'].append(parents)
|
|
1482
|
+
full_msgs.append(f_msg)
|
|
1483
|
+
reported_items.add((adj_f.identifier, base_f.identifier))
|
|
1484
|
+
full_msg = full_msgs if detailed else '\n'.join(full_msgs)
|
|
1485
|
+
if raise_exception and len(full_msgs) != 0:
|
|
1486
|
+
raise ValueError(full_msg)
|
|
1487
|
+
return full_msg
|
|
1488
|
+
|
|
1489
|
+
def resolve_zones(self):
|
|
1490
|
+
"""Resolve properties of Rooms across each zone such that E+ can simulate them.
|
|
1491
|
+
|
|
1492
|
+
This method is intended as a pre-step before translating the model to EnergyPlus
|
|
1493
|
+
or OpenStudio but it will mutate the Rooms of the model. So it is often
|
|
1494
|
+
best to duplicate() the model before running this method such that existing
|
|
1495
|
+
room and zone information is not lost. After running this method, the
|
|
1496
|
+
following will be true.
|
|
1497
|
+
|
|
1498
|
+
1. All Zone names in the model will use only ASCII characters that are
|
|
1499
|
+
accepted by EnergyPlus.
|
|
1500
|
+
|
|
1501
|
+
2. If not all Rooms of a zone have the same thermostat setpoint, these will
|
|
1502
|
+
be edited such that the strictest setpoint of the rooms at each timestep
|
|
1503
|
+
governs the thermostat setpoint of the whole zone. So all Rooms in each
|
|
1504
|
+
zone will have their setpoint property re-assigned.
|
|
1505
|
+
|
|
1506
|
+
3. If not all Rooms of a zone have the same ventilation requirements, these
|
|
1507
|
+
will be edited such that the ventilation requirement of the zone is equal
|
|
1508
|
+
to the sum of the individual room ventilation requirements. To total outdoor
|
|
1509
|
+
flow rates are added across the Rooms, flow-per-floor area gets recomputed using
|
|
1510
|
+
the floor area of each Room, ACH flow rates get recomputed using the volume
|
|
1511
|
+
of each Room, and the flow-per-person is set to the highest value of
|
|
1512
|
+
the Rooms in the zone. If all Rooms have ventilation schedules, then these
|
|
1513
|
+
are recomputed such that the highest value governs at each timestep.
|
|
1514
|
+
|
|
1515
|
+
4. If not all of the Rooms of the zone have the same multiplier, then the
|
|
1516
|
+
highest multiplier of the rooms in the zone sets the multiplier of the zone.
|
|
1517
|
+
|
|
1518
|
+
5. If not all Rooms of the zone have the same exclude_floor_area value,
|
|
1519
|
+
then the rooms with excluded floor area will be pulled out into separate
|
|
1520
|
+
zones from the ones that have included floor area, which will retain the
|
|
1521
|
+
original zone name.
|
|
1522
|
+
|
|
1523
|
+
Returns:
|
|
1524
|
+
A tuple with two elements.
|
|
1525
|
+
|
|
1526
|
+
- single_zones -- A list of all rooms in the model that have their
|
|
1527
|
+
room identifier equal to their zone identifier, in which case
|
|
1528
|
+
they can be translated without the need to distinguish spaces
|
|
1529
|
+
from zones.
|
|
1530
|
+
|
|
1531
|
+
- zone_dict -- A dictionary with (clean ASCII) identifiers of zones
|
|
1532
|
+
as keys a list of properties defining the zone as values, including
|
|
1533
|
+
the Room objects, zone setpoints, and zone ventilation. These
|
|
1534
|
+
can be used to write zones into the destination format.
|
|
1535
|
+
"""
|
|
1536
|
+
# set up variables to be returned from this method
|
|
1537
|
+
single_zones, zone_dict = [], {}
|
|
1538
|
+
zone_ids = {}
|
|
1539
|
+
|
|
1540
|
+
# adjust setpoints, ventilation, multipliers and exclude_floor_area
|
|
1541
|
+
for zone_name, rooms in self.host.zone_dict.items():
|
|
1542
|
+
if len(rooms) == 1: # simple case of one room in the zone
|
|
1543
|
+
room = rooms[0]
|
|
1544
|
+
if rooms[0].identifier == zone_name:
|
|
1545
|
+
single_zones.append(rooms[0])
|
|
1546
|
+
continue # room can be written without the need for space vs. zone
|
|
1547
|
+
zone_id = clean_and_number_ep_string(zone_name, zone_ids)
|
|
1548
|
+
room.zone = zone_id
|
|
1549
|
+
ceil_hgt = room.geometry.max.z - room.geometry.min.z
|
|
1550
|
+
inc_flr = 'No' if room.exclude_floor_area else 'Yes'
|
|
1551
|
+
z_prop = (room.multiplier, ceil_hgt, room.volume, room.floor_area, inc_flr)
|
|
1552
|
+
set_pt = room.properties.energy.setpoint
|
|
1553
|
+
vent = room.properties.energy.ventilation
|
|
1554
|
+
else: # resolve properties across the rooms of the zone
|
|
1555
|
+
zone_id = clean_and_number_ep_string(zone_name, zone_ids)
|
|
1556
|
+
# first determine whether zone must be split for excluded floor areas
|
|
1557
|
+
if all(not r.exclude_floor_area for r in rooms):
|
|
1558
|
+
inc_flr = 'Yes'
|
|
1559
|
+
elif all(r.exclude_floor_area for r in rooms):
|
|
1560
|
+
inc_flr = 'No'
|
|
1561
|
+
else: # split off excluded rooms into separate zones
|
|
1562
|
+
inc_flr, ex_r_i = 'Yes', []
|
|
1563
|
+
for i, r in enumerate(rooms):
|
|
1564
|
+
if r.exclude_floor_area:
|
|
1565
|
+
ex_r_i.append(i)
|
|
1566
|
+
r.zone = None # remove the room from the zone
|
|
1567
|
+
single_zones.append(r)
|
|
1568
|
+
for ex_i in reversed(ex_r_i):
|
|
1569
|
+
rooms.pop(ex_i)
|
|
1570
|
+
# determine the other zone geometry properties
|
|
1571
|
+
min_z = min(r.min.z for r in rooms)
|
|
1572
|
+
max_z = max(r.max.z for r in rooms)
|
|
1573
|
+
ceil_hgt = max_z - min_z
|
|
1574
|
+
mult = max(r.multiplier for r in rooms)
|
|
1575
|
+
vol = sum(r.volume for r in rooms)
|
|
1576
|
+
flr_area = sum(r.floor_area for r in rooms)
|
|
1577
|
+
z_prop = (mult, ceil_hgt, vol, flr_area, inc_flr)
|
|
1578
|
+
# determine the setpoint
|
|
1579
|
+
setpoints = [r.properties.energy.setpoint for r in rooms]
|
|
1580
|
+
setpoints = [s for s in setpoints if s is not None]
|
|
1581
|
+
if len(setpoints) == 0:
|
|
1582
|
+
set_pt = None # no setpoint object to be created
|
|
1583
|
+
elif len(setpoints) == 1:
|
|
1584
|
+
set_pt = setpoints[0] # no need to create a new setpoint object
|
|
1585
|
+
else:
|
|
1586
|
+
setpoints = list(set(setpoints))
|
|
1587
|
+
if len(setpoints) == 1:
|
|
1588
|
+
set_pt = setpoints[0] # no need to create a new setpoint object
|
|
1589
|
+
else:
|
|
1590
|
+
set_pt = setpoints[0].strictest(
|
|
1591
|
+
'{}_SetPt'.format(zone_id), setpoints)
|
|
1592
|
+
# determine the ventilation
|
|
1593
|
+
new_vent = False
|
|
1594
|
+
vents = [r.properties.energy.ventilation for r in rooms]
|
|
1595
|
+
if all(v is None for v in vents):
|
|
1596
|
+
vent = None
|
|
1597
|
+
elif len(set(vents)) == 1 and vents[0].flow_per_zone == 0.0:
|
|
1598
|
+
vent = vents[0] # no need to make a new custom ventilation object
|
|
1599
|
+
else:
|
|
1600
|
+
new_vent = True
|
|
1601
|
+
v_obj = [v for v in vents if v is not None][0]
|
|
1602
|
+
vent = v_obj.combine_room_ventilations(
|
|
1603
|
+
'{}_Vent'.format(zone_id), rooms)
|
|
1604
|
+
# edit the rooms so that the setpoint and ventilation are consistent
|
|
1605
|
+
for i, room in enumerate(rooms):
|
|
1606
|
+
room.zone = zone_id
|
|
1607
|
+
room.properties.energy.setpoint = set_pt
|
|
1608
|
+
if new_vent:
|
|
1609
|
+
room.properties.energy.ventilation = vent
|
|
1610
|
+
if room.identifier == zone_id:
|
|
1611
|
+
room.identifier = '{}_Space{}'.format(room.identifier, i)
|
|
1612
|
+
|
|
1613
|
+
# add to the dictionary of zone objects to be created
|
|
1614
|
+
zone_dict[zone_id] = (rooms, z_prop, set_pt, vent)
|
|
1615
|
+
|
|
1616
|
+
# return the list of single zones and the zone_dict
|
|
1617
|
+
return single_zones, zone_dict
|
|
1618
|
+
|
|
1619
|
+
def sync_detailed_hvac_ids(self, room_map):
|
|
1620
|
+
"""Sync room identifiers in DetailedHVAC with rooms that had their IDs changed.
|
|
1621
|
+
|
|
1622
|
+
This is useful after running the Model.reset_ids() command to ensure that
|
|
1623
|
+
the bi-directional Room references between DetailedHVAC and Honeybee Rooms
|
|
1624
|
+
is correct.
|
|
1625
|
+
|
|
1626
|
+
Args:
|
|
1627
|
+
room_map: A dictionary that relates the original Rooms identifiers (keys)
|
|
1628
|
+
to the new identifiers (values) of the Rooms in the Model.
|
|
1629
|
+
"""
|
|
1630
|
+
for hvac in self.hvacs:
|
|
1631
|
+
if isinstance(hvac, DetailedHVAC):
|
|
1632
|
+
hvac.sync_room_ids(room_map)
|
|
1633
|
+
|
|
1634
|
+
def reset_resource_ids(
|
|
1635
|
+
self, reset_materials=True, reset_constructions=True,
|
|
1636
|
+
reset_construction_sets=True, reset_schedules=True, reset_programs=True):
|
|
1637
|
+
"""Reset the identifiers of energy resource objects in this Model.
|
|
1638
|
+
|
|
1639
|
+
Note that this method may have unintended consequences if the resources
|
|
1640
|
+
assigned to this Model instance are also being used by another Model
|
|
1641
|
+
instance that exists in the current Python session. In this case,
|
|
1642
|
+
running this method will result in the resource identifiers of the
|
|
1643
|
+
other Model also being reset.
|
|
1644
|
+
|
|
1645
|
+
This method is useful when human-readable names are needed when the model
|
|
1646
|
+
is exported to other formats like IDF and OSM. Cases of duplicate IDs
|
|
1647
|
+
resulting from non-unique names will be resolved by adding integers
|
|
1648
|
+
to the ends of the new IDs that are derived from the name.
|
|
1649
|
+
|
|
1650
|
+
Args:
|
|
1651
|
+
reset_materials: Boolean to note whether the IDs of all materials in
|
|
1652
|
+
the model should be reset or kept. (Default: True).
|
|
1653
|
+
reset_constructions: Boolean to note whether the IDs of all constructions
|
|
1654
|
+
in the model should be reset or kept. (Default: True).
|
|
1655
|
+
reset_construction_sets: Boolean to note whether the IDs of all construction
|
|
1656
|
+
sets in the model should be reset or kept. (Default: True).
|
|
1657
|
+
reset_schedules: Boolean to note whether the IDs of all schedules
|
|
1658
|
+
in the model should be reset or kept. (Default: True).
|
|
1659
|
+
reset_programs: Boolean to note whether the IDs of all program
|
|
1660
|
+
types in the model should be reset or kept. (Default: True).
|
|
1661
|
+
|
|
1662
|
+
Returns:
|
|
1663
|
+
A dictionary with the original identifiers of resources as keys and the
|
|
1664
|
+
edited resource objects as values. This can be used to set the identifiers
|
|
1665
|
+
of the objects back to the original value after this method has been
|
|
1666
|
+
run and any other routines have been performed. This will help prevent
|
|
1667
|
+
unintended consequences of changing the resource identifiers in the
|
|
1668
|
+
global resource library or in other Models that may be using the same
|
|
1669
|
+
resource object instances.
|
|
1670
|
+
"""
|
|
1671
|
+
# set up the dictionaries used to check for uniqueness
|
|
1672
|
+
res_func = clean_and_number_ep_string
|
|
1673
|
+
mat_dict, con_dict, con_set_dict = {}, {}, {}
|
|
1674
|
+
sch_dict, sch_day_dict, prog_dict = {}, {}, {}
|
|
1675
|
+
ppl_dict, lgt_dict, equip_dict, hw_dict, inf_dict, vent_dict, spt_dict = \
|
|
1676
|
+
{}, {}, {}, {}, {}, {}, {}
|
|
1677
|
+
resource_map = {}
|
|
1678
|
+
|
|
1679
|
+
# change the identifiers of the materials
|
|
1680
|
+
if reset_materials:
|
|
1681
|
+
for mat in self.materials:
|
|
1682
|
+
mat.unlock()
|
|
1683
|
+
resource_map[mat.identifier] = mat
|
|
1684
|
+
mat.identifier = res_func(mat.display_name, mat_dict)
|
|
1685
|
+
mat.lock()
|
|
1686
|
+
|
|
1687
|
+
# change the identifiers of the constructions
|
|
1688
|
+
if reset_constructions:
|
|
1689
|
+
for con in self.constructions:
|
|
1690
|
+
con.unlock()
|
|
1691
|
+
resource_map[con.identifier] = con
|
|
1692
|
+
con.identifier = res_func(con.display_name, con_dict)
|
|
1693
|
+
con.lock()
|
|
1694
|
+
|
|
1695
|
+
# change the identifiers of the construction_sets
|
|
1696
|
+
if reset_construction_sets:
|
|
1697
|
+
for cs in self.construction_sets:
|
|
1698
|
+
cs.unlock()
|
|
1699
|
+
resource_map[cs.identifier] = cs
|
|
1700
|
+
cs.identifier = res_func(cs.display_name, con_set_dict)
|
|
1701
|
+
cs.lock()
|
|
1702
|
+
|
|
1703
|
+
# change the identifiers of the schedules
|
|
1704
|
+
if reset_schedules:
|
|
1705
|
+
sch_skip = ('Seated Adult Activity', 'HumidNoLimit', 'DeHumidNoLimit')
|
|
1706
|
+
for sch in self.schedules:
|
|
1707
|
+
if sch.identifier in sch_skip:
|
|
1708
|
+
continue
|
|
1709
|
+
sch.unlock()
|
|
1710
|
+
sch.identifier = res_func(sch.display_name, sch_dict)
|
|
1711
|
+
resource_map[sch.identifier] = sch
|
|
1712
|
+
if isinstance(sch, ScheduleRuleset):
|
|
1713
|
+
for day_sch in sch.day_schedules:
|
|
1714
|
+
day_sch.unlock()
|
|
1715
|
+
resource_map[day_sch.identifier] = day_sch
|
|
1716
|
+
day_sch.identifier = res_func(day_sch.display_name, sch_day_dict)
|
|
1717
|
+
day_sch.lock()
|
|
1718
|
+
sch.lock()
|
|
1719
|
+
|
|
1720
|
+
# change the identifiers of the program
|
|
1721
|
+
if reset_programs:
|
|
1722
|
+
for prg in self.program_types:
|
|
1723
|
+
prg.unlock()
|
|
1724
|
+
resource_map[prg.identifier] = prg
|
|
1725
|
+
prg.identifier = res_func(prg.display_name, prog_dict)
|
|
1726
|
+
if prg.people is not None:
|
|
1727
|
+
resource_map[prg.people.identifier] = prg.people
|
|
1728
|
+
prg.people.identifier = res_func(prg.people.display_name, ppl_dict)
|
|
1729
|
+
if prg.lighting is not None:
|
|
1730
|
+
resource_map[prg.lighting.identifier] = prg.lighting
|
|
1731
|
+
prg.lighting.identifier = \
|
|
1732
|
+
res_func(prg.lighting.display_name, lgt_dict)
|
|
1733
|
+
if prg.electric_equipment is not None:
|
|
1734
|
+
resource_map[prg.electric_equipment.identifier] = \
|
|
1735
|
+
prg.electric_equipment
|
|
1736
|
+
prg.electric_equipment.identifier = \
|
|
1737
|
+
res_func(prg.electric_equipment.display_name, equip_dict)
|
|
1738
|
+
if prg.gas_equipment is not None:
|
|
1739
|
+
resource_map[prg.gas_equipment.identifier] = \
|
|
1740
|
+
prg.gas_equipment
|
|
1741
|
+
prg.gas_equipment.identifier = \
|
|
1742
|
+
res_func(prg.gas_equipment.display_name, equip_dict)
|
|
1743
|
+
if prg.service_hot_water is not None:
|
|
1744
|
+
resource_map[prg.service_hot_water.identifier] = \
|
|
1745
|
+
prg.service_hot_water
|
|
1746
|
+
prg.service_hot_water.identifier = \
|
|
1747
|
+
res_func(prg.service_hot_water.display_name, hw_dict)
|
|
1748
|
+
if prg.infiltration is not None:
|
|
1749
|
+
resource_map[prg.infiltration.identifier] = prg.infiltration
|
|
1750
|
+
prg.infiltration.identifier = \
|
|
1751
|
+
res_func(prg.infiltration.display_name, inf_dict)
|
|
1752
|
+
if prg.ventilation is not None:
|
|
1753
|
+
resource_map[prg.ventilation.identifier] = prg.ventilation
|
|
1754
|
+
prg.ventilation.identifier = \
|
|
1755
|
+
res_func(prg.ventilation.display_name, vent_dict)
|
|
1756
|
+
if prg.setpoint is not None:
|
|
1757
|
+
resource_map[prg.setpoint.identifier] = prg.setpoint
|
|
1758
|
+
prg.setpoint.identifier = \
|
|
1759
|
+
res_func(prg.setpoint.display_name, spt_dict)
|
|
1760
|
+
prg.lock()
|
|
1761
|
+
|
|
1762
|
+
return resource_map
|
|
1763
|
+
|
|
1764
|
+
@staticmethod
|
|
1765
|
+
def restore_resource_ids(resource_map):
|
|
1766
|
+
"""Restore the identifiers of resource objects after resetting them.
|
|
1767
|
+
|
|
1768
|
+
This can be used to set the identifiers of the objects back to the original
|
|
1769
|
+
value after the reset_resource_ids() method was called. This will help prevent
|
|
1770
|
+
unintended consequences of changing the resource identifiers in the
|
|
1771
|
+
global resource library or in other Models that may be using the same
|
|
1772
|
+
resource object instances.
|
|
1773
|
+
|
|
1774
|
+
Args:
|
|
1775
|
+
resource_map: A dictionary with the original identifiers of resources
|
|
1776
|
+
as keys and the edited resource objects as values. This type of
|
|
1777
|
+
dictionary is output from the reset_resource_ids method.
|
|
1778
|
+
"""
|
|
1779
|
+
for orignal_id, res_obj in resource_map.items():
|
|
1780
|
+
res_obj.unlock()
|
|
1781
|
+
res_obj.identifier = orignal_id
|
|
1782
|
+
res_obj.lock()
|
|
1783
|
+
|
|
1784
|
+
def apply_properties_from_dict(self, data):
|
|
1785
|
+
"""Apply the energy properties of a dictionary to the host Model of this object.
|
|
1786
|
+
|
|
1787
|
+
Args:
|
|
1788
|
+
data: A dictionary representation of an entire honeybee-core Model.
|
|
1789
|
+
Note that this dictionary must have ModelEnergyProperties in order
|
|
1790
|
+
for this method to successfully apply the energy properties.
|
|
1791
|
+
"""
|
|
1792
|
+
assert 'energy' in data['properties'], \
|
|
1793
|
+
'Dictionary possesses no ModelEnergyProperties.'
|
|
1794
|
+
_, constructions, construction_sets, _, schedules, program_types, hvacs, shws = \
|
|
1795
|
+
self.load_properties_from_dict(data)
|
|
1796
|
+
|
|
1797
|
+
# collect lists of energy property dictionaries
|
|
1798
|
+
room_e_dicts, face_e_dicts, shd_e_dicts, ap_e_dicts, dr_e_dicts = \
|
|
1799
|
+
model_extension_dicts(data, 'energy', [], [], [], [], [])
|
|
1800
|
+
|
|
1801
|
+
# apply energy properties to objects using the energy property dictionaries
|
|
1802
|
+
for room, r_dict in zip(self.host.rooms, room_e_dicts):
|
|
1803
|
+
if r_dict is not None:
|
|
1804
|
+
room.properties.energy.apply_properties_from_dict(
|
|
1805
|
+
r_dict, construction_sets, program_types, hvacs, shws,
|
|
1806
|
+
schedules, constructions)
|
|
1807
|
+
for face, f_dict in zip(self.host.faces, face_e_dicts):
|
|
1808
|
+
if f_dict is not None:
|
|
1809
|
+
face.properties.energy.apply_properties_from_dict(f_dict, constructions)
|
|
1810
|
+
for aperture, a_dict in zip(self.host.apertures, ap_e_dicts):
|
|
1811
|
+
if a_dict is not None:
|
|
1812
|
+
aperture.properties.energy.apply_properties_from_dict(
|
|
1813
|
+
a_dict, constructions)
|
|
1814
|
+
for door, d_dict in zip(self.host.doors, dr_e_dicts):
|
|
1815
|
+
if d_dict is not None:
|
|
1816
|
+
door.properties.energy.apply_properties_from_dict(
|
|
1817
|
+
d_dict, constructions)
|
|
1818
|
+
all_shades = self.host.shades + self.host._shade_meshes
|
|
1819
|
+
for shade, s_dict in zip(all_shades, shd_e_dicts):
|
|
1820
|
+
if s_dict is not None:
|
|
1821
|
+
shade.properties.energy.apply_properties_from_dict(
|
|
1822
|
+
s_dict, constructions, schedules)
|
|
1823
|
+
|
|
1824
|
+
energy_prop = data['properties']['energy']
|
|
1825
|
+
# re-serialize the ventilation_simulation_control
|
|
1826
|
+
if 'ventilation_simulation_control' in energy_prop and \
|
|
1827
|
+
energy_prop['ventilation_simulation_control'] is not None:
|
|
1828
|
+
self.ventilation_simulation_control = \
|
|
1829
|
+
VentilationSimulationControl.from_dict(
|
|
1830
|
+
energy_prop['ventilation_simulation_control'])
|
|
1831
|
+
# re-serialize the electric_load_center
|
|
1832
|
+
if 'electric_load_center' in energy_prop and \
|
|
1833
|
+
energy_prop['electric_load_center'] is not None:
|
|
1834
|
+
self.electric_load_center = \
|
|
1835
|
+
ElectricLoadCenter.from_dict(energy_prop['electric_load_center'])
|
|
1836
|
+
|
|
1837
|
+
def to_dict(self):
|
|
1838
|
+
"""Return Model energy properties as a dictionary."""
|
|
1839
|
+
base = {'energy': {'type': 'ModelEnergyProperties'}}
|
|
1840
|
+
|
|
1841
|
+
# add all materials, constructions and construction sets to the dictionary
|
|
1842
|
+
schs = self._add_constr_type_objs_to_dict(base)
|
|
1843
|
+
|
|
1844
|
+
# add all schedule type limits, schedules, program types, hvacs, shws to the dict
|
|
1845
|
+
self._add_sched_type_objs_to_dict(base, schs)
|
|
1846
|
+
|
|
1847
|
+
# add ventilation_simulation_control
|
|
1848
|
+
base['energy']['ventilation_simulation_control'] = \
|
|
1849
|
+
self.ventilation_simulation_control.to_dict()
|
|
1850
|
+
# add electric_load_center
|
|
1851
|
+
base['energy']['electric_load_center'] = self.electric_load_center.to_dict()
|
|
1852
|
+
|
|
1853
|
+
return base
|
|
1854
|
+
|
|
1855
|
+
def add_autocal_properties_to_dict(self, data, exclude_hole_verts=False):
|
|
1856
|
+
"""Add auto-calculated energy properties to a Model dictionary.
|
|
1857
|
+
|
|
1858
|
+
This includes Room volumes, ceiling heights, and (in the case of Faces
|
|
1859
|
+
and Shades with holes) vertices. These properties are used
|
|
1860
|
+
in translation from Honeybee to OpenStudio to ensure that the properties
|
|
1861
|
+
in Honeybee are aligned with those in OSM, IDF, and gbXML.
|
|
1862
|
+
|
|
1863
|
+
Note that the host model must be in Meters in order for the
|
|
1864
|
+
added properties to be in the correct units system.
|
|
1865
|
+
|
|
1866
|
+
Args:
|
|
1867
|
+
data: A dictionary representation of an entire honeybee-core Model.
|
|
1868
|
+
exclude_hole_verts: A boolean to note whether hole vertices should
|
|
1869
|
+
be excluded from the translation. This is useful only for
|
|
1870
|
+
very particular interfaces that do not support holes like
|
|
1871
|
+
TRACE. (Default: False).
|
|
1872
|
+
"""
|
|
1873
|
+
# check that the host model is in meters and add geometry properties
|
|
1874
|
+
assert self.host.units == 'Meters', 'Host model units must be Meters to use ' \
|
|
1875
|
+
'add_autocal_properties_to_dict. Not {}.'.format(self.host.units)
|
|
1876
|
+
# process rooms to add volume, ceiling height, and faces with holes
|
|
1877
|
+
if len(self.host.rooms) != 0:
|
|
1878
|
+
for room, room_dict in zip(self.host.rooms, data['rooms']):
|
|
1879
|
+
room_dict['ceiling_height'] = room.geometry.max.z - room.geometry.min.z
|
|
1880
|
+
room_dict['volume'] = room.volume
|
|
1881
|
+
self._add_shade_vertices(room, room_dict)
|
|
1882
|
+
for face, face_dict in zip(room._faces, room_dict['faces']):
|
|
1883
|
+
self._add_shade_vertices(face, face_dict)
|
|
1884
|
+
if face.geometry.has_holes and not exclude_hole_verts:
|
|
1885
|
+
ul_verts = face.upper_left_vertices
|
|
1886
|
+
if isinstance(face.boundary_condition, Surface):
|
|
1887
|
+
# check if the first vertex is the upper-left vertex
|
|
1888
|
+
pt1, found_i = ul_verts[0], False
|
|
1889
|
+
for pt in ul_verts[1:]:
|
|
1890
|
+
if pt == pt1:
|
|
1891
|
+
found_i = True
|
|
1892
|
+
break
|
|
1893
|
+
if found_i: # reorder the vertices to have boundary first
|
|
1894
|
+
ul_verts = reversed(ul_verts)
|
|
1895
|
+
face_dict['geometry']['vertices'] = \
|
|
1896
|
+
[[round(v, 3) for v in pt] for pt in ul_verts]
|
|
1897
|
+
if len(face._apertures) != 0:
|
|
1898
|
+
for ap, ap_dict in zip(face._apertures, face_dict['apertures']):
|
|
1899
|
+
self._add_shade_vertices(ap, ap_dict)
|
|
1900
|
+
if ap.properties.energy.construction.has_frame:
|
|
1901
|
+
ap_dict['properties']['energy']['frame'] = \
|
|
1902
|
+
ap.properties.energy.construction.frame.identifier
|
|
1903
|
+
if len(face._doors) != 0:
|
|
1904
|
+
for dr, dr_dict in zip(face._doors, face_dict['doors']):
|
|
1905
|
+
self._add_shade_vertices(dr, dr_dict)
|
|
1906
|
+
if dr.properties.energy.construction.has_frame:
|
|
1907
|
+
dr_dict['properties']['energy']['frame'] = \
|
|
1908
|
+
dr.properties.energy.construction.frame.identifier
|
|
1909
|
+
# process orphaned shades to add geometries with holes
|
|
1910
|
+
if len(self.host._orphaned_shades) != 0:
|
|
1911
|
+
for shd, shd_d in zip(self.host._orphaned_shades, data['orphaned_shades']):
|
|
1912
|
+
if shd.geometry.has_holes:
|
|
1913
|
+
shd_d['geometry']['vertices'] = \
|
|
1914
|
+
[pt.to_array() for pt in shd.upper_left_vertices]
|
|
1915
|
+
# process orphaned faces for punched geometry and aperture transmittance
|
|
1916
|
+
trans_orphaned = False
|
|
1917
|
+
if len(self.host._orphaned_faces) != 0:
|
|
1918
|
+
for shd, shd_d in zip(self.host._orphaned_faces, data['orphaned_faces']):
|
|
1919
|
+
shd_d['geometry']['vertices'] = \
|
|
1920
|
+
[pt.to_array() for pt in shd.punched_vertices]
|
|
1921
|
+
if 'apertures' in shd_d:
|
|
1922
|
+
for ap, ap_d in zip(shd._apertures, shd_d['apertures']):
|
|
1923
|
+
ap_con = ap.properties.energy.construction
|
|
1924
|
+
ap_d['transmit'] = str(round(ap_con.solar_transmittance, 3))
|
|
1925
|
+
trans_orphaned = True
|
|
1926
|
+
if 'doors' in shd_d:
|
|
1927
|
+
for dr, dr_d in zip(shd._doors, shd_d['doors']):
|
|
1928
|
+
if dr.is_glass:
|
|
1929
|
+
dr_con = dr.properties.energy.construction
|
|
1930
|
+
dr_d['transmit'] = str(round(dr_con.solar_transmittance, 3))
|
|
1931
|
+
trans_orphaned = True
|
|
1932
|
+
# add auto-generated transmittance schedules for transparent orphaned objects
|
|
1933
|
+
if len(self.host._orphaned_apertures) != 0:
|
|
1934
|
+
trans_orphaned = True
|
|
1935
|
+
ap_iter = zip(self.host._orphaned_apertures, data['orphaned_apertures'])
|
|
1936
|
+
for ap, ap_d in ap_iter:
|
|
1937
|
+
ap_con = ap.properties.energy.construction
|
|
1938
|
+
ap_d['transmit'] = str(round(ap_con.solar_transmittance, 3))
|
|
1939
|
+
if len(self.host._orphaned_doors) != 0:
|
|
1940
|
+
for dr, dr_d in zip(self.host._orphaned_doors, data['orphaned_doors']):
|
|
1941
|
+
if dr.is_glass:
|
|
1942
|
+
dr_con = dr.properties.energy.construction
|
|
1943
|
+
dr_d['transmit'] = str(round(dr_con.solar_transmittance, 3))
|
|
1944
|
+
trans_orphaned = True
|
|
1945
|
+
# add transmittance schedules to the model if needed
|
|
1946
|
+
if trans_orphaned:
|
|
1947
|
+
for sched in self.orphaned_trans_schedules:
|
|
1948
|
+
data['properties']['energy']['schedules'].append(
|
|
1949
|
+
sched.to_dict(abridged=True))
|
|
1950
|
+
# if there are any detailed HVACs, add the path to the ironbug installation
|
|
1951
|
+
# this is the only reliable way to pass the path to the honeybee-openstudio gem
|
|
1952
|
+
if folders.ironbug_exe is not None:
|
|
1953
|
+
for hvac_dict in data['properties']['energy']['hvacs']:
|
|
1954
|
+
if hvac_dict['type'] == 'DetailedHVAC':
|
|
1955
|
+
hvac_dict['ironbug_exe'] = folders.ironbug_exe
|
|
1956
|
+
# CWM: there is a very weird bug in OpenStudio
|
|
1957
|
+
# program types cannot have the same name as the model (Building) in OpenStudio
|
|
1958
|
+
model_id = data['identifier']
|
|
1959
|
+
for prog in data['properties']['energy']['program_types']:
|
|
1960
|
+
if prog['identifier'] == model_id:
|
|
1961
|
+
data['identifier'] = '{}1'.format(data['identifier'])
|
|
1962
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
1963
|
+
data['display_name'] = '{}1'.format(data['display_name'])
|
|
1964
|
+
break
|
|
1965
|
+
|
|
1966
|
+
@staticmethod
|
|
1967
|
+
def _add_shade_vertices(obj, obj_dict):
|
|
1968
|
+
if len(obj._outdoor_shades) != 0:
|
|
1969
|
+
for shd, shd_dict in zip(obj._outdoor_shades, obj_dict['outdoor_shades']):
|
|
1970
|
+
if shd.geometry.has_holes:
|
|
1971
|
+
shd_dict['geometry']['vertices'] = \
|
|
1972
|
+
[pt.to_array() for pt in shd.upper_left_vertices]
|
|
1973
|
+
|
|
1974
|
+
def simplify_window_constructions_in_dict(self, data):
|
|
1975
|
+
"""Convert all window constructions in a model dictionary to SimpleGlazSys.
|
|
1976
|
+
|
|
1977
|
+
This is useful when translating to gbXML and other formats that do not
|
|
1978
|
+
support layered window constructions.
|
|
1979
|
+
|
|
1980
|
+
Args:
|
|
1981
|
+
data: A dictionary representation of an entire honeybee-core Model.
|
|
1982
|
+
"""
|
|
1983
|
+
# add the window construction u-factors
|
|
1984
|
+
mat_dicts = data['properties']['energy']['materials']
|
|
1985
|
+
con_dicts = data['properties']['energy']['constructions']
|
|
1986
|
+
w_cons = (WindowConstruction, WindowConstructionShade, WindowConstructionDynamic)
|
|
1987
|
+
for i, con in enumerate(self.constructions):
|
|
1988
|
+
if isinstance(con, WindowConstruction):
|
|
1989
|
+
new_con = con.to_simple_construction()
|
|
1990
|
+
elif isinstance(con, WindowConstructionShade):
|
|
1991
|
+
new_con = con.window_construction.to_simple_construction()
|
|
1992
|
+
elif isinstance(con, WindowConstructionDynamic):
|
|
1993
|
+
new_con = con.constructions[0].to_simple_construction()
|
|
1994
|
+
if isinstance(con, w_cons):
|
|
1995
|
+
con_dicts[i] = new_con.to_dict(abridged=True)
|
|
1996
|
+
mat_dicts.append(new_con.materials[0].to_dict())
|
|
1997
|
+
|
|
1998
|
+
def duplicate(self, new_host=None):
|
|
1999
|
+
"""Get a copy of this object.
|
|
2000
|
+
|
|
2001
|
+
Args:
|
|
2002
|
+
new_host: A new Model object that hosts these properties.
|
|
2003
|
+
If None, the properties will be duplicated with the same host.
|
|
2004
|
+
"""
|
|
2005
|
+
_host = new_host or self._host
|
|
2006
|
+
return ModelEnergyProperties(
|
|
2007
|
+
_host, self._ventilation_simulation_control.duplicate(),
|
|
2008
|
+
self.electric_load_center.duplicate())
|
|
2009
|
+
|
|
2010
|
+
@staticmethod
|
|
2011
|
+
def load_properties_from_dict(data, skip_invalid=False):
|
|
2012
|
+
"""Load model energy properties of a dictionary to Python objects.
|
|
2013
|
+
|
|
2014
|
+
Loaded objects include Materials, Constructions, ConstructionSets,
|
|
2015
|
+
ScheduleTypeLimits, Schedules, and ProgramTypes.
|
|
2016
|
+
|
|
2017
|
+
The function is called when re-serializing a Model object from a dictionary
|
|
2018
|
+
to load honeybee_energy objects into their Python object form before
|
|
2019
|
+
applying them to the Model geometry.
|
|
2020
|
+
|
|
2021
|
+
Args:
|
|
2022
|
+
data: A dictionary representation of an entire honeybee-core Model.
|
|
2023
|
+
Note that this dictionary must have ModelEnergyProperties in order
|
|
2024
|
+
for this method to successfully load the energy properties.
|
|
2025
|
+
skip_invalid: A boolean to note whether objects that cannot be loaded
|
|
2026
|
+
should be ignored (True) or whether an exception should be raised
|
|
2027
|
+
about the invalid object (False). (Default: False).
|
|
2028
|
+
|
|
2029
|
+
Returns:
|
|
2030
|
+
A tuple with eight elements
|
|
2031
|
+
|
|
2032
|
+
- materials -- A dictionary with identifiers of materials as keys
|
|
2033
|
+
and Python material objects as values.
|
|
2034
|
+
|
|
2035
|
+
- constructions -- A dictionary with identifiers of constructions
|
|
2036
|
+
as keys and Python construction objects as values.
|
|
2037
|
+
|
|
2038
|
+
- construction_sets -- A dictionary with identifiers of construction
|
|
2039
|
+
sets as keys and Python construction set objects as values.
|
|
2040
|
+
|
|
2041
|
+
- schedule_type_limits -- A dictionary with identifiers of schedule
|
|
2042
|
+
type limits as keys and Python schedule type limit objects as values.
|
|
2043
|
+
|
|
2044
|
+
- schedules -- A dictionary with identifiers of schedules as keys
|
|
2045
|
+
and Python schedule objects as values.
|
|
2046
|
+
|
|
2047
|
+
- program_types -- A dictionary with identifiers of program types
|
|
2048
|
+
as keys and Python program type objects as values.
|
|
2049
|
+
|
|
2050
|
+
- hvacs -- A dictionary with identifiers of HVAC systems as keys
|
|
2051
|
+
and Python HVACSystem objects as values.
|
|
2052
|
+
|
|
2053
|
+
- shws -- A dictionary with identifiers of SHW systems as keys
|
|
2054
|
+
and Python SHWSystem objects as values.
|
|
2055
|
+
"""
|
|
2056
|
+
assert 'energy' in data['properties'], \
|
|
2057
|
+
'Dictionary possesses no ModelEnergyProperties.'
|
|
2058
|
+
|
|
2059
|
+
# process all schedule type limits in the ModelEnergyProperties dictionary
|
|
2060
|
+
schedule_type_limits = {}
|
|
2061
|
+
if 'schedule_type_limits' in data['properties']['energy'] and \
|
|
2062
|
+
data['properties']['energy']['schedule_type_limits'] is not None:
|
|
2063
|
+
for t_lim in data['properties']['energy']['schedule_type_limits']:
|
|
2064
|
+
try:
|
|
2065
|
+
schedule_type_limits[t_lim['identifier']] = \
|
|
2066
|
+
ScheduleTypeLimit.from_dict(t_lim)
|
|
2067
|
+
except Exception as e:
|
|
2068
|
+
if not skip_invalid:
|
|
2069
|
+
invalid_dict_error(t_lim, e)
|
|
2070
|
+
|
|
2071
|
+
# process all schedules in the ModelEnergyProperties dictionary
|
|
2072
|
+
schedules = {}
|
|
2073
|
+
if 'schedules' in data['properties']['energy'] and \
|
|
2074
|
+
data['properties']['energy']['schedules'] is not None:
|
|
2075
|
+
for sched in data['properties']['energy']['schedules']:
|
|
2076
|
+
try:
|
|
2077
|
+
if sched['type'] in SCHEDULE_TYPES:
|
|
2078
|
+
schedules[sched['identifier']] = dict_to_schedule(sched)
|
|
2079
|
+
else:
|
|
2080
|
+
schedules[sched['identifier']] = dict_abridged_to_schedule(
|
|
2081
|
+
sched, schedule_type_limits)
|
|
2082
|
+
except Exception as e:
|
|
2083
|
+
if not skip_invalid:
|
|
2084
|
+
invalid_dict_error(sched, e)
|
|
2085
|
+
|
|
2086
|
+
# process all materials in the ModelEnergyProperties dictionary
|
|
2087
|
+
materials = {}
|
|
2088
|
+
if 'materials' in data['properties']['energy'] and \
|
|
2089
|
+
data['properties']['energy']['materials'] is not None:
|
|
2090
|
+
for mat in data['properties']['energy']['materials']:
|
|
2091
|
+
try:
|
|
2092
|
+
materials[mat['identifier']] = dict_to_material(mat)
|
|
2093
|
+
except Exception as e:
|
|
2094
|
+
if not skip_invalid:
|
|
2095
|
+
invalid_dict_error(mat, e)
|
|
2096
|
+
|
|
2097
|
+
# process all constructions in the ModelEnergyProperties dictionary
|
|
2098
|
+
constructions = {}
|
|
2099
|
+
if 'constructions' in data['properties']['energy'] and \
|
|
2100
|
+
data['properties']['energy']['constructions'] is not None:
|
|
2101
|
+
for cnstr in data['properties']['energy']['constructions']:
|
|
2102
|
+
try:
|
|
2103
|
+
if cnstr['type'] in CONSTRUCTION_TYPES:
|
|
2104
|
+
constructions[cnstr['identifier']] = dict_to_construction(cnstr)
|
|
2105
|
+
else:
|
|
2106
|
+
constructions[cnstr['identifier']] = \
|
|
2107
|
+
dict_abridged_to_construction(cnstr, materials, schedules)
|
|
2108
|
+
except Exception as e:
|
|
2109
|
+
if not skip_invalid:
|
|
2110
|
+
invalid_dict_error(cnstr, e)
|
|
2111
|
+
|
|
2112
|
+
# process all construction sets in the ModelEnergyProperties dictionary
|
|
2113
|
+
construction_sets = {}
|
|
2114
|
+
if 'construction_sets' in data['properties']['energy'] and \
|
|
2115
|
+
data['properties']['energy']['construction_sets'] is not None:
|
|
2116
|
+
for c_set in data['properties']['energy']['construction_sets']:
|
|
2117
|
+
try:
|
|
2118
|
+
if c_set['type'] == 'ConstructionSet':
|
|
2119
|
+
construction_sets[c_set['identifier']] = \
|
|
2120
|
+
ConstructionSet.from_dict(c_set)
|
|
2121
|
+
else:
|
|
2122
|
+
construction_sets[c_set['identifier']] = \
|
|
2123
|
+
ConstructionSet.from_dict_abridged(c_set, constructions)
|
|
2124
|
+
except Exception as e:
|
|
2125
|
+
if not skip_invalid:
|
|
2126
|
+
invalid_dict_error(c_set, e)
|
|
2127
|
+
|
|
2128
|
+
# process all ProgramType in the ModelEnergyProperties dictionary
|
|
2129
|
+
program_types = {}
|
|
2130
|
+
if 'program_types' in data['properties']['energy'] and \
|
|
2131
|
+
data['properties']['energy']['program_types'] is not None:
|
|
2132
|
+
for p_typ in data['properties']['energy']['program_types']:
|
|
2133
|
+
try:
|
|
2134
|
+
if p_typ['type'] == 'ProgramType':
|
|
2135
|
+
program_types[p_typ['identifier']] = ProgramType.from_dict(p_typ)
|
|
2136
|
+
else:
|
|
2137
|
+
program_types[p_typ['identifier']] = \
|
|
2138
|
+
ProgramType.from_dict_abridged(p_typ, schedules)
|
|
2139
|
+
except Exception as e:
|
|
2140
|
+
if not skip_invalid:
|
|
2141
|
+
invalid_dict_error(p_typ, e)
|
|
2142
|
+
|
|
2143
|
+
# process all HVAC systems in the ModelEnergyProperties dictionary
|
|
2144
|
+
hvacs = {}
|
|
2145
|
+
if 'hvacs' in data['properties']['energy'] and \
|
|
2146
|
+
data['properties']['energy']['hvacs'] is not None:
|
|
2147
|
+
for hvac in data['properties']['energy']['hvacs']:
|
|
2148
|
+
hvac_class = HVAC_TYPES_DICT[hvac['type'].replace('Abridged', '')]
|
|
2149
|
+
try:
|
|
2150
|
+
hvacs[hvac['identifier']] = \
|
|
2151
|
+
hvac_class.from_dict_abridged(hvac, schedules)
|
|
2152
|
+
except Exception as e:
|
|
2153
|
+
if not skip_invalid:
|
|
2154
|
+
invalid_dict_error(hvac, e)
|
|
2155
|
+
|
|
2156
|
+
# process all SHW systems in the ModelEnergyProperties dictionary
|
|
2157
|
+
shws = {}
|
|
2158
|
+
if 'shws' in data['properties']['energy'] and \
|
|
2159
|
+
data['properties']['energy']['shws'] is not None:
|
|
2160
|
+
for shw in data['properties']['energy']['shws']:
|
|
2161
|
+
try:
|
|
2162
|
+
shws[shw['identifier']] = SHWSystem.from_dict(shw)
|
|
2163
|
+
except Exception as e:
|
|
2164
|
+
if not skip_invalid:
|
|
2165
|
+
invalid_dict_error(shw, e)
|
|
2166
|
+
|
|
2167
|
+
return materials, constructions, construction_sets, schedule_type_limits, \
|
|
2168
|
+
schedules, program_types, hvacs, shws
|
|
2169
|
+
|
|
2170
|
+
@staticmethod
|
|
2171
|
+
def dump_properties_to_dict(
|
|
2172
|
+
materials=None, constructions=None, construction_sets=None,
|
|
2173
|
+
schedule_type_limits=None, schedules=None, program_types=None,
|
|
2174
|
+
hvacs=None, shws=None):
|
|
2175
|
+
"""Get a ModelEnergyProperties dictionary from arrays of Python objects.
|
|
2176
|
+
|
|
2177
|
+
Args:
|
|
2178
|
+
materials: A list or tuple of energy material objects.
|
|
2179
|
+
constructions: A list or tuple of construction objects.
|
|
2180
|
+
construction_sets: A list or tuple of construction set objects.
|
|
2181
|
+
schedule_type_limits: A list or tuple of schedule type limit objects.
|
|
2182
|
+
schedules: A list or tuple of schedule objects.
|
|
2183
|
+
program_types: A list or tuple of program type objects.
|
|
2184
|
+
hvacs: A list or tuple of HVACSystem objects.
|
|
2185
|
+
shws: A list or tuple of SHWSystem objects.
|
|
2186
|
+
|
|
2187
|
+
Returns:
|
|
2188
|
+
data: A dictionary representation of ModelEnergyProperties. Note that
|
|
2189
|
+
all objects in this dictionary will follow the abridged schema.
|
|
2190
|
+
"""
|
|
2191
|
+
# process the input schedules and type limits
|
|
2192
|
+
type_lim = [] if schedule_type_limits is None else list(schedule_type_limits)
|
|
2193
|
+
scheds = [] if schedules is None else list(schedules)
|
|
2194
|
+
|
|
2195
|
+
# process the program types
|
|
2196
|
+
all_progs, misc_p_scheds = [], []
|
|
2197
|
+
if program_types is not None:
|
|
2198
|
+
for program in program_types:
|
|
2199
|
+
all_progs.append(program)
|
|
2200
|
+
misc_p_scheds.extend(program.schedules)
|
|
2201
|
+
|
|
2202
|
+
# process the materials, constructions, and construction sets
|
|
2203
|
+
all_m = [] if materials is None else list(materials)
|
|
2204
|
+
all_c = [] if constructions is None else list(constructions)
|
|
2205
|
+
all_con_sets = [] if construction_sets is None else list(construction_sets)
|
|
2206
|
+
for con_set in all_con_sets:
|
|
2207
|
+
all_c.extend(con_set.modified_constructions)
|
|
2208
|
+
|
|
2209
|
+
# get schedules assigned in constructions
|
|
2210
|
+
misc_c_scheds = []
|
|
2211
|
+
for constr in all_c:
|
|
2212
|
+
if isinstance(constr, (WindowConstructionShade, WindowConstructionDynamic)):
|
|
2213
|
+
misc_c_scheds.append(constr.schedule)
|
|
2214
|
+
elif isinstance(constr, AirBoundaryConstruction):
|
|
2215
|
+
misc_c_scheds.append(constr.air_mixing_schedule)
|
|
2216
|
+
|
|
2217
|
+
# process the HVAC and SHW systems
|
|
2218
|
+
all_hvac = [] if hvacs is None else list(hvacs)
|
|
2219
|
+
all_shw = [] if shws is None else list(shws)
|
|
2220
|
+
misc_hvac_scheds = []
|
|
2221
|
+
for hvac_obj in all_hvac:
|
|
2222
|
+
misc_hvac_scheds.extend(hvac_obj.schedules)
|
|
2223
|
+
|
|
2224
|
+
# get sets of unique objects
|
|
2225
|
+
all_scheds = set(scheds + misc_p_scheds + misc_c_scheds + misc_hvac_scheds)
|
|
2226
|
+
sched_tl = [sch.schedule_type_limit for sch in all_scheds
|
|
2227
|
+
if sch.schedule_type_limit is not None]
|
|
2228
|
+
all_typ_lim = set(type_lim + sched_tl)
|
|
2229
|
+
all_cons = set(all_c)
|
|
2230
|
+
misc_c_mats = []
|
|
2231
|
+
for constr in all_cons:
|
|
2232
|
+
try:
|
|
2233
|
+
misc_c_mats.extend(constr.materials)
|
|
2234
|
+
if constr.has_frame:
|
|
2235
|
+
misc_c_mats.append(constr.frame)
|
|
2236
|
+
if constr.has_shade:
|
|
2237
|
+
if constr.is_switchable_glazing:
|
|
2238
|
+
misc_c_mats.append(constr.switched_glass_material)
|
|
2239
|
+
if constr.shade_location == 'Between':
|
|
2240
|
+
materials.append(constr.window_construction.materials[-2])
|
|
2241
|
+
except AttributeError: # not a construction with materials
|
|
2242
|
+
pass
|
|
2243
|
+
all_mats = set(all_m + misc_c_mats)
|
|
2244
|
+
|
|
2245
|
+
# add all object dictionaries into one object
|
|
2246
|
+
data = {'type': 'ModelEnergyProperties'}
|
|
2247
|
+
data['schedule_type_limits'] = [tl.to_dict() for tl in all_typ_lim]
|
|
2248
|
+
data['schedules'] = [sch.to_dict(abridged=True) for sch in all_scheds]
|
|
2249
|
+
data['program_types'] = [pro.to_dict(abridged=True) for pro in all_progs]
|
|
2250
|
+
data['materials'] = [m.to_dict() for m in all_mats]
|
|
2251
|
+
data['constructions'] = []
|
|
2252
|
+
for con in all_cons:
|
|
2253
|
+
try:
|
|
2254
|
+
data['constructions'].append(con.to_dict(abridged=True))
|
|
2255
|
+
except TypeError: # no abridged option
|
|
2256
|
+
data['constructions'].append(con.to_dict())
|
|
2257
|
+
data['construction_sets'] = [cs.to_dict(abridged=True) for cs in all_con_sets]
|
|
2258
|
+
data['hvacs'] = [hv.to_dict(abridged=True) for hv in all_hvac]
|
|
2259
|
+
data['shws'] = [sw.to_dict(abridged=True) for sw in all_shw]
|
|
2260
|
+
return data
|
|
2261
|
+
|
|
2262
|
+
@staticmethod
|
|
2263
|
+
def reset_resource_ids_in_dict(
|
|
2264
|
+
data, add_uuid=False, reset_materials=True, reset_constructions=True,
|
|
2265
|
+
reset_construction_sets=True, reset_schedules=True, reset_programs=True):
|
|
2266
|
+
"""Reset the identifiers of energy resource objects in a Model dictionary.
|
|
2267
|
+
|
|
2268
|
+
This is useful when human-readable names are needed when the model is
|
|
2269
|
+
exported to other formats like IDF and OSM and the uniqueness of the
|
|
2270
|
+
identifiers is less of a concern.
|
|
2271
|
+
|
|
2272
|
+
Args:
|
|
2273
|
+
data: A dictionary representation of an entire honeybee-core Model.
|
|
2274
|
+
Note that this dictionary must have ModelEnergyProperties in order
|
|
2275
|
+
for this method to successfully edit the energy properties.
|
|
2276
|
+
add_uuid: Boolean to note whether newly-generated resource object IDs
|
|
2277
|
+
should be derived only from a cleaned display_name (False) or
|
|
2278
|
+
whether this new ID should also have a unique set of 8 characters
|
|
2279
|
+
appended to it to guarantee uniqueness. (Default: False).
|
|
2280
|
+
reset_materials: Boolean to note whether the IDs of all materials in
|
|
2281
|
+
the model should be reset or kept. (Default: True).
|
|
2282
|
+
reset_constructions: Boolean to note whether the IDs of all constructions
|
|
2283
|
+
in the model should be reset or kept. (Default: True).
|
|
2284
|
+
reset_construction_sets: Boolean to note whether the IDs of all construction
|
|
2285
|
+
sets in the model should be reset or kept. (Default: True).
|
|
2286
|
+
reset_schedules: Boolean to note whether the IDs of all schedules
|
|
2287
|
+
in the model should be reset or kept. (Default: True).
|
|
2288
|
+
reset_programs: Boolean to note whether the IDs of all program
|
|
2289
|
+
types in the model should be reset or kept. (Default: True).
|
|
2290
|
+
|
|
2291
|
+
Returns:
|
|
2292
|
+
A new Model dictionary with the resource identifiers reset. All references
|
|
2293
|
+
to the reset resources will be correct and valid in the resulting dictionary,
|
|
2294
|
+
assuming that the input is valid.
|
|
2295
|
+
"""
|
|
2296
|
+
model = Model.from_dict(data)
|
|
2297
|
+
materials, constructions, construction_sets, schedule_type_limits, \
|
|
2298
|
+
schedules, program_types, _, _ = \
|
|
2299
|
+
model.properties.energy.load_properties_from_dict(data)
|
|
2300
|
+
res_func = clean_and_id_ep_string if add_uuid else clean_ep_string
|
|
2301
|
+
|
|
2302
|
+
# change the identifiers of the materials
|
|
2303
|
+
if reset_materials:
|
|
2304
|
+
model_mats = set()
|
|
2305
|
+
for mat in model.properties.energy.materials:
|
|
2306
|
+
mat.unlock()
|
|
2307
|
+
old_id, new_id = mat.identifier, res_func(mat.display_name)
|
|
2308
|
+
mat.identifier = new_id
|
|
2309
|
+
materials[old_id].unlock()
|
|
2310
|
+
materials[old_id].identifier = new_id
|
|
2311
|
+
model_mats.add(old_id)
|
|
2312
|
+
for old_id, mat in materials.items():
|
|
2313
|
+
if old_id not in model_mats:
|
|
2314
|
+
mat.unlock()
|
|
2315
|
+
mat.identifier = res_func(mat.display_name)
|
|
2316
|
+
|
|
2317
|
+
# change the identifiers of the constructions
|
|
2318
|
+
if reset_constructions:
|
|
2319
|
+
model_cons = set()
|
|
2320
|
+
for con in model.properties.energy.constructions:
|
|
2321
|
+
con.unlock()
|
|
2322
|
+
old_id, new_id = con.identifier, res_func(con.display_name)
|
|
2323
|
+
con.identifier = new_id
|
|
2324
|
+
constructions[old_id].unlock()
|
|
2325
|
+
constructions[old_id].identifier = new_id
|
|
2326
|
+
model_cons.add(old_id)
|
|
2327
|
+
for old_id, con in constructions.items():
|
|
2328
|
+
if old_id not in model_cons:
|
|
2329
|
+
con.unlock()
|
|
2330
|
+
con.identifier = res_func(con.display_name)
|
|
2331
|
+
|
|
2332
|
+
# change the identifiers of the construction_sets
|
|
2333
|
+
if reset_construction_sets:
|
|
2334
|
+
model_cs = set()
|
|
2335
|
+
for cs in model.properties.energy.construction_sets:
|
|
2336
|
+
cs.unlock()
|
|
2337
|
+
old_id, new_id = cs.identifier, res_func(cs.display_name)
|
|
2338
|
+
cs.identifier = new_id
|
|
2339
|
+
construction_sets[old_id].unlock()
|
|
2340
|
+
construction_sets[old_id].identifier = new_id
|
|
2341
|
+
model_cs.add(old_id)
|
|
2342
|
+
for old_id, cs in construction_sets.items():
|
|
2343
|
+
if old_id not in model_cs:
|
|
2344
|
+
cs.unlock()
|
|
2345
|
+
cs.identifier = res_func(cs.display_name)
|
|
2346
|
+
|
|
2347
|
+
# change the identifiers of the schedules
|
|
2348
|
+
if reset_schedules:
|
|
2349
|
+
sch_skip = ('Seated Adult Activity', 'HumidNoLimit', 'DeHumidNoLimit')
|
|
2350
|
+
model_sch = set()
|
|
2351
|
+
for sch in model.properties.energy.schedules:
|
|
2352
|
+
if sch.identifier in sch_skip:
|
|
2353
|
+
schedules[sch.identifier] = sch
|
|
2354
|
+
model_sch.add(sch.identifier)
|
|
2355
|
+
continue
|
|
2356
|
+
sch.unlock()
|
|
2357
|
+
old_id, new_id = sch.identifier, res_func(sch.display_name)
|
|
2358
|
+
sch.identifier = new_id
|
|
2359
|
+
schedules[old_id].unlock()
|
|
2360
|
+
schedules[old_id].identifier = new_id
|
|
2361
|
+
model_sch.add(old_id)
|
|
2362
|
+
if isinstance(sch, ScheduleRuleset):
|
|
2363
|
+
for day_sch in sch.day_schedules:
|
|
2364
|
+
day_sch.identifier = res_func(day_sch.display_name)
|
|
2365
|
+
for day_sch in schedules[old_id].day_schedules:
|
|
2366
|
+
day_sch.identifier = res_func(day_sch.display_name)
|
|
2367
|
+
for old_id, sch in schedules.items():
|
|
2368
|
+
if old_id not in model_sch:
|
|
2369
|
+
sch.unlock()
|
|
2370
|
+
sch.identifier = res_func(sch.display_name)
|
|
2371
|
+
if isinstance(sch, ScheduleRuleset):
|
|
2372
|
+
for day_sch in schedules[old_id].day_schedules:
|
|
2373
|
+
day_sch.identifier = res_func(day_sch.display_name)
|
|
2374
|
+
|
|
2375
|
+
# change the identifiers of the program
|
|
2376
|
+
if reset_programs:
|
|
2377
|
+
model_prg = set()
|
|
2378
|
+
for prg in model.properties.energy.program_types:
|
|
2379
|
+
prg.unlock()
|
|
2380
|
+
old_id, new_id = prg.identifier, res_func(prg.display_name)
|
|
2381
|
+
prg.identifier = new_id
|
|
2382
|
+
program_types[old_id].unlock()
|
|
2383
|
+
program_types[old_id].identifier = new_id
|
|
2384
|
+
model_prg.add(old_id)
|
|
2385
|
+
for old_id, prg in program_types.items():
|
|
2386
|
+
if old_id not in model_prg:
|
|
2387
|
+
prg.unlock()
|
|
2388
|
+
prg.identifier = res_func(prg.display_name)
|
|
2389
|
+
|
|
2390
|
+
# create the model dictionary and update any unreferenced resources
|
|
2391
|
+
model_dict = model.to_dict()
|
|
2392
|
+
me_props = model_dict['properties']['energy']
|
|
2393
|
+
me_props['materials'] = [mat.to_dict() for mat in materials.values()]
|
|
2394
|
+
me_props['constructions'] = []
|
|
2395
|
+
for cnst in constructions.values():
|
|
2396
|
+
try:
|
|
2397
|
+
me_props['constructions'].append(cnst.to_dict(abridged=True))
|
|
2398
|
+
except TypeError: # ShadeConstruction
|
|
2399
|
+
me_props['constructions'].append(cnst.to_dict())
|
|
2400
|
+
me_props['construction_sets'] = \
|
|
2401
|
+
[cs.to_dict(abridged=True) for cs in construction_sets.values()]
|
|
2402
|
+
me_props['schedule_type_limits'] = \
|
|
2403
|
+
[stl.to_dict() for stl in schedule_type_limits.values()]
|
|
2404
|
+
me_props['schedules'] = [sc.to_dict(abridged=True) for sc in schedules.values()]
|
|
2405
|
+
me_props['program_types'] = \
|
|
2406
|
+
[p.to_dict(abridged=True) for p in program_types.values()]
|
|
2407
|
+
return model_dict
|
|
2408
|
+
|
|
2409
|
+
def _add_constr_type_objs_to_dict(self, base):
|
|
2410
|
+
"""Add materials, constructions and construction sets to a base dictionary.
|
|
2411
|
+
|
|
2412
|
+
Args:
|
|
2413
|
+
base: A base dictionary for a Honeybee Model.
|
|
2414
|
+
|
|
2415
|
+
Returns:
|
|
2416
|
+
A list of of schedules that are assigned to the constructions.
|
|
2417
|
+
"""
|
|
2418
|
+
# add the global construction set to the dictionary
|
|
2419
|
+
gs = self.global_construction_set.to_dict(abridged=True, none_for_defaults=False)
|
|
2420
|
+
gs['type'] = 'GlobalConstructionSet'
|
|
2421
|
+
del gs['identifier']
|
|
2422
|
+
g_constr = self.global_construction_set.constructions_unique
|
|
2423
|
+
g_materials = []
|
|
2424
|
+
for constr in g_constr:
|
|
2425
|
+
try:
|
|
2426
|
+
g_materials.extend(constr.materials)
|
|
2427
|
+
except AttributeError:
|
|
2428
|
+
pass # ShadeConstruction or AirBoundaryConstruction
|
|
2429
|
+
gs['context_construction'] = generic_context.identifier
|
|
2430
|
+
gs['constructions'] = [generic_context.to_dict()]
|
|
2431
|
+
for cnst in g_constr:
|
|
2432
|
+
try:
|
|
2433
|
+
gs['constructions'].append(cnst.to_dict(abridged=True))
|
|
2434
|
+
except TypeError: # ShadeConstruction
|
|
2435
|
+
gs['constructions'].append(cnst.to_dict())
|
|
2436
|
+
gs['materials'] = [mat.to_dict() for mat in set(g_materials)]
|
|
2437
|
+
base['energy']['global_construction_set'] = gs
|
|
2438
|
+
|
|
2439
|
+
# add all ConstructionSets to the dictionary
|
|
2440
|
+
base['energy']['construction_sets'] = []
|
|
2441
|
+
construction_sets = self.construction_sets
|
|
2442
|
+
for cnstr_set in construction_sets:
|
|
2443
|
+
base['energy']['construction_sets'].append(cnstr_set.to_dict(abridged=True))
|
|
2444
|
+
|
|
2445
|
+
# add all unique Constructions to the dictionary
|
|
2446
|
+
room_constrs = []
|
|
2447
|
+
for cnstr_set in construction_sets:
|
|
2448
|
+
room_constrs.extend(cnstr_set.modified_constructions_unique)
|
|
2449
|
+
mass_constrs = []
|
|
2450
|
+
for room in self.host.rooms:
|
|
2451
|
+
for int_mass in room.properties.energy._internal_masses:
|
|
2452
|
+
constr = int_mass.construction
|
|
2453
|
+
if not self._instance_in_array(constr, mass_constrs):
|
|
2454
|
+
mass_constrs.append(constr)
|
|
2455
|
+
all_constrs = room_constrs + mass_constrs + \
|
|
2456
|
+
self.face_constructions + self.shade_constructions
|
|
2457
|
+
constructions = list(set(all_constrs))
|
|
2458
|
+
base['energy']['constructions'] = []
|
|
2459
|
+
for cnst in constructions:
|
|
2460
|
+
try:
|
|
2461
|
+
base['energy']['constructions'].append(cnst.to_dict(abridged=True))
|
|
2462
|
+
except TypeError: # ShadeConstruction
|
|
2463
|
+
base['energy']['constructions'].append(cnst.to_dict())
|
|
2464
|
+
|
|
2465
|
+
# add all unique Materials to the dictionary
|
|
2466
|
+
materials = []
|
|
2467
|
+
for cnstr in constructions:
|
|
2468
|
+
try:
|
|
2469
|
+
materials.extend(cnstr.materials)
|
|
2470
|
+
if cnstr.has_frame:
|
|
2471
|
+
materials.append(cnstr.frame)
|
|
2472
|
+
if isinstance(cnstr, WindowConstructionShade):
|
|
2473
|
+
if cnstr.is_switchable_glazing:
|
|
2474
|
+
materials.append(cnstr.switched_glass_material)
|
|
2475
|
+
if cnstr.shade_location == 'Between':
|
|
2476
|
+
materials.append(cnstr.window_construction.materials[-2])
|
|
2477
|
+
except AttributeError:
|
|
2478
|
+
pass # ShadeConstruction
|
|
2479
|
+
base['energy']['materials'] = [mat.to_dict() for mat in set(materials)]
|
|
2480
|
+
|
|
2481
|
+
# extract all of the schedules from the constructions
|
|
2482
|
+
schedules = []
|
|
2483
|
+
for constr in constructions:
|
|
2484
|
+
if isinstance(constr, AirBoundaryConstruction):
|
|
2485
|
+
self._check_and_add_schedule(constr.air_mixing_schedule, schedules)
|
|
2486
|
+
elif isinstance(constr, WindowConstructionShade):
|
|
2487
|
+
if constr.schedule is not None:
|
|
2488
|
+
self._check_and_add_schedule(constr.schedule, schedules)
|
|
2489
|
+
elif isinstance(constr, WindowConstructionDynamic):
|
|
2490
|
+
self._check_and_add_schedule(constr.schedule, schedules)
|
|
2491
|
+
return schedules
|
|
2492
|
+
|
|
2493
|
+
def _add_sched_type_objs_to_dict(self, base, schs):
|
|
2494
|
+
"""Add type limits, schedules, program types, hvacs, shws to a base dictionary.
|
|
2495
|
+
|
|
2496
|
+
Args:
|
|
2497
|
+
base: A base dictionary for a Honeybee Model.
|
|
2498
|
+
schs: A list of additional schedules to be serialized to the
|
|
2499
|
+
base dictionary.
|
|
2500
|
+
"""
|
|
2501
|
+
# add all unique hvacs to the dictionary
|
|
2502
|
+
hvacs = self.hvacs
|
|
2503
|
+
base['energy']['hvacs'] = []
|
|
2504
|
+
for hvac in hvacs:
|
|
2505
|
+
base['energy']['hvacs'].append(hvac.to_dict(abridged=True))
|
|
2506
|
+
|
|
2507
|
+
# add all unique shws to the dictionary
|
|
2508
|
+
base['energy']['shws'] = [shw.to_dict() for shw in self.shws]
|
|
2509
|
+
|
|
2510
|
+
# add all unique program types to the dictionary
|
|
2511
|
+
program_types = self.program_types
|
|
2512
|
+
base['energy']['program_types'] = []
|
|
2513
|
+
for p_type in program_types:
|
|
2514
|
+
base['energy']['program_types'].append(p_type.to_dict(abridged=True))
|
|
2515
|
+
|
|
2516
|
+
# add all unique Schedules to the dictionary
|
|
2517
|
+
p_type_scheds = []
|
|
2518
|
+
for p_type in program_types:
|
|
2519
|
+
for sched in p_type.schedules:
|
|
2520
|
+
self._check_and_add_schedule(sched, p_type_scheds)
|
|
2521
|
+
hvac_scheds = []
|
|
2522
|
+
for hvac in hvacs:
|
|
2523
|
+
for sched in hvac.schedules:
|
|
2524
|
+
self._check_and_add_schedule(sched, hvac_scheds)
|
|
2525
|
+
all_scheds = hvac_scheds + p_type_scheds + self.room_schedules + \
|
|
2526
|
+
self.shade_schedules + schs
|
|
2527
|
+
schedules = list(set(all_scheds))
|
|
2528
|
+
base['energy']['schedules'] = []
|
|
2529
|
+
for sched in schedules:
|
|
2530
|
+
base['energy']['schedules'].append(sched.to_dict(abridged=True))
|
|
2531
|
+
|
|
2532
|
+
# add all unique ScheduleTypeLimits to the dictionary
|
|
2533
|
+
type_limits = []
|
|
2534
|
+
for sched in schedules:
|
|
2535
|
+
t_lim = sched.schedule_type_limit
|
|
2536
|
+
if t_lim is not None and not self._instance_in_array(t_lim, type_limits):
|
|
2537
|
+
type_limits.append(t_lim)
|
|
2538
|
+
base['energy']['schedule_type_limits'] = \
|
|
2539
|
+
[s_typ.to_dict() for s_typ in set(type_limits)]
|
|
2540
|
+
|
|
2541
|
+
def _check_and_add_obj_construction(self, obj, constructions):
|
|
2542
|
+
"""Check if a construction is assigned to an object and add it to a list."""
|
|
2543
|
+
constr = obj.properties.energy._construction
|
|
2544
|
+
if constr is not None:
|
|
2545
|
+
if not self._instance_in_array(constr, constructions):
|
|
2546
|
+
constructions.append(constr)
|
|
2547
|
+
|
|
2548
|
+
def _check_and_add_obj_construction_inc_parent(self, obj, constructions):
|
|
2549
|
+
"""Check if a construction is assigned to an object and add it to a list."""
|
|
2550
|
+
constr = obj.properties.energy.construction
|
|
2551
|
+
if not self._instance_in_array(constr, constructions):
|
|
2552
|
+
constructions.append(constr)
|
|
2553
|
+
|
|
2554
|
+
def _check_and_add_shade_schedule(self, obj, schedules):
|
|
2555
|
+
"""Check if a schedule is assigned to a shade and add it to a list."""
|
|
2556
|
+
sched = obj.properties.energy._transmittance_schedule
|
|
2557
|
+
if sched is not None:
|
|
2558
|
+
if not self._instance_in_array(sched, schedules):
|
|
2559
|
+
schedules.append(sched)
|
|
2560
|
+
|
|
2561
|
+
def _check_and_add_schedule(self, sched, schedules):
|
|
2562
|
+
"""Check if a schedule is in a list and add it if not."""
|
|
2563
|
+
if not self._instance_in_array(sched, schedules):
|
|
2564
|
+
schedules.append(sched)
|
|
2565
|
+
|
|
2566
|
+
def _check_and_add_obj_transmit(self, hb_obj, transmittances):
|
|
2567
|
+
"""Check and add the transmittance of a honeybee object to a set."""
|
|
2568
|
+
constr = hb_obj.properties.energy.construction
|
|
2569
|
+
transmittances.add(round(constr.solar_transmittance, 3))
|
|
2570
|
+
|
|
2571
|
+
def _assign_room_modifier_sets(self, unique_mod_sets):
|
|
2572
|
+
"""Assign modifier sets to rooms using a dictionary of unique modifier sets."""
|
|
2573
|
+
for room in self._host.rooms:
|
|
2574
|
+
room.properties.radiance.modifier_set = \
|
|
2575
|
+
unique_mod_sets[room.properties.energy.construction_set.identifier]
|
|
2576
|
+
|
|
2577
|
+
def _assign_face_modifiers(self, unique_mods):
|
|
2578
|
+
"""Assign modifiers to faces, apertures, doors and shades using a dictionary."""
|
|
2579
|
+
for room in self._host.rooms:
|
|
2580
|
+
for face in room.faces: # check Face constructions
|
|
2581
|
+
self._assign_obj_modifier_shade(face, unique_mods)
|
|
2582
|
+
for ap in face.apertures: # check Aperture constructions
|
|
2583
|
+
self._assign_obj_modifier_shade(ap, unique_mods)
|
|
2584
|
+
for dr in face.doors: # check Door constructions
|
|
2585
|
+
self._assign_obj_modifier_shade(dr, unique_mods)
|
|
2586
|
+
for shade in room.shades:
|
|
2587
|
+
self._assign_obj_modifier(shade, unique_mods)
|
|
2588
|
+
for face in self.host.orphaned_faces: # check orphaned Face constructions
|
|
2589
|
+
self._assign_obj_modifier_shade(face, unique_mods)
|
|
2590
|
+
for ap in face.apertures: # check Aperture constructions
|
|
2591
|
+
self._assign_obj_modifier_shade(ap, unique_mods)
|
|
2592
|
+
for dr in face.doors: # check Door constructions
|
|
2593
|
+
self._assign_obj_modifier_shade(dr, unique_mods)
|
|
2594
|
+
for ap in self.host.orphaned_apertures: # check orphaned Aperture constructions
|
|
2595
|
+
self._assign_obj_modifier_shade(ap, unique_mods)
|
|
2596
|
+
for dr in self.host.orphaned_doors: # check orphaned Door constructions
|
|
2597
|
+
self._assign_obj_modifier_shade(dr, unique_mods)
|
|
2598
|
+
for shade in self.host.orphaned_shades:
|
|
2599
|
+
self._assign_obj_modifier(shade, unique_mods)
|
|
2600
|
+
for sm in self.host.shade_meshes:
|
|
2601
|
+
self._assign_obj_modifier(sm, unique_mods)
|
|
2602
|
+
|
|
2603
|
+
def _assign_obj_modifier_shade(self, obj, unique_mods):
|
|
2604
|
+
"""Check if an object or child shades have a unique constr and assign a modifier.
|
|
2605
|
+
"""
|
|
2606
|
+
self._assign_obj_modifier(obj, unique_mods)
|
|
2607
|
+
for shade in obj.shades:
|
|
2608
|
+
self._assign_obj_modifier(shade, unique_mods)
|
|
2609
|
+
|
|
2610
|
+
@staticmethod
|
|
2611
|
+
def _always_on_schedule():
|
|
2612
|
+
"""Get the Always On schedule."""
|
|
2613
|
+
return always_on
|
|
2614
|
+
|
|
2615
|
+
@staticmethod
|
|
2616
|
+
def _assign_obj_modifier(obj, unique_mods):
|
|
2617
|
+
"""Check if an object has a unique construction and assign a modifier."""
|
|
2618
|
+
if obj.properties.energy._construction is not None:
|
|
2619
|
+
obj.properties.radiance.modifier = \
|
|
2620
|
+
unique_mods[obj.properties.energy._construction.identifier]
|
|
2621
|
+
|
|
2622
|
+
@staticmethod
|
|
2623
|
+
def _instance_in_array(object_instance, object_array):
|
|
2624
|
+
"""Check if a specific object instance is already in an array.
|
|
2625
|
+
|
|
2626
|
+
This can be much faster than `if object_instance in object_array`
|
|
2627
|
+
when you expect to be testing a lot of the same instance of an object for
|
|
2628
|
+
inclusion in an array since the builtin method uses an == operator to
|
|
2629
|
+
test inclusion.
|
|
2630
|
+
"""
|
|
2631
|
+
for val in object_array:
|
|
2632
|
+
if val is object_instance:
|
|
2633
|
+
return True
|
|
2634
|
+
return False
|
|
2635
|
+
|
|
2636
|
+
def ToString(self):
|
|
2637
|
+
return self.__repr__()
|
|
2638
|
+
|
|
2639
|
+
def __repr__(self):
|
|
2640
|
+
return 'Model Energy Properties: [host: {}]'.format(self.host.display_name)
|