openstudio-standards 0.2.12.rc4 → 0.2.12.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/OpenStudio_Standards-ashrae_90_1(space_types).xlsx +0 -0
  3. data/data/standards/OpenStudio_Standards-ashrae_90_1.xlsx +0 -0
  4. data/data/standards/test_performance_expected_dd_results.csv +950 -950
  5. data/lib/openstudio-standards.rb +8 -1
  6. data/lib/openstudio-standards/btap/btap.model.rb +1 -1
  7. data/lib/openstudio-standards/btap/economics.rb +14 -11
  8. data/lib/openstudio-standards/btap/envelope.rb +185 -257
  9. data/lib/openstudio-standards/btap/fileio.rb +1 -0
  10. data/lib/openstudio-standards/btap/geometry.rb +21 -1
  11. data/lib/openstudio-standards/btap/measures.rb +12 -11
  12. data/lib/openstudio-standards/btap/schedules.rb +3 -12
  13. data/lib/openstudio-standards/hvac_sizing/Siz.AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.rb +178 -0
  14. data/lib/openstudio-standards/hvac_sizing/Siz.CoilCoolingDXMultiSpeed.rb +8 -8
  15. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +3 -0
  16. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +3 -3
  17. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +25 -23
  18. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXMultiSpeed.rb +91 -0
  19. data/lib/openstudio-standards/standards/Standards.CoilDX.rb +20 -2
  20. data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +39 -0
  21. data/lib/openstudio-standards/standards/Standards.Model.rb +29 -0
  22. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +37 -4
  23. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.unitary_acs.json +15 -15
  24. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.unitary_acs.json +15 -15
  25. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.unitary_acs.json +5 -5
  26. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.unitary_acs.json +15 -15
  27. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.spc_typ.json +5963 -2723
  28. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.spc_typ.json +5917 -2697
  29. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/data/nrel_zne_ready_2017.spc_typ.json +2011 -1112
  30. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/data/ze_aedg_multifamily.spc_typ.json +1946 -1106
  31. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/btap_1980to2010.rb +2 -18
  32. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/space_types.json +1677 -1005
  33. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +64 -13
  34. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +31 -19
  35. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/curves.json +75 -0
  36. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/heat_pumps.json +16 -16
  37. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/space_types.json +1677 -1005
  38. data/lib/openstudio-standards/standards/necb/ECMS/data/boiler_set.json +29 -0
  39. data/lib/openstudio-standards/standards/necb/ECMS/data/curves.json +913 -0
  40. data/lib/openstudio-standards/standards/necb/ECMS/data/equip_eff_lim.json +52 -0
  41. data/lib/openstudio-standards/standards/necb/ECMS/data/erv.json +105 -0
  42. data/lib/openstudio-standards/standards/necb/ECMS/data/furnace_set.json +23 -0
  43. data/lib/openstudio-standards/standards/necb/ECMS/data/heat_pumps.json +803 -0
  44. data/lib/openstudio-standards/standards/necb/ECMS/data/heat_pumps_heating.json +787 -0
  45. data/lib/openstudio-standards/standards/necb/ECMS/data/shw_set.json +29 -0
  46. data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +87 -0
  47. data/lib/openstudio-standards/standards/necb/ECMS/erv.rb +22 -0
  48. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +1593 -0
  49. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +68 -33
  50. data/lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb +24 -13
  51. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +104 -99
  52. data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +24 -24
  53. data/lib/openstudio-standards/standards/necb/NECB2011/data/curves.json +50 -0
  54. data/lib/openstudio-standards/standards/necb/NECB2011/data/erv.json +31 -0
  55. data/lib/openstudio-standards/standards/necb/NECB2011/data/led_lighting_data.json +2028 -0
  56. data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +1745 -1297
  57. data/lib/openstudio-standards/standards/necb/NECB2011/daylighting_control.md +70 -0
  58. data/lib/openstudio-standards/standards/necb/NECB2011/demand_controlled_ventilation.md +46 -0
  59. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb +69 -107
  60. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb +24 -1
  61. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb +139 -141
  62. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb +24 -0
  63. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +344 -234
  64. data/lib/openstudio-standards/standards/necb/NECB2011/led_lighting.md +51 -0
  65. data/lib/openstudio-standards/standards/necb/NECB2011/lighting.rb +57 -9
  66. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +1060 -34
  67. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +9 -1
  68. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +1 -1
  69. data/lib/openstudio-standards/standards/necb/NECB2015/data/led_lighting_data.json +2883 -0
  70. data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +2554 -1916
  71. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +32 -1
  72. data/lib/openstudio-standards/standards/necb/NECB2017/data/led_lighting_data.json +2883 -0
  73. data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +2554 -1916
  74. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +29 -0
  75. data/lib/openstudio-standards/version.rb +1 -1
  76. metadata +21 -2
@@ -0,0 +1,51 @@
1
+ # High Performance LED Lighting Measure
2
+ This measure adds a new lighting definition regarding LED lighting in each space of the model,
3
+ and replace the existing lighting definition with the LED lighting definition in each space.
4
+
5
+ # Description
6
+ The workflow of this measure is as follows:
7
+ 1. Define a new LED lighting definition for each space.
8
+ 2. Use the new LED lighting definition instead of the existing lighting definition in each space.
9
+
10
+ # Approach
11
+ This measure follows the functions already existed in the BTAP environment with respect to setting lights in spaces.<br>
12
+ However, a new function called **set_lighting_per_area_led_lighting** has been created in necb_2011.rb to set lighting power density (LPD) for LED lighting.<br>
13
+ Moreover, the **apply_standard_lights** function (in lighting.rb) has been modified to set the three fields of
14
+ fraction radiant, fraction visible, and return air fraction for LED lighting.<br>
15
+ Furthermore, two variables have been added to the **apply_standard_lights** function:
16
+ (1) **lights_type** to specify which lighting type to be used in the model. The lighting types that a user can choose are: CFL, LED.
17
+ (2) **scale** to specify whether LPD default values are used or a fraction of LPD default values are used in the model.
18
+
19
+ # Testing Plan
20
+ * This measure has been called in the **apply_loads** function (in necb_2011.rb) -> **model_add_loads** function (in necb_2011.rb)
21
+ -> **space_type_apply_internal_loads** function (in beps_compliance_path.rb) -> **apply_standard_lights** function (in lighting.rb).
22
+ * This measure was tested for NECB 2011 full service restaurant archetype.
23
+ * Note that regarding atriums' heights, since none of the archetypes has atriums,
24
+ the testing procedure was performed for the space type including the "Dining" term and with some tweaks in the LPD equations.
25
+
26
+ # Waiting On
27
+ * There are four fields in the OS:Lights:Definition object that need to be updated in standards/lib/openstudio-standards/standards/necb/NECB2011/**data/led_lighting_data.json**, as follows:
28
+ 1. LPD (W/m<sup>2</sup>)
29
+ 2. Fraction Radiant
30
+ 3. Fraction Visible
31
+ 4. Return Air Fraction
32
+
33
+ * Note that three xlsx files (**led_lighting_data_necb2011.xlsx**, **led_lighting_data_necb2015.xlsx**, **led_lighting_data_necb2017.xlsx**)
34
+ should be updated as per Mike Lubun's xlsx files for lighting sets.
35
+ * To this end, openstudio-standards/lib/openstudio-standards/utilities/**LEDLightingData_xlsx_to_json.rb** can be used to convert the xlsx files to json format.
36
+ * Once the openstudio-standards/lib/openstudio-standards/**btap/led_lighting_data.json** file is generated using LEDLightingData_xlsx_to_json.rb,
37
+ openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/**data/led_lighting_data.json** should be updated manually (copy and paste from **btap/led_lighting_data.json**).
38
+
39
+ # Files Added/Modified
40
+ * Files have been modified:
41
+ * **necb_2011.rb**
42
+ * **beps_compliance_path.rb**
43
+ * **lighting.rb**
44
+ * Files have been added:
45
+ * **led_lighting.md**
46
+ * openstudio-standards/lib/openstudio-standards/utilities/**LEDLightingData_xlsx_to_json.rb**
47
+ * openstudio-standards/lib/openstudio-standards/btap/**led_lighting_data_necb2011.xlsx**
48
+ * openstudio-standards/lib/openstudio-standards/btap/**led_lighting_data_necb2015.xlsx**
49
+ * openstudio-standards/lib/openstudio-standards/btap/**led_lighting_data_necb2017.xlsx**
50
+ * openstudio-standards/lib/openstudio-standards/**btap/led_lighting_data.json**
51
+ * openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/**data/led_lighting_data.json**
@@ -1,5 +1,10 @@
1
1
  class NECB2011
2
- def apply_standard_lights(set_lights, space_type, space_type_properties)
2
+
3
+ def apply_standard_lights(set_lights: true,
4
+ space_type:, space_type_properties:,
5
+ lights_type: 'NECB_Default',
6
+ lights_scale: 1.0)
7
+
3
8
  lights_have_info = false
4
9
  lighting_per_area = space_type_properties['lighting_per_area'].to_f
5
10
  lighting_per_person = space_type_properties['lighting_per_person'].to_f
@@ -7,8 +12,27 @@ class NECB2011
7
12
  lights_frac_radiant = space_type_properties['lighting_fraction_radiant'].to_f
8
13
  lights_frac_visible = space_type_properties['lighting_fraction_visible'].to_f
9
14
  lights_frac_replaceable = space_type_properties['lighting_fraction_replaceable'].to_f
10
- lights_have_info = true unless lighting_per_area.zero?
11
- lights_have_info = true unless lighting_per_person.zero?
15
+ lights_have_info = true if !lighting_per_area.zero? or !lighting_per_person.zero?
16
+
17
+
18
+ ##### NOTE: Reference for LED lighting's return air, radiant, and visible fraction values is: page 142, NREL (2014), "Proven Energy-Saving Technologies for Commercial Properties", available at https://www.nrel.gov/docs/fy15osti/63807.pdf
19
+ if lights_type == 'LED'
20
+ led_lights_have_info = false
21
+ led_spacetype_data = @standards_data['tables']['led_lighting_data']['table']
22
+ standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil
23
+ standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
24
+ led_space_type_properties = led_spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) }
25
+ if led_space_type_properties.nil?
26
+ raise("#{standards_building_type} for #{standards_space_type} was not found please verify the led lighting database names match the space type names.")
27
+ end
28
+
29
+ lighting_per_area_led_lighting = led_space_type_properties['lighting_per_area'].to_f
30
+ lights_frac_to_return_air_led_lighting = led_space_type_properties['lighting_fraction_to_return_air'].to_f
31
+ lights_frac_radiant_led_lighting = led_space_type_properties['lighting_fraction_radiant'].to_f
32
+ lights_frac_visible_led_lighting = led_space_type_properties['lighting_fraction_visible'].to_f
33
+ led_lights_have_info = true unless lighting_per_area_led_lighting.zero?
34
+
35
+ end
12
36
 
13
37
  if set_lights && lights_have_info
14
38
 
@@ -16,9 +40,15 @@ class NECB2011
16
40
  instances = space_type.lights.sort
17
41
  if instances.size.zero?
18
42
  definition = OpenStudio::Model::LightsDefinition.new(space_type.model)
19
- definition.setName("#{space_type.name} Lights Definition")
43
+ if lights_type == 'NECB_Default'
44
+ definition.setName("#{space_type.name} Lights Definition")
45
+ elsif lights_type == 'LED'
46
+ definition.setName("#{space_type.name} Lights Definition - LED lighting")
47
+ end
48
+ # puts definition.name().to_s
20
49
  instance = OpenStudio::Model::Lights.new(definition)
21
50
  instance.setName("#{space_type.name} Lights")
51
+ # puts instance.name.to_s
22
52
  instance.setSpaceType(space_type)
23
53
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no lights, one has been created.")
24
54
  instances << instance
@@ -34,20 +64,39 @@ class NECB2011
34
64
  space_type.lights.sort.each do |inst|
35
65
  definition = inst.lightsDefinition
36
66
  unless lighting_per_area.zero?
37
- set_lighting_per_area(space_type, definition, lighting_per_area)
67
+ if lights_type == 'NECB_Default'
68
+ set_lighting_per_area(space_type, definition, lighting_per_area)
69
+ elsif lights_type == 'LED'
70
+ set_lighting_per_area_led_lighting(space_type: space_type,
71
+ definition: definition,
72
+ lighting_per_area_led_lighting: lighting_per_area_led_lighting,
73
+ lights_scale: lights_scale)
74
+ end
38
75
  end
39
76
  unless lighting_per_person.zero?
40
77
  definition.setWattsperPerson(OpenStudio.convert(lighting_per_person.to_f, 'W/person', 'W/person').get)
41
78
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting to #{lighting_per_person} W/person.")
42
79
  end
43
80
  unless lights_frac_to_return_air.zero?
44
- definition.setReturnAirFraction(lights_frac_to_return_air)
81
+ if lights_type == 'NECB_Default'
82
+ definition.setReturnAirFraction(lights_frac_to_return_air)
83
+ elsif lights_type == 'LED'
84
+ definition.setReturnAirFraction(lights_frac_to_return_air_led_lighting)
85
+ end
45
86
  end
46
87
  unless lights_frac_radiant.zero?
47
- definition.setFractionRadiant(lights_frac_radiant)
88
+ if lights_type == 'NECB_Default'
89
+ definition.setFractionRadiant(lights_frac_radiant)
90
+ elsif lights_type == 'LED'
91
+ definition.setFractionRadiant(lights_frac_radiant_led_lighting)
92
+ end
48
93
  end
49
94
  unless lights_frac_visible.zero?
50
- definition.setFractionVisible(lights_frac_visible)
95
+ if lights_type == 'NECB_Default'
96
+ definition.setFractionVisible(lights_frac_visible)
97
+ elsif lights_type == 'LED'
98
+ definition.setFractionVisible(lights_frac_visible_led_lighting)
99
+ end
51
100
  end
52
101
  # unless lights_frac_replaceable.zero?
53
102
  # definition.setFractionReplaceable(lights_frac_replaceable)
@@ -70,7 +119,6 @@ class NECB2011
70
119
  additional_lights.setName("#{space_type.name} Additional Lights")
71
120
  additional_lights.setSpaceType(space_type)
72
121
  end
73
-
74
122
  end
75
123
  end
76
124
 
@@ -40,7 +40,7 @@ class NECB2011 < Standard
40
40
  else
41
41
  path = "#{File.dirname(__FILE__)}/../common/"
42
42
  raise ('Could not find common folder') unless Dir.exist?(path)
43
- files = Dir.glob("#{path}/*.json").select {|e| File.file? e}
43
+ files = Dir.glob("#{path}/*.json").select { |e| File.file? e }
44
44
  files.each do |file|
45
45
  data = JSON.parse(File.read(file))
46
46
  if not data["tables"].nil?
@@ -62,7 +62,7 @@ class NECB2011 < Standard
62
62
  end
63
63
  end
64
64
  else
65
- files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select {|e| File.file? e}
65
+ files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select { |e| File.file? e }
66
66
  files.each do |file|
67
67
  data = JSON.parse(File.read(file))
68
68
  if not data["tables"].nil?
@@ -117,7 +117,7 @@ class NECB2011 < Standard
117
117
  end
118
118
 
119
119
  def get_all_spacetype_names
120
- return @standards_data['space_types'].map {|space_types| [space_types['building_type'], space_types['space_type']]}
120
+ return @standards_data['space_types'].map { |space_types| [space_types['building_type'], space_types['space_type']] }
121
121
  end
122
122
 
123
123
  # Enter in [latitude, longitude] for each loc and this method will return the distance.
@@ -129,8 +129,8 @@ class NECB2011 < Standard
129
129
  dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad
130
130
  dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg
131
131
 
132
- lat1_rad, lon1_rad = loc1.map {|i| i * rad_per_deg}
133
- lat2_rad, lon2_rad = loc2.map {|i| i * rad_per_deg}
132
+ lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg }
133
+ lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg }
134
134
 
135
135
  a = Math.sin(dlat_rad / 2) ** 2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2) ** 2
136
136
  c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a))
@@ -169,13 +169,67 @@ class NECB2011 < Standard
169
169
  epw_file:,
170
170
  debug: false,
171
171
  sizing_run_dir: Dir.pwd,
172
- primary_heating_fuel: 'DefaultFuel')
172
+ primary_heating_fuel: 'DefaultFuel',
173
+ dcv_type: 'NECB_Default',
174
+ lights_type: 'NECB_Default',
175
+ lights_scale: 1.0,
176
+ daylighting_type: 'NECB_Default',
177
+ ecm_system_name: 'NECB_Default',
178
+ erv_package: 'NECB_Default',
179
+ boiler_eff: nil,
180
+ furnace_eff: nil,
181
+ shw_eff: nil,
182
+ ext_wall_cond: nil,
183
+ ext_floor_cond: nil,
184
+ ext_roof_cond: nil,
185
+ ground_wall_cond: nil,
186
+ ground_floor_cond: nil,
187
+ ground_roof_cond: nil,
188
+ door_construction_cond: nil,
189
+ fixed_window_cond: nil,
190
+ glass_door_cond: nil,
191
+ overhead_door_cond: nil,
192
+ skylight_cond: nil,
193
+ glass_door_solar_trans: nil,
194
+ fixed_wind_solar_trans: nil,
195
+ skylight_solar_trans: nil,
196
+ fdwr_set: -1.0,
197
+ srr_set: -1.0
198
+
199
+ )
173
200
 
174
201
  model = load_building_type_from_library(building_type: building_type)
175
202
  return model_apply_standard(model: model,
176
203
  epw_file: epw_file,
177
204
  sizing_run_dir: sizing_run_dir,
178
- primary_heating_fuel: primary_heating_fuel)
205
+ primary_heating_fuel: primary_heating_fuel,
206
+ dcv_type: dcv_type, # Four options: (1) 'NECB_Default', (2) 'No_DCV', (3) 'Occupancy_based_DCV' , (4) 'CO2_based_DCV'
207
+ lights_type: lights_type, # Two options: (1) 'NECB_Default', (2) 'LED'
208
+ lights_scale: lights_scale,
209
+ daylighting_type: daylighting_type, # Two options: (1) 'NECB_Default', (2) 'add_daylighting_controls'
210
+ ecm_system_name: ecm_system_name,
211
+ erv_package: erv_package,
212
+ boiler_eff: boiler_eff,
213
+ furnace_eff: furnace_eff,
214
+ shw_eff: shw_eff,
215
+ ext_wall_cond: ext_wall_cond,
216
+ ext_floor_cond: ext_floor_cond,
217
+ ext_roof_cond: ext_roof_cond,
218
+ ground_wall_cond: ground_wall_cond,
219
+ ground_floor_cond: ground_floor_cond,
220
+ ground_roof_cond: ground_roof_cond,
221
+ door_construction_cond: door_construction_cond,
222
+ fixed_window_cond: fixed_window_cond,
223
+ glass_door_cond: glass_door_cond,
224
+ overhead_door_cond: overhead_door_cond,
225
+ skylight_cond: skylight_cond,
226
+ glass_door_solar_trans: glass_door_solar_trans,
227
+ fixed_wind_solar_trans: fixed_wind_solar_trans,
228
+ skylight_solar_trans: skylight_solar_trans,
229
+ fdwr_set: fdwr_set,
230
+ srr_set: srr_set
231
+
232
+ )
179
233
  end
180
234
 
181
235
  def load_building_type_from_library(building_type:)
@@ -191,25 +245,146 @@ class NECB2011 < Standard
191
245
  def model_apply_standard(model:,
192
246
  epw_file:,
193
247
  sizing_run_dir: Dir.pwd,
194
- primary_heating_fuel: 'DefaultFuel')
248
+ primary_heating_fuel: 'DefaultFuel',
249
+ dcv_type: 'NECB_Default',
250
+ lights_type: 'NECB_Default',
251
+ lights_scale: 1.0,
252
+ daylighting_type: 'NECB_Default',
253
+ ecm_system_name: 'NECB_Default',
254
+ erv_package: 'NECB_Default',
255
+ boiler_eff: nil,
256
+ furnace_eff: nil,
257
+ shw_eff: nil,
258
+ ext_wall_cond: nil,
259
+ ext_floor_cond: nil,
260
+ ext_roof_cond: nil,
261
+ ground_wall_cond: nil,
262
+ ground_floor_cond: nil,
263
+ ground_roof_cond: nil,
264
+ door_construction_cond: nil,
265
+ fixed_window_cond: nil,
266
+ glass_door_cond: nil,
267
+ overhead_door_cond: nil,
268
+ skylight_cond: nil,
269
+ glass_door_solar_trans: nil,
270
+ fixed_wind_solar_trans: nil,
271
+ skylight_solar_trans: nil,
272
+ fdwr_set: -1,
273
+ srr_set: -1,
274
+ rotation_degrees: nil,
275
+ scale_x: nil,
276
+ scale_y: nil,
277
+ scale_z: nil
278
+ )
279
+
280
+ BTAP::Geometry::rotate_building(model: model,degrees: rotation_degrees) unless rotation_degrees.nil?
281
+ unless scale_x.nil? && scale_y.nil? && scale_z.nil?
282
+ scale_x = 1 if scale_x.nil?
283
+ scale_y = 1 if scale_y.nil?
284
+ scale_z = 1 if scale_y.nil?
285
+ BTAP::Geometry::scale_model(model, scale_x, scale_y, scale_z)
286
+ end
287
+
195
288
  apply_weather_data(model: model, epw_file: epw_file)
196
- apply_loads(model: model)
197
- apply_envelope(model: model)
198
- apply_fdwr_srr_daylighting(model: model)
199
- apply_auto_zoning(model: model, sizing_run_dir: sizing_run_dir)
289
+ apply_loads(model: model, lights_type: lights_type, lights_scale: lights_scale)
290
+ apply_envelope(model: model,
291
+ ext_wall_cond: ext_wall_cond,
292
+ ext_floor_cond: ext_floor_cond,
293
+ ext_roof_cond: ext_roof_cond,
294
+ ground_wall_cond: ground_wall_cond,
295
+ ground_floor_cond: ground_floor_cond,
296
+ ground_roof_cond: ground_roof_cond,
297
+ door_construction_cond: door_construction_cond,
298
+ fixed_window_cond: fixed_window_cond,
299
+ glass_door_cond: glass_door_cond,
300
+ overhead_door_cond: overhead_door_cond,
301
+ skylight_cond: skylight_cond,
302
+ glass_door_solar_trans: glass_door_solar_trans,
303
+ fixed_wind_solar_trans: fixed_wind_solar_trans,
304
+ skylight_solar_trans: skylight_solar_trans)
305
+ apply_fdwr_srr_daylighting(model: model,
306
+ fdwr_set: fdwr_set,
307
+ srr_set: srr_set)
308
+ apply_auto_zoning(model: model,
309
+ sizing_run_dir: sizing_run_dir,
310
+ lights_type: lights_type,
311
+ lights_scale: lights_scale)
312
+ apply_systems_and_efficiencies(model: model,
313
+ primary_heating_fuel: primary_heating_fuel,
314
+ sizing_run_dir: sizing_run_dir,
315
+ dcv_type: dcv_type,
316
+ ecm_system_name: ecm_system_name,
317
+ erv_package: erv_package,
318
+ boiler_eff: boiler_eff,
319
+ furnace_eff: furnace_eff,
320
+ shw_eff: shw_eff,
321
+ daylighting_type: daylighting_type
322
+ )
323
+ return model
324
+ end
325
+
326
+
327
+ def apply_systems_and_efficiencies(model:,
328
+ primary_heating_fuel:,
329
+ sizing_run_dir:,
330
+ dcv_type: 'NECB_Default',
331
+ ecm_system_name: 'NECB_Default',
332
+ erv_package: 'NECB_Default',
333
+ boiler_eff: nil,
334
+ furnace_eff: nil,
335
+ shw_eff: nil,
336
+ daylighting_type: 'NECB_Default')
337
+ # Create ECM object.
338
+ ecm = ECMS.new
339
+
340
+ # -------- Systems Layout-----------
341
+
342
+ # Create Default Systems.
200
343
  apply_systems(model: model, primary_heating_fuel: primary_heating_fuel, sizing_run_dir: sizing_run_dir)
344
+
345
+ # Apply new ECM system. Overwrite standard as required.
346
+ ecm.apply_system_ecm(model: model, ecm_system_name: ecm_system_name, template_standard: self)
347
+
348
+ # Apply ERV equipment as required.
349
+ ecm.apply_erv_ecm(model: model, erv_package: erv_package)
350
+
351
+
352
+ # -------- Performace, Efficiencies, Controls and Sensors ------------
353
+ #
354
+ # Set code standard equipment charecteristics.
201
355
  apply_standard_efficiencies(model: model, sizing_run_dir: sizing_run_dir)
202
- model = apply_loop_pump_power(model: model, sizing_run_dir: sizing_run_dir)
203
- return model
356
+ # Apply System
357
+ ecm.apply_system_efficiencies_ecm(model: model, ecm_system_name: ecm_system_name)
358
+
359
+ # Apply ECM ERV charecteristics as required. Part 2 of above ECM.
360
+ ecm.apply_erv_ecm_efficiency(model: model, erv_package: erv_package)
361
+ # Apply DCV as required
362
+ model_enable_demand_controlled_ventilation(model, dcv_type)
363
+ # Apply Boiler Efficiency
364
+ ecm.modify_boiler_efficiency(model: model, boiler_eff: boiler_eff)
365
+ # Apply Furnace Efficiency
366
+ ecm.modify_furnace_efficiency(model: model, furnace_eff: furnace_eff)
367
+ # Apply SHW Efficiency
368
+ ecm.modify_shw_efficiency(model: model, shw_eff: shw_eff)
369
+ # Apply daylight controls.
370
+ model_add_daylighting_controls(model) if daylighting_type == 'add_daylighting_controls'
371
+
372
+
373
+ # -------Pump sizing required by some vintages----------------
374
+ # Apply Pump power as required.
375
+ apply_loop_pump_power(model: model, sizing_run_dir: sizing_run_dir)
204
376
  end
205
377
 
206
- def apply_loads(model:)
207
- raise('validation of model failed.') unless validate_initial_model(model)
208
- raise('validation of spacetypes failed.') unless validate_and_upate_space_types(model)
378
+
379
+ def apply_loads(model:, lights_type: 'NECB_Default', lights_scale: 1.0, validate: true)
380
+ if validate
381
+ raise('validation of model failed.') unless validate_initial_model(model)
382
+ raise('validation of spacetypes failed.') unless validate_and_upate_space_types(model)
383
+ end
209
384
  #this sets/stores the template version loads that the model uses.
210
385
  model.getBuilding.setStandardsTemplate(self.class.name)
211
386
  set_occ_sensor_spacetypes(model, @space_type_map)
212
- model_add_loads(model)
387
+ model_add_loads(model, lights_type, lights_scale)
213
388
  end
214
389
 
215
390
  def apply_weather_data(model:, epw_file:)
@@ -223,23 +398,44 @@ class NECB2011 < Standard
223
398
  end
224
399
 
225
400
  def apply_envelope(model:,
226
- properties: {
227
- 'outdoors_wall_conductance' => nil,
228
- 'outdoors_floor_conductance' => nil,
229
- 'outdoors_roofceiling_conductance' => nil,
230
- 'ground_wall_conductance' => nil,
231
- 'ground_floor_conductance' => nil,
232
- 'ground_roofceiling_conductance' => nil,
233
- 'outdoors_door_conductance' => nil,
234
- 'outdoors_fixedwindow_conductance' => nil
235
- })
401
+ ext_wall_cond: nil,
402
+ ext_floor_cond: nil,
403
+ ext_roof_cond: nil,
404
+ ground_wall_cond: nil,
405
+ ground_floor_cond: nil,
406
+ ground_roof_cond: nil,
407
+ door_construction_cond: nil,
408
+ fixed_window_cond: nil,
409
+ glass_door_cond: nil,
410
+ overhead_door_cond: nil,
411
+ skylight_cond: nil,
412
+ glass_door_solar_trans: nil,
413
+ fixed_wind_solar_trans: nil,
414
+ skylight_solar_trans: nil)
236
415
  raise('validation of model failed.') unless validate_initial_model(model)
237
416
  model_apply_infiltration_standard(model)
238
417
  model.getInsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
239
418
  model.getOutsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
240
419
  model_add_constructions(model)
241
- apply_standard_construction_properties(model: model, properties: properties)
420
+ apply_standard_construction_properties(model: model,
421
+ ext_wall_cond: ext_wall_cond,
422
+ ext_floor_cond: ext_floor_cond,
423
+ ext_roof_cond: ext_roof_cond,
424
+ ground_wall_cond: ground_wall_cond,
425
+ ground_floor_cond: ground_floor_cond,
426
+ ground_roof_cond: ground_roof_cond,
427
+ door_construction_cond: door_construction_cond,
428
+ fixed_window_cond: fixed_window_cond,
429
+ glass_door_cond: glass_door_cond,
430
+ overhead_door_cond: overhead_door_cond,
431
+ skylight_cond: skylight_cond,
432
+ glass_door_solar_trans: glass_door_solar_trans,
433
+ fixed_wind_solar_trans: fixed_wind_solar_trans,
434
+ skylight_solar_trans: skylight_solar_trans)
435
+
436
+
242
437
  model_create_thermal_zones(model, @space_multiplier_map)
438
+
243
439
  end
244
440
 
245
441
  # Thermal zones need to be set to determine conditioned spaces when applying fdwr and srr limits.
@@ -254,19 +450,20 @@ class NECB2011 < Standard
254
450
  def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0)
255
451
  apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set)
256
452
  apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set)
257
- model_add_daylighting_controls(model) # to be removed after refactor.
453
+ # model_add_daylighting_controls(model) # to be removed after refactor.
258
454
  end
259
455
 
260
- def apply_standard_efficiencies(model:, sizing_run_dir:)
456
+ def apply_standard_efficiencies(model:, sizing_run_dir:, dcv_type: 'NECB_Default')
261
457
  raise('validation of model failed.') unless validate_initial_model(model)
262
458
  climate_zone = 'NECB HDD Method'
263
459
  raise("sizing run 1 failed! check #{sizing_run_dir}") if model_run_sizing_run(model, "#{sizing_run_dir}/plant_loops") == false
264
460
  # This is needed for NECB2011 as a workaround for sizing the reheat boxes
265
- model.getAirTerminalSingleDuctVAVReheats.each {|iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj)}
461
+ model.getAirTerminalSingleDuctVAVReheats.each { |iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj) }
266
462
  # Apply the prototype HVAC assumptions
267
463
  model_apply_prototype_hvac_assumptions(model, nil, climate_zone)
268
464
  # Apply the HVAC efficiency standard
269
465
  model_apply_hvac_efficiency_standard(model, climate_zone)
466
+ model_enable_demand_controlled_ventilation(model, dcv_type)
270
467
  end
271
468
 
272
469
  def apply_loop_pump_power(model:, sizing_run_dir:)
@@ -294,7 +491,7 @@ class NECB2011 < Standard
294
491
  #Now iterate though each vintage
295
492
  space_type_vintage_list.each do |template|
296
493
  #Create the standard object and get a list of all the spacetypes available for that vintage.
297
- standard_space_type_list = Standard.build(template).get_all_spacetype_names.map {|spacetype| [spacetype[0].to_s + '-' + spacetype[1].to_s]}
494
+ standard_space_type_list = Standard.build(template).get_all_spacetype_names.map { |spacetype| [spacetype[0].to_s + '-' + spacetype[1].to_s] }
298
495
  # set array to contain unknown spacetypes.
299
496
  unknown_spacetypes = []
300
497
  # iterate though all space types that the model is using
@@ -331,7 +528,7 @@ class NECB2011 < Standard
331
528
  bt_target_vintage_string = "#{self.class.name}_building_type"
332
529
  space_type_upgrade_map = @standards_data['space_type_upgrade_map']
333
530
  model.getSpaceTypes.sort.each do |st|
334
- space_type_map = space_type_upgrade_map.detect {|row| (row[st_model_vintage_string] == st.standardsSpaceType.get.to_s) && (row[bt_model_vintage_string] == st.standardsBuildingType.get.to_s)}
531
+ space_type_map = space_type_upgrade_map.detect { |row| (row[st_model_vintage_string] == st.standardsSpaceType.get.to_s) && (row[bt_model_vintage_string] == st.standardsBuildingType.get.to_s) }
335
532
  st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip)
336
533
  raise('could not set buildingtype') unless st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip)
337
534
  raise('could not set this') unless st.setStandardsSpaceType(space_type_map[st_target_vintage_string].to_s.strip)
@@ -518,4 +715,833 @@ class NECB2011 < Standard
518
715
  end
519
716
 
520
717
 
718
+ def model_add_daylighting_controls(model)
719
+
720
+ ##### Ask user's inputs for daylighting controls illuminance setpoint and number of stepped control steps.
721
+ ##### Note that the minimum number of stepped control steps is two steps as per NECB2011.
722
+ def daylighting_controls_settings(illuminance_setpoint: 500.0,
723
+ number_of_stepped_control_steps: 2)
724
+ return illuminance_setpoint, number_of_stepped_control_steps
725
+ end
726
+
727
+ ##### Find spaces with exterior fenestration including fixed window, operable window, and skylight.
728
+ daylight_spaces = []
729
+ model.getSpaces.sort.each do |space|
730
+ space.surfaces.sort.each do |surface|
731
+ surface.subSurfaces.sort.each do |subsurface|
732
+ if subsurface.outsideBoundaryCondition == "Outdoors" &&
733
+ (subsurface.subSurfaceType == "FixedWindow" ||
734
+ subsurface.subSurfaceType == "OperableWindow" ||
735
+ subsurface.subSurfaceType == "Skylight")
736
+ daylight_spaces << space
737
+ end #subsurface.outsideBoundaryCondition == "Outdoors" && (subsurface.subSurfaceType == "FixedWindow" || "OperableWindow")
738
+ end #surface.subSurfaces.each do |subsurface|
739
+ end #space.surfaces.each do |surface|
740
+ end #model.getSpaces.sort.each do |space|
741
+
742
+ ##### Remove duplicate spaces from the "daylight_spaces" array, as a daylighted space may have various fenestration types.
743
+ daylight_spaces = daylight_spaces.uniq
744
+ # puts daylight_spaces
745
+
746
+ ##### Create hashes for "Primary Sidelighted Areas", "Sidelighting Effective Aperture", "Daylighted Area Under Skylights",
747
+ ##### and "Skylight Effective Aperture" for the whole model.
748
+ ##### Each of these hashes will be used later in this function (i.e. model_add_daylighting_controls)
749
+ ##### to provide a dictionary of daylighted space names and the associated value (i.e. daylighted area or effective aperture).
750
+ primary_sidelighted_area_hash = {}
751
+ sidelighting_effective_aperture_hash = {}
752
+ daylighted_area_under_skylights_hash = {}
753
+ skylight_effective_aperture_hash = {}
754
+
755
+ ##### Calculate "Primary Sidelighted Areas" AND "Sidelighting Effective Aperture" as per NECB2011. #TODO: consider removing overlapped sidelighted area
756
+ daylight_spaces.sort.each do |daylight_space|
757
+ # puts daylight_space.name.to_s
758
+ primary_sidelighted_area = 0.0
759
+ area_weighted_vt_handle = 0.0
760
+ area_weighted_vt = 0.0
761
+ window_area_sum = 0.0
762
+
763
+ ##### Calculate floor area of the daylight_space and get floor vertices of the daylight_space (to be used for the calculation of daylight_space depth)
764
+ floor_surface = nil
765
+ floor_area = 0.0
766
+ floor_vertices = []
767
+ daylight_space.surfaces.sort.each do |surface|
768
+ if surface.surfaceType == "Floor"
769
+ floor_surface = surface
770
+ floor_area += surface.netArea
771
+ floor_vertices << surface.vertices
772
+ end
773
+ end
774
+
775
+ ##### Loop through the surfaces of each daylight_space to calculate primary_sidelighted_area and
776
+ ##### area-weighted visible transmittance and window_area_sum which are used to calculate sidelighting_effective_aperture
777
+ primary_sidelighted_area, area_weighted_vt_handle, window_area_sum =
778
+ get_parameters_sidelighting(daylight_space: daylight_space,
779
+ floor_surface: floor_surface,
780
+ floor_vertices: floor_vertices,
781
+ floor_area: floor_area,
782
+ primary_sidelighted_area: primary_sidelighted_area,
783
+ area_weighted_vt_handle: area_weighted_vt_handle,
784
+ window_area_sum: window_area_sum)
785
+
786
+ primary_sidelighted_area_hash[daylight_space.name.to_s] = primary_sidelighted_area
787
+
788
+ ##### Calculate area-weighted VT of glazing (this is used to calculate sidelighting effective aperture; see NECB2011: 4.2.2.10.).
789
+ area_weighted_vt = area_weighted_vt_handle / window_area_sum
790
+ sidelighting_effective_aperture_hash[daylight_space.name.to_s] = window_area_sum * area_weighted_vt / primary_sidelighted_area
791
+
792
+ end #daylight_spaces.each do |daylight_space|
793
+
794
+
795
+ ##### Calculate "Daylighted Area Under Skylights" AND "Skylight Effective Aperture"
796
+ daylight_spaces.sort.each do |daylight_space|
797
+ # puts daylight_space.name.to_s
798
+ skylight_area = 0.0
799
+ skylight_area_weighted_vt_handle = 0.0
800
+ skylight_area_weighted_vt = 0.0
801
+ skylight_area_sum = 0.0
802
+ daylighted_under_skylight_area = 0.0
803
+
804
+ ##### Loop through the surfaces of each daylight_space to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space
805
+ daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum =
806
+ get_parameters_skylight(daylight_space: daylight_space,
807
+ skylight_area_weighted_vt_handle: skylight_area_weighted_vt_handle,
808
+ skylight_area_sum: skylight_area_sum,
809
+ daylighted_under_skylight_area: daylighted_under_skylight_area)
810
+
811
+ daylighted_area_under_skylights_hash[daylight_space.name.to_s] = daylighted_under_skylight_area
812
+
813
+ ##### Calculate skylight_effective_aperture as per NECB2011: 4.2.2.7.
814
+ ##### Note that it was assumed that the skylight is flush with the ceiling. Therefore, area-weighted average well factor (WF) was set to 0.9 in the below Equation.
815
+ skylight_area_weighted_vt = skylight_area_weighted_vt_handle / skylight_area_sum
816
+ skylight_effective_aperture_hash[daylight_space.name.to_s] = 0.85 * skylight_area_sum * skylight_area_weighted_vt * 0.9 / daylighted_under_skylight_area
817
+
818
+ end #daylight_spaces.each do |daylight_space|
819
+
820
+ # puts primary_sidelighted_area_hash
821
+ # puts sidelighting_effective_aperture_hash
822
+ # puts daylighted_area_under_skylights_hash
823
+ # puts skylight_effective_aperture_hash
824
+
825
+ ##### Find office spaces >= 25m2 among daylight_spaces
826
+ offices_larger_25m2 = []
827
+ daylight_spaces.sort.each do |daylight_space|
828
+
829
+ ## The following steps are for in case an office has multiple floors at various heights
830
+ ## 1. Calculate number of floors of each daylight_space
831
+ ## 2. Find the lowest z among all floors of each daylight_space
832
+ ## 3. Find lowest floors of each daylight_space (these floors are at the same level)
833
+ ## 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space
834
+
835
+ ## 1. Calculate number of floors of daylight_space
836
+ floor_vertices = []
837
+ number_floor = 0
838
+ daylight_space.surfaces.sort.each do |surface|
839
+ if surface.surfaceType == 'Floor'
840
+ floor_vertices << surface.vertices
841
+ number_floor += 1
842
+ end
843
+ end
844
+
845
+ ## 2. Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space
846
+ lowest_floor_z = []
847
+ highest_floor_z = []
848
+ for i in 0..number_floor - 1
849
+ if i == 0
850
+ lowest_floor_z = floor_vertices[i][0].z
851
+ highest_floor_z = floor_vertices[i][0].z
852
+ else
853
+ if lowest_floor_z > floor_vertices[i][0].z
854
+ lowest_floor_z = floor_vertices[i][0].z
855
+ else
856
+ lowest_floor_z = lowest_floor_z
857
+ end
858
+ if highest_floor_z < floor_vertices[i][0].z
859
+ highest_floor_z = floor_vertices[i][0].z
860
+ else
861
+ highest_floor_z = highest_floor_z
862
+ end
863
+ end
864
+ end
865
+
866
+ ## 3 and 4. Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space,
867
+ ## and gather the vertices of all the lowest floors of daylight_space
868
+ daylight_space_area = 0
869
+ lowest_floors_vertices = []
870
+ floor_vertices = []
871
+ daylight_space.surfaces.sort.each do |surface|
872
+ if surface.surfaceType == 'Floor'
873
+ floor_vertices = surface.vertices
874
+ if floor_vertices[0].z == lowest_floor_z
875
+ lowest_floors_vertices << floor_vertices
876
+ daylight_space_area = daylight_space_area + surface.netArea
877
+ end
878
+ end
879
+ end
880
+
881
+ if daylight_space.spaceType.get.standardsSpaceType.get.to_s == "Office - enclosed" && daylight_space_area >= 25.0
882
+ offices_larger_25m2 << daylight_space.name.to_s
883
+ end
884
+ end
885
+
886
+ ##### find daylight_spaces which do not need daylight sensor controls based on the primary_sidelighted_area as per NECB2011: 4.2.2.8.
887
+ ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their primary_sidelighted_area <= 100m2), as per NECB2011: 4.2.2.2.
888
+ daylight_spaces_exception = []
889
+ primary_sidelighted_area_hash.sort.each do |key_daylight_space_name, value_primary_sidelighted_area|
890
+ if value_primary_sidelighted_area <= 100.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
891
+ daylight_spaces_exception << key_daylight_space_name
892
+ end
893
+ end
894
+
895
+ ##### find daylight_spaces which do not need daylight sensor controls based on the sidelighting_effective_aperture as per NECB2011: 4.2.2.8.
896
+ ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their sidelighting_effective_aperture <= 10%), as per NECB2011: 4.2.2.2.
897
+ sidelighting_effective_aperture_hash.sort.each do |key_daylight_space_name, value_sidelighting_effective_aperture|
898
+ if value_sidelighting_effective_aperture <= 0.1 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
899
+ daylight_spaces_exception << key_daylight_space_name
900
+ end
901
+ end
902
+
903
+ ##### find daylight_spaces which do not need daylight sensor controls based on the daylighted_area_under_skylights as per NECB2011: 4.2.2.4.
904
+ ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their daylighted_area_under_skylights <= 400m2), as per NECB2011: 4.2.2.2.
905
+ daylighted_area_under_skylights_hash.sort.each do |key_daylight_space_name, value_daylighted_area_under_skylights|
906
+ if value_daylighted_area_under_skylights <= 400.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
907
+ daylight_spaces_exception << key_daylight_space_name
908
+ end
909
+ end
910
+
911
+ ##### find daylight_spaces which do not need daylight sensor controls based on the skylight_effective_aperture criterion as per NECB2011: 4.2.2.4.
912
+ ##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their skylight_effective_aperture <= 0.6%), as per NECB2011: 4.2.2.2.
913
+ skylight_effective_aperture_hash.sort.each do |key_daylight_space_name, value_skylight_effective_aperture|
914
+ if value_skylight_effective_aperture <= 0.006 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
915
+ daylight_spaces_exception << key_daylight_space_name
916
+ end
917
+ end
918
+ # puts daylight_spaces_exception
919
+
920
+ ##### Loop through the daylight_spaces and exclude the daylight_spaces that do not meet the criteria (see above) as per NECB2011: 4.2.2.4. and 4.2.2.8.
921
+ daylight_spaces_exception.sort.each do |daylight_space_exception|
922
+ daylight_spaces.sort.each do |daylight_space|
923
+ if daylight_space.name.to_s == daylight_space_exception
924
+ daylight_spaces.delete(daylight_space)
925
+ end
926
+ end
927
+ end
928
+ # puts daylight_spaces
929
+
930
+ ##### Create one daylighting sensor and put it at the center of each daylight_space if the space area < 250m2;
931
+ ##### otherwise, create two daylight sensors, divide the space into two parts and put each of the daylight sensors at the center of each part of the space.
932
+ daylight_spaces.sort.each do |daylight_space|
933
+ # puts daylight_space.name.to_s
934
+ ##### 1. Calculate number of floors of each daylight_space
935
+ ##### 2. Find the lowest z among all floors of each daylight_space
936
+ ##### 3. Find lowest floors of each daylight_space (these floors are at the same level)
937
+ ##### 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space
938
+ ##### 5. Find min and max of x and y among vertices of all the lowest floors of each daylight_space
939
+
940
+ ##### Calculate number of floors of daylight_space
941
+ floor_vertices = []
942
+ number_floor = 0
943
+ daylight_space.surfaces.sort.each do |surface|
944
+ if surface.surfaceType == 'Floor'
945
+ floor_vertices << surface.vertices
946
+ number_floor += 1
947
+ end
948
+ end
949
+
950
+ ##### Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space
951
+ lowest_floor_z = []
952
+ highest_floor_z = []
953
+ for i in 0..number_floor - 1
954
+ if i == 0
955
+ lowest_floor_z = floor_vertices[i][0].z
956
+ highest_floor_z = floor_vertices[i][0].z
957
+ else
958
+ if lowest_floor_z > floor_vertices[i][0].z
959
+ lowest_floor_z = floor_vertices[i][0].z
960
+ else
961
+ lowest_floor_z = lowest_floor_z
962
+ end
963
+ if highest_floor_z < floor_vertices[i][0].z
964
+ highest_floor_z = floor_vertices[i][0].z
965
+ else
966
+ highest_floor_z = highest_floor_z
967
+ end
968
+ end
969
+ end
970
+ # puts lowest_floor_z
971
+
972
+ ##### Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space,
973
+ ##### and gather the vertices of all the lowest floors of daylight_space
974
+ daylight_space_area = 0
975
+ lowest_floors_vertices = []
976
+ floor_vertices = []
977
+ daylight_space.surfaces.sort.each do |surface|
978
+ if surface.surfaceType == 'Floor'
979
+ floor_vertices = surface.vertices
980
+ if floor_vertices[0].z == lowest_floor_z
981
+ lowest_floors_vertices << floor_vertices
982
+ daylight_space_area = daylight_space_area + surface.netArea
983
+ end
984
+ end
985
+ end
986
+ # puts daylight_space.name.to_s
987
+ # puts number_floor
988
+ # puts lowest_floors_vertices
989
+ # puts daylight_space_area
990
+
991
+ ##### Loop through all lowest floors of daylight_space and find the min and max of x and y among their vertices
992
+ xmin = lowest_floors_vertices[0][0].x
993
+ ymin = lowest_floors_vertices[0][0].y
994
+ xmax = lowest_floors_vertices[0][0].x
995
+ ymax = lowest_floors_vertices[0][0].y
996
+ zmin = lowest_floor_z
997
+ for i in 0..lowest_floors_vertices.count - 1 #this loops through each of the lowers floors of daylight_space
998
+ for j in 0..lowest_floors_vertices[i].count - 1 #this loops through each of vertices of each of the lowers floors of daylight_space
999
+
1000
+ if xmin > lowest_floors_vertices[i][j].x
1001
+ xmin = lowest_floors_vertices[i][j].x
1002
+ end
1003
+ if ymin > lowest_floors_vertices[i][j].y
1004
+ ymin = lowest_floors_vertices[i][j].y
1005
+ end
1006
+ if xmax < lowest_floors_vertices[i][j].x
1007
+ xmax = lowest_floors_vertices[i][j].x
1008
+ end
1009
+ if ymax < lowest_floors_vertices[i][j].y
1010
+ ymax = lowest_floors_vertices[i][j].y
1011
+ end
1012
+ end
1013
+ end
1014
+ # puts daylight_space.name.to_s
1015
+ # puts xmin
1016
+ # puts xmax
1017
+ # puts ymin
1018
+ # puts ymax
1019
+
1020
+ ##### Get the thermal zone of daylight_space (this is used later to assign daylighting sensor)
1021
+ zone = daylight_space.thermalZone
1022
+ if !zone.empty?
1023
+ zone = daylight_space.thermalZone.get
1024
+ ##### Get the floor of the daylight_space
1025
+ floors = []
1026
+ daylight_space.surfaces.sort.each do |surface|
1027
+ if surface.surfaceType == "Floor"
1028
+ floors << surface
1029
+ end
1030
+ end
1031
+
1032
+ ##### Get user's input for daylighting controls illuminance setpoint and number of stepped control steps
1033
+ illuminance_setpoint, number_of_stepped_control_steps = daylighting_controls_settings(illuminance_setpoint: 500.0, number_of_stepped_control_steps: 2)
1034
+
1035
+ ##### Create daylighting sensor control
1036
+ ##### NOTE: NECB2011 has some requirements on the number of sensors in spaces based on the area of the spaces.
1037
+ ##### However, EnergyPlus/OpenStudio allows to put maximum two built-in sensors in each thermal zone rather than in each space.
1038
+ ##### Since a thermal zone may include several spaces which are not next to each other on the same floor, or
1039
+ ##### a thermal zone may include spaces on different floors, a simplified method has been used to create a daylighting sensor.
1040
+ ##### So, in each thermal zone, only one daylighting sensor has been created even if the area of that thermal zone requires more than one daylighting sensor.
1041
+ ##### Also, it has been assumed that a thermal zone includes spaces which are next to each other and are on the same floor.
1042
+ ##### Furthermore, the one daylighting sensor in each thermal zone (where the thermal zone needs daylighting sensor),
1043
+ ##### the sensor has been put at the intersection of the minimum and maximum x and y of the lowest floor of that thermal zones.
1044
+ sensor = OpenStudio::Model::DaylightingControl.new(daylight_space.model)
1045
+ sensor.setName("#{daylight_space.name.to_s} daylighting control")
1046
+ sensor.setSpace(daylight_space)
1047
+ sensor.setIlluminanceSetpoint(illuminance_setpoint)
1048
+ sensor.setLightingControlType('Stepped')
1049
+ sensor.setNumberofSteppedControlSteps(number_of_stepped_control_steps)
1050
+ x_pos = (xmin + xmax) / 2.0
1051
+ y_pos = (ymin + ymax) / 2.0
1052
+ z_pos = zmin + 0.8 #put it 0.8 meter above the floor
1053
+ sensor_vertex = OpenStudio::Point3d.new(x_pos, y_pos, z_pos)
1054
+ sensor.setPosition(sensor_vertex)
1055
+ zone.setPrimaryDaylightingControl(sensor)
1056
+ zone.setFractionofZoneControlledbyPrimaryDaylightingControl(1.0)
1057
+
1058
+ end #if !zone.empty?
1059
+ end #daylight_spaces.each do |daylight_space|
1060
+ end
1061
+
1062
+ #def model_add_daylighting_controls(model)
1063
+
1064
+
1065
+ def model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV') # Note: Values for dcv_type are: 'Occupancy_based_DCV', 'CO2_based_DCV', 'No_DCV', 'NECB_Default'
1066
+ if dcv_type != 'NECB_Default'
1067
+ if dcv_type == 'Occupancy_based_DCV' || dcv_type == 'CO2_based_DCV'
1068
+ #TODO: IMPORTANT: (upon other BTAP tasks) Set a value for the "Outdoor Air Flow per Person" field of the "OS:DesignSpecification:OutdoorAir" object
1069
+ # Note: The "Outdoor Air Flow per Person" field is required for occupancy-based DCV.
1070
+ # Note: The "Outdoor Air Flow per Person" values should be based on ASHRAE 62.1: Article 6.2.2.1.
1071
+ # Note: The "Outdoor Air Flow per Person" should be entered for "ventilation_per_person" in "lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json"
1072
+
1073
+ ##### Define ScheduleTypeLimits for Any_Number_ppm
1074
+ ##### TODO: (upon other BTAP tasks) This function can be added to btap/schedules.rb > module StandardScheduleTypeLimits
1075
+ def self.get_any_number_ppm(model)
1076
+ name = 'Any_Number_ppm'
1077
+ any_number_ppm_schedule_type_limits = model.getScheduleTypeLimitsByName(name)
1078
+ if any_number_ppm_schedule_type_limits.empty?
1079
+ any_number_ppm_schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
1080
+ any_number_ppm_schedule_type_limits.setName(name)
1081
+ any_number_ppm_schedule_type_limits.setNumericType('CONTINUOUS')
1082
+ any_number_ppm_schedule_type_limits.setUnitType('Dimensionless')
1083
+ any_number_ppm_schedule_type_limits.setLowerLimitValue(400.0)
1084
+ any_number_ppm_schedule_type_limits.setUpperLimitValue(1000.0)
1085
+ return any_number_ppm_schedule_type_limits
1086
+ else
1087
+ return any_number_ppm_schedule_type_limits.get
1088
+ end
1089
+ end
1090
+
1091
+ ##### Define indoor CO2 availability schedule (required for CO2-based DCV)
1092
+ ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
1093
+ ##### Note: the defined schedule here is redundant as the schedule says it is always on AND
1094
+ ##### the "ZoneControl:ContaminantController" object says that "If this field is left blank, the schedule has a value of 1 for all time periods".
1095
+ indoor_co2_availability_schedule = OpenStudio::Model::ScheduleCompact.new(model)
1096
+ indoor_co2_availability_schedule.setName('indoor_co2_availability_schedule')
1097
+ indoor_co2_availability_schedule.setScheduleTypeLimits(BTAP::Resources::Schedules::StandardScheduleTypeLimits::get_fraction(model))
1098
+ indoor_co2_availability_schedule.to_ScheduleCompact.get
1099
+ # indoor_co2_availability_schedule.setString(1,"indoor_co2_availability_schedule")
1100
+ indoor_co2_availability_schedule.setString(3, "Through: 12/31")
1101
+ indoor_co2_availability_schedule.setString(4, "For: Weekdays SummerDesignDay")
1102
+ indoor_co2_availability_schedule.setString(5, "Until: 07:00")
1103
+ indoor_co2_availability_schedule.setString(6, "0.0")
1104
+ indoor_co2_availability_schedule.setString(7, "Until: 22:00")
1105
+ indoor_co2_availability_schedule.setString(8, "1.0")
1106
+ indoor_co2_availability_schedule.setString(9, "Until: 24:00")
1107
+ indoor_co2_availability_schedule.setString(10, "0.0")
1108
+ indoor_co2_availability_schedule.setString(11, "For: Saturday WinterDesignDay")
1109
+ indoor_co2_availability_schedule.setString(12, "Until: 07:00")
1110
+ indoor_co2_availability_schedule.setString(13, "0.0")
1111
+ indoor_co2_availability_schedule.setString(14, "Until: 18:00")
1112
+ indoor_co2_availability_schedule.setString(15, "1.0")
1113
+ indoor_co2_availability_schedule.setString(16, "Until: 24:00")
1114
+ indoor_co2_availability_schedule.setString(17, "0.0")
1115
+ indoor_co2_availability_schedule.setString(18, "For: AllOtherDays")
1116
+ indoor_co2_availability_schedule.setString(19, "Until: 24:00")
1117
+ indoor_co2_availability_schedule.setString(20, "0.0")
1118
+
1119
+ ##### Define indoor CO2 setpoint schedule (required for CO2-based DCV)
1120
+ ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
1121
+ indoor_co2_setpoint_schedule = OpenStudio::Model::ScheduleCompact.new(model)
1122
+ indoor_co2_setpoint_schedule.setName('indoor_co2_setpoint_schedule')
1123
+ indoor_co2_setpoint_schedule.setScheduleTypeLimits(get_any_number_ppm(model))
1124
+ indoor_co2_setpoint_schedule.to_ScheduleCompact.get
1125
+ indoor_co2_setpoint_schedule.setString(3, "Through: 12/31")
1126
+ indoor_co2_setpoint_schedule.setString(4, "For: AllDays")
1127
+ indoor_co2_setpoint_schedule.setString(5, "Until: 24:00")
1128
+ indoor_co2_setpoint_schedule.setString(6, "1000.0")
1129
+ # indoor_co2_setpoint_schedule.setToConstantValue(1000.0) #1000 ppm
1130
+
1131
+
1132
+ ##### Define outdoor CO2 schedule (required for CO2-based DCV
1133
+ ##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
1134
+ outdoor_co2_schedule = OpenStudio::Model::ScheduleCompact.new(model)
1135
+ outdoor_co2_schedule.setName('outdoor_co2_schedule')
1136
+ outdoor_co2_schedule.setScheduleTypeLimits(get_any_number_ppm(model))
1137
+ outdoor_co2_schedule.to_ScheduleCompact.get
1138
+ outdoor_co2_schedule.setString(3, "Through: 12/31")
1139
+ outdoor_co2_schedule.setString(4, "For: AllDays")
1140
+ outdoor_co2_schedule.setString(5, "Until: 24:00")
1141
+ outdoor_co2_schedule.setString(6, "400.0")
1142
+ # outdoor_co2_schedule.setToConstantValue(400.0) #400 ppm
1143
+
1144
+ ##### Define ZoneAirContaminantBalance (required for CO2-based DCV)
1145
+ zone_air_contaminant_balance = model.getZoneAirContaminantBalance()
1146
+ zone_air_contaminant_balance.setCarbonDioxideConcentration(true)
1147
+ zone_air_contaminant_balance.setOutdoorCarbonDioxideSchedule(outdoor_co2_schedule)
1148
+
1149
+ ##### Set CO2 controller in each space (required for CO2-based DCV)
1150
+ model.getSpaces.sort.each do |space|
1151
+ # puts space.name.to_s
1152
+ zone = space.thermalZone
1153
+ if !zone.empty?
1154
+ zone = space.thermalZone.get
1155
+ end
1156
+ zone_control_co2 = OpenStudio::Model::ZoneControlContaminantController.new(zone.model)
1157
+ zone_control_co2.setName("#{space.name.to_s} Zone Control Contaminant Controller")
1158
+ zone_control_co2.setCarbonDioxideControlAvailabilitySchedule(indoor_co2_availability_schedule)
1159
+ zone_control_co2.setCarbonDioxideSetpointSchedule(indoor_co2_setpoint_schedule)
1160
+ zone.setZoneControlContaminantController(zone_control_co2)
1161
+ end
1162
+
1163
+ end #if dcv_type == "Occupancy_based_DCV" || dcv_type == "CO2_based_DCV"
1164
+
1165
+ ##### Loop through AirLoopHVACs
1166
+ model.getAirLoopHVACs.sort.each do |air_loop|
1167
+ ##### Loop through AirLoopHVAC's supply nodes to:
1168
+ ##### (1) Find its AirLoopHVAC:OutdoorAirSystem using the supply node;
1169
+ ##### (2) Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem;
1170
+ ##### (3) Get "Controller Mechanical Ventilation" from Controller:OutdoorAir.
1171
+ air_loop.supplyComponents.sort.each do |supply_component|
1172
+ ##### Find AirLoopHVAC:OutdoorAirSystem of AirLoopHVAC using the supply node.
1173
+ hvac_component = supply_component.to_AirLoopHVACOutdoorAirSystem
1174
+
1175
+ if !hvac_component.empty?
1176
+ ##### Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem.
1177
+ hvac_component = hvac_component.get
1178
+ controller_oa = hvac_component.getControllerOutdoorAir
1179
+
1180
+ ##### Get "Controller Mechanical Ventilation" from Controller:OutdoorAir.
1181
+ controller_mv = controller_oa.controllerMechanicalVentilation
1182
+
1183
+ ##### Set "Demand Controlled Ventilation" to "Yes" or "No" in Controller:MechanicalVentilation depending on dcv_type.
1184
+ if (dcv_type == 'CO2_based_DCV') || (dcv_type == 'Occupancy_based_DCV') #Occupancy
1185
+ controller_mv.setDemandControlledVentilation(true)
1186
+ ##### Set the "System Outdoor Air Method" field based on dcv_type in the Controller:MechanicalVentilation object
1187
+ if dcv_type == 'CO2_based_DCV'
1188
+ controller_mv.setSystemOutdoorAirMethod('IndoorAirQualityProcedure')
1189
+ else #dcv_type == 'Occupancy_based_DCV'
1190
+ controller_mv.setSystemOutdoorAirMethod('ZoneSum')
1191
+ end
1192
+ elsif dcv_type == 'No_DCV'
1193
+ controller_mv.setDemandControlledVentilation(false)
1194
+ end
1195
+ # puts controller_mv
1196
+
1197
+ end #if !hvac_component.empty?
1198
+
1199
+ end #air_loop.supplyComponents.each do |supply_component|
1200
+ end #model.getAirLoopHVACs.each do |air_loop|
1201
+ end #if dcv_type != 'NECB_Default'
1202
+ end
1203
+
1204
+ #def model_enable_demand_controlled_ventilation
1205
+
1206
+
1207
+ def set_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:)
1208
+ # puts "#{space_type.name.to_s} - 'space_height' - #{space_height.to_s}"
1209
+ occ_sens_lpd_frac = 1.0
1210
+ # NECB2011 space types that require a reduction in the LPD to account for
1211
+ # the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2))
1212
+ reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation',
1213
+ 'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D',
1214
+ 'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I',
1215
+ 'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B',
1216
+ 'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G',
1217
+ 'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting']
1218
+ if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get)
1219
+ # Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed"
1220
+ # LPD should only be reduced if their space areas are less than specific area values.
1221
+ # This is checked in a space loop after this function in the calling routine.
1222
+ occ_sens_lpd_frac = 0.9
1223
+ end
1224
+
1225
+ # ##### Since Atrium's LPD for LED lighting depends on atrium's height, the height of the atrium (if applicable) should be found.
1226
+ standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
1227
+ if standards_space_type.include? 'Atrium' #TODO: Note that since none of the archetypes has Atrium, this was tested for 'Dining'. #Atrium
1228
+ puts "#{standards_space_type} - has atrium" #space_type.name.to_s
1229
+ # Get the max height for the spacetype.
1230
+ max_space_height_for_spacetype = get_max_space_height_for_space_type(space_type: space_type)
1231
+ if max_space_height_for_spacetype < 12.0 #TODO: Note that since none of the archetypes has Atrium, this was tested for 'Dining' with the threshold of 5.0 m for space_height.
1232
+ # TODO: Regarding the below equations, identify which version of ASHRAE 90.1 was used in NECB2015.
1233
+ atrium_lpd_eq_smaller_12_intercept = 0
1234
+ atrium_lpd_eq_smaller_12_slope = 1.06
1235
+ atrium_lpd_eq_larger_12_intercept = 4.3
1236
+ atrium_lpd_eq_larger_12_slope = 1.06
1237
+ lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_smaller_12_intercept + atrium_lpd_eq_smaller_12_slope * 12.0) * 0.092903 # W/ft2 TODO: Note that for NECB2011, a constant LPD is used for atrium based on NECB2015's equations. NECB2011's threshold for height is 13.0 m.
1238
+ elsif max_space_height_for_spacetype >= 12.0 && max_space_height_for_spacetype < 13.0
1239
+ lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 12.5) * 0.092903 # W/ft2
1240
+ else #i.e. space_height >= 13.0
1241
+ lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 13.0) * 0.092903 # W/ft2
1242
+ end
1243
+ puts "#{standards_space_type} - has lighting_per_area_led_lighting_atrium - #{lighting_per_area_led_lighting_atrium}"
1244
+ lighting_per_area_led_lighting = lighting_per_area_led_lighting_atrium
1245
+ end
1246
+ lighting_per_area_led_lighting = lighting_per_area_led_lighting * lights_scale
1247
+ definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area_led_lighting.to_f * occ_sens_lpd_frac, 'W/ft^2', 'W/m^2').get)
1248
+
1249
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set LPD to #{lighting_per_area_led_lighting} W/ft^2.")
1250
+ end
1251
+
1252
+ # Adds the loads and associated schedules for each space type
1253
+ # as defined in the OpenStudio_Standards_space_types.json file.
1254
+ # This includes lights, plug loads, occupants, ventilation rate requirements,
1255
+ # infiltration, gas equipment (for kitchens, etc.) and typical schedules for each.
1256
+ # Some loads are governed by the standard, others are typical values
1257
+ # pulled from sources such as the DOE Reference and DOE Prototype Buildings.
1258
+ #
1259
+ # @return [Bool] returns true if successful, false if not
1260
+ def model_add_loads(model, lights_type, lights_scale)
1261
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying space types (loads)')
1262
+
1263
+ # Loop through all the space types currently in the model,
1264
+ # which are placeholders, and give them appropriate loads and schedules
1265
+ model.getSpaceTypes.sort.each do |space_type|
1266
+ # Rendering color
1267
+ space_type_apply_rendering_color(space_type)
1268
+
1269
+ # Loads
1270
+ space_type_apply_internal_loads(space_type: space_type, lights_type: lights_type, lights_scale: lights_scale)
1271
+
1272
+ # Schedules
1273
+ space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true)
1274
+ end
1275
+
1276
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying space types (loads)')
1277
+
1278
+ return true
1279
+ end
1280
+
1281
+ ##### This method is needed to the space_type_apply_internal_loads space needed for the calculation of atriums' LPD when LED lighting is used in atriums. ***END***
1282
+ def get_max_space_height_for_space_type(space_type:)
1283
+ # initialize return value to zero.
1284
+ max_space_height_for_space_type = 0.0
1285
+ # Interate through all spaces in model.. not just ones that have space type defined.. Is this right sara?
1286
+ space_type.spaces.sort.each do |space|
1287
+ # Get only the wall type surfaces and iterate throught them.
1288
+ space.surfaces.sort.select(&:surfaceType == 'Wall').each do |wall_surface|
1289
+ # Find the vertex with the max z value.
1290
+ vertex_with_max_height = wall_surface.vertices.max_by(&:z)
1291
+ # replace max if this surface has something bigger.
1292
+ max_space_height_for_space_type = vertex_with_max_height.z if vertex_with_max_height.z > max_space_height_for_space_type
1293
+ end
1294
+ end
1295
+ return max_space_height_for_space_type
1296
+ end
1297
+
1298
+ ##### The below method is for the 'model_add_daylighting_controls' method
1299
+ def get_parameters_sidelighting(daylight_space:, floor_surface:, floor_vertices:, floor_area:, primary_sidelighted_area:, area_weighted_vt_handle:, window_area_sum:)
1300
+
1301
+ daylight_space.surfaces.sort.each do |surface|
1302
+
1303
+ ##### Get the vertices of each exterior wall of the daylight_space on the floor
1304
+ ##### (these vertices will be used to calculate daylight_space depth in relation to the exterior wall, and
1305
+ ##### the distance of the window to vertical walls on each side of the window)
1306
+ if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
1307
+ wall_vertices_x_on_floor = []
1308
+ wall_vertices_y_on_floor = []
1309
+ surface_z_min = [surface.vertices[0].z, surface.vertices[1].z, surface.vertices[2].z, surface.vertices[3].z].min
1310
+ surface.vertices.each do |vertex|
1311
+ # puts vertex.z
1312
+ if vertex.z == surface_z_min && surface_z_min == floor_vertices[0][0].z
1313
+ wall_vertices_x_on_floor << vertex.x
1314
+ wall_vertices_y_on_floor << vertex.y
1315
+ end
1316
+ end
1317
+ end
1318
+
1319
+ if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" && surface_z_min == floor_vertices[0][0].z
1320
+
1321
+ ##### Calculate the daylight_space depth in relation to the considered exterior wall.
1322
+ ##### To calculate daylight_space depth, first get the floor vertices which are on the opposite side of the considered exterior wall.
1323
+ floor_vertices_x_wall_opposite = []
1324
+ floor_vertices_y_wall_opposite = []
1325
+ floor_vertices[0].each do |floor_vertex|
1326
+ if (floor_vertex.x != wall_vertices_x_on_floor[0] && floor_vertex.y != wall_vertices_y_on_floor[0]) || (floor_vertex.x != wall_vertices_x_on_floor[1] && floor_vertex.y != wall_vertices_y_on_floor[1])
1327
+ floor_vertices_x_wall_opposite << floor_vertex.x
1328
+ floor_vertices_y_wall_opposite << floor_vertex.y
1329
+ end
1330
+ end
1331
+
1332
+ ##### To calculate daylight_space depth, second calculate floor length on both sides: (1) exterior wall side, (2) on the opposite side of the exterior wall
1333
+ floor_width_wall_side = Math.sqrt((wall_vertices_x_on_floor[0] - wall_vertices_x_on_floor[1]) ** 2 + (wall_vertices_y_on_floor[0] - wall_vertices_y_on_floor[1]) ** 2)
1334
+ floor_width_wall_opposite = Math.sqrt((floor_vertices_x_wall_opposite[0] - floor_vertices_x_wall_opposite[1]) ** 2 + (floor_vertices_y_wall_opposite[0] - floor_vertices_y_wall_opposite[1]) ** 2)
1335
+
1336
+ ##### Now, daylight_space depth can be calculated using the floor area and two lengths of the floor (note that these two lengths are in parallel to each other).
1337
+ daylight_space_depth = 2 * floor_area / (floor_width_wall_side + floor_width_wall_opposite)
1338
+
1339
+ ##### Loop through the windows (including fixed and operable ones) to get window specification (width, height, area, visible transmittance (VT)), and area-weighted VT
1340
+ surface.subSurfaces.sort.each do |subsurface|
1341
+ # puts subsurface.name.to_s
1342
+ if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
1343
+ window_vt = subsurface.visibleTransmittance
1344
+ window_vt = window_vt.get
1345
+ window_area = subsurface.netArea
1346
+ window_area_sum += window_area
1347
+ area_weighted_vt_handle += window_area * window_vt
1348
+ window_vertices = subsurface.vertices
1349
+ if window_vertices[0].z.round(2) == window_vertices[1].z.round(2)
1350
+ window_width = Math.sqrt((window_vertices[0].x - window_vertices[1].x) ** 2.0 + (window_vertices[0].y - window_vertices[1].y) ** 2.0)
1351
+ else
1352
+ window_width = Math.sqrt((window_vertices[1].x - window_vertices[2].x) ** 2.0 + (window_vertices[1].y - window_vertices[2].y) ** 2.0)
1353
+ end
1354
+ window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2)
1355
+ primary_sidelighted_area_depth = [window_head_height, daylight_space_depth].min #as per NECB2011: 4.2.2.9.
1356
+
1357
+ ##### Calculate the distance of the window to vertical walls on each side of the window (this is used to determine the sidelighted area's width).
1358
+ window_vertices_on_floor = []
1359
+ window_vertices.each do |vertex|
1360
+ window_vertices_on_floor << floor_surface.plane.project(vertex)
1361
+ end
1362
+ window_wall_distance_side1 = [Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[0].x) ** 2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[0].y) ** 2.0),
1363
+ Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[2].x) ** 2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[2].y) ** 2.0),
1364
+ 0.6].min # 0.6 m as per NECB2011: 4.2.2.9.
1365
+ window_wall_distance_side2 = [Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[0].x) ** 2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[0].y) ** 2.0),
1366
+ Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[2].x) ** 2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[2].y) ** 2.0),
1367
+ 0.6].min # 0.6 m as per NECB2011: 4.2.2.9.
1368
+ primary_sidelighted_area_width = window_wall_distance_side1 + window_width + window_wall_distance_side2
1369
+ primary_sidelighted_area = primary_sidelighted_area + primary_sidelighted_area_depth * primary_sidelighted_area_width
1370
+ end #if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
1371
+ end #surface.subSurfaces.each do |subsurface|
1372
+ end #if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" && surface_z_min == floor_vertices[0][0].z
1373
+ end #daylight_space.surfaces.each do |surface|
1374
+
1375
+ return primary_sidelighted_area, area_weighted_vt_handle, window_area_sum
1376
+ end
1377
+
1378
+ ##### The below method is for the 'model_add_daylighting_controls' method
1379
+ def get_parameters_skylight(daylight_space:, skylight_area_weighted_vt_handle:, skylight_area_sum:, daylighted_under_skylight_area:)
1380
+
1381
+ daylight_space.surfaces.sort.each do |surface|
1382
+ ##### Get roof vertices
1383
+ if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "RoofCeiling"
1384
+ roof_vertices = surface.vertices
1385
+ end
1386
+
1387
+ ##### Loop through each subsurafce to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space
1388
+ surface.subSurfaces.sort.each do |subsurface|
1389
+ if subsurface.subSurfaceType == "Skylight"
1390
+ skylight_vt = subsurface.visibleTransmittance
1391
+ skylight_vt = skylight_vt.get
1392
+ skylight_area = subsurface.netArea
1393
+ skylight_area_sum += skylight_area
1394
+ skylight_area_weighted_vt_handle += skylight_area * skylight_vt
1395
+
1396
+ ##### Get skylight vertices
1397
+ skylight_vertices = subsurface.vertices
1398
+
1399
+ ##### Calculate skylight width and height
1400
+ skylight_width = Math.sqrt((skylight_vertices[0].x - skylight_vertices[1].x) ** 2.0 + (skylight_vertices[0].y - skylight_vertices[1].y) ** 2.0)
1401
+ skylight_length = Math.sqrt((skylight_vertices[0].x - skylight_vertices[3].x) ** 2.0 + (skylight_vertices[0].y - skylight_vertices[3].y) ** 2.0)
1402
+
1403
+ ##### Get ceiling height assuming the skylight is flush with the ceiling
1404
+ ceiling_height = skylight_vertices[0].z
1405
+
1406
+ ##### Calculate roof lengths
1407
+ ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
1408
+ roof_length_0 = Math.sqrt((roof_vertices[0].x - roof_vertices[1].x) ** 2.0 + (roof_vertices[0].y - roof_vertices[1].y) ** 2.0)
1409
+ roof_length_1 = Math.sqrt((roof_vertices[1].x - roof_vertices[2].x) ** 2.0 + (roof_vertices[1].y - roof_vertices[2].y) ** 2.0)
1410
+ roof_length_2 = Math.sqrt((roof_vertices[2].x - roof_vertices[3].x) ** 2.0 + (roof_vertices[2].y - roof_vertices[3].y) ** 2.0)
1411
+ roof_length_3 = Math.sqrt((roof_vertices[3].x - roof_vertices[0].x) ** 2.0 + (roof_vertices[3].y - roof_vertices[0].y) ** 2.0)
1412
+
1413
+ ##### Find the skylight point that is the closest one to roof_vertex_0
1414
+ ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
1415
+ roof_vertex_0_skylight_vertex_0 = Math.sqrt((roof_vertices[0].x - skylight_vertices[0].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[0].y) ** 2.0)
1416
+ roof_vertex_0_skylight_vertex_1 = Math.sqrt((roof_vertices[0].x - skylight_vertices[1].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[1].y) ** 2.0)
1417
+ roof_vertex_0_skylight_vertex_2 = Math.sqrt((roof_vertices[0].x - skylight_vertices[2].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[2].y) ** 2.0)
1418
+ roof_vertex_0_skylight_vertex_3 = Math.sqrt((roof_vertices[0].x - skylight_vertices[3].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[3].y) ** 2.0)
1419
+ roof_vertex_0_closest_distance = [roof_vertex_0_skylight_vertex_0, roof_vertex_0_skylight_vertex_1, roof_vertex_0_skylight_vertex_2, roof_vertex_0_skylight_vertex_3].min
1420
+ if roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_0
1421
+ roof_vertex_0_closest_point = skylight_vertices[0]
1422
+ elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_1
1423
+ roof_vertex_0_closest_point = skylight_vertices[1]
1424
+ elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_2
1425
+ roof_vertex_0_closest_point = skylight_vertices[2]
1426
+ elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_3
1427
+ roof_vertex_0_closest_point = skylight_vertices[3]
1428
+ end
1429
+
1430
+ ##### Find the skylight point that is the closest one to roof_vertex_2
1431
+ ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
1432
+ roof_vertex_2_skylight_vertex_0 = Math.sqrt((roof_vertices[2].x - skylight_vertices[0].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[0].y) ** 2.0)
1433
+ roof_vertex_2_skylight_vertex_1 = Math.sqrt((roof_vertices[2].x - skylight_vertices[1].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[1].y) ** 2.0)
1434
+ roof_vertex_2_skylight_vertex_2 = Math.sqrt((roof_vertices[2].x - skylight_vertices[2].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[2].y) ** 2.0)
1435
+ roof_vertex_2_skylight_vertex_3 = Math.sqrt((roof_vertices[2].x - skylight_vertices[3].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[3].y) ** 2.0)
1436
+ roof_vertex_2_closest_distance = [roof_vertex_2_skylight_vertex_0, roof_vertex_2_skylight_vertex_1, roof_vertex_2_skylight_vertex_2, roof_vertex_2_skylight_vertex_3].min
1437
+ if roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_0
1438
+ roof_vertex_2_closest_point = skylight_vertices[0]
1439
+ elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_1
1440
+ roof_vertex_2_closest_point = skylight_vertices[1]
1441
+ elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_2
1442
+ roof_vertex_2_closest_point = skylight_vertices[2]
1443
+ elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_3
1444
+ roof_vertex_2_closest_point = skylight_vertices[3]
1445
+ end
1446
+
1447
+ ##### Calculate the vertical distance from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2
1448
+ ##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
1449
+ ##### For the calculation of each vertical distance: (1) first the area of the triangle is calculated knowing the cooridantes of its three corners;
1450
+ ##### (2) the vertical distance (i.e. triangle height) is calculated knowing the triangle area and the associated roof length.
1451
+ rv_0_triangle_0_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[1].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) -
1452
+ ((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[1].y))).abs
1453
+ rv_0_distance_0 = (2.0 * rv_0_triangle_0_area) / roof_length_0
1454
+ rv_0_triangle_3_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[3].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) -
1455
+ ((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[3].y))).abs
1456
+ rv_0_distance_3 = (2.0 * rv_0_triangle_3_area) / roof_length_3
1457
+
1458
+ rv_2_triangle_1_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[1].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) -
1459
+ ((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[1].y))).abs
1460
+ rv_2_distance_1 = (2.0 * rv_2_triangle_1_area) / roof_length_1
1461
+ rv_2_triangle_2_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[3].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) -
1462
+ ((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[3].y))).abs
1463
+ rv_2_distance_2 = (2.0 * rv_2_triangle_2_area) / roof_length_2
1464
+
1465
+ ##### Set the vertical distances from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2
1466
+ distance_1 = rv_0_distance_0
1467
+ distance_2 = rv_0_distance_3
1468
+ distance_3 = rv_2_distance_1
1469
+ distance_4 = rv_2_distance_2
1470
+
1471
+ ##### Calculate the width and length of the daylighted area under the skylight as per NECB2011: 4.2.2.5.
1472
+ ##### Note: In the below loops, if any exterior walls has window(s), the width and length of the daylighted area under the skylight are re-calculated as per NECB2011: 4.2.2.5.
1473
+ daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4].min
1474
+ daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3].min
1475
+
1476
+ ##### As noted above, the width and length of the daylighted area under the skylight are re-calculated (as per NECB2011: 4.2.2.5.), if any exterior walls has window(s).
1477
+ ##### To this end, the window_head_height should be calculated, as below:
1478
+ daylight_space.surfaces.sort.each do |surface|
1479
+ if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
1480
+ wall_vertices_on_floor_x = []
1481
+ wall_vertices_on_floor_y = []
1482
+ wall_vertices = surface.vertices
1483
+ if wall_vertices[0].z == wall_vertices[1].z
1484
+ wall_vertices_on_floor_x << wall_vertices[0].x
1485
+ wall_vertices_on_floor_x << wall_vertices[1].x
1486
+ wall_vertices_on_floor_y << wall_vertices[0].y
1487
+ wall_vertices_on_floor_y << wall_vertices[1].y
1488
+ elsif wall_vertices[0].z == wall_vertices[3].z
1489
+ wall_vertices_on_floor_x << wall_vertices[0].x
1490
+ wall_vertices_on_floor_x << wall_vertices[3].x
1491
+ wall_vertices_on_floor_y << wall_vertices[0].y
1492
+ wall_vertices_on_floor_y << wall_vertices[3].y
1493
+ end
1494
+ window_vertices = subsurface.vertices
1495
+ window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2)
1496
+
1497
+ ##### Calculate the exterior wall length (on the floor)
1498
+ exterior_wall_length = Math.sqrt((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) ** 2.0 + (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]) ** 2.0)
1499
+
1500
+ ##### Calculate the vertical distance of skylight_vertices[0] projection onto the roof/floor to the exterior wall
1501
+ skylight_vertex_0_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[0].y)) -
1502
+ ((wall_vertices_on_floor_x[0] - skylight_vertices[0].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
1503
+ skylight_vertex_0_distance = (2.0 * skylight_vertex_0_triangle_area) / exterior_wall_length
1504
+
1505
+ ##### Calculate the vertical distance of skylight_vertices[1] projection onto the roof/floor to the exterior wall
1506
+ skylight_vertex_1_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[1].y)) -
1507
+ ((wall_vertices_on_floor_x[0] - skylight_vertices[1].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
1508
+ skylight_vertex_1_distance = (2.0 * skylight_vertex_1_triangle_area) / exterior_wall_length
1509
+
1510
+ ##### Calculate the vertical distance of skylight_vertices[3] projection onto the roof/floor to the exterior wall
1511
+ skylight_vertex_3_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[3].y)) -
1512
+ ((wall_vertices_on_floor_x[0] - skylight_vertices[3].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
1513
+ skylight_vertex_3_distance = (2.0 * skylight_vertex_3_triangle_area) / exterior_wall_length
1514
+
1515
+ ##### Loop through the subsurfaces that has exterior windows to re-calculate the width and length of the daylighted area under the skylight
1516
+ surface.subSurfaces.sort.each do |subsurface|
1517
+ if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
1518
+
1519
+ if skylight_vertex_0_distance == skylight_vertex_1_distance #skylight_01 is in parellel to the exterior wall
1520
+ if skylight_vertex_0_distance.round(2) == distance_2.round(2)
1521
+ daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2, distance_2 - window_head_height].min + [0.7 * ceiling_height, distance_3].min
1522
+ elsif skylight_vertex_0_distance.round(2) == distance_3.round(2)
1523
+ daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3, distance_3 - window_head_height].min
1524
+ end
1525
+ elsif skylight_vertex_0_distance == skylight_vertex_3_distance #skylight_03 is in parellel to the exterior wall
1526
+ if skylight_vertex_0_distance.round(2) == distance_1.round(2)
1527
+ daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1, distance_1 - window_head_height].min + [0.7 * ceiling_height, distance_4].min
1528
+ elsif skylight_vertex_0_distance.round(2) == distance_4.round(2)
1529
+ daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4, distance_4 - window_head_height].min
1530
+ end
1531
+ end #if skylight_vertex_0_distance == skylight_vertex_1_distance
1532
+
1533
+ daylighted_under_skylight_area += daylighted_under_skylight_length * daylighted_under_skylight_width
1534
+
1535
+ end #if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
1536
+ end #surface.subSurfaces.each do |subsurface|
1537
+ end #if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
1538
+ end #daylight_space.surfaces.each do |surface|
1539
+
1540
+ end #if subsurface.subSurfaceType == "Skylight"
1541
+ end #surface.subSurfaces.each do |subsurface|
1542
+ end #daylight_space.surfaces.each do |surface|
1543
+
1544
+ return daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum
1545
+ end
1546
+
521
1547
  end