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.
Files changed (162) hide show
  1. honeybee_energy/__init__.py +24 -0
  2. honeybee_energy/__main__.py +4 -0
  3. honeybee_energy/_extend_honeybee.py +145 -0
  4. honeybee_energy/altnumber.py +21 -0
  5. honeybee_energy/baseline/__init__.py +2 -0
  6. honeybee_energy/baseline/create.py +608 -0
  7. honeybee_energy/baseline/data/__init__.py +1 -0
  8. honeybee_energy/baseline/data/constructions.csv +64 -0
  9. honeybee_energy/baseline/data/fen_ratios.csv +15 -0
  10. honeybee_energy/baseline/data/lpd_building.csv +21 -0
  11. honeybee_energy/baseline/data/pci_2016.csv +22 -0
  12. honeybee_energy/baseline/data/pci_2019.csv +22 -0
  13. honeybee_energy/baseline/data/pci_2022.csv +22 -0
  14. honeybee_energy/baseline/data/shw.csv +21 -0
  15. honeybee_energy/baseline/pci.py +512 -0
  16. honeybee_energy/baseline/result.py +371 -0
  17. honeybee_energy/boundarycondition.py +128 -0
  18. honeybee_energy/cli/__init__.py +69 -0
  19. honeybee_energy/cli/baseline.py +475 -0
  20. honeybee_energy/cli/edit.py +327 -0
  21. honeybee_energy/cli/lib.py +1154 -0
  22. honeybee_energy/cli/result.py +810 -0
  23. honeybee_energy/cli/setconfig.py +124 -0
  24. honeybee_energy/cli/settings.py +569 -0
  25. honeybee_energy/cli/simulate.py +380 -0
  26. honeybee_energy/cli/translate.py +1714 -0
  27. honeybee_energy/cli/validate.py +224 -0
  28. honeybee_energy/config.json +11 -0
  29. honeybee_energy/config.py +842 -0
  30. honeybee_energy/construction/__init__.py +1 -0
  31. honeybee_energy/construction/_base.py +374 -0
  32. honeybee_energy/construction/air.py +325 -0
  33. honeybee_energy/construction/dictutil.py +89 -0
  34. honeybee_energy/construction/dynamic.py +607 -0
  35. honeybee_energy/construction/opaque.py +460 -0
  36. honeybee_energy/construction/shade.py +319 -0
  37. honeybee_energy/construction/window.py +1096 -0
  38. honeybee_energy/construction/windowshade.py +847 -0
  39. honeybee_energy/constructionset.py +1655 -0
  40. honeybee_energy/dictutil.py +56 -0
  41. honeybee_energy/generator/__init__.py +5 -0
  42. honeybee_energy/generator/loadcenter.py +204 -0
  43. honeybee_energy/generator/pv.py +535 -0
  44. honeybee_energy/hvac/__init__.py +21 -0
  45. honeybee_energy/hvac/_base.py +124 -0
  46. honeybee_energy/hvac/_template.py +270 -0
  47. honeybee_energy/hvac/allair/__init__.py +22 -0
  48. honeybee_energy/hvac/allair/_base.py +349 -0
  49. honeybee_energy/hvac/allair/furnace.py +168 -0
  50. honeybee_energy/hvac/allair/psz.py +131 -0
  51. honeybee_energy/hvac/allair/ptac.py +163 -0
  52. honeybee_energy/hvac/allair/pvav.py +109 -0
  53. honeybee_energy/hvac/allair/vav.py +128 -0
  54. honeybee_energy/hvac/detailed.py +337 -0
  55. honeybee_energy/hvac/doas/__init__.py +28 -0
  56. honeybee_energy/hvac/doas/_base.py +345 -0
  57. honeybee_energy/hvac/doas/fcu.py +127 -0
  58. honeybee_energy/hvac/doas/radiant.py +329 -0
  59. honeybee_energy/hvac/doas/vrf.py +81 -0
  60. honeybee_energy/hvac/doas/wshp.py +91 -0
  61. honeybee_energy/hvac/heatcool/__init__.py +23 -0
  62. honeybee_energy/hvac/heatcool/_base.py +177 -0
  63. honeybee_energy/hvac/heatcool/baseboard.py +61 -0
  64. honeybee_energy/hvac/heatcool/evapcool.py +72 -0
  65. honeybee_energy/hvac/heatcool/fcu.py +92 -0
  66. honeybee_energy/hvac/heatcool/gasunit.py +53 -0
  67. honeybee_energy/hvac/heatcool/radiant.py +269 -0
  68. honeybee_energy/hvac/heatcool/residential.py +77 -0
  69. honeybee_energy/hvac/heatcool/vrf.py +54 -0
  70. honeybee_energy/hvac/heatcool/windowac.py +70 -0
  71. honeybee_energy/hvac/heatcool/wshp.py +62 -0
  72. honeybee_energy/hvac/idealair.py +699 -0
  73. honeybee_energy/internalmass.py +310 -0
  74. honeybee_energy/lib/__init__.py +1 -0
  75. honeybee_energy/lib/_loadconstructions.py +194 -0
  76. honeybee_energy/lib/_loadconstructionsets.py +117 -0
  77. honeybee_energy/lib/_loadmaterials.py +83 -0
  78. honeybee_energy/lib/_loadprogramtypes.py +125 -0
  79. honeybee_energy/lib/_loadschedules.py +87 -0
  80. honeybee_energy/lib/_loadtypelimits.py +64 -0
  81. honeybee_energy/lib/constructions.py +207 -0
  82. honeybee_energy/lib/constructionsets.py +95 -0
  83. honeybee_energy/lib/materials.py +67 -0
  84. honeybee_energy/lib/programtypes.py +125 -0
  85. honeybee_energy/lib/schedules.py +61 -0
  86. honeybee_energy/lib/scheduletypelimits.py +31 -0
  87. honeybee_energy/load/__init__.py +1 -0
  88. honeybee_energy/load/_base.py +190 -0
  89. honeybee_energy/load/daylight.py +397 -0
  90. honeybee_energy/load/dictutil.py +47 -0
  91. honeybee_energy/load/equipment.py +771 -0
  92. honeybee_energy/load/hotwater.py +543 -0
  93. honeybee_energy/load/infiltration.py +460 -0
  94. honeybee_energy/load/lighting.py +480 -0
  95. honeybee_energy/load/people.py +497 -0
  96. honeybee_energy/load/process.py +472 -0
  97. honeybee_energy/load/setpoint.py +816 -0
  98. honeybee_energy/load/ventilation.py +550 -0
  99. honeybee_energy/material/__init__.py +1 -0
  100. honeybee_energy/material/_base.py +166 -0
  101. honeybee_energy/material/dictutil.py +59 -0
  102. honeybee_energy/material/frame.py +367 -0
  103. honeybee_energy/material/gas.py +1087 -0
  104. honeybee_energy/material/glazing.py +854 -0
  105. honeybee_energy/material/opaque.py +1351 -0
  106. honeybee_energy/material/shade.py +1360 -0
  107. honeybee_energy/measure.py +472 -0
  108. honeybee_energy/programtype.py +723 -0
  109. honeybee_energy/properties/__init__.py +1 -0
  110. honeybee_energy/properties/aperture.py +333 -0
  111. honeybee_energy/properties/door.py +342 -0
  112. honeybee_energy/properties/extension.py +244 -0
  113. honeybee_energy/properties/face.py +274 -0
  114. honeybee_energy/properties/model.py +2640 -0
  115. honeybee_energy/properties/room.py +1747 -0
  116. honeybee_energy/properties/shade.py +314 -0
  117. honeybee_energy/properties/shademesh.py +262 -0
  118. honeybee_energy/reader.py +48 -0
  119. honeybee_energy/result/__init__.py +1 -0
  120. honeybee_energy/result/colorobj.py +648 -0
  121. honeybee_energy/result/emissions.py +290 -0
  122. honeybee_energy/result/err.py +101 -0
  123. honeybee_energy/result/eui.py +100 -0
  124. honeybee_energy/result/generation.py +160 -0
  125. honeybee_energy/result/loadbalance.py +890 -0
  126. honeybee_energy/result/match.py +202 -0
  127. honeybee_energy/result/osw.py +90 -0
  128. honeybee_energy/result/rdd.py +59 -0
  129. honeybee_energy/result/zsz.py +190 -0
  130. honeybee_energy/run.py +1577 -0
  131. honeybee_energy/schedule/__init__.py +1 -0
  132. honeybee_energy/schedule/day.py +626 -0
  133. honeybee_energy/schedule/dictutil.py +59 -0
  134. honeybee_energy/schedule/fixedinterval.py +1012 -0
  135. honeybee_energy/schedule/rule.py +619 -0
  136. honeybee_energy/schedule/ruleset.py +1867 -0
  137. honeybee_energy/schedule/typelimit.py +310 -0
  138. honeybee_energy/shw.py +315 -0
  139. honeybee_energy/simulation/__init__.py +1 -0
  140. honeybee_energy/simulation/control.py +214 -0
  141. honeybee_energy/simulation/daylightsaving.py +185 -0
  142. honeybee_energy/simulation/dictutil.py +51 -0
  143. honeybee_energy/simulation/output.py +646 -0
  144. honeybee_energy/simulation/parameter.py +606 -0
  145. honeybee_energy/simulation/runperiod.py +443 -0
  146. honeybee_energy/simulation/shadowcalculation.py +295 -0
  147. honeybee_energy/simulation/sizing.py +546 -0
  148. honeybee_energy/ventcool/__init__.py +5 -0
  149. honeybee_energy/ventcool/_crack_data.py +91 -0
  150. honeybee_energy/ventcool/afn.py +289 -0
  151. honeybee_energy/ventcool/control.py +269 -0
  152. honeybee_energy/ventcool/crack.py +126 -0
  153. honeybee_energy/ventcool/fan.py +493 -0
  154. honeybee_energy/ventcool/opening.py +365 -0
  155. honeybee_energy/ventcool/simulation.py +314 -0
  156. honeybee_energy/writer.py +1078 -0
  157. honeybee_energy-1.116.106.dist-info/METADATA +113 -0
  158. honeybee_energy-1.116.106.dist-info/RECORD +162 -0
  159. honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
  160. honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
  161. honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
  162. honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
@@ -0,0 +1,847 @@
1
+ # coding=utf-8
2
+ """Window Construction with shades/blinds or a dynamically-controlled glass pane."""
3
+ from __future__ import division
4
+
5
+ from .window import WindowConstruction
6
+ from ..material.dictutil import dict_to_material
7
+ from ..material.glazing import EnergyWindowMaterialGlazing, \
8
+ EnergyWindowMaterialSimpleGlazSys
9
+ from ..material.shade import _EnergyWindowMaterialShadeBase, EnergyWindowMaterialBlind
10
+ from ..schedule.dictutil import dict_to_schedule
11
+ from ..schedule.ruleset import ScheduleRuleset
12
+ from ..schedule.fixedinterval import ScheduleFixedInterval
13
+ from ..writer import generate_idf_string
14
+ from ..properties.extension import WindowConstructionShadeProperties
15
+
16
+
17
+ from honeybee._lockable import lockable
18
+ from honeybee.typing import valid_ep_string
19
+
20
+
21
+ @lockable
22
+ class WindowConstructionShade(object):
23
+ """Window Construction with shades/blinds or a dynamically-controlled glass pane.
24
+
25
+ Args:
26
+ identifier: Text string for a unique Construction ID. Must be < 100 characters
27
+ and not contain any EnergyPlus special characters. This will be used to
28
+ identify the object across a model and in the exported IDF.
29
+ window_construction: A WindowConstruction object that serves as the
30
+ "switched off" version of the construction (aka. the "bare construction").
31
+ The shade_material and shade_location will be used to modify this
32
+ starting construction.
33
+ shade_material: An EnergyWindowMaterialShade or an EnergyWindowMaterialBlind
34
+ that serves as the shading layer for this construction. This can also
35
+ be an EnergyWindowMaterialGlazing, which will indicate that the
36
+ WindowConstruction has a dynamically-controlled glass pane like an
37
+ electrochromic window assembly.
38
+ shade_location: Text to indicate where in the window assembly the shade_material
39
+ is located. (Default: "Interior"). Choose from the following 3 options:
40
+
41
+ * Interior
42
+ * Between
43
+ * Exterior
44
+
45
+ Note that the WindowConstruction must have at least one gas gap to use
46
+ the "Between" option. Also note that, for a WindowConstruction with more
47
+ than one gas gap, the "Between" option defaults to using the inner gap
48
+ as this is the only option that EnergyPlus supports.
49
+ control_type: Text to indicate how the shading device is controlled, which
50
+ determines when the shading is “on” or “off.” (Default: "AlwaysOn").
51
+ Choose from the options below (units for the values of the corresponding
52
+ setpoint are noted in parentheses next to each option):
53
+
54
+ * AlwaysOn
55
+ * OnIfHighSolarOnWindow (W/m2)
56
+ * OnIfHighHorizontalSolar (W/m2)
57
+ * OnIfHighOutdoorAirTemperature (C)
58
+ * OnIfHighZoneAirTemperature (C)
59
+ * OnIfHighZoneCooling (W)
60
+ * OnNightIfLowOutdoorTempAndOffDay (C)
61
+ * OnNightIfLowInsideTempAndOffDay (C)
62
+ * OnNightIfHeatingAndOffDay (W)
63
+
64
+ setpoint: A number that corresponds to the specified control_type. This can
65
+ be a value in (W/m2), (C) or (W) depending upon the control type.
66
+ schedule: An optional ScheduleRuleset or ScheduleFixedInterval to be applied
67
+ on top of the control_type. If None, the control_type will govern all
68
+ behavior of the construction. (Default: None).
69
+
70
+ Properties:
71
+ * identifier
72
+ * display_name
73
+ * window_construction
74
+ * shade_material
75
+ * shade_location
76
+ * control_type
77
+ * setpoint
78
+ * schedule
79
+ * materials
80
+ * layers
81
+ * unique_materials
82
+ * frame
83
+ * r_value
84
+ * u_value
85
+ * u_factor
86
+ * r_factor
87
+ * is_symmetric
88
+ * is_switchable_glazing
89
+ * has_frame
90
+ * has_shade
91
+ * is_dynamic
92
+ * inside_emissivity
93
+ * outside_emissivity
94
+ * solar_transmittance
95
+ * visible_transmittance
96
+ * shgc
97
+ * thickness
98
+ * glazing_count
99
+ * gap_count
100
+ * is_groupable
101
+ * is_zone_groupable
102
+ * inside_material
103
+ * outside_material
104
+ * user_data
105
+ * properties
106
+ """
107
+
108
+ __slots__ = ('_identifier', '_display_name', '_window_construction',
109
+ '_shade_material', '_shade_location', '_control_type',
110
+ '_setpoint', '_schedule', '_between_gap', '_locked', '_user_data',
111
+ "_properties", )
112
+ SHADE_LOCATIONS = ('Interior', 'Between', 'Exterior')
113
+ CONTROL_TYPES = (
114
+ 'AlwaysOn', 'OnIfHighSolarOnWindow', 'OnIfHighHorizontalSolar',
115
+ 'OnIfHighOutdoorAirTemperature', 'OnIfHighZoneAirTemperature',
116
+ 'OnIfHighZoneCooling', 'OnNightIfLowOutdoorTempAndOffDay',
117
+ 'OnNightIfLowInsideTempAndOffDay', 'OnNightIfHeatingAndOffDay')
118
+ GROUPABLE_TYPES = (
119
+ 'AlwaysOn', 'OnIfHighHorizontalSolar', 'OnIfHighOutdoorAirTemperature',
120
+ 'OnNightIfLowOutdoorTempAndOffDay')
121
+ ROOM_GROUPABLE_TYPES = (
122
+ 'OnIfHighZoneAirTemperature', 'OnIfHighZoneCooling',
123
+ 'OnNightIfLowInsideTempAndOffDay', 'OnNightIfHeatingAndOffDay')
124
+
125
+ def __init__(self, identifier, window_construction, shade_material,
126
+ shade_location='Interior', control_type='AlwaysOn',
127
+ setpoint=None, schedule=None):
128
+ """Initialize shaded window construction."""
129
+ self._locked = False # unlocked by default
130
+ self.identifier = identifier
131
+ self._display_name = None
132
+ self._between_gap = None # will be used if 'Between' option is used
133
+ self._user_data = None
134
+ # check that the window construction, shade, and shade location are compatible
135
+ assert isinstance(window_construction, WindowConstruction), \
136
+ 'Expected WindowConstruction for WindowConstructionShade. ' \
137
+ 'Got {}.'.format(type(window_construction))
138
+ shade_types = (_EnergyWindowMaterialShadeBase, EnergyWindowMaterialGlazing)
139
+ assert isinstance(shade_material, shade_types), \
140
+ 'Expected Shade/Blind or Glazing material for WindowConstructionShade. ' \
141
+ 'Got {}.'.format(type(shade_material))
142
+ assert shade_location in self.SHADE_LOCATIONS, \
143
+ 'Invalid input "{}" for shade location. Must be one ' \
144
+ 'of the following:\n{}'.format(shade_location, self.SHADE_LOCATIONS)
145
+ if isinstance(shade_material, EnergyWindowMaterialGlazing):
146
+ ext_pane = window_construction[0]
147
+ assert not isinstance(ext_pane, EnergyWindowMaterialSimpleGlazSys), \
148
+ 'WindowConstruction cannot be a SimpleGlazSys when shading material ' \
149
+ 'is a glass pane.'
150
+ elif shade_location == 'Between': # it's a shade/blind between glass panes
151
+ assert window_construction.gap_count >= 1, 'WindowConstruction must have ' \
152
+ 'at least one gap in order to use "Between" shade_location.'
153
+ # calculate the thickness of the gaps on either side of the shade
154
+ int_gap = window_construction[-2]
155
+ if isinstance(shade_material, EnergyWindowMaterialBlind):
156
+ assert shade_material.slat_width < int_gap.thickness, \
157
+ 'Blind slat_width must be less than the width of the gap in which ' \
158
+ 'it sits. {} > {}.'.format(
159
+ shade_material.slat_width, int_gap.thickness)
160
+ shd_thick = 0 if isinstance(shade_material, EnergyWindowMaterialBlind) \
161
+ else shade_material.thickness
162
+ gap_thick = (int_gap.thickness - shd_thick) / 2
163
+ assert gap_thick > 0, \
164
+ 'Shade thickness is greater than the gap in which it sits.'
165
+ # create the split gap material to be used on either side of the shade
166
+ between_int_gap = int_gap.duplicate()
167
+ between_int_gap.identifier = \
168
+ '{}_Split{}'.format(int_gap.identifier, round(gap_thick, 3))
169
+ between_int_gap.thickness = gap_thick
170
+ self._between_gap = between_int_gap
171
+ window_construction.lock() # lock to avoid illegal shade/material combinations
172
+ self._window_construction = window_construction
173
+ self._shade_material = shade_material
174
+ self._shade_location = shade_location
175
+
176
+ # assign the control type, setpoint and schedule
177
+ assert control_type in self.CONTROL_TYPES, \
178
+ 'Invalid input "{}" for shading control type.' \
179
+ ' Must be one of the following:\n{}'.format(control_type, self.CONTROL_TYPES)
180
+ self._control_type = control_type
181
+ self.setpoint = setpoint
182
+ self.schedule = schedule
183
+ self._properties = WindowConstructionShadeProperties(self)
184
+
185
+ @property
186
+ def identifier(self):
187
+ """Get or set the text string for construction identifier."""
188
+ return self._identifier
189
+
190
+ @identifier.setter
191
+ def identifier(self, identifier):
192
+ self._identifier = valid_ep_string(identifier, 'construction identifier')
193
+
194
+ @property
195
+ def display_name(self):
196
+ """Get or set a string for the object name without any character restrictions.
197
+
198
+ If not set, this will be equal to the identifier.
199
+ """
200
+ if self._display_name is None:
201
+ return self._identifier
202
+ return self._display_name
203
+
204
+ @display_name.setter
205
+ def display_name(self, value):
206
+ if value is not None:
207
+ try:
208
+ value = str(value)
209
+ except UnicodeEncodeError: # Python 2 machine lacking the character set
210
+ pass # keep it as unicode
211
+ self._display_name = value
212
+
213
+ @property
214
+ def window_construction(self):
215
+ """Get the WindowConstruction serving as the "switched off" version."""
216
+ return self._window_construction
217
+
218
+ @property
219
+ def shade_material(self):
220
+ """Get the material that serves as the shading layer for this construction."""
221
+ return self._shade_material
222
+
223
+ @property
224
+ def shade_location(self):
225
+ """Get text to indicate where in the construction the shade_material is located.
226
+
227
+ This will be either "Interior", "Between" or "Exterior". Note that, for a
228
+ WindowConstruction with more than one gas gap, the "Between" option defaults
229
+ to using the inner gap as this is the only option that EnergyPlus supports.
230
+ """
231
+ return self._shade_location
232
+
233
+ @property
234
+ def control_type(self):
235
+ """Get or set the text indicating how the shading device is controlled.
236
+
237
+ Choose from the options below:
238
+
239
+ * AlwaysOn
240
+ * OnIfHighSolarOnWindow
241
+ * OnIfHighHorizontalSolar
242
+ * OnIfHighOutdoorAirTemperature
243
+ * OnIfHighZoneAirTemperature
244
+ * OnIfHighZoneCooling
245
+ * OnNightIfLowOutdoorTempAndOffDay
246
+ * OnNightIfLowInsideTempAndOffDay
247
+ * OnNightIfHeatingAndOffDay
248
+ """
249
+ return self._control_type
250
+
251
+ @control_type.setter
252
+ def control_type(self, value):
253
+ assert value in self.CONTROL_TYPES, \
254
+ 'Invalid input "{}" for shading control type.' \
255
+ ' Must be one of the following:\n{}'.format(value, self.CONTROL_TYPES)
256
+ if value != 'AlwaysOn':
257
+ assert self._setpoint is not None, 'Control setpoint must not ' \
258
+ 'be None to use "{}" control type.'.format(value)
259
+ self._control_type = value
260
+
261
+ @property
262
+ def setpoint(self):
263
+ """A number for the setpoint that corresponds to the specified control_type.
264
+
265
+ This can be a value in (W/m2), (C) or (W) depending upon the control type.
266
+ """
267
+ return self._setpoint
268
+
269
+ @setpoint.setter
270
+ def setpoint(self, value):
271
+ if value is not None:
272
+ try:
273
+ value = float(value)
274
+ except (ValueError, TypeError):
275
+ raise TypeError('Input setpoint must be a number. Got '
276
+ '{}: {}.'.format(type(value), value))
277
+ else:
278
+ assert self._control_type == 'AlwaysOn', 'Control setpoint cannot ' \
279
+ 'be None for control type "{}"'.format(self._control_type)
280
+ self._setpoint = value
281
+
282
+ @property
283
+ def schedule(self):
284
+ """Get or set a fractional schedule to be applied on top of the control_type.
285
+
286
+ If None, the control_type will govern all behavior of the construction.
287
+ """
288
+ return self._schedule
289
+
290
+ @schedule.setter
291
+ def schedule(self, value):
292
+ if value is not None:
293
+ assert isinstance(value, (ScheduleRuleset, ScheduleFixedInterval)), \
294
+ 'Expected schedule for window construction shaded schedule. ' \
295
+ 'Got {}.'.format(type(value))
296
+ if value.schedule_type_limit is not None:
297
+ assert value.schedule_type_limit.unit == 'fraction', 'Window ' \
298
+ 'construction schedule should be fractional. Got a schedule ' \
299
+ 'of unit_type [{}].'.format(value.schedule_type_limit.unit_type)
300
+ value.lock() # lock editing in case schedule has multiple references
301
+ self._schedule = value
302
+
303
+ @property
304
+ def materials(self):
305
+ """Get the list of materials in the construction (outside to inside).
306
+
307
+ This will include the shade material layer in its correct position.
308
+ """
309
+ base_mats = list(self._window_construction.materials)
310
+ if self.is_switchable_glazing:
311
+ if self._shade_location == 'Interior':
312
+ base_mats[-1] = self._shade_material
313
+ elif self._shade_location == 'Exterior' or \
314
+ self._window_construction.gap_count == 0:
315
+ base_mats[0] = self._shade_material
316
+ else: # middle glass pane
317
+ base_mats[-3] = self._shade_material
318
+ else:
319
+ if self._shade_location == 'Interior':
320
+ base_mats.append(self._shade_material)
321
+ elif self._shade_location == 'Exterior':
322
+ base_mats.insert(0, self._shade_material)
323
+ else: # between glass shade/blind
324
+ base_mats[-2] = self._between_gap
325
+ base_mats.insert(-1, self._shade_material)
326
+ base_mats.insert(-1, self._between_gap)
327
+ return base_mats
328
+
329
+ @property
330
+ def layers(self):
331
+ """Get a list of material identifiers in the construction (outside to inside).
332
+
333
+ This will include the shade material layer in its correct position.
334
+ """
335
+ return [mat.identifier for mat in self.materials]
336
+
337
+ @property
338
+ def unique_materials(self):
339
+ """Get a list of only unique material objects in the construction.
340
+
341
+ This will include the shade material layer. It will include both types of glass
342
+ layers if the construction is a switchable glazing.
343
+ """
344
+ if self.is_switchable_glazing:
345
+ return list(set(
346
+ self._window_construction.materials + (self.shade_material,)))
347
+ return list(set(self.materials))
348
+
349
+ @property
350
+ def frame(self):
351
+ """Get a window frame for the frame material surrounding the construction."""
352
+ return self._window_construction.frame
353
+
354
+ @property
355
+ def r_value(self):
356
+ """R-value of the bare window construction [m2-K/W] (excluding air films).
357
+
358
+ Note that this excludes all effects of the shade layer.
359
+ """
360
+ return self._window_construction.r_value
361
+
362
+ @property
363
+ def u_value(self):
364
+ """U-value of the bare window construction [W/m2-K] (excluding air films).
365
+
366
+ Note that this excludes all effects of the shade layer.
367
+ """
368
+ return self._window_construction.u_value
369
+
370
+ @property
371
+ def r_factor(self):
372
+ """Bare window construction R-factor [m2-K/W] (with standard air resistances).
373
+
374
+ Note that this excludes all effects of the shade layer.
375
+ Formulas for film coefficients come from EN673 / ISO10292.
376
+ """
377
+ return self._window_construction.r_factor
378
+
379
+ @property
380
+ def u_factor(self):
381
+ """Bare window construction U-factor [W/m2-K] (with standard air resistances).
382
+
383
+ Note that this excludes all effects of the shade layer.
384
+ Formulas for film coefficients come from EN673 / ISO10292.
385
+ """
386
+ return self._window_construction.u_factor
387
+
388
+ @property
389
+ def solar_transmittance(self):
390
+ """The solar transmittance of the bare window construction at normal incidence.
391
+
392
+ Note that this excludes all effects of the shade layer.
393
+ """
394
+ return self._window_construction.solar_transmittance
395
+
396
+ @property
397
+ def visible_transmittance(self):
398
+ """The visible transmittance of the bare window construction at normal incidence.
399
+
400
+ Note that this excludes all effects of the shade layer.
401
+ """
402
+ return self._window_construction.visible_transmittance
403
+
404
+ @property
405
+ def shgc(self):
406
+ """The solar heat gain coefficient (SHGC) of the bare window construction.
407
+
408
+ Note that this excludes all effects of the shade layer.
409
+ """
410
+ return self._window_construction.shgc
411
+
412
+ @property
413
+ def is_symmetric(self):
414
+ """Get a boolean for whether the construction layers are symmetric.
415
+
416
+ Symmetric means that the materials in reversed order are equal to those
417
+ in the current order (eg. 'Glass', 'Air Gap', 'Glass'). This is particularly
418
+ helpful for interior constructions, which need to have matching materials
419
+ in reversed order between adjacent Faces.
420
+ """
421
+ mats = self.materials
422
+ half_mat = int(len(mats) / 2)
423
+ for i in range(half_mat):
424
+ if mats[i] != mats[-(i + 1)]:
425
+ return False
426
+ return True
427
+
428
+ @property
429
+ def has_frame(self):
430
+ """Get a boolean noting whether the construction has a frame assigned to it."""
431
+ return self._window_construction.has_frame
432
+
433
+ @property
434
+ def has_shade(self):
435
+ """Get a boolean noting whether dynamic materials are in the construction.
436
+
437
+ This should always be True for this class.
438
+ """
439
+ return True
440
+
441
+ @property
442
+ def is_dynamic(self):
443
+ """Get a boolean noting whether the construction is dynamic.
444
+
445
+ This will always be True for this class.
446
+ """
447
+ return True
448
+
449
+ @property
450
+ def is_switchable_glazing(self):
451
+ """Get a boolean to note whether the construction is switchable glazing.
452
+
453
+ The construction is a switchable glazing if the shade material is a
454
+ glass material.
455
+ """
456
+ return isinstance(self.shade_material, EnergyWindowMaterialGlazing)
457
+
458
+ @property
459
+ def switched_glass_material(self):
460
+ """Get material replaced by shade glass when construction is switchable glazing.
461
+
462
+ This can be used to compare the properties of the glass layer replaced by
463
+ the shade glass. Will be None if the construction is not a switchable glazing.
464
+ """
465
+ if not self.is_switchable_glazing:
466
+ return None
467
+ base_mats = self._window_construction.materials
468
+ if self._shade_location == 'Interior':
469
+ return self.base_mats[-1]
470
+ elif self._shade_location == 'Exterior' or \
471
+ self._window_construction.gap_count == 0:
472
+ return base_mats[0]
473
+ else: # middle glass pane
474
+ return base_mats[-3]
475
+
476
+ @property
477
+ def inside_emissivity(self):
478
+ """"The emissivity of the inside face of the construction.
479
+
480
+ This will use the emissivity of the shade layer if it is interior.
481
+ """
482
+ mats = self.materials
483
+ if isinstance(mats[-1], EnergyWindowMaterialSimpleGlazSys):
484
+ return 0.84
485
+ try:
486
+ return mats[-1].emissivity_back
487
+ except AttributeError:
488
+ return mats[-1].emissivity
489
+
490
+ @property
491
+ def outside_emissivity(self):
492
+ """"The emissivity of the outside face of the construction.
493
+
494
+ This will use the emissivity of the shade layer if it is interior.
495
+ """
496
+ mats = self.materials
497
+ if isinstance(mats[0], EnergyWindowMaterialSimpleGlazSys):
498
+ return 0.84
499
+ return mats[0].emissivity
500
+
501
+ @property
502
+ def thickness(self):
503
+ """Thickness of the construction [m], excluding the shade layer.
504
+
505
+ This is effectively the thickness that EnergyPlus assumes.
506
+ """
507
+ return self._window_construction.thickness
508
+
509
+ @property
510
+ def glazing_count(self):
511
+ """The number of glazing materials contained within the construction.
512
+
513
+ Note that Simple Glazing System materials do not count.
514
+ """
515
+ return self._window_construction.glazing_count
516
+
517
+ @property
518
+ def gap_count(self):
519
+ """Get the number of gas gaps contained within the construction."""
520
+ count = self._window_construction.gap_count
521
+ if self.shade_location == 'Between' and not self.is_switchable_glazing:
522
+ count += 1
523
+ return count
524
+
525
+ @property
526
+ def is_groupable(self):
527
+ """Get a boolean for whether controls allow the construction to be grouped."""
528
+ return self.control_type in self.GROUPABLE_TYPES
529
+
530
+ @property
531
+ def is_room_groupable(self):
532
+ """Get a boolean for whether controls allow grouping by room."""
533
+ return self.control_type in self.ROOM_GROUPABLE_TYPES
534
+
535
+ @property
536
+ def inside_material(self):
537
+ """The the inside material layer of the construction.
538
+
539
+ Useful for checking that an asymmetric construction is correctly assigned.
540
+ """
541
+ return self.materials[-1]
542
+
543
+ @property
544
+ def outside_material(self):
545
+ """The the outside material layer of the construction.
546
+
547
+ Useful for checking that an asymmetric construction is correctly assigned.
548
+ """
549
+ return self.materials[0]
550
+
551
+ @property
552
+ def _ep_shading_type(self):
553
+ """Text for the Shading Type field that EnergyPlus wants in the IDF."""
554
+ if self.is_switchable_glazing:
555
+ return 'SwitchableGlazing'
556
+ elif isinstance(self.shade_material, EnergyWindowMaterialBlind):
557
+ return 'BetweenGlassBlind' if self.shade_location == 'Between' \
558
+ else '{}Blind'.format(self.shade_location)
559
+ else:
560
+ return 'BetweenGlassShade' if self.shade_location == 'Between' \
561
+ else '{}Shade'.format(self.shade_location)
562
+
563
+ @property
564
+ def user_data(self):
565
+ """Get or set an optional dictionary for additional meta data for this object.
566
+
567
+ This will be None until it has been set. All keys and values of this
568
+ dictionary should be of a standard Python type to ensure correct
569
+ serialization of the object to/from JSON (eg. str, float, int, list, dict)
570
+ """
571
+ if self._user_data is not None:
572
+ return self._user_data
573
+
574
+ @user_data.setter
575
+ def user_data(self, value):
576
+ if value is not None:
577
+ assert isinstance(value, dict), 'Expected dictionary for honeybee_energy' \
578
+ 'object user_data. Got {}.'.format(type(value))
579
+ self._user_data = value
580
+
581
+ @property
582
+ def properties(self):
583
+ """Get properties for extensions."""
584
+ return self._properties
585
+
586
+ @classmethod
587
+ def from_dict(cls, data):
588
+ """Create a WindowConstructionShade from a dictionary.
589
+
590
+ Note that the dictionary must be a non-abridged version for this
591
+ classmethod to work.
592
+
593
+ Args:
594
+ data: A python dictionary in the following format
595
+
596
+ .. code-block:: python
597
+
598
+ {
599
+ "type": 'WindowConstructionShade',
600
+ "identifier": 'Double Pane U-250 IntBlind-0025',
601
+ "display_name": 'Double Pane with Interior Blind',
602
+ "window_construction": {} # a WindowConstruction dictionary representation
603
+ "shade_material": {} # a shade/blind/glass dictionary representation
604
+ "shade_location": 'Interior', # text for shade layer location
605
+ "control_type": 'OnIfHighSolarOnWindow', # text for shade control type
606
+ "setpoint": 200, # number for control setpoint
607
+ "schedule": {} # optional ScheduleRuleset or ScheduleFixedInterval dict
608
+ }
609
+ """
610
+ # check the type
611
+ assert data['type'] == 'WindowConstructionShade', \
612
+ 'Expected WindowConstructionShade. Got {}.'.format(data['type'])
613
+
614
+ # re-serialize required inputs
615
+ window_constr = WindowConstruction.from_dict(data['window_construction'])
616
+ shade_material = dict_to_material(data['shade_material'])
617
+
618
+ # re-serialize optional inputs
619
+ shade_location, control_type, setpoint = cls._from_dict_defaults(data)
620
+ schedule = dict_to_schedule(data['schedule']) if 'schedule' in data and \
621
+ data['schedule'] is not None else None
622
+
623
+ new_obj = cls(data['identifier'], window_constr, shade_material, shade_location,
624
+ control_type, setpoint, schedule)
625
+ if 'display_name' in data and data['display_name'] is not None:
626
+ new_obj.display_name = data['display_name']
627
+ if 'user_data' in data and data['user_data'] is not None:
628
+ new_obj.user_data = data['user_data']
629
+ if 'properties' in data and data['properties'] is not None:
630
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
631
+ return new_obj
632
+
633
+ @classmethod
634
+ def from_dict_abridged(cls, data, materials, schedules):
635
+ """Create a WindowConstructionShade from an abridged dictionary.
636
+
637
+ Args:
638
+ data: An WindowConstructionShade dictionary with the format below.
639
+ materials: A dictionary with identifiers of materials as keys and
640
+ Python material objects as values.
641
+ schedules: A dictionary with schedule identifiers as keys and
642
+ honeybee schedule objects as values.
643
+
644
+ .. code-block:: python
645
+
646
+ {
647
+ "type": 'WindowConstructionShadeAbridged',
648
+ "identifier": 'Double Pane U-250 IntBlind-0025',
649
+ "display_name": 'Double Pane with Interior Blind',
650
+ "window_construction": {} # a WindowConstructionAbridged dictionary
651
+ "shade_material": 'Blind-0025' # a shade/blind/glass identifier
652
+ "shade_location": 'Interior', # text for shade layer location
653
+ "control_type": 'OnIfHighSolarOnWindow', # text for shade control type
654
+ "setpoint": 200, # number for control setpoint
655
+ "schedule": 'DayNight_Schedule' # optional schedule identifier
656
+ }
657
+ """
658
+ # check the type
659
+ assert data['type'] == 'WindowConstructionShadeAbridged', \
660
+ 'Expected WindowConstructionShadeAbridged. Got {}.'.format(data['type'])
661
+
662
+ # re-serialize required inputs
663
+ window_constr = WindowConstruction.from_dict_abridged(
664
+ data['window_construction'], materials)
665
+ shade_material = materials[data['shade_material']]
666
+
667
+ # re-serialize optional inputs
668
+ shade_location, control_type, setpoint = cls._from_dict_defaults(data)
669
+ schedule = schedules[data['schedule']] if 'schedule' in data and \
670
+ data['schedule'] is not None else None
671
+
672
+ new_obj = cls(data['identifier'], window_constr, shade_material, shade_location,
673
+ control_type, setpoint, schedule)
674
+ if 'display_name' in data and data['display_name'] is not None:
675
+ new_obj.display_name = data['display_name']
676
+ if 'user_data' in data and data['user_data'] is not None:
677
+ new_obj.user_data = data['user_data']
678
+ if 'properties' in data and data['properties'] is not None:
679
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
680
+ return new_obj
681
+
682
+ def to_idf(self):
683
+ """IDF string representation of construction object.
684
+
685
+ Note that this method only outputs a single string for the bare window
686
+ construction and, to write the full construction into an IDF, the
687
+ construction's unique_materials must also be written along with the
688
+ output of to_shaded_idf, which contains the shaded construction.
689
+ Also, for each Aperture to which this construction is assigned, a
690
+ ShadingControl object must also be written, which can be obtained from
691
+ the to_shading_control_idf. If the construction has a frame, the frame
692
+ definition must also be written.
693
+
694
+ Returns:
695
+ Text string representation of the bare (unshaded) construction.
696
+
697
+ .. code-block:: shell
698
+
699
+ Construction,
700
+ Generic Double Pane, !- name
701
+ Generic Low-e Glass, !- layer 1
702
+ Generic Window Air Gap, !- layer 2
703
+ Generic Clear Glass; !- layer 3
704
+ """
705
+ return self._window_construction.to_idf()
706
+
707
+ def to_shaded_idf(self):
708
+ """IDF string representation of construction in its shaded state.
709
+
710
+ Returns:
711
+ Text string representation of the shaded construction.
712
+ """
713
+ materials = self.materials
714
+ values = (self.identifier,) + tuple(mat.identifier for mat in materials)
715
+ comments = ('name',) + tuple('layer %s' % (i + 1) for i in range(len(materials)))
716
+ return generate_idf_string('Construction', values, comments)
717
+
718
+ def to_shading_control_idf(self, aperture_identifier, room_identifier):
719
+ """IDF string representation of a WindowShadingControl object.
720
+
721
+ This has to be written for every Aperture to which this construction is
722
+ assigned in order for EnergyPlus to simulate it correctly.
723
+
724
+ Args:
725
+ aperture_identifier: The identifier of the honeybee Aperture to
726
+ which this construction is assigned.
727
+ room_identifier: The identifier of the honeybee Room to which the
728
+ aperture belongs.
729
+
730
+ Returns:
731
+ Text string representation of the WindowShadingControl.
732
+ """
733
+ control_name = '{}_ShdControl'.format(aperture_identifier)
734
+ control_type = 'OnIfScheduleAllows' if self.schedule is not None and \
735
+ self.control_type == 'AlwaysOn' else self.control_type
736
+ sch = self.schedule.identifier if self.schedule is not None else ''
737
+ sch_bool = 'Yes' if self.schedule is not None else 'No'
738
+ setpt = self.setpoint if self.setpoint is not None else ''
739
+ values = (control_name, room_identifier, 1, self._ep_shading_type,
740
+ self.identifier, control_type, sch, setpt, sch_bool,
741
+ '', '', '', '', '', '', '', aperture_identifier)
742
+ comments = \
743
+ ('name', 'zone name', 'sequence number', 'shading type',
744
+ 'construction with shade', 'control type', 'schedule', 'setpoint',
745
+ 'is scheduled', 'is glare controlled', 'shade material', 'slat control',
746
+ 'slat schedule', 'setpoint 2', 'daylight object', 'multiple control type',
747
+ 'fenestration surface')
748
+ return generate_idf_string('WindowShadingControl', values, comments)
749
+
750
+ def to_radiance_solar(self):
751
+ """Honeybee Radiance material for the bare (unshaded) construction."""
752
+ # TODO: add method that represents blinds with BSDF + shades with Trans
753
+ return self._window_construction.to_radiance_solar()
754
+
755
+ def to_radiance_visible(self):
756
+ """Honeybee Radiance material for the bare (unshaded) construction."""
757
+ # TODO: add method that represents blinds with BSDF + shades with Trans
758
+ return self._window_construction.to_radiance_visible()
759
+
760
+ def to_dict(self, abridged=False):
761
+ """Window construction dictionary representation.
762
+
763
+ Args:
764
+ abridged: Boolean to note whether the full dictionary describing the
765
+ object should be returned (False) or just an abridged version (True),
766
+ which only specifies the identifiers of material layers and
767
+ schedules. (Default: False).
768
+ """
769
+ base = {'type': 'WindowConstructionShade'} if not \
770
+ abridged else {'type': 'WindowConstructionShadeAbridged'}
771
+ base['identifier'] = self.identifier
772
+ base['window_construction'] = self.window_construction.to_dict(abridged)
773
+ base['shade_material'] = self.shade_material.identifier if abridged \
774
+ else self.shade_material.to_dict()
775
+ base['shade_location'] = self.shade_location
776
+ base['control_type'] = self.control_type
777
+ if self.control_type != 'AlwaysOn':
778
+ base['setpoint'] = self.setpoint
779
+ if self.schedule is not None:
780
+ base['schedule'] = self.schedule.identifier if abridged \
781
+ else self.schedule.to_dict()
782
+ if self._display_name is not None:
783
+ base['display_name'] = self.display_name
784
+ if self._user_data is not None:
785
+ base['user_data'] = self.user_data
786
+ base['properties'] = self.properties.to_dict() if self._properties else None
787
+ return base
788
+
789
+ def lock(self):
790
+ """The lock() method will also lock the shade_material."""
791
+ self._locked = True
792
+ self.shade_material.lock()
793
+
794
+ def unlock(self):
795
+ """The unlock() method will also unlock the shade_material."""
796
+ self._locked = False
797
+ self.shade_material.unlock()
798
+
799
+ def duplicate(self):
800
+ """Get a copy of this construction."""
801
+ return self.__copy__()
802
+
803
+ @staticmethod
804
+ def _from_dict_defaults(data):
805
+ "Re-serialize default values from a dictionary."
806
+ shade_location = data['shade_location'] if 'shade_location' in data and \
807
+ data['shade_location'] is not None else 'Interior'
808
+ control_type = data['control_type'] if 'control_type' in data and \
809
+ data['control_type'] is not None else 'AlwaysOn'
810
+ setpoint = data['setpoint'] if 'setpoint' in data \
811
+ else None
812
+ return shade_location, control_type, setpoint
813
+
814
+ def __copy__(self):
815
+ new_con = WindowConstructionShade(
816
+ self.identifier, self.window_construction, self.shade_material,
817
+ self.shade_location, self.control_type, self.setpoint,
818
+ self.schedule)
819
+ new_con._between_gap = self._between_gap
820
+ new_con._display_name = self._display_name
821
+ new_con._user_data = None if self._user_data is None else self._user_data.copy()
822
+ new_con._properties._duplicate_extension_attr(self._properties)
823
+ return new_con
824
+
825
+ def __key(self):
826
+ """A tuple based on the object properties, useful for hashing."""
827
+ sch = hash(self.schedule) if self.schedule is not None else None
828
+ return (self._identifier, hash(self.window_construction),
829
+ hash(self.shade_material), self.shade_location, self.control_type,
830
+ self.setpoint, sch)
831
+
832
+ def __hash__(self):
833
+ return hash(self.__key())
834
+
835
+ def __eq__(self, other):
836
+ return isinstance(other, WindowConstructionShade) and \
837
+ self.__key() == other.__key()
838
+
839
+ def __ne__(self, other):
840
+ return not self.__eq__(other)
841
+
842
+ def ToString(self):
843
+ """Overwrite .NET ToString."""
844
+ return self.__repr__()
845
+
846
+ def __repr__(self):
847
+ return self.to_shaded_idf()