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,1078 @@
1
+ # coding=utf-8
2
+ """Methods to write to idf."""
3
+ from .config import folders
4
+
5
+ from ladybug_geometry.geometry3d import Face3D
6
+ from honeybee.room import Room
7
+ from honeybee.face import Face
8
+ from honeybee.boundarycondition import Outdoors, Surface, Ground, boundary_conditions
9
+ from honeybee.facetype import Wall, Floor, RoofCeiling, AirBoundary
10
+ from honeybee.units import parse_distance_string, conversion_factor_to_meters
11
+
12
+ try:
13
+ from itertools import izip as zip # python 2
14
+ except ImportError:
15
+ xrange = range # python 3
16
+
17
+
18
+ def generate_idf_string(object_type, values, comments=None):
19
+ """Get an IDF string representation of an EnergyPlus object.
20
+
21
+ Args:
22
+ object_type: Text representing the expected start of the IDF object.
23
+ (ie. WindowMaterial:Glazing).
24
+ values: A list of values associated with the EnergyPlus object in the
25
+ order that they are supposed to be written to IDF format.
26
+ comments: A list of text comments with the same length as the values.
27
+ If None, no comments will be written into the object.
28
+
29
+ Returns:
30
+ ep_str -- Am EnergyPlus IDF string representing a single object.
31
+ """
32
+ if comments is not None:
33
+ space_count = tuple((25 - len(str(n))) for n in values)
34
+ spaces = tuple(s_c * ' ' if s_c > 0 else ' ' for s_c in space_count)
35
+ body_str = '\n '.join('{},{}!- {}'.format(val, spc, com) for val, spc, com in
36
+ zip(values[:-1], spaces[:-1], comments[:-1]))
37
+ ep_str = '{},\n {}'.format(object_type, body_str)
38
+ if len(values) == 1: # ensure we don't have an extra line break
39
+ ep_str = ''.join(
40
+ (ep_str, '{};{}!- {}'.format(values[-1], spaces[-1], comments[-1])))
41
+ else: # include an extra line break
42
+ end_str = '\n {};{}!- {}'.format(values[-1], spaces[-1], comments[-1]) \
43
+ if comments[-1] != '' else '\n {};'.format(values[-1])
44
+ ep_str = ''.join((ep_str, end_str))
45
+ else:
46
+ body_str = '\n '.join('{},'.format(val) for val in values[:-1])
47
+ ep_str = '{},\n {}'.format(object_type, body_str)
48
+ if len(values) == 1: # ensure we don't have an extra line break
49
+ ep_str = ''.join((ep_str, '{};'.format(values[-1])))
50
+ else: # include an extra line break
51
+ ep_str = ''.join((ep_str, '\n {};'.format(values[-1])))
52
+ return ep_str
53
+
54
+
55
+ def shade_mesh_to_idf(shade_mesh):
56
+ """Generate an IDF string representation of a ShadeMesh.
57
+
58
+ Note that the resulting string will possess both the Shading object
59
+ as well as a ShadingProperty:Reflectance if the Shade's construction
60
+ is not in line with the EnergyPlus default of 0.2 reflectance.
61
+
62
+ Args:
63
+ shade_mesh: A honeybee ShadeMesh for which an IDF representation
64
+ will be returned.
65
+ """
66
+ trans_sched = shade_mesh.properties.energy.transmittance_schedule.identifier if \
67
+ shade_mesh.properties.energy.transmittance_schedule is not None else ''
68
+ all_shd_str = []
69
+ for i, shade in enumerate(shade_mesh.geometry.face_vertices):
70
+ # process the geometry to get upper-left vertices
71
+ shade_face = Face3D(shade)
72
+ ul_verts = shade_face.upper_left_counter_clockwise_vertices
73
+
74
+ # create the Shading:Detailed IDF string
75
+ values = (
76
+ '{}_{}'.format(shade_mesh.identifier, i),
77
+ trans_sched,
78
+ len(ul_verts),
79
+ ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
80
+ )
81
+ comments = (
82
+ 'name',
83
+ 'transmittance schedule',
84
+ 'number of vertices',
85
+ ''
86
+ )
87
+ shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
88
+ all_shd_str.append(shade_str)
89
+
90
+ # create the ShadingProperty:Reflectance if construction is not default
91
+ construction = shade_mesh.properties.energy.construction
92
+ if not construction.is_default:
93
+ values = (
94
+ shade_mesh.identifier,
95
+ construction.solar_reflectance,
96
+ construction.visible_reflectance
97
+ )
98
+ comments = (
99
+ 'shading surface name',
100
+ 'diffuse solar reflectance',
101
+ 'diffuse visible reflectance'
102
+ )
103
+ if construction.is_specular:
104
+ values = values + (1, construction.identifier)
105
+ comments = comments + ('glazed fraction', 'glazing construction')
106
+ constr_str = generate_idf_string(
107
+ 'ShadingProperty:Reflectance', values, comments)
108
+ all_shd_str.append(constr_str)
109
+
110
+ return '\n\n'.join(all_shd_str)
111
+
112
+
113
+ def shade_to_idf(shade):
114
+ """Generate an IDF string representation of a Shade.
115
+
116
+ Note that the resulting string will possess both the Shading object
117
+ as well as a ShadingProperty:Reflectance if the Shade's construction
118
+ is not in line with the EnergyPlus default of 0.2 reflectance.
119
+
120
+ Args:
121
+ shade: A honeybee Shade for which an IDF representation will be returned.
122
+ """
123
+ # create the Shading:Detailed IDF string
124
+ trans_sched = shade.properties.energy.transmittance_schedule.identifier if \
125
+ shade.properties.energy.transmittance_schedule is not None else ''
126
+ ul_verts = shade.upper_left_vertices
127
+ if shade.has_parent and not isinstance(shade.parent, Room):
128
+ if isinstance(shade.parent, Face):
129
+ base_srf = shade.parent.identifier
130
+ else: # aperture or door for parent
131
+ try:
132
+ base_srf = shade.parent.parent.identifier
133
+ except AttributeError:
134
+ base_srf = 'unknown' # aperture without a parent (not simulate-able)
135
+ values = (
136
+ shade.identifier,
137
+ base_srf,
138
+ trans_sched,
139
+ len(shade.vertices),
140
+ ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
141
+ )
142
+ comments = (
143
+ 'name',
144
+ 'base surface',
145
+ 'transmittance schedule',
146
+ 'number of vertices',
147
+ ''
148
+ )
149
+ shade_str = generate_idf_string('Shading:Zone:Detailed', values, comments)
150
+ else: # orphaned shade
151
+ values = (
152
+ shade.identifier,
153
+ trans_sched,
154
+ len(shade.vertices),
155
+ ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
156
+ )
157
+ comments = (
158
+ 'name',
159
+ 'transmittance schedule',
160
+ 'number of vertices',
161
+ ''
162
+ )
163
+ shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
164
+
165
+ # create the ShadingProperty:Reflectance IDF string if construction is not default
166
+ construction = shade.properties.energy.construction
167
+ if construction.is_default:
168
+ return shade_str
169
+ else:
170
+ values = (
171
+ shade.identifier,
172
+ construction.solar_reflectance,
173
+ construction.visible_reflectance
174
+ )
175
+ comments = (
176
+ 'shading surface name',
177
+ 'diffuse solar reflectance',
178
+ 'diffuse visible reflectance'
179
+ )
180
+ if construction.is_specular:
181
+ values = values + (1, construction.identifier)
182
+ comments = comments + ('glazed fraction of surface', 'glazing construction')
183
+ constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
184
+ return '\n\n'.join((shade_str, constr_str))
185
+
186
+
187
+ def door_to_idf(door):
188
+ """Generate an IDF string representation of a Door.
189
+
190
+ Note that the resulting string does not include full construction definitions
191
+ but it will include a WindowShadingControl definition if a WindowConstructionShade
192
+ is assigned to the door. It will also include a ventilation object if the door
193
+ has a VentilationOpening object assigned to it.
194
+
195
+ Also note that shades assigned to the Door are not included in the resulting
196
+ string. To write these objects into a final string, you must loop through the
197
+ Door.shades, and call the to.idf method on each one.
198
+
199
+ If the input door is orphaned, the resulting string will possess both the
200
+ Shading object as well as a ShadingProperty:Reflectance that aligns with the
201
+ Doors's exterior construction properties. However, a transmittance schedule
202
+ that matches the transmittance of the window construction will only be
203
+ referenced and not included in the resulting string. All transmittance schedules
204
+ follow the format of 'Constant %.3f Transmittance'.
205
+
206
+ Args:
207
+ door: A honeybee Door for which an IDF representation will be returned.
208
+ """
209
+ # IF ORPHANED: write the door as a shade
210
+ if not door.has_parent:
211
+ # create the Shading:Detailed IDF string
212
+ cns = door.properties.energy.construction
213
+ trans_sch = 'Constant %.3f Transmittance' % cns.solar_transmittance \
214
+ if door.is_glass else ''
215
+ verts = door.upper_left_vertices
216
+ verts_str = ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in verts)
217
+ values = (door.identifier, trans_sch, len(verts), verts_str)
218
+ comments = ('name', 'transmittance schedule', 'number of vertices', '')
219
+ shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
220
+
221
+ # create the ShadingProperty:Reflectance
222
+ comments = (
223
+ 'shade surface name', 'diffuse solar reflectance', 'diffuse visible reflectance')
224
+ if door.is_glass:
225
+ values = (door.identifier, 0.2, 0.2, 1, cns.identifier)
226
+ comments = comments + ('glazed fraction of surface', 'glazing construction')
227
+ else:
228
+ values = (door.identifier, cns.outside_solar_reflectance,
229
+ cns.outside_visible_reflectance)
230
+ constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
231
+ return '\n\n'.join((shade_str, constr_str))
232
+
233
+ # IF CHILD: write the door as a fenestration surface
234
+ # set defaults for missing fields
235
+ door_bc_obj = door.boundary_condition.boundary_condition_object if \
236
+ isinstance(door.boundary_condition, Surface) else ''
237
+ construction = door.properties.energy.construction
238
+ frame_name = construction.frame.identifier if construction.has_frame else ''
239
+ if construction.has_shade:
240
+ constr_name = construction.window_construction.identifier
241
+ elif construction.is_dynamic:
242
+ constr_name = '{}State0'.format(construction.constructions[0].identifier)
243
+ else:
244
+ constr_name = construction.identifier
245
+ if door.has_parent:
246
+ parent_face = door.parent.identifier
247
+ parent_room = door.parent.parent.identifier if door.parent.has_parent \
248
+ else 'unknown'
249
+ else:
250
+ parent_room = parent_face = 'unknown'
251
+
252
+ # create the fenestration surface string
253
+ ul_verts = door.upper_left_vertices
254
+ values = (
255
+ door.identifier,
256
+ 'Door' if not door.is_glass else 'GlassDoor',
257
+ constr_name,
258
+ parent_face,
259
+ door_bc_obj,
260
+ door.boundary_condition.view_factor,
261
+ frame_name,
262
+ '1',
263
+ len(door.vertices),
264
+ ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
265
+ )
266
+ comments = (
267
+ 'name',
268
+ 'surface type',
269
+ 'construction name',
270
+ 'building surface name',
271
+ 'boundary condition object',
272
+ 'view factor to ground',
273
+ 'frame and divider name',
274
+ 'multiplier',
275
+ 'number of vertices',
276
+ ''
277
+ )
278
+ fen_str = generate_idf_string('FenestrationSurface:Detailed', values, comments)
279
+
280
+ # create the WindowShadingControl object if it is needed
281
+ if construction.has_shade:
282
+ shd_prop_str = construction.to_shading_control_idf(door.identifier, parent_room)
283
+ fen_str = '\n\n'.join((fen_str, shd_prop_str))
284
+
285
+ # create the VentilationOpening object if it is needed
286
+ if door.properties.energy.vent_opening is not None:
287
+ try:
288
+ vent_str = door.properties.energy.vent_opening.to_idf()
289
+ fen_str = '\n\n'.join((fen_str, vent_str))
290
+ except AssertionError: # door does not have a parent room
291
+ pass
292
+ return fen_str
293
+
294
+
295
+ def aperture_to_idf(aperture):
296
+ """Generate an IDF string representation of an Aperture.
297
+
298
+ Note that the resulting string does not include full construction definitions
299
+ but it will include a WindowShadingControl definition if a WindowConstructionShade
300
+ is assigned to the aperture. It will also include a ventilation object if the
301
+ aperture has a VentilationOpening object assigned to it.
302
+
303
+ Also note that shades assigned to the Aperture are not included in the resulting
304
+ string. To write these objects into a final string, you must loop through the
305
+ Aperture.shades, and call the to.idf method on each one.
306
+
307
+ If the input aperture is orphaned, the resulting string will possess both the
308
+ Shading object as well as a ShadingProperty:Reflectance that aligns with the
309
+ Aperture's exterior construction properties. However, a transmittance schedule
310
+ that matches the transmittance of the window construction will only be
311
+ referenced and not included in the resulting string. All transmittance schedules
312
+ follow the format of 'Constant %.3f Transmittance'.
313
+
314
+ Args:
315
+ aperture: A honeybee Aperture for which an IDF representation will be returned.
316
+ """
317
+ # IF ORPHANED: write the aperture as a shade
318
+ if not aperture.has_parent:
319
+ # create the Shading:Detailed IDF string
320
+ cns = aperture.properties.energy.construction
321
+ trans_sch = 'Constant %.3f Transmittance' % cns.solar_transmittance
322
+ verts = aperture.upper_left_vertices
323
+ verts_str = ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in verts)
324
+ values = (aperture.identifier, trans_sch, len(verts), verts_str)
325
+ comments = ('name', 'transmittance schedule', 'number of vertices', '')
326
+ shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
327
+
328
+ # create the ShadingProperty:Reflectance
329
+ values = (aperture.identifier, 0.2, 0.2, 1, cns.identifier)
330
+ comments = (
331
+ 'shade surface name', 'diffuse solar reflectance', 'diffuse visible reflectance',
332
+ 'glazed fraction of surface', 'glazing construction'
333
+ )
334
+ constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
335
+ return '\n\n'.join((shade_str, constr_str))
336
+
337
+ # IF CHILD: write the aperture as a fenestration surface
338
+ # set defaults for missing fields
339
+ ap_bc_obj = aperture.boundary_condition.boundary_condition_object if \
340
+ isinstance(aperture.boundary_condition, Surface) else ''
341
+ construction = aperture.properties.energy.construction
342
+ frame_name = construction.frame.identifier if construction.has_frame else ''
343
+ if construction.has_shade:
344
+ constr_name = construction.window_construction.identifier
345
+ elif construction.is_dynamic:
346
+ constr_name = '{}State0'.format(construction.constructions[0].identifier)
347
+ else:
348
+ constr_name = construction.identifier
349
+ if aperture.has_parent:
350
+ parent_face = aperture.parent.identifier
351
+ parent_room = aperture.parent.parent.identifier if aperture.parent.has_parent \
352
+ else 'unknown'
353
+ else:
354
+ parent_room = parent_face = 'unknown'
355
+
356
+ # create the fenestration surface string
357
+ ul_verts = aperture.upper_left_vertices
358
+ values = (
359
+ aperture.identifier,
360
+ 'Window',
361
+ constr_name,
362
+ parent_face,
363
+ ap_bc_obj,
364
+ aperture.boundary_condition.view_factor,
365
+ frame_name,
366
+ '1',
367
+ len(aperture.vertices),
368
+ ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
369
+ )
370
+ comments = (
371
+ 'name',
372
+ 'surface type',
373
+ 'construction name',
374
+ 'building surface name',
375
+ 'boundary condition object',
376
+ 'view factor to ground',
377
+ 'frame and divider name',
378
+ 'multiplier',
379
+ 'number of vertices',
380
+ ''
381
+ )
382
+ fen_str = generate_idf_string('FenestrationSurface:Detailed', values, comments)
383
+
384
+ # create the WindowShadingControl object if it is needed
385
+ if construction.has_shade:
386
+ shd_prop_str = construction.to_shading_control_idf(
387
+ aperture.identifier, parent_room)
388
+ fen_str = '\n\n'.join((fen_str, shd_prop_str))
389
+
390
+ # create the VentilationOpening object if it is needed
391
+ if aperture.properties.energy.vent_opening is not None:
392
+ try:
393
+ vent_str = aperture.properties.energy.vent_opening.to_idf()
394
+ fen_str = '\n\n'.join((fen_str, vent_str))
395
+ except AssertionError: # aperture does not have a parent room
396
+ pass
397
+ return fen_str
398
+
399
+
400
+ def face_to_idf(face):
401
+ """Generate an IDF string representation of a Face.
402
+
403
+ Note that the resulting string does not include full construction definitions.
404
+
405
+ Also note that this does not include any of the shades assigned to the Face
406
+ in the resulting string. Nor does it include the strings for the
407
+ apertures or doors. To write these objects into a final string, you must
408
+ loop through the Face.shades, Face.apertures, and Face.doors and call the
409
+ to.idf method on each one.
410
+
411
+ If the input face is orphaned, the resulting string will possess both the
412
+ Shading object as well as a ShadingProperty:Reflectance that aligns with
413
+ the Face's exterior construction properties. Furthermore, any child
414
+ apertures of doors in the face will also be included as shading geometries.
415
+
416
+ Args:
417
+ face: A honeybee Face for which an IDF representation will be returned.
418
+ """
419
+ # IF ORPHANED: write the face as a shade
420
+ if not face.has_parent:
421
+ # create the Shading:Detailed IDF string
422
+ verts = face.punched_geometry.upper_left_counter_clockwise_vertices
423
+ verts_str = ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in verts)
424
+ values = (face.identifier, '', len(verts), verts_str)
425
+ comments = ('name', 'transmittance schedule', 'number of vertices', '')
426
+ shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
427
+
428
+ # create the ShadingProperty:Reflectance IDF string
429
+ cns = face.properties.energy.construction
430
+ values = (
431
+ face.identifier, cns.outside_solar_reflectance, cns.outside_visible_reflectance)
432
+ comments = (
433
+ 'shade surface name', 'diffuse solar reflectance', 'diffuse visible reflectance')
434
+ constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
435
+
436
+ # translate any child apertures or doors
437
+ face_str = [shade_str, constr_str]
438
+ for ap in face.apertures:
439
+ ap._parent = None # remove parent to translate as orphaned
440
+ face_str.append(aperture_to_idf(ap))
441
+ ap._parent = face # put back the parent
442
+ for dr in face.doors:
443
+ dr._parent = None # remove parent to translate as orphaned
444
+ face_str.append(door_to_idf(dr))
445
+ dr._parent = face # put back the parent
446
+ return '\n\n'.join(face_str)
447
+
448
+ # IF CHILD: write the aperture as a fenestration surface
449
+ # select the correct face type
450
+ if isinstance(face.type, AirBoundary):
451
+ face_type = 'Wall' # air boundaries are not a Surface type in EnergyPlus
452
+ elif isinstance(face.type, RoofCeiling):
453
+ if face.altitude < 0:
454
+ face_type = 'Wall' # ensure E+ does not try to flip the Face
455
+ elif isinstance(face.boundary_condition, (Outdoors, Ground)):
456
+ face_type = 'Roof' # E+ distinguishes between Roof and Ceiling
457
+ else:
458
+ face_type = 'Ceiling'
459
+ elif isinstance(face.type, Floor) and face.altitude > 0:
460
+ face_type = 'Wall' # ensure E+ does not try to flip the Face
461
+ else:
462
+ face_type = face.type.name
463
+ # select the correct boundary condition
464
+ bc_name, append_txt = face.boundary_condition.name, None
465
+ if isinstance(face.boundary_condition, Surface):
466
+ face_bc_obj = face.boundary_condition.boundary_condition_object
467
+ elif face.boundary_condition.name == 'OtherSideTemperature':
468
+ face_bc_obj = '{}_OtherTemp'.format(face.identifier)
469
+ append_txt = face.boundary_condition.to_idf(face_bc_obj)
470
+ bc_name = 'OtherSideCoefficients'
471
+ else:
472
+ face_bc_obj = ''
473
+ # process the geometry correctly if it has holes
474
+ ul_verts = face.upper_left_vertices
475
+ if face.geometry.has_holes and isinstance(face.boundary_condition, Surface):
476
+ # check if the first vertex is the upper-left vertex
477
+ pt1, found_i = ul_verts[0], False
478
+ for pt in ul_verts[1:]:
479
+ if pt == pt1:
480
+ found_i = True
481
+ break
482
+ if found_i: # reorder the vertices to have boundary first
483
+ ul_verts = reversed(ul_verts)
484
+ # assemble the values and the comments
485
+ if face.has_parent:
486
+ if face.parent.identifier == face.parent.zone:
487
+ zone_name, space_name = face.parent.zone, ''
488
+ else:
489
+ zone_name, space_name = face.parent.zone, face.parent.identifier
490
+ else:
491
+ zone_name, space_name = 'unknown', ''
492
+ values = (
493
+ face.identifier,
494
+ face_type,
495
+ face.properties.energy.construction.identifier,
496
+ zone_name,
497
+ space_name,
498
+ bc_name,
499
+ face_bc_obj,
500
+ face.boundary_condition.sun_exposure_idf,
501
+ face.boundary_condition.wind_exposure_idf,
502
+ face.boundary_condition.view_factor,
503
+ len(face.vertices),
504
+ ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
505
+ )
506
+ comments = (
507
+ 'name',
508
+ 'surface type',
509
+ 'construction name',
510
+ 'zone name',
511
+ 'space name',
512
+ 'boundary condition',
513
+ 'boundary condition object',
514
+ 'sun exposure',
515
+ 'wind exposure',
516
+ 'view factor to ground',
517
+ 'number of vertices',
518
+ ''
519
+ )
520
+ face_idf = generate_idf_string('BuildingSurface:Detailed', values, comments)
521
+ return face_idf if not append_txt else face_idf + append_txt
522
+
523
+
524
+ def room_to_idf(room):
525
+ """Generate an IDF string representation of a Room.
526
+
527
+ The resulting string will include all internal gain definitions for the Room
528
+ (people, lights, equipment, process) and the infiltration definition. It will
529
+ also include internal masses, ventilation fans, and daylight controls. However,
530
+ complete schedule definitions assigned to these load objects are excluded.
531
+
532
+ If the room's zone name is the same as the room identifier, the resulting IDF
533
+ string will be for an EnergyPlus Zone and it will include ventilation
534
+ requirements and thermostat objects. Otherwise, the IDF string will be for
535
+ a Space with ventilation and thermostats excluded (with the assumption
536
+ that these objects are to be written separately with the parent Zone).
537
+
538
+ The Room's HVAC is always excluded in the string returned from this method
539
+ regardless of whether the room represents an entire zone or an individual
540
+ space within a larger zone.
541
+
542
+ Also note that this method does not write any of the geometry of the Room
543
+ into the resulting string. To represent the Room geometry, you must loop
544
+ through the Room.shades and Room.faces and call the to.idf method on
545
+ each one. Note that you will likely also need to call to.idf on the
546
+ apertures, doors and shades of each face as well as the shades on each
547
+ aperture.
548
+
549
+ Args:
550
+ room: A honeybee Room for which an IDF representation will be returned.
551
+ """
552
+ # clean the room name so that it can be written into a comment
553
+ clean_name = room.display_name.replace('\n', '')
554
+
555
+ if room.identifier == room.zone: # write the zone definition
556
+ is_zone = True
557
+ room_str = ['!- ________ZONE:{}________\n'.format(clean_name)]
558
+ ceil_height = room.geometry.max.z - room.geometry.min.z
559
+ include_floor = 'No' if room.exclude_floor_area else 'Yes'
560
+ zone_values = (room.identifier, '', '', '', '', '', room.multiplier,
561
+ ceil_height, room.volume, room.floor_area, '', '', include_floor)
562
+ zone_comments = ('name', 'north', 'x', 'y', 'z', 'type', 'multiplier',
563
+ 'ceiling height', 'volume', 'floor area', 'inside convection',
564
+ 'outside convection', 'include floor area')
565
+ room_str.append(generate_idf_string('Zone', zone_values, zone_comments))
566
+ else: # write the space definition
567
+ is_zone = False
568
+ room_str = ['!- ________SPACE:{}________\n'.format(clean_name)]
569
+ ceil_height = room.geometry.max.z - room.geometry.min.z
570
+ space_values = (room.identifier, room.zone,
571
+ ceil_height, room.volume, room.floor_area)
572
+ space_comments = ('name', 'zone name', 'ceiling height', 'volume', 'floor area')
573
+ room_str.append(generate_idf_string('Space', space_values, space_comments))
574
+
575
+ # write the load definitions
576
+ people = room.properties.energy.people
577
+ lighting = room.properties.energy.lighting
578
+ electric_equipment = room.properties.energy.electric_equipment
579
+ gas_equipment = room.properties.energy.gas_equipment
580
+ shw = room.properties.energy.service_hot_water
581
+ infiltration = room.properties.energy.infiltration
582
+ ventilation = room.properties.energy.ventilation
583
+
584
+ if people is not None:
585
+ room_str.append(people.to_idf(room.identifier))
586
+ if lighting is not None:
587
+ room_str.append(lighting.to_idf(room.identifier))
588
+ if electric_equipment is not None:
589
+ room_str.append(electric_equipment.to_idf(room.identifier))
590
+ if gas_equipment is not None:
591
+ room_str.append(gas_equipment.to_idf(room.identifier))
592
+ if shw is not None:
593
+ shw_str, shw_sch = shw.to_idf(room)
594
+ room_str.append(shw_str)
595
+ room_str.extend(shw_sch)
596
+ if infiltration is not None:
597
+ room_str.append(infiltration.to_idf(room.identifier))
598
+
599
+ # write the ventilation and thermostat
600
+ if is_zone:
601
+ if ventilation is not None:
602
+ room_str.append(ventilation.to_idf(room.identifier))
603
+ if room.properties.energy.is_conditioned and \
604
+ room.properties.energy.setpoint is not None:
605
+ room_str.append(room.properties.energy.setpoint.to_idf(room.identifier))
606
+
607
+ # write any ventilation fan definitions
608
+ for fan in room.properties.energy._fans:
609
+ room_str.append(fan.to_idf(room.identifier))
610
+
611
+ # write the daylighting control
612
+ if room.properties.energy.daylighting_control is not None:
613
+ room_str.extend(room.properties.energy.daylighting_control.to_idf())
614
+
615
+ # write any process load definitions
616
+ for p_load in room.properties.energy._process_loads:
617
+ room_str.append(p_load.to_idf(room.identifier))
618
+
619
+ # write any internal mass definitions
620
+ for int_mass in room.properties.energy._internal_masses:
621
+ room_str.append(int_mass.to_idf(room.identifier, is_zone))
622
+
623
+ return '\n\n'.join(room_str)
624
+
625
+
626
+ def model_to_idf(
627
+ model, schedule_directory=None, use_ideal_air_equivalent=True,
628
+ patch_missing_adjacencies=False
629
+ ):
630
+ r"""Generate an IDF string representation of a Model.
631
+
632
+ The resulting string will include all geometry (Rooms, Faces, Shades, Apertures,
633
+ Doors), all fully-detailed constructions + materials, all fully-detailed
634
+ schedules, and the room properties (loads, thermostats with setpoints, and HVAC).
635
+
636
+ Essentially, the string includes everything needed to simulate the model
637
+ except the simulation parameters. So joining this string with the output of
638
+ SimulationParameter.to_idf() should create a simulate-able IDF.
639
+
640
+ Args:
641
+ model: A honeybee Model for which an IDF representation will be returned.
642
+ schedule_directory: An optional file directory to which all file-based
643
+ schedules should be written to. If None, all ScheduleFixedIntervals
644
+ will be translated to Schedule:Compact and written fully into the
645
+ IDF string instead of to Schedule:File. (Default: None).
646
+ use_ideal_air_equivalent: Boolean to note whether any detailed HVAC system
647
+ templates should be converted to an equivalent IdealAirSystem upon export.
648
+ If False and the Model contains detailed systems, a ValueError will
649
+ be raised since this method does not support the translation of
650
+ detailed systems. (Default:True).
651
+ patch_missing_adjacencies: Boolean to note whether any missing adjacencies
652
+ in the model should be replaced with Adiabatic boundary conditions.
653
+ This is useful when the input model is only a portion of a much
654
+ larger model. (Default: False).
655
+
656
+ Usage:
657
+
658
+ .. code-block:: python
659
+
660
+ import os
661
+ from ladybug.futil import write_to_file
662
+ from honeybee.model import Model
663
+ from honeybee.room import Room
664
+ from honeybee.config import folders
665
+ from honeybee_energy.lib.programtypes import office_program
666
+ from honeybee_energy.hvac.idealair import IdealAirSystem
667
+ from honeybee_energy.simulation.parameter import SimulationParameter
668
+
669
+ # Get input Model
670
+ room = Room.from_box('Tiny House Zone', 5, 10, 3)
671
+ room.properties.energy.program_type = office_program
672
+ room.properties.energy.add_default_ideal_air()
673
+ model = Model('Tiny House', [room])
674
+
675
+ # Get the input SimulationParameter
676
+ sim_par = SimulationParameter()
677
+ sim_par.output.add_zone_energy_use()
678
+ ddy_file = 'C:/EnergyPlusV9-0-1/WeatherData/USA_CO_Golden-NREL.724666_TMY3.ddy'
679
+ sim_par.sizing_parameter.add_from_ddy_996_004(ddy_file)
680
+
681
+ # create the IDF string for simulation parameters and model
682
+ idf_str = '\n\n'.join((sim_par.to_idf(), model.to.idf(model)))
683
+
684
+ # write the final string into an IDF
685
+ idf = os.path.join(folders.default_simulation_folder, 'test_file', 'in.idf')
686
+ write_to_file(idf, idf_str, True)
687
+ """
688
+ # duplicate model to avoid mutating it as we edit it for energy simulation
689
+ original_model = model
690
+ model = model.duplicate()
691
+ # scale the model if the units are not meters
692
+ if model.units != 'Meters':
693
+ model.convert_to_units('Meters')
694
+ # remove degenerate geometry within native E+ tolerance of 0.01 meters
695
+ try:
696
+ model.remove_degenerate_geometry(0.01)
697
+ except ValueError:
698
+ error = 'Failed to remove degenerate Rooms.\nYour Model units system is: {}. ' \
699
+ 'Is this correct?'.format(original_model.units)
700
+ raise ValueError(error)
701
+
702
+ # convert model to simple ventilation and Ideal Air Systems
703
+ model.properties.energy.ventilation_simulation_control.vent_control_type = \
704
+ 'SingleZone'
705
+ if use_ideal_air_equivalent:
706
+ for room in model.rooms:
707
+ room.properties.energy.assign_ideal_air_equivalent()
708
+
709
+ # patch missing adjacencies
710
+ if patch_missing_adjacencies:
711
+ model.properties.energy.missing_adjacencies_to_adiabatic()
712
+
713
+ # resolve the properties across zones
714
+ single_zones, zone_dict = model.properties.energy.resolve_zones()
715
+
716
+ # write the building object into the string
717
+ model_str = ['!- =======================================\n'
718
+ '!- ================ MODEL ================\n'
719
+ '!- =======================================\n']
720
+
721
+ # write all of the schedules and type limits
722
+ sched_strs = []
723
+ type_limits = []
724
+ used_day_sched_ids, used_day_count = {}, 1
725
+ always_on_included = False
726
+ all_scheds = model.properties.energy.schedules + \
727
+ model.properties.energy.orphaned_trans_schedules
728
+ for sched in all_scheds:
729
+ if sched.identifier == 'Always On':
730
+ always_on_included = True
731
+ try: # ScheduleRuleset
732
+ year_schedule, week_schedules = sched.to_idf()
733
+ if week_schedules is None: # ScheduleConstant
734
+ sched_strs.append(year_schedule)
735
+ else: # ScheduleYear
736
+ # check that day schedules aren't referenced by other model schedules
737
+ day_scheds = []
738
+ for day in sched.day_schedules:
739
+ if day.identifier not in used_day_sched_ids:
740
+ day_scheds.append(day.to_idf(sched.schedule_type_limit))
741
+ used_day_sched_ids[day.identifier] = day
742
+ elif day != used_day_sched_ids[day.identifier]:
743
+ new_day = day.duplicate()
744
+ new_day.identifier = 'Schedule Day {}'.format(used_day_count)
745
+ day_scheds.append(new_day.to_idf(sched.schedule_type_limit))
746
+ for i, week_sch in enumerate(week_schedules):
747
+ week_schedules[i] = \
748
+ week_sch.replace(day.identifier, new_day.identifier)
749
+ used_day_count += 1
750
+ sched_strs.extend([year_schedule] + week_schedules + day_scheds)
751
+ except TypeError: # ScheduleFixedInterval
752
+ if schedule_directory is None:
753
+ sched_strs.append(sched.to_idf_compact())
754
+ else:
755
+ sched_strs.append(sched.to_idf(schedule_directory))
756
+ t_lim = sched.schedule_type_limit
757
+ if t_lim is not None and not _instance_in_array(t_lim, type_limits):
758
+ type_limits.append(t_lim)
759
+ if not always_on_included:
760
+ always_schedule, _ = model.properties.energy._always_on_schedule().to_idf()
761
+ sched_strs.append(always_schedule)
762
+ model_str.append('!- ========= SCHEDULE TYPE LIMITS =========\n')
763
+ model_str.extend([type_limit.to_idf() for type_limit in set(type_limits)])
764
+ model_str.append('!- ============== SCHEDULES ==============\n')
765
+ model_str.extend(sched_strs)
766
+
767
+ # get the default generic construction set
768
+ # must be imported here to avoid circular imports
769
+ from .lib.constructionsets import generic_construction_set
770
+
771
+ # write all of the materials and constructions
772
+ materials = []
773
+ construction_strs = []
774
+ dynamic_cons = []
775
+ all_constrs = model.properties.energy.constructions + \
776
+ generic_construction_set.constructions_unique
777
+ for constr in set(all_constrs):
778
+ try:
779
+ materials.extend(constr.materials)
780
+ construction_strs.append(constr.to_idf())
781
+ if constr.has_frame:
782
+ materials.append(constr.frame)
783
+ if constr.has_shade:
784
+ if constr.window_construction in all_constrs:
785
+ construction_strs.pop(-1) # avoid duplicate specification
786
+ if constr.is_switchable_glazing:
787
+ materials.append(constr.switched_glass_material)
788
+ construction_strs.append(constr.to_shaded_idf())
789
+ elif constr.is_dynamic:
790
+ dynamic_cons.append(constr)
791
+ except AttributeError:
792
+ try: # AirBoundaryConstruction or ShadeConstruction
793
+ construction_strs.append(constr.to_idf()) # AirBoundaryConstruction
794
+ except TypeError:
795
+ pass # ShadeConstruction; no need to write it
796
+ model_str.append('!- ============== MATERIALS ==============\n')
797
+ model_str.extend([mat.to_idf() for mat in set(materials)])
798
+ model_str.append('!- ============ CONSTRUCTIONS ============\n')
799
+ model_str.extend(construction_strs)
800
+
801
+ # write all of the HVAC systems for zones
802
+ model_str.append('!- ============ HVAC SYSTEMS ============\n')
803
+ for zone_id, zone_data in zone_dict.items():
804
+ rooms, z_prop, set_pt, vent = zone_data
805
+ mult, ceil_hgt, vol, flr_area, inc_flr = z_prop
806
+ model_str.append('!- ________ZONE:{}________\n'.format(zone_id))
807
+ zone_values = (zone_id, '', '', '', '', '', mult,
808
+ ceil_hgt, vol, flr_area, '', '', inc_flr)
809
+ zone_comments = ('name', 'north', 'x', 'y', 'z', 'type', 'multiplier',
810
+ 'ceiling height', 'volume', 'floor area', 'inside convection',
811
+ 'outside convection', 'include floor area')
812
+ model_str.append(generate_idf_string('Zone', zone_values, zone_comments))
813
+ if vent is not None:
814
+ model_str.append(vent.to_idf(zone_id))
815
+ hvacs = [r.properties.energy.hvac for r in rooms
816
+ if r.properties.energy.hvac is not None]
817
+ if set_pt is not None and len(hvacs) != 0:
818
+ model_str.append(set_pt.to_idf(zone_id))
819
+ try:
820
+ model_str.append(hvacs[0].to_idf_zone(zone_id, set_pt, vent))
821
+ except AttributeError:
822
+ raise TypeError(
823
+ 'HVAC system type "{}" does not support direct translation to IDF.\n'
824
+ 'Use the export to OpenStudio workflow instead.'.format(
825
+ room.properties.energy.hvac.__class__.__name__))
826
+ # write all of the HVAC systems for individual rooms not using zones
827
+ for room in single_zones:
828
+ if room.properties.energy.hvac is not None \
829
+ and room.properties.energy.setpoint is not None:
830
+ try:
831
+ model_str.append(room.properties.energy.hvac.to_idf(room))
832
+ except AttributeError:
833
+ raise TypeError(
834
+ 'HVAC system type "{}" does not support direct translation to IDF.\n'
835
+ 'Use the export to OpenStudio workflow instead.'.format(
836
+ room.properties.energy.hvac.__class__.__name__))
837
+
838
+ # get the default air boundary construction
839
+ # must be imported here to avoid circular imports
840
+ from .lib.constructions import air_boundary
841
+
842
+ # write all of the room geometry
843
+ model_str.append('!- ============ ROOM GEOMETRY ============\n')
844
+ sf_objs = []
845
+ found_ab = []
846
+ for room in model.rooms:
847
+ model_str.append(room.to.idf(room))
848
+ for face in room.faces:
849
+ model_str.append(face.to.idf(face))
850
+ if isinstance(face.type, AirBoundary): # write the air mixing objects
851
+ air_constr = face.properties.energy.construction
852
+ try:
853
+ if face.identifier not in found_ab:
854
+ adj_face = face.boundary_condition.boundary_condition_object
855
+ adj_room = face.boundary_condition.boundary_condition_objects[-1]
856
+ try:
857
+ model_str.append(
858
+ air_constr.to_cross_mixing_idf(face, adj_room))
859
+ except AttributeError: # opaque construction for air boundary
860
+ model_str.append(
861
+ air_boundary.to_cross_mixing_idf(face, adj_room))
862
+ found_ab.append(adj_face)
863
+ except AttributeError as e:
864
+ raise ValueError(
865
+ 'Face "{}" is an Air Boundary but lacks a Surface boundary '
866
+ 'condition.\n{}'.format(face.full_id, e))
867
+ for ap in face.apertures:
868
+ if len(ap.geometry) <= 4: # ignore apertures to be triangulated
869
+ model_str.append(ap.to.idf(ap))
870
+ sf_objs.append(ap)
871
+ for shade in ap.outdoor_shades:
872
+ model_str.append(shade.to.idf(shade))
873
+ for dr in face.doors:
874
+ if len(dr.geometry) <= 4: # ignore doors to be triangulated
875
+ model_str.append(dr.to.idf(dr))
876
+ sf_objs.append(dr)
877
+ for shade in dr.outdoor_shades:
878
+ model_str.append(shade.to.idf(shade))
879
+ for shade in face.outdoor_shades:
880
+ model_str.append(shade.to.idf(shade))
881
+ for shade in room.outdoor_shades:
882
+ model_str.append(shade.to.idf(shade))
883
+
884
+ # triangulate any apertures or doors with more than 4 vertices
885
+ tri_apertures, _ = model.triangulated_apertures()
886
+ for tri_aps in tri_apertures:
887
+ for i, ap in enumerate(tri_aps):
888
+ if i != 0:
889
+ ap.properties.energy.vent_opening = None
890
+ model_str.append(ap.to.idf(ap))
891
+ sf_objs.append(ap)
892
+ tri_doors, _ = model.triangulated_doors()
893
+ for tri_drs in tri_doors:
894
+ for i, dr in enumerate(tri_drs):
895
+ if i != 0:
896
+ dr.properties.energy.vent_opening = None
897
+ model_str.append(dr.to.idf(dr))
898
+ sf_objs.append(dr)
899
+
900
+ # write all context shade geometry
901
+ model_str.append('!- ========== CONTEXT GEOMETRY ==========\n')
902
+ pv_objects = []
903
+ for shade in model.orphaned_shades:
904
+ model_str.append(shade.to.idf(shade))
905
+ if shade.properties.energy.pv_properties is not None:
906
+ pv_objects.append(shade)
907
+ for shade_mesh in model.shade_meshes:
908
+ model_str.append(shade_mesh.to.idf(shade_mesh))
909
+ for face in model.orphaned_faces:
910
+ model_str.append(face_to_idf(face))
911
+ for ap in model.orphaned_apertures:
912
+ model_str.append(aperture_to_idf(ap))
913
+ for dr in model.orphaned_doors:
914
+ model_str.append(door_to_idf(dr))
915
+
916
+ # write any EMS programs for dynamic constructions
917
+ if len(dynamic_cons) != 0:
918
+ model_str.append('!- ========== EMS PROGRAMS ==========\n')
919
+ dyn_dict = {}
920
+ for sf in sf_objs:
921
+ con = sf.properties.energy.construction
922
+ try:
923
+ dyn_dict[con.identifier].append(sf.identifier)
924
+ except KeyError:
925
+ dyn_dict[con.identifier] = [sf.identifier]
926
+ for con in dynamic_cons:
927
+ model_str.append(con.to_program_idf(dyn_dict[con.identifier]))
928
+ model_str.append(dynamic_cons[0].idf_program_manager(dynamic_cons))
929
+
930
+ # write any generator objects that were discovered in the model
931
+ if len(pv_objects) != 0:
932
+ model_str.append('!- ========== PHOTOVOLTAIC GENERATORS ==========\n')
933
+ for shade in pv_objects:
934
+ model_str.append(shade.properties.energy.pv_properties.to_idf(shade))
935
+ model_str.extend(model.properties.energy.electric_load_center.to_idf(pv_objects))
936
+
937
+ return '\n\n'.join(model_str)
938
+
939
+
940
+ def energyplus_idf_version(version_array=None):
941
+ """Get IDF text for the version of EnergyPlus.
942
+
943
+ This will match the version of EnergyPlus found in the config if it it exists.
944
+ It will be None otherwise.
945
+
946
+ Args:
947
+ version_array: An array of up to 3 integers for the version of EnergyPlus
948
+ for which an IDF string should be generated. If None, the energyplus_version
949
+ from the config will be used if it exists.
950
+ """
951
+ if version_array:
952
+ ver_str = '.'.join((str(d) for d in version_array))
953
+ return generate_idf_string('Version', [ver_str], ['version identifier'])
954
+ elif folders.energyplus_version:
955
+ ver_str = '.'.join((str(d) for d in folders.energyplus_version))
956
+ return generate_idf_string('Version', [ver_str], ['version identifier'])
957
+ return None
958
+
959
+
960
+ def _instance_in_array(object_instance, object_array):
961
+ """Check if a specific object instance is already in an array.
962
+
963
+ This can be much faster than `if object_instance in object_array`
964
+ when you expect to be testing a lot of the same instance of an object for
965
+ inclusion in an array since the builtin method uses an == operator to
966
+ test inclusion.
967
+ """
968
+ for val in object_array:
969
+ if val is object_instance:
970
+ return True
971
+ return False
972
+
973
+
974
+ def _preprocess_model_for_trace(
975
+ model, single_window=True, rect_sub_distance='0.15m',
976
+ frame_merge_distance='0.2m'):
977
+ """Pre-process a Honeybee Model to be written to TRANE TRACE as a gbXML.
978
+
979
+ Args:
980
+ model: A Honeybee Model to be converted to a TRACE-compatible gbXML.
981
+ single_window: A boolean for whether all windows within walls should be
982
+ converted to a single window with an area that matches the original
983
+ geometry. (Default: True).
984
+ rect_sub_distance: Text string of a number for the resolution at which
985
+ non-rectangular Apertures will be subdivided into smaller rectangular
986
+ units. This is required as TRACE 3D plus cannot model non-rectangular
987
+ geometries. This can include the units of the distance (eg. 0.5ft) or,
988
+ if no units are provided, the value will be interpreted in the
989
+ honeybee model units. (Default: 0.15m).
990
+ frame_merge_distance: Text string of a number for the maximum distance
991
+ between non-rectangular Apertures at which point the Apertures will
992
+ be merged into a single rectangular geometry. This is often helpful
993
+ when there are several triangular Apertures that together make a
994
+ rectangle when they are merged across their frames. This can include
995
+ the units of the distance (eg. 0.5ft) or, if no units are provided,
996
+ the value will be interpreted in the honeybee model units. (Default: 0.2m).
997
+
998
+ Returns:
999
+ The input Model modified such that it can import to TRACE as a gbXML
1000
+ without issues.
1001
+ """
1002
+ # make sure there are rooms and remove all shades and orphaned objects
1003
+ assert len(model.rooms) != 0, \
1004
+ 'Model contains no Rooms and therefore cannot be simulated in TRACE.'
1005
+ model.remove_all_shades()
1006
+ model.remove_faces()
1007
+ model.remove_apertures()
1008
+ model.remove_doors()
1009
+
1010
+ # remove degenerate geometry within native E+ tolerance of 0.01 meters
1011
+ original_units = model.units
1012
+ model.convert_to_units('Meters')
1013
+ try:
1014
+ model.remove_degenerate_geometry(0.01)
1015
+ except ValueError:
1016
+ error = 'Failed to remove degenerate Rooms.\nYour Model units system is: {}. ' \
1017
+ 'Is this correct?'.format(original_units)
1018
+ raise ValueError(error)
1019
+ rect_sub_distance = parse_distance_string(rect_sub_distance, original_units)
1020
+ frame_merge_distance = parse_distance_string(frame_merge_distance, original_units)
1021
+ if original_units != 'Meters':
1022
+ c_factor = conversion_factor_to_meters(original_units)
1023
+ rect_sub_distance = rect_sub_distance * c_factor
1024
+ frame_merge_distance = frame_merge_distance * c_factor
1025
+
1026
+ # remove all interior windows in the model
1027
+ for room in model.rooms:
1028
+ for face in room.faces:
1029
+ if isinstance(face.boundary_condition, Surface):
1030
+ face.remove_sub_faces()
1031
+
1032
+ # convert all rooms to extrusions and patch the resulting missing adjacencies
1033
+ model.rooms_to_extrusions()
1034
+ model.properties.energy.missing_adjacencies_to_adiabatic()
1035
+
1036
+ # convert windows in walls to a single geometry
1037
+ if single_window:
1038
+ for room in model.rooms:
1039
+ for face in room.faces:
1040
+ if isinstance(face.type, Wall) and face.has_sub_faces:
1041
+ face.boundary_condition = boundary_conditions.outdoors
1042
+ face.apertures_by_ratio(face.aperture_ratio, 0.01, rect_split=False)
1043
+
1044
+ # convert all of the Aperture geometries to rectangles so they can be translated
1045
+ model.rectangularize_apertures(
1046
+ subdivision_distance=rect_sub_distance, max_separation=frame_merge_distance,
1047
+ merge_all=True, resolve_adjacency=False
1048
+ )
1049
+
1050
+ # if there are still multiple windows in a given Face, ensure they do not touch
1051
+ for room in model.rooms:
1052
+ for face in room.faces:
1053
+ if len(face.apertures) > 1:
1054
+ face.offset_aperture_edges(-0.01, 0.01)
1055
+
1056
+ # re-solve adjacency given that all of the previous operations have messed with it
1057
+ model.solve_adjacency(merge_coplanar=True, intersect=True, overwrite=True)
1058
+
1059
+ # reset all display_names so that they are unique (derived from reset identifiers)
1060
+ model.reset_ids() # sets the identifiers based on the display_name
1061
+ for room in model.rooms:
1062
+ room.display_name = room.identifier.replace('_', ' ')
1063
+ if room.story is not None and room.story.startswith('-'):
1064
+ room.story = 'neg{}'.format(room.story[1:])
1065
+
1066
+ # remove the HVAC from any Rooms lacking setpoints
1067
+ rem_msgs = model.properties.energy.remove_hvac_from_no_setpoints()
1068
+ if len(rem_msgs) != 0:
1069
+ print('\n'.join(rem_msgs))
1070
+
1071
+ # rename all face geometry so that it is easy to identify in TRACE 700
1072
+ for room in model.rooms:
1073
+ room.rename_faces_by_attribute()
1074
+ room.rename_apertures_by_attribute()
1075
+ room.rename_doors_by_attribute()
1076
+ model.reset_ids()
1077
+
1078
+ return model