openstudio-standards 0.2.15 → 0.2.16.rc1

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/data/geometry/ASHRAEHighriseApartment.osm +0 -27
  3. data/data/standards/OpenStudio_Standards-ashrae_90_1.xlsx +0 -0
  4. data/data/standards/OpenStudio_Standards-ashrae_90_1_28Jan2022.xlsx +0 -0
  5. data/data/standards/test_performance_expected_dd_results.csv +710 -710
  6. data/lib/openstudio-standards/btap/btap_result.rb +2 -2
  7. data/lib/openstudio-standards/btap/reporting.rb +2 -2
  8. data/lib/openstudio-standards/btap/simmanager.rb +2 -2
  9. data/lib/openstudio-standards/hvac_sizing/Siz.ControllerOutdoorAir.rb +0 -54
  10. data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +11 -1
  11. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +1 -1
  12. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.College.rb +26 -5
  13. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilCoolingWaterToAirHeatPumpEquationFit.rb +35 -16
  14. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingWaterToAirHeatPumpEquationFit.rb +23 -10
  15. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb +36 -0
  16. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +6 -6
  17. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +0 -3
  18. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +2 -2
  19. data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +9 -3
  20. data/lib/openstudio-standards/standards/Standards.CoilHeatingGas.rb +2 -0
  21. data/lib/openstudio-standards/standards/Standards.Construction.rb +12 -6
  22. data/lib/openstudio-standards/standards/Standards.Model.rb +38 -7
  23. data/lib/openstudio-standards/standards/Standards.Space.rb +1 -1
  24. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +7 -0
  25. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Model.rb +32 -11
  26. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +22 -742
  27. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_sets.json +2 -2
  28. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.prototype_inputs.json +3 -3
  29. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.spc_typ.json +6 -6
  30. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +19 -559
  31. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_sets.json +2 -2
  32. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.prototype_inputs.json +3 -3
  33. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.spc_typ.json +6 -6
  34. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +19 -559
  35. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_sets.json +2 -2
  36. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.prototype_inputs.json +5 -5
  37. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.spc_typ.json +7 -7
  38. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +19 -559
  39. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_sets.json +2 -2
  40. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.prototype_inputs.json +5 -5
  41. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.spc_typ.json +7 -7
  42. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +370 -910
  43. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_sets.json +2 -2
  44. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.prototype_inputs.json +6 -6
  45. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.refrigeration_system.json +0 -8
  46. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.spc_typ.json +12 -12
  47. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +19 -6
  48. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +2380 -1300
  49. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_sets.json +2 -2
  50. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.prototype_inputs.json +6 -6
  51. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.refrigeration_system.json +0 -8
  52. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.spc_typ.json +12 -12
  53. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.constructions.json +140 -0
  54. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +1176 -312
  55. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.construction_properties.json +172 -1132
  56. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.construction_sets.json +14 -14
  57. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.prototype_inputs.json +2 -2
  58. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.spc_typ.json +9 -9
  59. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.construction_properties.json +180 -1140
  60. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.construction_sets.json +14 -14
  61. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.prototype_inputs.json +2 -2
  62. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.spc_typ.json +10 -10
  63. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/data/nrel_zne_ready_2017.construction_properties.json +9 -9
  64. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/data/ze_aedg_multifamily.construction_properties.json +9 -9
  65. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_3_and_8_single_speed.rb +12 -6
  66. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_4.rb +12 -6
  67. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_system_6.rb +16 -8
  68. data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +10 -20
  69. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +209 -37
  70. data/lib/openstudio-standards/standards/necb/ECMS/loads.rb +1 -0
  71. data/lib/openstudio-standards/standards/necb/ECMS/pv_ground.rb +8 -6
  72. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +16 -9
  73. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/HighriseApartment.osm +1 -1
  74. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LowriseApartment.osm +1 -1
  75. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/MidriseApartment.osm +1 -1
  76. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb +9 -5
  77. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb +10 -6
  78. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_2_and_5.rb +9 -5
  79. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb +14 -8
  80. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb +14 -8
  81. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_4.rb +13 -6
  82. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_6.rb +12 -6
  83. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +4 -2
  84. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +38 -19
  85. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +2 -2
  86. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +15 -4
  87. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +10 -651
  88. data/lib/openstudio-standards/standards/necb/NECB2020/necb_2020.rb +8 -38
  89. data/lib/openstudio-standards/standards/necb/NECB2020/service_water_heating.rb +159 -0
  90. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +41 -43
  91. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +7 -4
  92. data/lib/openstudio-standards/version.rb +1 -1
  93. data/lib/openstudio-standards.rb +1 -0
  94. metadata +4 -2
@@ -1,314 +1,5 @@
1
- class NECB2020
2
-
3
- # Reduces the WWR to the values specified by the NECB
4
- # NECB 3.2.1.4
5
- def apply_standard_window_to_wall_ratio(model:, fdwr_set: -1.0)
6
- # NECB FDWR limit
7
- hdd = self.get_necb_hdd18(model)
8
-
9
- # Get the maximum NECB fdwr
10
- # fdwr_set settings:
11
- # 0-1: Remove all windows and add windows to match this fdwr
12
- # -1: Remove all windows and add windows to match max fdwr from NECB
13
- # -2: Do not apply any fdwr changes, leave windows alone (also works for fdwr > 1)
14
- # -3: Use old method which reduces existing window size (if necessary) to meet maximum NECB fdwr limit
15
- # <-3.1: Remove all the windows
16
- # > 1: Do nothing
17
-
18
- if fdwr_set.to_f > 1.0
19
- return
20
- elsif fdwr_set.to_f >= 0.0 and fdwr_set <= 1.0
21
- apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f)
22
- return
23
- elsif fdwr_set.to_f >= -1.1 and fdwr_set <= -0.9
24
- fdwr_lim = (max_fwdr(hdd)).round(3)
25
- apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_lim.to_f)
26
- return
27
- elsif fdwr_set.to_f >= -2.1 and fdwr_set <= -1.9
28
- return
29
- elsif fdwr_set.to_f >= -3.1 and fdwr_set <= -2.9
30
- fdwr_lim = (max_fwdr(hdd) * 100.0).round(1)
31
- return apply_limit_fdwr(model: model, fdwr_lim: fdwr_lim.to_f)
32
- elsif fdwr_set < -3.1
33
- apply_max_fdwr_nrcan(model: model, fdwr_lim: fdwr_set.to_f)
34
- return
35
- end
36
- end
37
-
38
- def apply_limit_fdwr(model:, fdwr_lim:)
39
- # Loop through all spaces in the model, and
40
- # per the PNNL PRM Reference Manual, find the areas
41
- # of each space conditioning category (res, nonres, semi-heated)
42
- # separately. Include space multipliers.
43
- nr_wall_m2 = 0.001 # Avoids divide by zero errors later
44
- nr_wind_m2 = 0
45
- res_wall_m2 = 0.001
46
- res_wind_m2 = 0
47
- sh_wall_m2 = 0.001
48
- sh_wind_m2 = 0
49
- total_wall_m2 = 0.001
50
- total_subsurface_m2 = 0.0
51
- # Store the space conditioning category for later use
52
- space_cats = {}
53
- model.getSpaces.sort.each do |space|
54
- # Loop through all surfaces in this space
55
- wall_area_m2 = 0
56
- wind_area_m2 = 0
57
- space.surfaces.sort.each do |surface|
58
- # Skip non-outdoor surfaces
59
- next unless surface.outsideBoundaryCondition == 'Outdoors'
60
- # Skip non-walls
61
- next unless surface.surfaceType.casecmp('wall').zero?
62
- # This wall's gross area (including window area)
63
- wall_area_m2 += surface.grossArea * space.multiplier
64
- # Subsurfaces in this surface
65
- surface.subSurfaces.sort.each do |ss|
66
- wind_area_m2 += ss.netArea * space.multiplier
67
- end
68
- end
69
-
70
- # Determine the space category
71
- # zTODO This should really use the heating/cooling loads
72
- # from the proposed building. However, in an attempt
73
- # to avoid another sizing run just for this purpose,
74
- # conditioned status is based on heating/cooling
75
- # setpoints. If heated-only, will be assumed Semiheated.
76
- # The full-bore method is on the next line in case needed.
77
- # cat = thermal_zone_conditioning_category(space, template, climate_zone)
78
- cooled = space_cooled?(space)
79
- heated = space_heated?(space)
80
- cat = 'Unconditioned'
81
- # Unconditioned
82
- if !heated && !cooled
83
- cat = 'Unconditioned'
84
- # Heated-Only
85
- elsif heated && !cooled
86
- cat = 'Semiheated'
87
- # Heated and Cooled
88
- else
89
- res = thermal_zone_residential?(space.thermalZone.get)
90
- cat = if res
91
- 'ResConditioned'
92
- else
93
- 'NonResConditioned'
94
- end
95
- end
96
- space_cats[space] = cat
97
- # NECB2011 keep track of totals for NECB regardless of conditioned or not.
98
- total_wall_m2 += wall_area_m2
99
- total_subsurface_m2 += wind_area_m2 # this contains doors as well.
100
-
101
- # Add to the correct category
102
- case cat
103
- when 'Unconditioned'
104
- next # Skip unconditioned spaces
105
- when 'NonResConditioned'
106
- nr_wall_m2 += wall_area_m2
107
- nr_wind_m2 += wind_area_m2
108
- when 'ResConditioned'
109
- res_wall_m2 += wall_area_m2
110
- res_wind_m2 += wind_area_m2
111
- when 'Semiheated'
112
- sh_wall_m2 += wall_area_m2
113
- sh_wind_m2 += wind_area_m2
114
- end
115
- end
116
-
117
- # Calculate the WWR of each category
118
- wwr_nr = ((nr_wind_m2 / nr_wall_m2) * 100.0).round(1)
119
- wwr_res = ((res_wind_m2 / res_wall_m2) * 100).round(1)
120
- wwr_sh = ((sh_wind_m2 / sh_wall_m2) * 100).round(1)
121
- fdwr = ((total_subsurface_m2 / total_wall_m2) * 100).round(1) # used by NECB2011
122
-
123
- # Convert to IP and report
124
- nr_wind_ft2 = OpenStudio.convert(nr_wind_m2, 'm^2', 'ft^2').get
125
- nr_wall_ft2 = OpenStudio.convert(nr_wall_m2, 'm^2', 'ft^2').get
126
-
127
- res_wind_ft2 = OpenStudio.convert(res_wind_m2, 'm^2', 'ft^2').get
128
- res_wall_ft2 = OpenStudio.convert(res_wall_m2, 'm^2', 'ft^2').get
129
-
130
- sh_wind_ft2 = OpenStudio.convert(sh_wind_m2, 'm^2', 'ft^2').get
131
- sh_wall_ft2 = OpenStudio.convert(sh_wall_m2, 'm^2', 'ft^2').get
132
-
133
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR NonRes = #{wwr_nr.round}%; window = #{nr_wind_ft2.round} ft2, wall = #{nr_wall_ft2.round} ft2.")
134
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Res = #{wwr_res.round}%; window = #{res_wind_ft2.round} ft2, wall = #{res_wall_ft2.round} ft2.")
135
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "WWR Semiheated = #{wwr_sh.round}%; window = #{sh_wind_ft2.round} ft2, wall = #{sh_wall_ft2.round} ft2.")
136
-
137
- # WWR limit
138
- wwr_lim = 40.0
139
-
140
- # Check against WWR limit
141
- red_nr = wwr_nr > wwr_lim
142
- red_res = wwr_res > wwr_lim
143
- red_sh = wwr_sh > wwr_lim
144
-
145
- # puts "Current FDWR is #{fdwr}, must be less than #{fdwr_lim}."
146
- # puts "Current subsurf area is #{total_subsurface_m2} and gross surface area is #{total_wall_m2}"
147
- # Stop here unless windows / doors need reducing
148
- return true unless fdwr > fdwr_lim
149
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{wwr_lim.round}%.")
150
- # Determine the factors by which to reduce the window / door area
151
- mult = fdwr_lim / fdwr
152
- # Reduce the window area if any of the categories necessary
153
- model.getSpaces.sort.each do |space|
154
- # Loop through all surfaces in this space
155
- space.surfaces.sort.each do |surface|
156
- # Skip non-outdoor surfaces
157
- next unless surface.outsideBoundaryCondition == 'Outdoors'
158
- # Skip non-walls
159
- next unless surface.surfaceType == 'Wall'
160
- # Subsurfaces in this surface
161
- surface.subSurfaces.sort.each do |ss|
162
- # Reduce the size of the window
163
- red = 1.0 - mult
164
- sub_surface_reduce_area_by_percent_by_raising_sill(ss, red)
165
- end
166
- end
167
- end
168
- return true
169
- end
170
-
171
- # Reduces the SRR to the values specified by the PRM. SRR reduction
172
- # will be done by shrinking vertices toward the centroid.
173
- #
174
- def apply_standard_skylight_to_roof_ratio(model:, srr_set: -1.0)
175
-
176
- # If srr_set is between 1.0 and 1.2 set it to the maximum allowed by the NECB. If srr_set is between 0.0 and 1.0
177
- # apply whatever was passed. If srr_set >= 1.2 then set the existing srr of the building to be the necb maximum
178
- # only if the the srr exceeds this maximum (otherwise leave it to be whatever was modeled).
179
-
180
- # srr_set settings:
181
- # 0-1: Remove all skylights and add skylights to match this srr
182
- # -1: Remove all skylights and add skylights to match max srr from NECB
183
- # -2: Do not apply any srr changes, leave skylights alone (also works for srr > 1)
184
- # -3: Use old method which reduces existing skylight size (if necessary) to meet maximum NECB skylight limit
185
- # <-3.1: Remove all the skylights
186
- # > 1: Do nothing
187
-
188
- if srr_set.to_f > 1.0
189
- return
190
- elsif srr_set.to_f >= 0.0 && srr_set <= 1.0
191
- apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f)
192
- return
193
- elsif srr_set.to_f >= -1.1 && srr_set <= -0.9
194
- # Get the maximum NECB srr
195
- srr_lim = self.get_standards_constant('skylight_to_roof_ratio_max_value')
196
- apply_max_srr_nrcan(model: model, srr_lim: srr_lim.to_f)
197
- return
198
- elsif srr_set.to_f >= -2.1 && srr_set <= -1.9
199
- return
200
- elsif srr_set.to_f >= -3.1 && srr_set <= -2.9
201
- # Continue with the rest of this method, use old method which reduces existing skylight size (if necessary) to
202
- # meet maximum srr limit
203
- elsif srr_set < -3.1
204
- apply_max_srr_nrcan(model: model, srr_lim: srr_set.to_f)
205
- return
206
- else
207
- return
208
- end
209
-
210
- # SRR limit
211
- srr_lim = self.get_standards_constant('skylight_to_roof_ratio_max_value') * 100.0
212
-
213
- # Loop through all spaces in the model, and
214
- # per the PNNL PRM Reference Manual, find the areas
215
- # of each space conditioning category (res, nonres, semi-heated)
216
- # separately. Include space multipliers.
217
- nr_wall_m2 = 0.001 # Avoids divide by zero errors later
218
- nr_sky_m2 = 0
219
- res_wall_m2 = 0.001
220
- res_sky_m2 = 0
221
- sh_wall_m2 = 0.001
222
- sh_sky_m2 = 0
223
- total_roof_m2 = 0.001
224
- total_subsurface_m2 = 0
225
- model.getSpaces.sort.each do |space|
226
- # Loop through all surfaces in this space
227
- wall_area_m2 = 0
228
- sky_area_m2 = 0
229
- space.surfaces.sort.each do |surface|
230
- # Skip non-outdoor surfaces
231
- next unless surface.outsideBoundaryCondition == 'Outdoors'
232
- # Skip non-walls
233
- next unless surface.surfaceType == 'RoofCeiling'
234
- # This wall's gross area (including skylight area)
235
- wall_area_m2 += surface.grossArea * space.multiplier
236
- # Subsurfaces in this surface
237
- surface.subSurfaces.sort.each do |ss|
238
- sky_area_m2 += ss.netArea * space.multiplier
239
- end
240
- end
241
-
242
- # Determine the space category
243
- cat = 'NonRes'
244
- if space_residential?(space)
245
- cat = 'Res'
246
- end
247
- # if space.is_semiheated
248
- # cat = 'Semiheated'
249
- # end
250
-
251
- # Add to the correct category
252
- case cat
253
- when 'NonRes'
254
- nr_wall_m2 += wall_area_m2
255
- nr_sky_m2 += sky_area_m2
256
- when 'Res'
257
- res_wall_m2 += wall_area_m2
258
- res_sky_m2 += sky_area_m2
259
- when 'Semiheated'
260
- sh_wall_m2 += wall_area_m2
261
- sh_sky_m2 += sky_area_m2
262
- end
263
- total_roof_m2 += wall_area_m2
264
- total_subsurface_m2 += sky_area_m2
265
- end
266
-
267
- # Calculate the SRR of each category
268
- srr_nr = ((nr_sky_m2 / nr_wall_m2) * 100).round(1)
269
- srr_res = ((res_sky_m2 / res_wall_m2) * 100).round(1)
270
- srr_sh = ((sh_sky_m2 / sh_wall_m2) * 100).round(1)
271
- srr = ((total_subsurface_m2 / total_roof_m2) * 100.0).round(1)
272
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The skylight to roof ratios (SRRs) are: NonRes: #{srr_nr.round}%, Res: #{srr_res.round}%.")
273
-
274
- # Check against SRR limit
275
- red_nr = srr_nr > srr_lim
276
- red_res = srr_res > srr_lim
277
- red_sh = srr_sh > srr_lim
278
1
 
279
- # Stop here unless windows need reducing
280
- return true unless srr > srr_lim
281
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Reducing the size of all windows (by raising sill height) to reduce window area down to the limit of #{srr_lim.round}%.")
282
- # Determine the factors by which to reduce the window / door area
283
- mult = srr_lim / srr
284
-
285
- # Reduce the subsurface areas
286
- model.getSpaces.sort.each do |space|
287
- # Loop through all surfaces in this space
288
- space.surfaces.sort.each do |surface|
289
- # Skip non-outdoor surfaces
290
- next unless surface.outsideBoundaryCondition == 'Outdoors'
291
- # Skip non-walls
292
- next unless surface.surfaceType == 'RoofCeiling'
293
- # Subsurfaces in this surface
294
- surface.subSurfaces.sort.each do |ss|
295
- # Reduce the size of the subsurface
296
- red = 1.0 - mult
297
- sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(ss, red)
298
- end
299
- end
300
- end
301
-
302
- return true
303
- end
304
-
305
- # @author phylroy.lopez@nrcan.gc.ca
306
- # @param hdd [Float]
307
- # @return [Double] a constant float
308
- def max_fwdr(hdd)
309
- # get formula from json database.
310
- return eval(get_standards_formula('fdwr_formula'))
311
- end
2
+ class NECB2020
312
3
 
313
4
  # Go through the default construction sets and hard-assigned
314
5
  # constructions. Clone the existing constructions and set their
@@ -365,20 +56,21 @@ class NECB2020
365
56
  return false
366
57
  end
367
58
 
59
+ # hdd required to get correct conductance values from the json file.
60
+ hdd = get_necb_hdd18(model)
61
+
368
62
  # Lambdas are preferred over methods in methods for small utility methods.
369
63
  correct_cond = lambda do |conductivity, surface_type|
370
- # hdd required in scope for eval function.
371
- hdd = get_necb_hdd18(model)
372
- return conductivity.nil? || conductivity.to_f <= 0.0 || conductivity =="NECB_Default" ? eval(model_find_objects(@standards_data['surface_thermal_transmittance'], surface_type)[0]['formula']) : conductivity.to_f
64
+ return conductivity.nil? || conductivity.to_f <= 0.0 || conductivity == 'NECB_Default' ? eval(model_find_objects(@standards_data['surface_thermal_transmittance'], surface_type)[0]['formula']) : conductivity.to_f
373
65
  end
374
66
 
375
67
  # Converts trans and vis to nil if requesting default.. or casts the string to a float.
376
68
  correct_vis_trans = lambda do |value|
377
- return value.nil? || value.to_f <= 0.0 || value =="NECB_Default" ? nil : value.to_f
69
+ return value.nil? || value.to_f <= 0.0 || value == 'NECB_Default' ? nil : value.to_f
378
70
  end
379
71
 
380
72
  BTAP::Resources::Envelope::ConstructionSets.customize_default_surface_construction_set!(model: model,
381
- name: "#{default_surface_construction_set.name.get} at hdd = #{get_necb_hdd18(model)}",
73
+ name: "#{default_surface_construction_set.name.get} at hdd = #{hdd}",
382
74
  default_surface_construction_set: default_surface_construction_set,
383
75
  # ext surfaces
384
76
  ext_wall_cond: correct_cond.call(ext_wall_cond, {'boundary_condition' => 'Outdoors', 'surface' => 'Wall'}),
@@ -416,57 +108,12 @@ class NECB2020
416
108
  tubular_daylight_diffuser_solar_trans: correct_vis_trans.call(tubular_daylight_diffuser_solar_trans),
417
109
  tubular_daylight_diffuser_vis_trans: correct_vis_trans.call(tubular_daylight_diffuser_vis_trans)
418
110
  )
419
-
420
-
421
111
  end
422
112
  # sets all surfaces to use default constructions sets except adiabatic, where it does a hard assignment of the interior wall construction type.
423
113
  model.getPlanarSurfaces.sort.each(&:resetConstruction)
424
114
  # if the default construction set is defined..try to assign the interior wall to the adiabatic surfaces
425
115
  BTAP::Resources::Envelope.assign_interior_surface_construction_to_adiabatic_surfaces(model, nil)
426
116
  BTAP.runner_register('Info', ' apply_standard_construction_properties was sucessful.', runner)
427
-
428
- end
429
-
430
- # Set all external surface conductances to NECB values.
431
- # @author phylroy.lopez@nrcan.gc.ca
432
- # @param surface [String]
433
- # @param hdd [Float]
434
- # @param is_radiant [Boolian]
435
- # @param scaling_factor [Float]
436
- # @return [String] surface as RSI
437
- def set_necb_external_surface_conductance(surface, hdd, is_radiant = false, scaling_factor = 1.0)
438
- conductance_value = 0
439
-
440
- if surface.outsideBoundaryCondition.casecmp('outdoors').zero?
441
-
442
- case surface.surfaceType.downcase
443
- when 'wall'
444
- conductance_value = @standards_data['conductances']['Wall'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
445
- when 'floor'
446
- conductance_value = @standards_data['conductances']['Floor'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
447
- when 'roofceiling'
448
- conductance_value = @standards_data['conductances']['Roof'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
449
- end
450
- if is_radiant
451
- conductance_value *= 0.80
452
- end
453
- return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value)
454
- end
455
-
456
- if surface.outsideBoundaryCondition.downcase =~ /ground/
457
- case surface.surfaceType.downcase
458
- when 'wall'
459
- conductance_value = @standards_data['conductances']['GroundWall'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
460
- when 'floor'
461
- conductance_value = @standards_data['conductances']['GroundFloor'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
462
- when 'roofceiling'
463
- conductance_value = @standards_data['conductances']['GroundRoof'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
464
- end
465
- if is_radiant
466
- conductance_value *= 0.80
467
- end
468
- return BTAP::Geometry::Surfaces.set_surfaces_construction_conductance([surface], conductance_value)
469
- end
470
117
  end
471
118
 
472
119
  # Set all external subsurfaces (doors, windows, skylights) to NECB values.
@@ -480,300 +127,12 @@ class NECB2020
480
127
  case subsurface.subSurfaceType.downcase
481
128
  when /window/
482
129
  conductance_value = @standards_data['conductances']['Window'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
130
+ when /skylight/
131
+ conductance_value = @standards_data['conductances']['Skylight'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
483
132
  when /door/
484
133
  conductance_value = @standards_data['conductances']['Door'].find { |i| i['hdd'] > hdd }['thermal_transmittance'] * scaling_factor
485
134
  end
486
135
  subsurface.setRSI(1 / conductance_value)
487
136
  end
488
137
  end
489
-
490
- # Adds code-minimum constructions based on the building type
491
- # as defined in the OpenStudio_Standards_construction_sets.json file.
492
- # Where there is a separate construction set specified for the
493
- # individual space type, this construction set will be created and applied
494
- # to this space type, overriding the whole-building construction set.
495
- #
496
- # @param building_type [String] the type of building
497
- # @param climate_zone [String] the name of the climate zone the building is in
498
- # @return [Bool] returns true if successful, false if not
499
- def model_add_constructions(model)
500
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying constructions')
501
-
502
- # Assign construction to adiabatic construction
503
- # Assign a material to all internal mass objects
504
- assign_contruction_to_adiabatic_surfaces(model)
505
- # The constructions lookup table uses a slightly different list of
506
- # building types.
507
- apply_building_default_constructionset(model)
508
- # Make a construction set for each space type, if one is specified
509
- # apply_default_constructionsets_to_spacetypes(climate_zone, model)
510
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying constructions')
511
- return true
512
- end
513
-
514
- def apply_building_default_constructionset(model)
515
-
516
- bldg_def_const_set = model_add_construction_set_from_osm(model: model)
517
- model.getBuilding.setDefaultConstructionSet(bldg_def_const_set)
518
-
519
- end
520
-
521
- def apply_default_constructionsets_to_spacetypes(climate_zone, model)
522
- model.getSpaceTypes.sort.each do |space_type|
523
- # Get the standards building type
524
- stds_building_type = nil
525
- if space_type.standardsBuildingType.is_initialized
526
- stds_building_type = space_type.standardsBuildingType.get
527
- else
528
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.")
529
- end
530
-
531
- # Get the standards space type
532
- stds_spc_type = nil
533
- if space_type.standardsSpaceType.is_initialized
534
- stds_spc_type = space_type.standardsSpaceType.get
535
- else
536
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards space type.")
537
- end
538
-
539
- # If the standards space type is Attic,
540
- # the building type should be blank.
541
- if stds_spc_type == 'Attic'
542
- stds_building_type = ''
543
- end
544
-
545
- # Attempt to make a construction set for this space type
546
- # and assign it if it can be created.
547
- spc_type_const_set = model_add_construction_set_from_osm(model: model)
548
- if spc_type_const_set.is_initialized
549
- space_type.setDefaultConstructionSet(spc_type_const_set.get)
550
- end
551
- end
552
- end
553
-
554
- def model_add_construction_set_from_osm(model:,
555
- construction_set_name: 'BTAP-Mass',
556
- osm_path: File.absolute_path(File.join(__FILE__, '..', '..', 'common/construction_defaults.osm')))
557
- # load resources model
558
- construction_library = BTAP::FileIO::load_osm(osm_path)
559
-
560
- if not construction_library.getDefaultConstructionSetByName(construction_set_name.to_s).is_initialized
561
- runner.registerError('Did not find the expected construction in library.')
562
- return false
563
- end
564
- selected_construction_set = construction_library.getDefaultConstructionSetByName(construction_set_name.to_s).get
565
- new_construction_set = selected_construction_set.clone(model).to_DefaultConstructionSet.get
566
- return new_construction_set
567
- end
568
-
569
- def assign_contruction_to_adiabatic_surfaces(model)
570
- cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
571
- cp02_carpet_pad.setName('CP02 CARPET PAD')
572
- cp02_carpet_pad.setRoughness('VeryRough')
573
- cp02_carpet_pad.setThermalResistance(0.21648)
574
- cp02_carpet_pad.setThermalAbsorptance(0.9)
575
- cp02_carpet_pad.setSolarAbsorptance(0.7)
576
- cp02_carpet_pad.setVisibleAbsorptance(0.8)
577
-
578
- normalweight_concrete_floor = OpenStudio::Model::StandardOpaqueMaterial.new(model)
579
- normalweight_concrete_floor.setName('100mm Normalweight concrete floor')
580
- normalweight_concrete_floor.setRoughness('MediumSmooth')
581
- normalweight_concrete_floor.setThickness(0.1016)
582
- normalweight_concrete_floor.setConductivity(2.31)
583
- normalweight_concrete_floor.setDensity(2322)
584
- normalweight_concrete_floor.setSpecificHeat(832)
585
-
586
- nonres_floor_insulation = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
587
- nonres_floor_insulation.setName('Nonres_Floor_Insulation')
588
- nonres_floor_insulation.setRoughness('MediumSmooth')
589
- nonres_floor_insulation.setThermalResistance(2.88291975297193)
590
- nonres_floor_insulation.setThermalAbsorptance(0.9)
591
- nonres_floor_insulation.setSolarAbsorptance(0.7)
592
- nonres_floor_insulation.setVisibleAbsorptance(0.7)
593
-
594
- floor_adiabatic_construction = OpenStudio::Model::Construction.new(model)
595
- floor_adiabatic_construction.setName('Floor Adiabatic construction')
596
- floor_layers = OpenStudio::Model::MaterialVector.new
597
- floor_layers << cp02_carpet_pad
598
- floor_layers << normalweight_concrete_floor
599
- floor_layers << nonres_floor_insulation
600
- floor_adiabatic_construction.setLayers(floor_layers)
601
-
602
- g01_13mm_gypsum_board = OpenStudio::Model::StandardOpaqueMaterial.new(model)
603
- g01_13mm_gypsum_board.setName('G01 13mm gypsum board')
604
- g01_13mm_gypsum_board.setRoughness('Smooth')
605
- g01_13mm_gypsum_board.setThickness(0.0127)
606
- g01_13mm_gypsum_board.setConductivity(0.1600)
607
- g01_13mm_gypsum_board.setDensity(800)
608
- g01_13mm_gypsum_board.setSpecificHeat(1090)
609
- g01_13mm_gypsum_board.setThermalAbsorptance(0.9)
610
- g01_13mm_gypsum_board.setSolarAbsorptance(0.7)
611
- g01_13mm_gypsum_board.setVisibleAbsorptance(0.5)
612
-
613
- wall_adiabatic_construction = OpenStudio::Model::Construction.new(model)
614
- wall_adiabatic_construction.setName('Wall Adiabatic construction')
615
- wall_layers = OpenStudio::Model::MaterialVector.new
616
- wall_layers << g01_13mm_gypsum_board
617
- wall_layers << g01_13mm_gypsum_board
618
- wall_adiabatic_construction.setLayers(wall_layers)
619
-
620
- m10_200mm_concrete_block_basement_wall = OpenStudio::Model::StandardOpaqueMaterial.new(model)
621
- m10_200mm_concrete_block_basement_wall.setName('M10 200mm concrete block basement wall')
622
- m10_200mm_concrete_block_basement_wall.setRoughness('MediumRough')
623
- m10_200mm_concrete_block_basement_wall.setThickness(0.2032)
624
- m10_200mm_concrete_block_basement_wall.setConductivity(1.326)
625
- m10_200mm_concrete_block_basement_wall.setDensity(1842)
626
- m10_200mm_concrete_block_basement_wall.setSpecificHeat(912)
627
-
628
- basement_wall_construction = OpenStudio::Model::Construction.new(model)
629
- basement_wall_construction.setName('Basement Wall construction')
630
- basement_wall_layers = OpenStudio::Model::MaterialVector.new
631
- basement_wall_layers << m10_200mm_concrete_block_basement_wall
632
- basement_wall_construction.setLayers(basement_wall_layers)
633
-
634
- basement_floor_construction = OpenStudio::Model::Construction.new(model)
635
- basement_floor_construction.setName('Basement Floor construction')
636
- basement_floor_layers = OpenStudio::Model::MaterialVector.new
637
- basement_floor_layers << m10_200mm_concrete_block_basement_wall
638
- basement_floor_layers << cp02_carpet_pad
639
- basement_floor_construction.setLayers(basement_floor_layers)
640
-
641
- model.getSurfaces.sort.each do |surface|
642
- if surface.outsideBoundaryCondition.to_s == 'Adiabatic'
643
- if surface.surfaceType.to_s == 'Wall'
644
- surface.setConstruction(wall_adiabatic_construction)
645
- else
646
- surface.setConstruction(floor_adiabatic_construction)
647
- end
648
- elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients'
649
- # Ground
650
- if surface.surfaceType.to_s == 'Wall'
651
- surface.setOutsideBoundaryCondition('Ground')
652
- surface.setConstruction(basement_wall_construction)
653
- else
654
- surface.setOutsideBoundaryCondition('Ground')
655
- surface.setConstruction(basement_floor_construction)
656
- end
657
- end
658
- end
659
- end
660
-
661
- def scale_model_geometry(model, x_scale, y_scale, z_scale)
662
- # Identity matrix for setting space origins
663
- m = OpenStudio::Matrix.new(4, 4, 0)
664
-
665
- m[0, 0] = 1.0 / x_scale
666
- m[1, 1] = 1.0 / y_scale
667
- m[2, 2] = 1.0 / z_scale
668
- m[3, 3] = 1.0
669
- t = OpenStudio::Transformation.new(m)
670
- model.getPlanarSurfaceGroups().each do |planar_surface|
671
- planar_surface.changeTransformation(t)
672
- end
673
- return model
674
- end
675
-
676
- # This method applies the maximum fenestration and door to wall ratio to a building as per NECB 2011 8.4.4.3 and
677
- # 3.2.1.4 (or equivalent in other versions of the NECB). It first checks for al exterior walls adjacent to conditioned
678
- # spaces. It distinguishes between plenums and other conditioned spaces. It uses both to calculate the maximum window
679
- # area to be applied to the building but attempts to put these windows only on non-plenum conditioned spaces (if
680
- # possible).
681
- def apply_max_fdwr_nrcan(model:, fdwr_lim:)
682
- # First determine which vertical (between 89 and 91 degrees from horizontal) walls are adjacent to conditioned
683
- # spaces.
684
- exp_surf_info = find_exposed_conditioned_vertical_surfaces(model)
685
- # If there are none (or very few) then throw a warning.
686
- if exp_surf_info["total_exp_wall_area_m2"] < 0.1
687
- OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "This building has no exposed walls adjacent to heated spaces.")
688
- return false
689
- end
690
-
691
- construct_set = model.getBuilding.defaultConstructionSet.get
692
- fixed_window_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.fixedWindowConstruction.get
693
-
694
- # IF FDWR is greater than 1 then something is wrong raise an error. If it is less than 0.001 assume all the windows
695
- # should go.
696
- if fdwr_lim > 1
697
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "This building requires a larger window area than there is wall area.")
698
- return false
699
- elsif fdwr_lim < 0.001
700
- exp_surf_info["exp_nonplenum_walls"].sort.each do |exp_surf|
701
- remove_All_Subsurfaces(surface: exp_surf)
702
- end
703
- return true
704
- end
705
- # Get the required window area.
706
- win_area = fdwr_lim * exp_surf_info["total_exp_wall_area_m2"]
707
- # Try to put the windows on non-plenum walls if possible. So determine if you can fit the required window area
708
- # on the non-plenum wall area.
709
- if win_area <= exp_surf_info["exp_nonplenum_wall_area_m2"]
710
- # If you can fit the windows on the non-plenum wall area then recalculate the window ratio so that is is only for
711
- # the non-plenum walls.
712
- nonplenum_fdwr = win_area / exp_surf_info["exp_nonplenum_wall_area_m2"]
713
- exp_surf_info["exp_nonplenum_walls"].sort.each do |exp_surf|
714
- # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface
715
- # type (which will be 'fixedwindow')
716
- remove_All_Subsurfaces(surface: exp_surf)
717
- set_Window_To_Wall_Ratio_set_name(surface: exp_surf, area_fraction: nonplenum_fdwr, construction: fixed_window_construct_set)
718
- end
719
- else
720
- # There was not enough non-plenum wall area so add the windows to both the plenum and non-plenum walls. This is
721
- # done separately because the 'find_exposed_conditioned_vertical_surfaces' method returns the plenum and
722
- # non-plenum walls separately.
723
- exp_surf_info["exp_nonplenum_walls"].sort.each do |exp_surf|
724
- # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface
725
- # type (which will be 'fixedwindow')
726
- remove_All_Subsurfaces(surface: exp_surf)
727
- set_Window_To_Wall_Ratio_set_name(surface: exp_surf, area_fraction: fdwr_lim, construction: fixed_window_construct_set)
728
- end
729
- exp_surf_info["exp_plenum_walls"].sort.each do |exp_surf|
730
- # Remove any subsurfaces, add the window, set the name to be whatever the surface name is plus the subsurface
731
- # type (which will be 'fixedwindow')
732
- remove_All_Subsurfaces(surface: exp_surf)
733
- set_Window_To_Wall_Ratio_set_name(surface: exp_surf, area_fraction: fdwr_lim, construction: fixed_window_construct_set)
734
- end
735
- end
736
- return true
737
- end
738
-
739
- # This method is similar to the 'apply_max_fdwr' method above but applies the maximum skylight to roof area ratio to a
740
- # building as per NECB 2011 8.4.4.3 and 3.2.1.4 (or equivalent in other versions of the NECB). It first checks for all
741
- # exterior roofs adjacent to conditioned spaces. It distinguishes between plenums and other conditioned spaces. It
742
- # uses only the non-plenum roof area to calculate the maximum skylight area to be applied to the building.
743
- def apply_max_srr_nrcan(model:, srr_lim:)
744
- # First determine which roof surfaces are adjacent to heated spaces (both plenum and non-plenum).
745
- exp_surf_info = find_exposed_conditioned_roof_surfaces(model)
746
- # If the non-plenum roof area is very small raise a warning. It may be perfectly fine but it is probably a good
747
- # idea to warn the user.
748
- if exp_surf_info["exp_nonplenum_roof_area_m2"] < 0.1
749
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "This building has no exposed ceilings adjacent to spaces that are not attics or plenums. No skylights will be added.")
750
- return false
751
- end
752
-
753
- # If the SRR is greater than one something is seriously wrong so raise an error. If it is less than 0.001 assume
754
- # all the skylights should go.
755
- if srr_lim > 1
756
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "This building requires a larger skylight area than there is roof area.")
757
- return false
758
- elsif srr_lim < 0.001
759
- exp_surf_info["exp_nonplenum_roofs"].sort.each do |exp_surf|
760
- remove_All_Subsurfaces(surface: exp_surf)
761
- end
762
- return true
763
- end
764
-
765
- construct_set = model.getBuilding.defaultConstructionSet.get
766
- skylight_construct_set = construct_set.defaultExteriorSubSurfaceConstructions.get.skylightConstruction.get
767
-
768
- # Go through all of exposed roofs adjacent to heated, non-plenum spaces, remove any existing subsurfaces, and add
769
- # a skylight in the centroid of the surface, with the same shape of the surface, only scaled to be the area
770
- # determined by the SRR. The name of the skylight will be the surface name with the subsurface type attached
771
- # ('skylight' in this case). Note that this method will only work if the surface does not fold into itself (like an
772
- # L or a V).
773
- exp_surf_info["exp_nonplenum_roofs"].sort.each do |roof|
774
- # sub_surface_create_centered_subsurface_from_scaled_surface(roof, srr_lim, model)
775
- sub_surface_create_scaled_subsurfaces_from_surface(surface: roof, area_fraction: srr_lim, model: model, consturction: skylight_construct_set)
776
- end
777
- return true
778
- end
779
- end
138
+ end