openstudio-standards 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/OpenStudio_Standards.xlsx +0 -0
  3. data/data/standards/OpenStudio_Standards_boilers.json +62 -4
  4. data/data/standards/OpenStudio_Standards_chillers.json +778 -68
  5. data/data/standards/OpenStudio_Standards_construction_sets.json +52 -93
  6. data/data/standards/OpenStudio_Standards_curve_biquadratics.json +36 -36
  7. data/data/standards/OpenStudio_Standards_curve_quadratics.json +3 -3
  8. data/data/standards/OpenStudio_Standards_heat_pumps.json +840 -0
  9. data/data/standards/OpenStudio_Standards_heat_pumps_heating.json +352 -0
  10. data/data/standards/OpenStudio_Standards_heat_rejection.json +48 -0
  11. data/data/standards/OpenStudio_Standards_motors.json +270 -0
  12. data/data/standards/OpenStudio_Standards_space_types.json +10390 -2824
  13. data/data/standards/OpenStudio_Standards_unitary_acs.json +794 -18
  14. data/data/weather/USA_CO_Boulder-Broomfield-Jefferson.County.AP.724699_TMY3.ddy +538 -0
  15. data/data/weather/USA_CO_Boulder-Broomfield-Jefferson.County.AP.724699_TMY3.epw +8768 -0
  16. data/data/weather/USA_CO_Boulder-Broomfield-Jefferson.County.AP.724699_TMY3.stat +493 -0
  17. data/data/weather/USA_CO_Denver.Intl.AP.725650_TMY3.ddy +536 -0
  18. data/data/weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw +8768 -0
  19. data/data/weather/USA_CO_Denver.Intl.AP.725650_TMY3.stat +554 -0
  20. data/data/weather/USA_CO_Fort.Collins.AWOS.724769_TMY3.ddy +536 -0
  21. data/data/weather/USA_CO_Fort.Collins.AWOS.724769_TMY3.epw +8768 -0
  22. data/data/weather/USA_CO_Fort.Collins.AWOS.724769_TMY3.stat +554 -0
  23. data/data/weather/envelope_info.csv +6 -0
  24. data/lib/openstudio-standards.rb +10 -11
  25. data/lib/openstudio-standards/btap/compliance.rb +251 -969
  26. data/lib/openstudio-standards/btap/envelope.rb +1 -1
  27. data/lib/openstudio-standards/btap/fileio.rb +37 -5
  28. data/lib/openstudio-standards/btap/geometry.rb +27 -17
  29. data/lib/openstudio-standards/btap/hvac.rb +80 -27
  30. data/lib/openstudio-standards/hvac_sizing/{HVACSizing.CoilHeatingDXMultiSpeed.rb → Siz.CoilHeatingDXMultiSpeed.rb} +0 -0
  31. data/lib/openstudio-standards/hvac_sizing/Siz.ControllerOutdoorAir.rb +30 -4
  32. data/lib/openstudio-standards/hvac_sizing/Siz.CoolingTowerTwoSpeed.rb +61 -5
  33. data/lib/openstudio-standards/hvac_sizing/Siz.CoolingTowerVariableSpeed.rb +37 -7
  34. data/lib/openstudio-standards/hvac_sizing/Siz.DistrictCooling.rb +27 -0
  35. data/lib/openstudio-standards/hvac_sizing/Siz.DistrictHeating.rb +27 -0
  36. data/lib/openstudio-standards/hvac_sizing/Siz.HeaderedPumpsConstantSpeed.rb +55 -0
  37. data/lib/openstudio-standards/hvac_sizing/Siz.HeaderedPumpsVariableSpeed.rb +55 -0
  38. data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +51 -9
  39. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +99 -17
  40. data/lib/openstudio-standards/hvac_sizing/Siz.PumpConstantSpeed.rb +1 -1
  41. data/lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb +29 -6
  42. data/lib/openstudio-standards/hvac_sizing/Siz.WaterHeaterMixed.rb +16 -0
  43. data/lib/openstudio-standards/prototypes/Prototype.AirTerminalSingleDuctVAVReheat.rb +43 -48
  44. data/lib/openstudio-standards/prototypes/Prototype.ControllerWaterCoil.rb +5 -9
  45. data/lib/openstudio-standards/prototypes/Prototype.Fan.rb +68 -0
  46. data/lib/openstudio-standards/prototypes/Prototype.FanConstantVolume.rb +39 -43
  47. data/lib/openstudio-standards/prototypes/Prototype.FanOnOff.rb +49 -51
  48. data/lib/openstudio-standards/prototypes/Prototype.FanVariableVolume.rb +55 -61
  49. data/lib/openstudio-standards/prototypes/Prototype.FanZoneExhaust.rb +8 -10
  50. data/lib/openstudio-standards/prototypes/Prototype.HeatExchangerAirToAirSensibleAndLatent.rb +15 -20
  51. data/lib/openstudio-standards/prototypes/Prototype.Model.hvac.rb +330 -322
  52. data/lib/openstudio-standards/prototypes/Prototype.Model.rb +501 -446
  53. data/lib/openstudio-standards/prototypes/Prototype.Model.swh.rb +221 -230
  54. data/lib/openstudio-standards/prototypes/Prototype.add_objects.rb +0 -2
  55. data/lib/openstudio-standards/prototypes/Prototype.full_service_restaurant.rb +130 -137
  56. data/lib/openstudio-standards/prototypes/Prototype.high_rise_apartment.rb +374 -291
  57. data/lib/openstudio-standards/prototypes/Prototype.hospital.rb +146 -193
  58. data/lib/openstudio-standards/prototypes/Prototype.hvac_systems.rb +1315 -1113
  59. data/lib/openstudio-standards/prototypes/Prototype.large_hotel.rb +65 -88
  60. data/lib/openstudio-standards/prototypes/Prototype.large_office.rb +101 -156
  61. data/lib/openstudio-standards/prototypes/Prototype.medium_office.rb +46 -96
  62. data/lib/openstudio-standards/prototypes/Prototype.mid_rise_apartment.rb +113 -123
  63. data/lib/openstudio-standards/prototypes/Prototype.outpatient.rb +356 -345
  64. data/lib/openstudio-standards/prototypes/Prototype.primary_school.rb +48 -103
  65. data/lib/openstudio-standards/prototypes/Prototype.quick_service_restaurant.rb +115 -123
  66. data/lib/openstudio-standards/prototypes/Prototype.retail_standalone.rb +30 -39
  67. data/lib/openstudio-standards/prototypes/Prototype.retail_stripmall.rb +32 -45
  68. data/lib/openstudio-standards/prototypes/Prototype.secondary_school.rb +98 -258
  69. data/lib/openstudio-standards/prototypes/Prototype.small_hotel.rb +429 -474
  70. data/lib/openstudio-standards/prototypes/Prototype.small_office.rb +28 -36
  71. data/lib/openstudio-standards/prototypes/Prototype.strip_model.rb +7 -7
  72. data/lib/openstudio-standards/prototypes/Prototype.utilities.rb +172 -146
  73. data/lib/openstudio-standards/prototypes/Prototype.warehouse.rb +46 -53
  74. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +885 -707
  75. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctParallelPIUReheat.rb +48 -57
  76. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctVAVReheat.rb +24 -31
  77. data/lib/openstudio-standards/standards/Standards.BoilerHotWater.rb +80 -93
  78. data/lib/openstudio-standards/standards/Standards.BuildingStory.rb +69 -0
  79. data/lib/openstudio-standards/standards/Standards.ChillerElectricEIR.rb +60 -72
  80. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXMultiSpeed.rb +104 -108
  81. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +190 -198
  82. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXTwoSpeed.rb +134 -146
  83. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXMultiSpeed.rb +56 -60
  84. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +151 -161
  85. data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +30 -34
  86. data/lib/openstudio-standards/standards/Standards.Construction.rb +116 -132
  87. data/lib/openstudio-standards/standards/Standards.CoolingTower.rb +138 -0
  88. data/lib/openstudio-standards/standards/Standards.CoolingTowerSingleSpeed.rb +11 -0
  89. data/lib/openstudio-standards/standards/Standards.CoolingTowerTwoSpeed.rb +11 -0
  90. data/lib/openstudio-standards/standards/Standards.CoolingTowerVariableSpeed.rb +16 -0
  91. data/lib/openstudio-standards/standards/Standards.Fan.rb +190 -236
  92. data/lib/openstudio-standards/standards/Standards.FanConstantVolume.rb +0 -2
  93. data/lib/openstudio-standards/standards/Standards.FanOnOff.rb +0 -2
  94. data/lib/openstudio-standards/standards/Standards.FanVariableVolume.rb +168 -14
  95. data/lib/openstudio-standards/standards/Standards.FanZoneExhaust.rb +0 -2
  96. data/lib/openstudio-standards/standards/Standards.HeaderedPumpsConstantSpeed.rb +33 -0
  97. data/lib/openstudio-standards/standards/Standards.HeaderedPumpsVariableSpeed.rb +83 -0
  98. data/lib/openstudio-standards/standards/Standards.HeatExchangerSensLat.rb +22 -0
  99. data/lib/openstudio-standards/standards/Standards.Model.rb +2385 -1622
  100. data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +83 -35
  101. data/lib/openstudio-standards/standards/Standards.PlantLoop.rb +805 -395
  102. data/lib/openstudio-standards/standards/Standards.Pump.rb +139 -119
  103. data/lib/openstudio-standards/standards/Standards.PumpConstantSpeed.rb +0 -2
  104. data/lib/openstudio-standards/standards/Standards.PumpVariableSpeed.rb +16 -15
  105. data/lib/openstudio-standards/standards/Standards.ScheduleCompact.rb +35 -0
  106. data/lib/openstudio-standards/standards/Standards.ScheduleConstant.rb +7 -13
  107. data/lib/openstudio-standards/standards/Standards.ScheduleRuleset.rb +144 -59
  108. data/lib/openstudio-standards/standards/Standards.Space.rb +1509 -1326
  109. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +254 -262
  110. data/lib/openstudio-standards/standards/Standards.SubSurface.rb +105 -105
  111. data/lib/openstudio-standards/standards/Standards.Surface.rb +27 -31
  112. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +882 -157
  113. data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +179 -69
  114. data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +75 -0
  115. data/lib/openstudio-standards/utilities/logging.rb +31 -38
  116. data/lib/openstudio-standards/utilities/simulation.rb +118 -82
  117. data/lib/openstudio-standards/version.rb +1 -1
  118. data/lib/openstudio-standards/weather/Weather.Model.rb +382 -390
  119. data/lib/openstudio-standards/weather/Weather.stat_file.rb +159 -78
  120. metadata +59 -6
@@ -0,0 +1,35 @@
1
+
2
+ # Reopen the OpenStudio class to add methods to apply standards to this object
3
+ class OpenStudio::Model::ScheduleCompact
4
+ # Returns the min and max value for this schedule.
5
+ #
6
+ # @author Andrew Parker, NREL.
7
+ # return [Hash] Hash has two keys, min and max.
8
+ def annual_min_max_value
9
+ vals = []
10
+ prev_str = ''
11
+ sch.extensibleGroups.each do |eg|
12
+ if prev_str.include?('until')
13
+ val = eg.getDouble(0)
14
+ if val.is_initialized
15
+ vals << eg.getDouble(0).get
16
+ end
17
+ end
18
+ str = eg.getString(0)
19
+ if str.is_initialized
20
+ prev_str = str.get.downcase
21
+ end
22
+ end
23
+
24
+ # Error if no values were found
25
+ if vals.size.zero?
26
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ScheduleCompact', "Could not find any value in #{name} when determining min and max.")
27
+ result = { 'min' => 999.9, 'max' => 999.9 }
28
+ return result
29
+ end
30
+
31
+ result = { 'min' => vals.min, 'max' => vals.max }
32
+
33
+ return result
34
+ end
35
+ end
@@ -1,19 +1,16 @@
1
1
 
2
2
  # Reopen the OpenStudio class to add methods to apply standards to this object
3
3
  class OpenStudio::Model::ScheduleConstant
4
-
5
4
  # Returns the equivalent full load hours (EFLH) for this schedule.
6
- # For example, an always-on fractional schedule
7
- # (always 1.0, 24/7, 365) would return a value of 8760.
5
+ # For example, an always-on fractional schedule
6
+ # (always 1.0, 24/7, 365) would return a value of 8760.
8
7
  #
9
8
  # @author Andrew Parker, NREL
10
9
  # return [Double] The total number of full load hours for this schedule
11
- def annual_equivalent_full_load_hrs()
12
-
13
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating total annual EFLH for schedule: #{self.name}")
14
-
15
- return annual_flh = self.value * 8760
10
+ def annual_equivalent_full_load_hrs
11
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.ScheduleRuleset', "Calculating total annual EFLH for schedule: #{name}")
16
12
 
13
+ return annual_flh = value * 8760
17
14
  end
18
15
 
19
16
  # Returns the min and max value for this schedule.
@@ -21,12 +18,9 @@ class OpenStudio::Model::ScheduleConstant
21
18
  #
22
19
  # @author David Goldwasser, NREL.
23
20
  # return [Hash] Hash has two keys, min and max.
24
- def annual_min_max_value()
25
-
26
- result = { 'min' => self.value, 'max' => self.value }
21
+ def annual_min_max_value
22
+ result = { 'min' => value, 'max' => value }
27
23
 
28
24
  return result
29
-
30
25
  end
31
-
32
26
  end
@@ -1,16 +1,14 @@
1
1
 
2
2
  # Reopen the OpenStudio class to add methods to apply standards to this object
3
3
  class OpenStudio::Model::ScheduleRuleset
4
-
5
4
  # Returns the equivalent full load hours (EFLH) for this schedule.
6
- # For example, an always-on fractional schedule
7
- # (always 1.0, 24/7, 365) would return a value of 8760.
5
+ # For example, an always-on fractional schedule
6
+ # (always 1.0, 24/7, 365) would return a value of 8760.
8
7
  #
9
8
  # @author Andrew Parker, NREL. Matt Leach, NORESCO.
10
- # return [Double] The total number of full load hours for this schedule.
11
- def annual_equivalent_full_load_hrs()
12
-
13
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating total annual EFLH for schedule: #{self.name}")
9
+ # @return [Double] The total number of full load hours for this schedule.
10
+ def annual_equivalent_full_load_hrs
11
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating total annual EFLH for schedule: #{self.name}")
14
12
 
15
13
  # Define the start and end date
16
14
  year_start_date = nil
@@ -18,47 +16,47 @@ class OpenStudio::Model::ScheduleRuleset
18
16
  if model.yearDescription.is_initialized
19
17
  year_description = model.yearDescription.get
20
18
  year = year_description.assumedYear
21
- year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new("January"),1,year)
22
- year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new("December"),31,year)
19
+ year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year)
20
+ year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year)
23
21
  else
24
- OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.ScheduleRuleset", "WARNING: Year description is not specified; assuming 2009, the default year OS uses.")
25
- year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new("January"),1,2009)
26
- year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new("December"),31,2009)
22
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', 'WARNING: Year description is not specified; assuming 2009, the default year OS uses.')
23
+ year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009)
24
+ year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009)
27
25
  end
28
26
 
29
27
  # Get the ordered list of all the day schedules
30
28
  # that are used by this schedule ruleset
31
- day_schs = self.getDaySchedules(year_start_date, year_end_date)
32
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "***Day Schedules Used***")
29
+ day_schs = getDaySchedules(year_start_date, year_end_date)
30
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "***Day Schedules Used***")
33
31
  day_schs.uniq.each do |day_sch|
34
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " #{day_sch.name.get}")
32
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " #{day_sch.name.get}")
35
33
  end
36
-
34
+
37
35
  # Get a 365-value array of which schedule is used on each day of the year,
38
- day_schs_used_each_day = self.getActiveRuleIndices(year_start_date, year_end_date)
36
+ day_schs_used_each_day = getActiveRuleIndices(year_start_date, year_end_date)
39
37
  if !day_schs_used_each_day.length == 365
40
- OpenStudio::logFree(OpenStudio::Error, "openstudio.standards.ScheduleRuleset", "#{self.name} does not have 365 daily schedules accounted for, cannot accurately calculate annual EFLH.")
38
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ScheduleRuleset', "#{name} does not have 365 daily schedules accounted for, cannot accurately calculate annual EFLH.")
41
39
  return 0
42
40
  end
43
-
41
+
44
42
  # Create a map that shows how many days each schedule is used
45
43
  day_sch_freq = day_schs_used_each_day.group_by { |n| n }
46
-
44
+
47
45
  # Build a hash that maps schedule day index to schedule day
48
46
  schedule_index_to_day = {}
49
- for i in 0..(day_schs.length-1)
50
- schedule_index_to_day[day_schs_used_each_day[i]] = day_schs[i]
47
+ day_schs.each_with_index do |day_sch, i|
48
+ schedule_index_to_day[day_schs_used_each_day[i]] = day_sch
51
49
  end
52
-
50
+
53
51
  # Loop through each of the schedules that is used, figure out the
54
52
  # full load hours for that day, then multiply this by the number
55
53
  # of days that day schedule applies and add this to the total.
56
54
  annual_flh = 0
57
55
  max_daily_flh = 0
58
- default_day_sch = self.defaultDaySchedule
56
+ default_day_sch = defaultDaySchedule
59
57
  day_sch_freq.each do |freq|
60
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", freq.inspect
61
- #exit
58
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", freq.inspect
59
+ # exit
62
60
 
63
61
  # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Schedule Index = #{freq[0]}"
64
62
  sch_index = freq[0]
@@ -66,64 +64,56 @@ class OpenStudio::Model::ScheduleRuleset
66
64
 
67
65
  # Get the day schedule at this index
68
66
  day_sch = nil
69
- if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule)
70
- day_sch = default_day_sch
71
- else
72
- day_sch = schedule_index_to_day[sch_index]
73
- end
74
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating EFLH for: #{day_sch.name}")
75
-
67
+ day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule)
68
+ default_day_sch
69
+ else
70
+ schedule_index_to_day[sch_index]
71
+ end
72
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating EFLH for: #{day_sch.name}")
73
+
76
74
  # Determine the full load hours for just one day
77
75
  daily_flh = 0
78
76
  values = day_sch.values
79
77
  times = day_sch.times
80
-
78
+
81
79
  previous_time_decimal = 0
82
- for i in 0..(times.length - 1)
83
- time_days = times[i].days
84
- time_hours = times[i].hours
85
- time_minutes = times[i].minutes
86
- time_seconds = times[i].seconds
87
- time_decimal = (time_days*24) + time_hours + (time_minutes/60) + (time_seconds/3600)
80
+ times.each_with_index do |time, i|
81
+ time_decimal = (time.days * 24) + time.hours + (time.minutes / 60) + (time.seconds / 3600)
88
82
  duration_of_value = time_decimal - previous_time_decimal
89
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " Value of #{values[i]} for #{duration_of_value} hours")
90
- daily_flh += values[i]*duration_of_value
83
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " Value of #{values[i]} for #{duration_of_value} hours")
84
+ daily_flh += values[i] * duration_of_value
91
85
  previous_time_decimal = time_decimal
92
86
  end
93
87
 
94
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " #{daily_flh.round(2)} EFLH per day")
95
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " Used #{number_of_days_sch_used} days per year")
88
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " #{daily_flh.round(2)} EFLH per day")
89
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " Used #{number_of_days_sch_used} days per year")
96
90
 
97
91
  # Multiply the daily EFLH by the number
98
92
  # of days this schedule is used per year
99
93
  # and add this to the overall total
100
94
  annual_flh += daily_flh * number_of_days_sch_used
101
-
102
95
  end
103
96
 
104
97
  # Warn if the max daily EFLH is more than 24,
105
- # which would indicate that this isn't a
98
+ # which would indicate that this isn't a
106
99
  # fractional schedule.
107
100
  if max_daily_flh > 24
108
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.ScheduleRuleset", "#{self.name} has more than 24 EFLH in one day schedule, indicating that it is not a fractional schedule.")
109
- end
110
-
111
- return annual_flh
101
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ScheduleRuleset', "#{name} has more than 24 EFLH in one day schedule, indicating that it is not a fractional schedule.")
102
+ end
112
103
 
104
+ return annual_flh
113
105
  end
114
106
 
115
107
  # Returns the min and max value for this schedule.
116
108
  # It doesn't evaluate design days only run-period conditions
117
109
  #
118
110
  # @author David Goldwasser, NREL.
119
- # return [Hash] Hash has two keys, min and max.
120
- def annual_min_max_value()
121
-
111
+ # @return [Hash] Hash has two keys, min and max.
112
+ def annual_min_max_value
122
113
  # gather profiles
123
114
  profiles = []
124
- defaultProfile = self.defaultDaySchedule
125
- profiles << defaultProfile
126
- rules = self.scheduleRules
115
+ profiles << defaultDaySchedule
116
+ rules = scheduleRules
127
117
  rules.each do |rule|
128
118
  profiles << rule.daySchedule
129
119
  end
@@ -136,19 +126,114 @@ class OpenStudio::Model::ScheduleRuleset
136
126
  if min.nil?
137
127
  min = value
138
128
  else
139
- if min > value then min = value end
129
+ min = value if min > value
140
130
  end
141
131
  if max.nil?
142
132
  max = value
143
133
  else
144
- if max < value then max = value end
134
+ max = value if max < value
145
135
  end
146
136
  end
147
137
  end
148
138
  result = { 'min' => min, 'max' => max }
149
139
 
150
140
  return result
151
-
152
141
  end
153
142
 
143
+ # Returns the total number of hours where the schedule
144
+ # is greater than the specified value.
145
+ #
146
+ # @author Andrew Parker, NREL.
147
+ # @param lower_limit [Double] the lower limit. Values equal to the limit
148
+ # will not be counted.
149
+ # @return [Double] The total number of hours
150
+ # this schedule is above the specified value.
151
+ def annual_hours_above_value(lower_limit)
152
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating total annual hours above #{lower_limit} for schedule: #{self.name}")
153
+
154
+ # Define the start and end date
155
+ year_start_date = nil
156
+ year_end_date = nil
157
+ if model.yearDescription.is_initialized
158
+ year_description = model.yearDescription.get
159
+ year = year_description.assumedYear
160
+ year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year)
161
+ year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year)
162
+ else
163
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', 'WARNING: Year description is not specified; assuming 2009, the default year OS uses.')
164
+ year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009)
165
+ year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009)
166
+ end
167
+
168
+ # Get the ordered list of all the day schedules
169
+ # that are used by this schedule ruleset
170
+ day_schs = getDaySchedules(year_start_date, year_end_date)
171
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "***Day Schedules Used***")
172
+ day_schs.uniq.each do |day_sch|
173
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " #{day_sch.name.get}")
174
+ end
175
+
176
+ # Get a 365-value array of which schedule is used on each day of the year,
177
+ day_schs_used_each_day = getActiveRuleIndices(year_start_date, year_end_date)
178
+ if !day_schs_used_each_day.length == 365
179
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ScheduleRuleset', "#{name} does not have 365 daily schedules accounted for, cannot accurately calculate annual EFLH.")
180
+ return 0
181
+ end
182
+
183
+ # Create a map that shows how many days each schedule is used
184
+ day_sch_freq = day_schs_used_each_day.group_by { |n| n }
185
+
186
+ # Build a hash that maps schedule day index to schedule day
187
+ schedule_index_to_day = {}
188
+ day_schs.each_with_index do |day_sch, i|
189
+ schedule_index_to_day[day_schs_used_each_day[i]] = day_sch
190
+ end
191
+
192
+ # Loop through each of the schedules that is used, figure out the
193
+ # hours for that day, then multiply this by the number
194
+ # of days that day schedule applies and add this to the total.
195
+ annual_hrs = 0
196
+ default_day_sch = defaultDaySchedule
197
+ day_sch_freq.each do |freq|
198
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", freq.inspect
199
+ # exit
200
+
201
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Schedule Index = #{freq[0]}"
202
+ sch_index = freq[0]
203
+ number_of_days_sch_used = freq[1].size
204
+
205
+ # Get the day schedule at this index
206
+ day_sch = nil
207
+ day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule)
208
+ default_day_sch
209
+ else
210
+ schedule_index_to_day[sch_index]
211
+ end
212
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", "Calculating hours above #{lower_limit} for: #{day_sch.name}")
213
+
214
+ # Determine the hours for just one day
215
+ daily_hrs = 0
216
+ values = day_sch.values
217
+ times = day_sch.times
218
+
219
+ previous_time_decimal = 0
220
+ times.each_with_index do |time, i|
221
+ time_decimal = (time.days * 24) + time.hours + (time.minutes / 60) + (time.seconds / 3600)
222
+ duration_of_value = time_decimal - previous_time_decimal
223
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " Value of #{values[i]} for #{duration_of_value} hours")
224
+ daily_hrs += values[i] * duration_of_value
225
+ previous_time_decimal = time_decimal
226
+ end
227
+
228
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " #{daily_hrs.round(2)} hours above #{lower_limit} per day")
229
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.ScheduleRuleset", " Used #{number_of_days_sch_used} days per year")
230
+
231
+ # Multiply the daily hours by the number
232
+ # of days this schedule is used per year
233
+ # and add this to the overall total
234
+ annual_hrs += daily_hrs * number_of_days_sch_used
235
+ end
236
+
237
+ return annual_hrs
238
+ end
154
239
  end
@@ -1,463 +1,36 @@
1
1
 
2
2
  # open the class to add methods to apply HVAC efficiency standards
3
3
  class OpenStudio::Model::Space
4
-
5
4
  # Returns values for the different types of daylighted areas in the space.
6
- # Definitions for each type of area follow the respective standard.
5
+ # Definitions for each type of area follow the respective template.
7
6
  # @note This method is super complicated because of all the polygon/geometry math required.
8
7
  # and therefore may not return perfect results. However, it works well in most tested
9
8
  # situations. When it fails, it will log warnings/errors for users to see.
10
- #
11
- # @param vintage [String] standard to use. valid choices:
9
+ #
10
+ # @param template [String] template to use. valid choices:
12
11
  # @param draw_daylight_areas_for_debugging [Bool] If this argument is set to true,
13
12
  # daylight areas will be added to the model as surfaces for visual debugging.
14
13
  # Yellow = toplighted area, Red = primary sidelighted area,
15
- # Blue = secondary sidelighted area, Light Blue = floor
14
+ # Blue = secondary sidelighted area, Light Blue = floor
16
15
  # @return [Hash] returns a hash of resulting areas (m^2).
17
- # Hash keys are: 'toplighted_area', 'primary_sidelighted_area',
16
+ # Hash keys are: 'toplighted_area', 'primary_sidelighted_area',
18
17
  # 'secondary_sidelighted_area', 'total_window_area', 'total_skylight_area'
19
- # @todo add a list of valid choices for vintage argument
18
+ # @todo add a list of valid choices for template argument
20
19
  # TODO stop skipping non-vertical walls
21
- def daylighted_areas(vintage, draw_daylight_areas_for_debugging = false)
22
-
23
- # A series of methods to modify polygons. Most are
24
- # wrappers of native OpenStudio methods, but with
25
- # workarounds for known issues or limitations.
26
-
27
- # Check the z coordinates of a polygon
28
- # @api private
29
- def check_z_zero(polygons, name, space)
30
- fails = []
31
- errs = 0
32
- polygons.each do |polygon|
33
- #OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Checking z=0: #{name} => #{polygon.to_s.gsub(/\[|\]/,'|')}.")
34
- polygon.each do |vertex|
35
- #clsss << vertex.class
36
- unless vertex.z == 0.0
37
- errs += 1
38
- fails << vertex.z
39
- end
40
- end
41
- end
42
- #OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Checking z=0: #{name} => #{clsss.uniq.to_s.gsub(/\[|\]/,'|')}.")
43
- if errs > 0
44
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "***FAIL*** #{space} z=0 failed for #{errs} vertices in #{name}; #{fails.join(', ')}.")
45
- end
46
- end
47
-
48
- # A method to convert an array of arrays to
49
- # an array of OpenStudio::Point3ds.
50
- # @api private
51
- def ruby_polygons_to_point3d_z_zero(ruby_polygons)
52
-
53
- # Convert the final polygons back to OpenStudio
54
- os_polygons = []
55
- ruby_polygons.each do |ruby_polygon|
56
- os_polygon = []
57
- ruby_polygon.each do |vertex|
58
- vertex = OpenStudio::Point3d.new(vertex[0], vertex[1], 0.0) # Set z to hard-zero instead of vertex[2]
59
- os_polygon << vertex
60
- end
61
- os_polygons << os_polygon
62
- end
63
-
64
- return os_polygons
65
-
66
- end
67
-
68
- # A method to zero-out the z vertex of an array of polygons
69
- # @api private
70
- def polygons_set_z(polygons, new_z)
71
-
72
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "### #{polygons}")
73
-
74
- # Convert the final polygons back to OpenStudio
75
- new_polygons = []
76
- polygons.each do |polygon|
77
- new_polygon = []
78
- polygon.each do |vertex|
79
- new_vertex = OpenStudio::Point3d.new(vertex.x, vertex.y, new_z) # Set z to hard-zero instead of vertex[2]
80
- new_polygon << new_vertex
81
- end
82
- new_polygons << new_polygon
83
- end
84
-
85
- return new_polygons
86
-
87
- end
88
-
89
- # A method to returns the number of duplicate vertices in a polygon.
90
- # TODO does not actually wor
91
- # @api private
92
- def find_duplicate_vertices(ruby_polygon, tol = 0.001)
93
-
94
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***")
95
- duplicates = []
96
-
97
- combos = ruby_polygon.combination(2).to_a
98
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "########{combos.size}")
99
- combos.each do |i, j|
100
-
101
- i_vertex = OpenStudio::Point3d.new(i[0], i[1], i[2])
102
- j_vertex = OpenStudio::Point3d.new(j[0], j[1], j[2])
103
-
104
- distance = OpenStudio.getDistance(i_vertex, j_vertex)
105
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "------- #{i.to_s} to #{j.to_s} = #{distance}")
106
- if distance < tol
107
- duplicates << i
108
- end
109
-
110
- end
111
-
112
- return duplicates
113
-
114
- end
115
-
116
- # Subtracts one array of polygons from the next,
117
- # returning an array of resulting polygons.
118
- # @api private
119
- def a_polygons_minus_b_polygons(a_polygons, b_polygons, a_name, b_name)
120
-
121
- final_polygons_ruby = []
122
-
123
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "#{a_polygons.size} #{a_name} minus #{b_polygons.size} #{b_name}")
124
-
125
- # Don't try to subtract anything if either set is empty
126
- if a_polygons.size == 0
127
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{a_name} - #{b_name}: #{a_name} contains no polygons.")
128
- return polygons_set_z(a_polygons, 0.0)
129
- elsif b_polygons.size == 0
130
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{a_name} - #{b_name}: #{b_name} contains no polygons.")
131
- return polygons_set_z(a_polygons, 0.0)
132
- end
133
-
134
- # Loop through all a polygons, and for each one,
135
- # subtract all the b polygons.
136
- a_polygons.each do |a_polygon|
137
-
138
- # Translate the polygon to plain arrays
139
- a_polygon_ruby = []
140
- a_polygon.each do |vertex|
141
- a_polygon_ruby << [vertex.x, vertex.y, vertex.z]
142
- end
143
-
144
- # TODO Skip really small polygons
145
- # reduced_b_polygons = []
146
- # b_polygons.each do |b_polygon|
147
- # next
148
- # end
149
-
150
- # Perform the subtraction
151
- a_minus_b_polygons = OpenStudio.subtract(a_polygon, b_polygons, 0.01)
152
-
153
- # Translate the resulting polygons to plain ruby arrays
154
- a_minus_b_polygons_ruby = []
155
- num_small_polygons = 0
156
- a_minus_b_polygons.each do |a_minus_b_polygon|
157
-
158
- # Drop any super small or zero-vertex polygons resulting from the subtraction
159
- area = OpenStudio.getArea(a_minus_b_polygon)
160
- if area.is_initialized
161
- if area.get < 0.5 # 5 square feet
162
- num_small_polygons += 1
163
- next
164
- end
165
- else
166
- num_small_polygons += 1
167
- next
168
- end
169
-
170
- # Translate polygon to ruby array
171
- a_minus_b_polygon_ruby = []
172
- a_minus_b_polygon.each do |vertex|
173
- a_minus_b_polygon_ruby << [vertex.x, vertex.y, vertex.z]
174
- end
175
-
176
- a_minus_b_polygons_ruby << a_minus_b_polygon_ruby
177
-
178
- end
179
-
180
- if num_small_polygons > 0
181
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---Dropped #{num_small_polygons} small or invalid polygons resulting from subtraction.")
182
- end
183
-
184
- # Remove duplicate polygons
185
- unique_a_minus_b_polygons_ruby = a_minus_b_polygons_ruby.uniq
186
-
187
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---Remove duplicates: #{a_minus_b_polygons_ruby.size} ==> #{unique_a_minus_b_polygons_ruby.size}")
188
-
189
- # TODO bug workaround?
190
- # If the result includes the a polygon, the a polygon
191
- # was unchanged; only include that polgon and throw away the other junk?/bug? polygons.
192
- # If the result does not include the a polygon, the a polygon was
193
- # split into multiple pieces. Keep all those pieces.
194
- if unique_a_minus_b_polygons_ruby.include?(a_polygon_ruby)
195
- if unique_a_minus_b_polygons_ruby.size == 1
196
- final_polygons_ruby.concat([a_polygon_ruby])
197
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---includes only original polygon, keeping that one")
198
- else
199
- # Remove the original polygon
200
- unique_a_minus_b_polygons_ruby.delete(a_polygon_ruby)
201
- final_polygons_ruby.concat(unique_a_minus_b_polygons_ruby)
202
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---includes the original and others; keeping all other polygons")
203
- end
204
- else
205
- final_polygons_ruby.concat(unique_a_minus_b_polygons_ruby)
206
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---does not include original, keeping all resulting polygons")
207
- end
208
-
209
- end
210
-
211
- # Remove duplicate polygons again
212
- unique_final_polygons_ruby = final_polygons_ruby.uniq
213
-
214
- # TODO remove this workaround
215
- # Split any polygons that are joined by a line into two separate
216
- # polygons. Do this by finding duplicate
217
- # unique_final_polygons_ruby.each do |unique_final_polygon_ruby|
218
- # next if unique_final_polygon_ruby.size == 4 # Don't check 4-sided polygons
219
- # dupes = find_duplicate_vertices(unique_final_polygon_ruby)
220
- # if dupes.size > 0
221
- # OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "---Two polygons attached by line = #{unique_final_polygon_ruby.to_s.gsub(/\[|\]/,'|')}")
222
- # end
223
- # end
20
+ def daylighted_areas(template, draw_daylight_areas_for_debugging = false)
21
+ ### Begin the actual daylight area calculations ###
224
22
 
225
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---Remove final duplicates: #{final_polygons_ruby.size} ==> #{unique_final_polygons_ruby.size}")
226
-
227
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{a_name} minus #{b_name} = #{unique_final_polygons_ruby.size} polygons.")
228
-
229
- # Convert the final polygons back to OpenStudio
230
- unique_final_polygons = ruby_polygons_to_point3d_z_zero(unique_final_polygons_ruby)
231
-
232
- return unique_final_polygons
233
-
234
- end
235
-
236
- # Wrapper to catch errors in joinAll method
237
- # [utilities.geometry.joinAll] <1> Expected polygons to join together
238
- # @api private
239
- def join_polygons(polygons, tol, name)
240
-
241
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "Joining #{name} from #{self.name}")
242
-
243
- combined_polygons = []
244
-
245
- # Don't try to combine an empty array of polygons
246
- if polygons.size == 0
247
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{name} contains no polygons, not combining.")
248
- return combined_polygons
249
- end
250
-
251
- # Open a log
252
- msg_log = OpenStudio::StringStreamLogSink.new
253
- msg_log.setLogLevel(OpenStudio::Info)
254
-
255
- # Combine the polygons
256
- combined_polygons = OpenStudio.joinAll(polygons, 0.01)
257
-
258
- # Count logged errors
259
- join_errs = 0
260
- inner_loop_errs = 0
261
- msg_log.logMessages.each do |msg|
262
- if /utilities.geometry/.match(msg.logChannel)
263
- if msg.logMessage.include?("Expected polygons to join together")
264
- join_errs += 1
265
- elsif msg.logMessage.include?("Union has inner loops")
266
- inner_loop_errs += 1
267
- end
268
- end
269
- end
270
-
271
- # TODO remove this workaround, which is tried if there
272
- # are any join errors. This handles the case of polygons
273
- # that make an inner loop, the most common case being
274
- # when all 4 sides of a space have windows.
275
- # If an error occurs, attempt to join n-1 polygons,
276
- # then subtract the
277
- if join_errs > 0 || inner_loop_errs > 0
278
-
279
- # Open a log
280
- msg_log_2 = OpenStudio::StringStreamLogSink.new
281
- msg_log_2.setLogLevel(OpenStudio::Info)
282
-
283
- first_polygon = polygons.first
284
- polygons = polygons.drop(1)
285
-
286
- combined_polygons_2 = OpenStudio.joinAll(polygons, 0.01)
287
-
288
- join_errs_2 = 0
289
- inner_loop_errs_2 = 0
290
- msg_log_2.logMessages.each do |msg|
291
- if /utilities.geometry/.match(msg.logChannel)
292
- if msg.logMessage.include?("Expected polygons to join together")
293
- join_errs_2 += 1
294
- elsif msg.logMessage.include?("Union has inner loops")
295
- inner_loop_errs_2 += 1
296
- end
297
- end
298
- end
299
-
300
- if join_errs_2 > 0 || inner_loop_errs_2 > 0
301
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, the workaround for joining polygons failed.")
302
- else
303
-
304
- # First polygon minus the already combined polygons
305
- first_polygon_minus_combined = a_polygons_minus_b_polygons([first_polygon], combined_polygons_2, 'first_polygon', 'combined_polygons_2')
306
-
307
- # Add the result back
308
- combined_polygons_2 += first_polygon_minus_combined
309
- combined_polygons = combined_polygons_2
310
- join_errs = 0
311
- inner_loop_errs = 0
312
-
313
- end
314
- end
315
-
316
- # Report logged errors to user
317
- if join_errs > 0
318
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, #{join_errs} of #{polygons.size} #{name} were not joined properly due to limitations of the geometry calculation methods. The resulting daylighted areas will be smaller than they should be.")
319
- end
320
- if inner_loop_errs > 0
321
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, #{inner_loop_errs} of #{polygons.size} #{name} were not joined properly becasue the joined polygons have an internal hole. The resulting daylighted areas will be smaller than they should be.")
322
- end
323
-
324
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---Joined #{polygons.size} #{name} into #{combined_polygons.size} polygons.")
325
-
326
- return combined_polygons
327
-
328
- end
329
-
330
- # Gets the total area of a series of polygons
331
- # @api private
332
- def total_area_of_polygons(polygons)
333
- total_area_m2 = 0
334
- polygons.each do |polygon|
335
- area_m2 = OpenStudio.getArea(polygon)
336
- if area_m2.is_initialized
337
- total_area_m2 += area_m2.get
338
- else
339
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Could not get area for a polygon in #{self.name}, daylighted area calculation will not be accurate.")
340
- end
341
- end
342
-
343
- return total_area_m2
344
-
345
- end
346
-
347
- # Returns an array of resulting polygons.
348
- # Assumes that a_polygons don't overlap one another, and that b_polygons don't overlap one another
349
- # @api private
350
- def area_a_polygons_overlap_b_polygons(a_polygons, b_polygons, a_name, b_name)
351
-
352
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "#{a_polygons.size} #{a_name} overlaps #{b_polygons.size} #{b_name}")
353
-
354
- overlap_area = 0
355
-
356
- # Don't try anything if either set is empty
357
- if a_polygons.size == 0
358
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{a_name} overlaps #{b_name}: #{a_name} contains no polygons.")
359
- return overlap_area
360
- elsif b_polygons.size == 0
361
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{a_name} overlaps #{b_name}: #{b_name} contains no polygons.")
362
- return overlap_area
363
- end
364
-
365
- # Loop through each base surface
366
- b_polygons.each do |b_polygon|
367
-
368
- # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "---b polygon = #{b_polygon_ruby.to_s.gsub(/\[|\]/,'|')}")
369
-
370
- # Loop through each overlap surface and determine if it overlaps this base surface
371
- a_polygons.each do |a_polygon|
372
-
373
- # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "------a polygon = #{a_polygon_ruby.to_s.gsub(/\[|\]/,'|')}")
374
-
375
- # If the entire a polygon is within the b polygon, count 100% of the area
376
- # as overlapping and remove a polygon from the list
377
- if OpenStudio.within(a_polygon, b_polygon, 0.01)
378
-
379
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---------a overlaps b ENTIRELY.")
380
-
381
- area = OpenStudio.getArea(a_polygon)
382
- if area.is_initialized
383
- overlap_area += area.get
384
- next
385
- else
386
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "Could not determine the area of #{a_polygon.to_s.gsub(/\[|\]/,'|')} in #{a_name}; #{a_name} overlaps #{b_name}.")
387
- end
388
-
389
- # If part of a polygon overlaps b polygon, determine the
390
- # original area of polygon b, subtract polygon a from b,
391
- # then add the difference in area to the total.
392
- elsif OpenStudio.intersects(a_polygon, b_polygon, 0.01)
393
-
394
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---------a overlaps b PARTIALLY.")
395
-
396
- # Get the initial area
397
- area_initial = 0
398
- area = OpenStudio.getArea(b_polygon)
399
- if area.is_initialized
400
- area_initial = area.get
401
- else
402
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "Could not determine the area of #{a_polygon.to_s.gsub(/\[|\]/,'|')} in #{a_name}; #{a_name} overlaps #{b_name}.")
403
- end
404
-
405
- # Perform the subtraction
406
- b_minus_a_polygons = OpenStudio.subtract(b_polygon, [a_polygon], 0.01)
407
-
408
- # Get the final area
409
- area_final = 0
410
- b_minus_a_polygons.each do |polygon|
411
- # Skip polygons that have no vertices
412
- # resulting from the subtraction.
413
- if polygon.size == 0
414
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "Zero-vertex polygon resulting from #{b_polygon.to_s.gsub(/\[|\]/,'|')} minus #{a_polygon.to_s.gsub(/\[|\]/,'|')}.")
415
- next
416
- end
417
- # Find the area of real polygons
418
- area = OpenStudio.getArea(polygon)
419
- if area.is_initialized
420
- area_final += area.get
421
- else
422
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Could not determine the area of #{polygon.to_s.gsub(/\[|\]/,'|')} in #{a_name}; #{a_name} overlaps #{b_name}.")
423
- end
424
- end
425
-
426
- # Add the diference to the total
427
- overlap_area += (area_initial - area_final)
23
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, calculating daylighted areas.")
428
24
 
429
- # There is no overlap
430
- else
431
-
432
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---------a does not overlaps b at all.")
433
-
434
- end
25
+ result = { 'toplighted_area' => 0.0,
26
+ 'primary_sidelighted_area' => 0.0,
27
+ 'secondary_sidelighted_area' => 0.0,
28
+ 'total_window_area' => 0.0,
29
+ 'total_skylight_area' => 0.0 }
435
30
 
436
- end
437
-
438
- end
439
-
440
- return overlap_area
441
-
442
- end
443
-
444
-
445
-
446
-
447
- ### Begin the actual daylight area calculations ###
448
-
449
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{self.name}, calculating daylighted areas.")
450
-
451
- result = {'toplighted_area' => nil,
452
- 'primary_sidelighted_area' => nil,
453
- 'secondary_sidelighted_area' => nil,
454
- 'total_window_area' => nil,
455
- 'total_skylight_area' => nil
456
- }
457
-
458
31
  total_window_area = 0
459
32
  total_skylight_area = 0
460
-
33
+
461
34
  # Make rendering colors to help debug visually
462
35
  if draw_daylight_areas_for_debugging
463
36
  # Yellow
@@ -466,7 +39,7 @@ class OpenStudio::Model::Space
466
39
  toplit_color.setRenderingRedValue(255)
467
40
  toplit_color.setRenderingGreenValue(255)
468
41
  toplit_color.setRenderingBlueValue(0)
469
- toplit_construction.setRenderingColor(toplit_color)
42
+ toplit_construction.setRenderingColor(toplit_color)
470
43
 
471
44
  # Red
472
45
  pri_sidelit_construction = OpenStudio::Model::Construction.new(model)
@@ -492,66 +65,66 @@ class OpenStudio::Model::Space
492
65
  flr_color.setRenderingBlueValue(255)
493
66
  flr_construction.setRenderingColor(flr_color)
494
67
  end
495
-
68
+
496
69
  # Move the polygon up slightly for viewability in sketchup
497
- up_translation_flr = OpenStudio::createTranslation(OpenStudio::Vector3d.new(0, 0, 0.05))
498
- up_translation_top = OpenStudio::createTranslation(OpenStudio::Vector3d.new(0, 0, 0.1))
499
- up_translation_pri = OpenStudio::createTranslation(OpenStudio::Vector3d.new(0, 0, 0.1))
500
- up_translation_sec = OpenStudio::createTranslation(OpenStudio::Vector3d.new(0, 0, 0.1))
501
-
70
+ up_translation_flr = OpenStudio.createTranslation(OpenStudio::Vector3d.new(0, 0, 0.05))
71
+ up_translation_top = OpenStudio.createTranslation(OpenStudio::Vector3d.new(0, 0, 0.1))
72
+ up_translation_pri = OpenStudio.createTranslation(OpenStudio::Vector3d.new(0, 0, 0.1))
73
+ up_translation_sec = OpenStudio.createTranslation(OpenStudio::Vector3d.new(0, 0, 0.1))
74
+
502
75
  # Get the space's surface group's transformation
503
- @space_transformation = self.transformation
504
-
76
+ @space_transformation = transformation
77
+
505
78
  # Record a floor in the space for later use
506
- floor_surface = nil
507
-
79
+ floor_surface = nil
80
+
508
81
  # Record all floor polygons
509
82
  floor_polygons = []
510
83
  floor_z = 0.0
511
- self.surfaces.sort.each do |surface|
512
- if surface.surfaceType == "Floor"
84
+ surfaces.sort.each do |surface|
85
+ if surface.surfaceType == 'Floor'
513
86
  floor_surface = surface
514
87
  floor_z = surface.vertices[0].z
515
88
  # floor_polygons << surface.vertices
516
- # Hard-set the z for the floor to zero
89
+ # Hard-set the z for the floor to zero
517
90
  new_floor_polygon = []
518
91
  surface.vertices.each do |vertex|
519
- new_floor_polygon << OpenStudio::Point3d.new(vertex.x, vertex.y, 0.0)
92
+ new_floor_polygon << OpenStudio::Point3d.new(vertex.x, vertex.y, 0.0)
520
93
  end
521
94
  floor_polygons << new_floor_polygon
522
95
  end
523
96
  end
524
-
97
+
525
98
  # Make sure there is one floor surface
526
99
  if floor_surface.nil?
527
- OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Could not find a floor in space #{self.name.get}, cannot determine daylighted areas.")
100
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Could not find a floor in space #{name.get}, cannot determine daylighted areas.")
528
101
  return result
529
102
  end
530
-
103
+
531
104
  # Make a set of vertices representing each subsurfaces sidelighteding area
532
105
  # and fold them all down onto the floor of the self.
533
106
  toplit_polygons = []
534
107
  pri_sidelit_polygons = []
535
108
  sec_sidelit_polygons = []
536
- self.surfaces.sort.each do |surface|
537
- if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
538
-
539
- # TODO stop skipping non-vertical walls
109
+ surfaces.sort.each do |surface|
110
+ if surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'Wall'
111
+
112
+ # TODO: stop skipping non-vertical walls
540
113
  surface_normal = surface.outwardNormal
541
114
  surface_normal_z = surface_normal.z
542
- unless surface_normal_z.abs < 0.001
543
- if surface.subSurfaces.size > 0
544
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Cannot currently handle non-vertical walls; skipping windows on #{surface.name} in #{self.name}.")
115
+ unless surface_normal_z.abs < 0.001
116
+ unless surface.subSurfaces.empty?
117
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Cannot currently handle non-vertical walls; skipping windows on #{surface.name} in #{name}.")
545
118
  next
546
119
  end
547
120
  end
548
-
121
+
549
122
  surface.subSurfaces.sort.each do |sub_surface|
550
- next unless sub_surface.outsideBoundaryCondition == "Outdoors" && (sub_surface.subSurfaceType == "FixedWindow" || sub_surface.subSurfaceType == "OperableWindow")
551
-
552
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***#{sub_surface.name}***"
123
+ next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && (sub_surface.subSurfaceType == 'FixedWindow' || sub_surface.subSurfaceType == 'OperableWindow' || sub_surface.subSurfaceType == 'GlassDoor')
124
+
125
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***#{sub_surface.name}***"
553
126
  total_window_area += sub_surface.netArea
554
-
127
+
555
128
  # Find the head height and sill height of the window
556
129
  vertex_heights_above_floor = []
557
130
  sub_surface.vertices.each do |vertex|
@@ -560,19 +133,19 @@ class OpenStudio::Model::Space
560
133
  end
561
134
  sill_height_m = vertex_heights_above_floor.min
562
135
  head_height_m = vertex_heights_above_floor.max
563
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "head height = #{head_height_m.round(2)}m, sill height = #{sill_height_m.round(2)}m")
564
-
136
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "head height = #{head_height_m.round(2)}m, sill height = #{sill_height_m.round(2)}m")
137
+
565
138
  # Find the width of the window
566
139
  rot_origin = nil
567
- if not sub_surface.vertices.size == 4
568
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "A sub-surface in space #{self.name} has other than 4 vertices; this sub-surface will not be included in the daylighted area calculation.")
140
+ unless sub_surface.vertices.size == 4
141
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "A sub-surface in space #{name} has other than 4 vertices; this sub-surface will not be included in the daylighted area calculation.")
569
142
  next
570
143
  end
571
144
  prev_vertex_on_floorplane = nil
572
145
  max_window_width_m = 0
573
146
  sub_surface.vertices.each do |vertex|
574
147
  vertex_on_floorplane = floor_surface.plane.project(vertex)
575
- if not prev_vertex_on_floorplane
148
+ unless prev_vertex_on_floorplane
576
149
  prev_vertex_on_floorplane = vertex_on_floorplane
577
150
  next
578
151
  end
@@ -582,23 +155,23 @@ class OpenStudio::Model::Space
582
155
  rot_origin = vertex_on_floorplane
583
156
  end
584
157
  end
585
-
158
+
586
159
  # Determine the extra width to add to the sidelighted area
587
160
  extra_width_m = 0
588
- if vintage == '90.1-2013'
161
+ if template == '90.1-2013'
589
162
  extra_width_m = head_height_m / 2
590
- elsif vintage == '90.1-2010'
163
+ elsif template == '90.1-2010'
591
164
  extra_width_m = OpenStudio.convert(2, 'ft', 'm').get
592
165
  end
593
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "Adding #{extra_width_m.round(2)}m to the width for the sidelighted area.")
594
-
166
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "Adding #{extra_width_m.round(2)}m to the width for the sidelighted area.")
167
+
595
168
  # Align the vertices with face coordinate system
596
169
  face_transform = OpenStudio::Transformation.alignFace(sub_surface.vertices)
597
170
  aligned_vertices = face_transform.inverse * sub_surface.vertices
598
-
171
+
599
172
  # Find the min and max x values
600
- min_x_val = 99999
601
- max_x_val = -99999
173
+ min_x_val = 99_999
174
+ max_x_val = -99_999
602
175
  aligned_vertices.each do |vertex|
603
176
  # Min x value
604
177
  if vertex.x < min_x_val
@@ -609,110 +182,107 @@ class OpenStudio::Model::Space
609
182
  max_x_val = vertex.x
610
183
  end
611
184
  end
612
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "min_x_val = #{min_x_val.round(2)}, max_x_val = #{max_x_val.round(2)}")
613
-
185
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "min_x_val = #{min_x_val.round(2)}, max_x_val = #{max_x_val.round(2)}")
186
+
614
187
  # Create polygons that are adjusted
615
188
  # to expand from the window shape to the sidelighteded areas.
616
189
  pri_sidelit_sub_polygon = []
617
190
  sec_sidelit_sub_polygon = []
618
191
  aligned_vertices.each do |vertex|
619
-
620
192
  # Primary sidelighted area
621
193
  # Move the x vertices outward by the specified amount.
622
- if vertex.x == min_x_val
194
+ if (vertex.x - min_x_val).abs < 0.01
623
195
  new_x = vertex.x - extra_width_m
624
- elsif vertex.x == max_x_val
196
+ elsif (vertex.x - max_x_val).abs < 0.01
625
197
  new_x = vertex.x + extra_width_m
626
198
  else
627
199
  new_x = 99.9
628
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "A window in space #{self.name} is non-rectangular; this sub-surface will not be included in the daylighted area calculation.")
200
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "A window in space #{name} is non-rectangular; this sub-surface will not be included in the primary daylighted area calculation. #{vertex.x} != #{min_x_val} or #{max_x_val}")
629
201
  end
630
-
631
- # Zero-out the y for the bottom edge because the
202
+
203
+ # Zero-out the y for the bottom edge because the
632
204
  # sidelighteding area extends down to the floor.
633
- if vertex.y == 0
634
- new_y = vertex.y - sill_height_m
635
- else
636
- new_y = vertex.y
637
- end
638
-
205
+ new_y = if vertex.y.zero?
206
+ vertex.y - sill_height_m
207
+ else
208
+ vertex.y
209
+ end
210
+
639
211
  # Set z = 0 so that intersection works.
640
212
  new_z = 0.0
641
213
 
642
214
  # Make the new vertex
643
215
  new_vertex = OpenStudio::Point3d.new(new_x, new_y, new_z)
644
216
  pri_sidelit_sub_polygon << new_vertex
645
- #OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "#{vertex.x.round(2)}, #{vertex.y.round(2)}, #{vertex.z.round(2)} ==> #{new_vertex.x.round(2)}, #{new_vertex.y.round(2)}, #{new_vertex.z.round(2)}")
646
-
217
+ # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "#{vertex.x.round(2)}, #{vertex.y.round(2)}, #{vertex.z.round(2)} ==> #{new_vertex.x.round(2)}, #{new_vertex.y.round(2)}, #{new_vertex.z.round(2)}")
218
+
647
219
  # Secondary sidelighted area
648
220
  # Move the x vertices outward by the specified amount.
649
- if vertex.x == min_x_val
221
+ if (vertex.x - min_x_val).abs < 0.01
650
222
  new_x = vertex.x - extra_width_m
651
- elsif vertex.x == max_x_val
223
+ elsif (vertex.x - max_x_val).abs < 0.01
652
224
  new_x = vertex.x + extra_width_m
653
225
  else
654
226
  new_x = 99.9
655
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "A window in space #{self.name} is non-rectangular; this sub-surface will not be included in the daylighted area calculation.")
227
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "A window in space #{name} is non-rectangular; this sub-surface will not be included in the secondary daylighted area calculation.")
656
228
  end
657
-
229
+
658
230
  # Add the head height of the window to all points
659
231
  # sidelighteding area extends down to the floor.
660
- if vertex.y == 0
661
- new_y = vertex.y - sill_height_m + head_height_m
662
- else
663
- new_y = vertex.y + head_height_m
664
- end
665
-
232
+ new_y = if vertex.y.zero?
233
+ vertex.y - sill_height_m + head_height_m
234
+ else
235
+ vertex.y + head_height_m
236
+ end
237
+
666
238
  # Set z = 0 so that intersection works.
667
239
  new_z = 0.0
668
240
 
669
241
  # Make the new vertex
670
242
  new_vertex = OpenStudio::Point3d.new(new_x, new_y, new_z)
671
- sec_sidelit_sub_polygon << new_vertex
672
-
243
+ sec_sidelit_sub_polygon << new_vertex
673
244
  end
674
-
245
+
675
246
  # Realign the vertices with space coordinate system
676
247
  pri_sidelit_sub_polygon = face_transform * pri_sidelit_sub_polygon
677
248
  sec_sidelit_sub_polygon = face_transform * sec_sidelit_sub_polygon
678
-
249
+
679
250
  # Rotate the sidelighteded areas down onto the floor
680
251
  down_vector = OpenStudio::Vector3d.new(0, 0, -1)
681
252
  outward_normal_vector = sub_surface.outwardNormal
682
253
  rot_vector = down_vector.cross(outward_normal_vector)
683
- ninety_deg_in_rad = OpenStudio::degToRad(90) # TODO change
684
- new_rotation = OpenStudio::createRotation(rot_origin, rot_vector, ninety_deg_in_rad)
254
+ ninety_deg_in_rad = OpenStudio.degToRad(90) # TODO: change
255
+ new_rotation = OpenStudio.createRotation(rot_origin, rot_vector, ninety_deg_in_rad)
685
256
  pri_sidelit_sub_polygon = new_rotation * pri_sidelit_sub_polygon
686
257
  sec_sidelit_sub_polygon = new_rotation * sec_sidelit_sub_polygon
687
258
 
688
259
  # Put the polygon vertices into counterclockwise order
689
260
  pri_sidelit_sub_polygon = pri_sidelit_sub_polygon.reverse
690
261
  sec_sidelit_sub_polygon = sec_sidelit_sub_polygon.reverse
691
-
262
+
692
263
  # Add these polygons to the list
693
264
  pri_sidelit_polygons << pri_sidelit_sub_polygon
694
265
  sec_sidelit_polygons << sec_sidelit_sub_polygon
695
-
696
266
  end # Next subsurface
697
- elsif surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "RoofCeiling"
698
-
699
- # TODO stop skipping non-horizontal roofs
267
+ elsif surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'RoofCeiling'
268
+
269
+ # TODO: stop skipping non-horizontal roofs
700
270
  surface_normal = surface.outwardNormal
701
271
  straight_upward = OpenStudio::Vector3d.new(0, 0, 1)
702
272
  unless surface_normal.to_s == straight_upward.to_s
703
- if surface.subSurfaces.size > 0
704
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Cannot currently handle non-horizontal roofs; skipping skylights on #{surface.name} in #{self.name}.")
705
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---Surface #{surface.name} has outward normal of #{surface_normal.to_s.gsub(/\[|\]/,'|')}; up is #{straight_upward.to_s.gsub(/\[|\]/,'|')}.")
273
+ unless surface.subSurfaces.empty?
274
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Cannot currently handle non-horizontal roofs; skipping skylights on #{surface.name} in #{name}.")
275
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---Surface #{surface.name} has outward normal of #{surface_normal.to_s.gsub(/\[|\]/, '|')}; up is #{straight_upward.to_s.gsub(/\[|\]/, '|')}.")
706
276
  next
707
277
  end
708
278
  end
709
-
279
+
710
280
  surface.subSurfaces.sort.each do |sub_surface|
711
- next unless sub_surface.outsideBoundaryCondition == "Outdoors" && sub_surface.subSurfaceType == "Skylight"
712
-
713
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***#{sub_surface.name}***")
281
+ next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && sub_surface.subSurfaceType == 'Skylight'
282
+
283
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***#{sub_surface.name}***")
714
284
  total_skylight_area += sub_surface.netArea
715
-
285
+
716
286
  # Project the skylight onto the floor plane
717
287
  polygon_on_floor = []
718
288
  vertex_heights_above_floor = []
@@ -721,20 +291,20 @@ class OpenStudio::Model::Space
721
291
  vertex_heights_above_floor << (vertex - vertex_on_floorplane).length
722
292
  polygon_on_floor << vertex_on_floorplane
723
293
  end
724
-
294
+
725
295
  # Determine the ceiling height.
726
296
  # Assumes skylight is flush with ceiling.
727
297
  ceiling_height_m = vertex_heights_above_floor.max
728
-
298
+
729
299
  # Align the vertices with face coordinate system
730
300
  face_transform = OpenStudio::Transformation.alignFace(polygon_on_floor)
731
301
  aligned_vertices = face_transform.inverse * polygon_on_floor
732
-
302
+
733
303
  # Find the min and max x and y values
734
- min_x_val = 99999
735
- max_x_val = -99999
736
- min_y_val = 99999
737
- max_y_val = -99999
304
+ min_x_val = 99_999
305
+ max_x_val = -99_999
306
+ min_y_val = 99_999
307
+ max_y_val = -99_999
738
308
  aligned_vertices.each do |vertex|
739
309
  # Min x value
740
310
  if vertex.x < min_x_val
@@ -753,15 +323,14 @@ class OpenStudio::Model::Space
753
323
  max_y_val = vertex.y
754
324
  end
755
325
  end
756
-
326
+
757
327
  # Figure out how much to expand the window
758
328
  additional_extent_m = 0.7 * ceiling_height_m
759
-
329
+
760
330
  # Create polygons that are adjusted
761
331
  # to expand from the window shape to the sidelighteded areas.
762
332
  toplit_sub_polygon = []
763
333
  aligned_vertices.each do |vertex|
764
-
765
334
  # Move the x vertices outward by the specified amount.
766
335
  if vertex.x == min_x_val
767
336
  new_x = vertex.x - additional_extent_m
@@ -769,9 +338,9 @@ class OpenStudio::Model::Space
769
338
  new_x = vertex.x + additional_extent_m
770
339
  else
771
340
  new_x = 99.9
772
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "A skylight in space #{self.name} is non-rectangular; this sub-surface will not be included in the daylighted area calculation.")
341
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "A skylight in space #{name} is non-rectangular; this sub-surface will not be included in the daylighted area calculation.")
773
342
  end
774
-
343
+
775
344
  # Move the y vertices outward by the specified amount.
776
345
  if vertex.y == min_y_val
777
346
  new_y = vertex.y - additional_extent_m
@@ -779,18 +348,17 @@ class OpenStudio::Model::Space
779
348
  new_y = vertex.y + additional_extent_m
780
349
  else
781
350
  new_y = 99.9
782
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "A skylight in space #{self.name} is non-rectangular; this sub-surface will not be included in the daylighted area calculation.")
783
- end
784
-
351
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "A skylight in space #{name} is non-rectangular; this sub-surface will not be included in the daylighted area calculation.")
352
+ end
353
+
785
354
  # Set z = 0 so that intersection works.
786
355
  new_z = 0.0
787
356
 
788
357
  # Make the new vertex
789
358
  new_vertex = OpenStudio::Point3d.new(new_x, new_y, new_z)
790
359
  toplit_sub_polygon << new_vertex
791
-
792
360
  end
793
-
361
+
794
362
  # Realign the vertices with space coordinate system
795
363
  toplit_sub_polygon = face_transform * toplit_sub_polygon
796
364
 
@@ -799,98 +367,96 @@ class OpenStudio::Model::Space
799
367
 
800
368
  # Add these polygons to the list
801
369
  toplit_polygons << toplit_sub_polygon
802
-
803
- end # Next subsurface
804
-
370
+ end # Next subsurface
371
+
805
372
  end # End if outdoor wall or roofceiling
806
-
807
373
  end # Next surface
808
374
 
809
375
  # Set z=0 for all the polygons so that intersection will work
810
376
  toplit_polygons = polygons_set_z(toplit_polygons, 0.0)
811
377
  pri_sidelit_polygons = polygons_set_z(pri_sidelit_polygons, 0.0)
812
378
  sec_sidelit_polygons = polygons_set_z(sec_sidelit_polygons, 0.0)
813
-
379
+
814
380
  # Check the initial polygons
815
- check_z_zero(floor_polygons, 'floor_polygons', self.name.get)
816
- check_z_zero(toplit_polygons, 'toplit_polygons', self.name.get)
817
- check_z_zero(pri_sidelit_polygons, 'pri_sidelit_polygons', self.name.get)
818
- check_z_zero(sec_sidelit_polygons, 'sec_sidelit_polygons', self.name.get)
819
-
381
+ check_z_zero(floor_polygons, 'floor_polygons', name.get)
382
+ check_z_zero(toplit_polygons, 'toplit_polygons', name.get)
383
+ check_z_zero(pri_sidelit_polygons, 'pri_sidelit_polygons', name.get)
384
+ check_z_zero(sec_sidelit_polygons, 'sec_sidelit_polygons', name.get)
385
+
820
386
  # Join, then subtract
821
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***Joining polygons***")
822
-
387
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '***Joining polygons***')
388
+
823
389
  # Join toplighted polygons into a single set
824
390
  combined_toplit_polygons = join_polygons(toplit_polygons, 0.01, 'toplit_polygons')
825
-
391
+
826
392
  # Join primary sidelighted polygons into a single set
827
393
  combined_pri_sidelit_polygons = join_polygons(pri_sidelit_polygons, 0.01, 'pri_sidelit_polygons')
828
-
394
+
829
395
  # Join secondary sidelighted polygons into a single set
830
396
  combined_sec_sidelit_polygons = join_polygons(sec_sidelit_polygons, 0.01, 'sec_sidelit_polygons')
831
-
397
+
832
398
  # Join floor polygons into a single set
833
399
  combined_floor_polygons = join_polygons(floor_polygons, 0.01, 'floor_polygons')
834
-
400
+
835
401
  # Check the joined polygons
836
- check_z_zero(combined_floor_polygons, 'combined_floor_polygons', self.name.get)
837
- check_z_zero(combined_toplit_polygons, 'combined_toplit_polygons', self.name.get)
838
- check_z_zero(combined_pri_sidelit_polygons, 'combined_pri_sidelit_polygons', self.name.get)
839
- check_z_zero(combined_sec_sidelit_polygons, 'combined_sec_sidelit_polygons', self.name.get)
402
+ check_z_zero(combined_floor_polygons, 'combined_floor_polygons', name.get)
403
+ check_z_zero(combined_toplit_polygons, 'combined_toplit_polygons', name.get)
404
+ check_z_zero(combined_pri_sidelit_polygons, 'combined_pri_sidelit_polygons', name.get)
405
+ check_z_zero(combined_sec_sidelit_polygons, 'combined_sec_sidelit_polygons', name.get)
840
406
 
841
407
  # Make a new surface for each of the resulting polygons to visually inspect it
842
408
  # OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***Making Surfaces to view in SketchUp***")
843
409
 
844
410
  # combined_toplit_polygons.each do |polygon|
845
- # dummy_space = OpenStudio::Model::Space.new(model)
846
- # polygon = up_translation_top * polygon
847
- # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
848
- # daylt_surf.setConstruction(toplit_construction)
849
- # daylt_surf.setSpace(dummy_space)
850
- # daylt_surf.setName("Top")
851
- # end
852
-
411
+ # dummy_space = OpenStudio::Model::Space.new(model)
412
+ # polygon = up_translation_top * polygon
413
+ # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
414
+ # daylt_surf.setConstruction(toplit_construction)
415
+ # daylt_surf.setSpace(dummy_space)
416
+ # daylt_surf.setName("Top")
417
+ # end
418
+
853
419
  # combined_pri_sidelit_polygons.each do |polygon|
854
- # dummy_space = OpenStudio::Model::Space.new(model)
855
- # polygon = up_translation_pri * polygon
856
- # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
857
- # daylt_surf.setConstruction(pri_sidelit_construction)
858
- # daylt_surf.setSpace(dummy_space)
859
- # daylt_surf.setName("Pri")
420
+ # dummy_space = OpenStudio::Model::Space.new(model)
421
+ # polygon = up_translation_pri * polygon
422
+ # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
423
+ # daylt_surf.setConstruction(pri_sidelit_construction)
424
+ # daylt_surf.setSpace(dummy_space)
425
+ # daylt_surf.setName("Pri")
860
426
  # end
861
-
427
+
862
428
  # combined_sec_sidelit_polygons.each do |polygon|
863
- # dummy_space = OpenStudio::Model::Space.new(model)
864
- # polygon = up_translation_sec * polygon
865
- # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
866
- # daylt_surf.setConstruction(sec_sidelit_construction)
867
- # daylt_surf.setSpace(dummy_space)
868
- # daylt_surf.setName("Sec")
429
+ # dummy_space = OpenStudio::Model::Space.new(model)
430
+ # polygon = up_translation_sec * polygon
431
+ # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
432
+ # daylt_surf.setConstruction(sec_sidelit_construction)
433
+ # daylt_surf.setSpace(dummy_space)
434
+ # daylt_surf.setName("Sec")
869
435
  # end
870
436
 
871
437
  # combined_floor_polygons.each do |polygon|
872
- # dummy_space = OpenStudio::Model::Space.new(model)
873
- # polygon = up_translation_flr * polygon
874
- # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
875
- # daylt_surf.setConstruction(flr_construction)
876
- # daylt_surf.setSpace(dummy_space)
877
- # daylt_surf.setName("Flr")
878
- # end
879
-
880
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***Subtracting overlapping areas***")
438
+ # dummy_space = OpenStudio::Model::Space.new(model)
439
+ # polygon = up_translation_flr * polygon
440
+ # daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
441
+ # daylt_surf.setConstruction(flr_construction)
442
+ # daylt_surf.setSpace(dummy_space)
443
+ # daylt_surf.setName("Flr")
444
+ # end
445
+
446
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '***Subtracting overlapping areas***')
881
447
 
882
448
  # Subtract lower-priority daylighting areas from higher priority ones
883
449
  pri_minus_top_polygons = a_polygons_minus_b_polygons(combined_pri_sidelit_polygons, combined_toplit_polygons, 'combined_pri_sidelit_polygons', 'combined_toplit_polygons')
884
-
450
+
885
451
  sec_minus_top_polygons = a_polygons_minus_b_polygons(combined_sec_sidelit_polygons, combined_toplit_polygons, 'combined_sec_sidelit_polygons', 'combined_toplit_polygons')
886
-
452
+
887
453
  sec_minus_top_minus_pri_polygons = a_polygons_minus_b_polygons(sec_minus_top_polygons, combined_pri_sidelit_polygons, 'sec_minus_top_polygons', 'combined_pri_sidelit_polygons')
888
454
 
889
455
  # Check the subtracted polygons
890
- check_z_zero(pri_minus_top_polygons, 'pri_minus_top_polygons', self.name.get)
891
- check_z_zero(sec_minus_top_polygons, 'sec_minus_top_polygons', self.name.get)
892
- check_z_zero(sec_minus_top_minus_pri_polygons, 'sec_minus_top_minus_pri_polygons', self.name.get)
893
-
456
+ check_z_zero(pri_minus_top_polygons, 'pri_minus_top_polygons', name.get)
457
+ check_z_zero(sec_minus_top_polygons, 'sec_minus_top_polygons', name.get)
458
+ check_z_zero(sec_minus_top_minus_pri_polygons, 'sec_minus_top_minus_pri_polygons', name.get)
459
+
894
460
  # Make a new surface for each of the resulting polygons to visually inspect it.
895
461
  # First reset the z so the surfaces show up on the correct plane.
896
462
  if draw_daylight_areas_for_debugging
@@ -900,17 +466,17 @@ class OpenStudio::Model::Space
900
466
  sec_minus_top_minus_pri_polygons_at_floor = polygons_set_z(sec_minus_top_minus_pri_polygons, floor_z)
901
467
  combined_floor_polygons_at_floor = polygons_set_z(combined_floor_polygons, floor_z)
902
468
 
903
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***Making Surfaces to view in SketchUp***")
469
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '***Making Surfaces to view in SketchUp***')
904
470
  dummy_space = OpenStudio::Model::Space.new(model)
905
-
471
+
906
472
  combined_toplit_polygons_at_floor.each do |polygon|
907
473
  polygon = up_translation_top * polygon
908
474
  polygon = @space_transformation * polygon
909
475
  daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
910
476
  daylt_surf.setConstruction(toplit_construction)
911
477
  daylt_surf.setSpace(dummy_space)
912
- daylt_surf.setName("Top")
913
- end
478
+ daylt_surf.setName('Top')
479
+ end
914
480
 
915
481
  pri_minus_top_polygons_at_floor.each do |polygon|
916
482
  polygon = up_translation_pri * polygon
@@ -918,8 +484,8 @@ class OpenStudio::Model::Space
918
484
  daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
919
485
  daylt_surf.setConstruction(pri_sidelit_construction)
920
486
  daylt_surf.setSpace(dummy_space)
921
- daylt_surf.setName("Pri")
922
- end
487
+ daylt_surf.setName('Pri')
488
+ end
923
489
 
924
490
  sec_minus_top_minus_pri_polygons_at_floor.each do |polygon|
925
491
  polygon = up_translation_sec * polygon
@@ -927,164 +493,158 @@ class OpenStudio::Model::Space
927
493
  daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
928
494
  daylt_surf.setConstruction(sec_sidelit_construction)
929
495
  daylt_surf.setSpace(dummy_space)
930
- daylt_surf.setName("Sec")
931
- end
932
-
496
+ daylt_surf.setName('Sec')
497
+ end
498
+
933
499
  combined_floor_polygons_at_floor.each do |polygon|
934
500
  polygon = up_translation_flr * polygon
935
501
  polygon = @space_transformation * polygon
936
502
  daylt_surf = OpenStudio::Model::Surface.new(polygon, model)
937
503
  daylt_surf.setConstruction(flr_construction)
938
504
  daylt_surf.setSpace(dummy_space)
939
- daylt_surf.setName("Flr")
505
+ daylt_surf.setName('Flr')
940
506
  end
941
507
  end
942
-
943
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "***Calculating Daylighted Areas***")
508
+
509
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '***Calculating Daylighted Areas***')
944
510
 
945
511
  # Get the total floor area
946
512
  total_floor_area_m2 = total_area_of_polygons(combined_floor_polygons)
947
513
  total_floor_area_ft2 = OpenStudio.convert(total_floor_area_m2, 'm^2', 'ft^2').get
948
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "total_floor_area_ft2 = #{total_floor_area_ft2.round(1)}")
949
-
514
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "total_floor_area_ft2 = #{total_floor_area_ft2.round(1)}")
515
+
950
516
  # Toplighted area
951
517
  toplighted_area_m2 = area_a_polygons_overlap_b_polygons(combined_toplit_polygons, combined_floor_polygons, 'combined_toplit_polygons', 'combined_floor_polygons')
952
-
518
+
953
519
  # Primary sidelighted area
954
520
  primary_sidelighted_area_m2 = area_a_polygons_overlap_b_polygons(pri_minus_top_polygons, combined_floor_polygons, 'pri_minus_top_polygons', 'combined_floor_polygons')
955
-
521
+
956
522
  # Secondary sidelighted area
957
523
  secondary_sidelighted_area_m2 = area_a_polygons_overlap_b_polygons(sec_minus_top_minus_pri_polygons, combined_floor_polygons, 'sec_minus_top_minus_pri_polygons', 'combined_floor_polygons')
958
-
524
+
959
525
  # Convert to IP for displaying
960
526
  toplighted_area_ft2 = OpenStudio.convert(toplighted_area_m2, 'm^2', 'ft^2').get
961
527
  primary_sidelighted_area_ft2 = OpenStudio.convert(primary_sidelighted_area_m2, 'm^2', 'ft^2').get
962
528
  secondary_sidelighted_area_ft2 = OpenStudio.convert(secondary_sidelighted_area_m2, 'm^2', 'ft^2').get
963
-
964
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "toplighted_area_ft2 = #{toplighted_area_ft2.round(1)}")
965
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "primary_sidelighted_area_ft2 = #{primary_sidelighted_area_ft2.round(1)}")
966
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "secondary_sidelighted_area_ft2 = #{secondary_sidelighted_area_ft2.round(1)}")
967
-
529
+
530
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "toplighted_area_ft2 = #{toplighted_area_ft2.round(1)}")
531
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "primary_sidelighted_area_ft2 = #{primary_sidelighted_area_ft2.round(1)}")
532
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "secondary_sidelighted_area_ft2 = #{secondary_sidelighted_area_ft2.round(1)}")
533
+
968
534
  result['toplighted_area'] = toplighted_area_m2
969
535
  result['primary_sidelighted_area'] = primary_sidelighted_area_m2
970
536
  result['secondary_sidelighted_area'] = secondary_sidelighted_area_m2
971
537
  result['total_window_area'] = total_window_area
972
538
  result['total_skylight_area'] = total_skylight_area
973
-
539
+
974
540
  return result
975
-
976
541
  end
977
542
 
978
543
  # Returns the sidelighting effective aperture
979
544
  # sidelighting_effective_aperture = E(window area * window VT) / primary_sidelighted_area
980
545
  #
981
546
  # @param primary_sidelighted_area [Double] the primary sidelighted area (m^2) of the space
982
- # @return [Double] the unitless sidelighting effective aperture metric
983
- def sidelightingEffectiveAperture(primary_sidelighted_area)
984
-
547
+ # @return [Double] the unitless sidelighting effective aperture metric
548
+ def sidelighting_effective_aperture(primary_sidelighted_area)
985
549
  # sidelighting_effective_aperture = E(window area * window VT) / primary_sidelighted_area
986
550
  sidelighting_effective_aperture = 9999
987
-
551
+
988
552
  num_sub_surfaces = 0
989
-
553
+
990
554
  # Loop through all windows and add up area * VT
991
555
  sum_window_area_times_vt = 0
992
556
  construction_name_to_vt_map = {}
993
- self.surfaces.sort.each do |surface|
994
- next unless surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
557
+ surfaces.sort.each do |surface|
558
+ next unless surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'Wall'
995
559
  surface.subSurfaces.sort.each do |sub_surface|
996
- next unless sub_surface.outsideBoundaryCondition == "Outdoors" && (sub_surface.subSurfaceType == "FixedWindow" || sub_surface.subSurfaceType == "OperableWindow")
997
-
560
+ next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && (sub_surface.subSurfaceType == 'FixedWindow' || sub_surface.subSurfaceType == 'OperableWindow' || sub_surface.subSurfaceType == 'GlassDoor')
561
+
998
562
  num_sub_surfaces += 1
999
-
563
+
1000
564
  # Get the area
1001
565
  area_m2 = sub_surface.netArea
1002
-
566
+
1003
567
  # Get the window construction name
1004
568
  construction_name = nil
1005
569
  construction = sub_surface.construction
1006
570
  if construction.is_initialized
1007
571
  construction_name = construction.get.name.get.upcase
1008
572
  else
1009
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, could not determine construction for #{sub_surface.name}, will not be included in sidelightingEffectiveAperture calculation.")
573
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "For #{name}, could not determine construction for #{sub_surface.name}, will not be included in sidelighting_effective_aperture calculation.")
1010
574
  next
1011
575
  end
1012
-
576
+
1013
577
  # Store VT for this construction in map if not already looked up
1014
578
  if construction_name_to_vt_map[construction_name].nil?
1015
-
1016
- sql = self.model.sqlFile
1017
-
579
+
580
+ sql = model.sqlFile
581
+
1018
582
  if sql.is_initialized
1019
583
  sql = sql.get
1020
-
584
+
1021
585
  row_query = "SELECT RowName
1022
586
  FROM tabulardatawithstrings
1023
587
  WHERE ReportName='EnvelopeSummary'
1024
588
  AND ReportForString='Entire Facility'
1025
589
  AND TableName='Exterior Fenestration'
1026
590
  AND Value='#{construction_name.upcase}'"
1027
-
591
+
1028
592
  row_id = sql.execAndReturnFirstString(row_query)
1029
-
593
+
1030
594
  if row_id.is_initialized
1031
595
  row_id = row_id.get
1032
596
  else
1033
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Model", "VT row ID not found for construction: #{construction_name}, #{sub_surface.name} will not be included in sidelightingEffectiveAperture calculation.")
597
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "VT row ID not found for construction: #{construction_name}, #{sub_surface.name} will not be included in sidelighting_effective_aperture calculation.")
1034
598
  row_id = 9999
1035
599
  end
1036
-
600
+
1037
601
  vt_query = "SELECT Value
1038
602
  FROM tabulardatawithstrings
1039
603
  WHERE ReportName='EnvelopeSummary'
1040
604
  AND ReportForString='Entire Facility'
1041
605
  AND TableName='Exterior Fenestration'
1042
606
  AND ColumnName='Glass Visible Transmittance'
1043
- AND RowName='#{row_id}'"
1044
-
607
+ AND RowName='#{row_id}'"
608
+
1045
609
  vt = sql.execAndReturnFirstDouble(vt_query)
1046
-
1047
- if vt.is_initialized
1048
- vt = vt.get
1049
- else
1050
- vt = nil
1051
- end
1052
-
610
+
611
+ vt = if vt.is_initialized
612
+ vt.get
613
+ end
614
+
1053
615
  # Record the VT
1054
616
  construction_name_to_vt_map[construction_name] = vt
1055
617
 
1056
618
  else
1057
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.Space', 'Model has no sql file containing results, cannot lookup data.')
619
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Space', 'Model has no sql file containing results, cannot lookup data.')
1058
620
  end
1059
621
 
1060
622
  end
1061
-
623
+
1062
624
  # Get the VT from the map
1063
625
  vt = construction_name_to_vt_map[construction_name]
1064
626
  if vt.nil?
1065
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, could not determine VLT for #{construction_name}, will not be included in sidelighting effective aperture caluclation.")
627
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "For #{name}, could not determine VLT for #{construction_name}, will not be included in sidelighting effective aperture caluclation.")
1066
628
  vt = 0
1067
629
  end
1068
-
630
+
1069
631
  sum_window_area_times_vt += area_m2 * vt
1070
-
1071
632
  end
1072
633
  end
1073
-
634
+
1074
635
  # Calculate the effective aperture
1075
- if sum_window_area_times_vt == 0
636
+ if sum_window_area_times_vt.zero?
1076
637
  sidelighting_effective_aperture = 9999
1077
638
  if num_sub_surfaces > 0
1078
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{self.name} has no windows where VLT could be determined, sidelighting effective aperture will be higher than it should.")
639
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{name} has no windows where VLT could be determined, sidelighting effective aperture will be higher than it should.")
1079
640
  end
1080
641
  else
1081
- sidelighting_effective_aperture = sum_window_area_times_vt/primary_sidelighted_area
642
+ sidelighting_effective_aperture = sum_window_area_times_vt / primary_sidelighted_area
1082
643
  end
1083
-
1084
- OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.Space', "For #{self.name} sidelighting effective aperture = #{sidelighting_effective_aperture.round(4)}.")
1085
-
644
+
645
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Space', "For #{name} sidelighting effective aperture = #{sidelighting_effective_aperture.round(4)}.")
646
+
1086
647
  return sidelighting_effective_aperture
1087
-
1088
648
  end
1089
649
 
1090
650
  # Returns the skylight effective aperture
@@ -1092,133 +652,127 @@ class OpenStudio::Model::Space
1092
652
  #
1093
653
  # @param toplighted_area [Double] the toplighted area (m^2) of the space
1094
654
  # @return [Double] the unitless skylight effective aperture metric
1095
- def skylightEffectiveAperture(toplighted_area)
1096
-
655
+ def skylight_effective_aperture(toplighted_area)
1097
656
  # skylight_effective_aperture = E(0.85 * skylight area * skylight VT * WF) / toplighted_area
1098
657
  skylight_effective_aperture = 0.0
1099
-
658
+
1100
659
  num_sub_surfaces = 0
1101
-
660
+
1102
661
  # Assume that well factor (WF) is 0.9 (all wells are less than 2 feet deep)
1103
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "Assuming that all skylight wells are less than 2 feet deep to calculate skylight effective aperture.")
662
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', 'Assuming that all skylight wells are less than 2 feet deep to calculate skylight effective aperture.')
1104
663
  wf = 0.9
1105
-
664
+
1106
665
  # Loop through all windows and add up area * VT
1107
666
  sum_85pct_times_skylight_area_times_vt_times_wf = 0
1108
667
  construction_name_to_vt_map = {}
1109
- self.surfaces.sort.each do |surface|
1110
- next unless surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "RoofCeiling"
668
+ surfaces.sort.each do |surface|
669
+ next unless surface.outsideBoundaryCondition == 'Outdoors' && surface.surfaceType == 'RoofCeiling'
1111
670
  surface.subSurfaces.sort.each do |sub_surface|
1112
- next unless sub_surface.outsideBoundaryCondition == "Outdoors" && sub_surface.subSurfaceType == "Skylight"
1113
-
671
+ next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && sub_surface.subSurfaceType == 'Skylight'
672
+
1114
673
  num_sub_surfaces += 1
1115
-
674
+
1116
675
  # Get the area
1117
676
  area_m2 = sub_surface.netArea
1118
-
677
+
1119
678
  # Get the window construction name
1120
679
  construction_name = nil
1121
680
  construction = sub_surface.construction
1122
681
  if construction.is_initialized
1123
682
  construction_name = construction.get.name.get.upcase
1124
683
  else
1125
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, ")
684
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "For #{name}, ")
1126
685
  next
1127
686
  end
1128
-
687
+
1129
688
  # Store VT for this construction in map if not already looked up
1130
689
  if construction_name_to_vt_map[construction_name].nil?
1131
-
1132
- sql = self.model.sqlFile
1133
-
690
+
691
+ sql = model.sqlFile
692
+
1134
693
  if sql.is_initialized
1135
694
  sql = sql.get
1136
-
695
+
1137
696
  row_query = "SELECT RowName
1138
697
  FROM tabulardatawithstrings
1139
698
  WHERE ReportName='EnvelopeSummary'
1140
699
  AND ReportForString='Entire Facility'
1141
700
  AND TableName='Exterior Fenestration'
1142
701
  AND Value='#{construction_name}'"
1143
-
702
+
1144
703
  row_id = sql.execAndReturnFirstString(row_query)
1145
-
704
+
1146
705
  if row_id.is_initialized
1147
706
  row_id = row_id.get
1148
707
  else
1149
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Model", "Data not found for query: #{row_query}")
708
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Data not found for query: #{row_query}")
1150
709
  next
1151
710
  end
1152
-
711
+
1153
712
  vt_query = "SELECT Value
1154
713
  FROM tabulardatawithstrings
1155
714
  WHERE ReportName='EnvelopeSummary'
1156
715
  AND ReportForString='Entire Facility'
1157
716
  AND TableName='Exterior Fenestration'
1158
717
  AND ColumnName='Glass Visible Transmittance'
1159
- AND RowName='#{row_id}'"
1160
-
1161
-
718
+ AND RowName='#{row_id}'"
719
+
1162
720
  vt = sql.execAndReturnFirstDouble(vt_query)
1163
-
1164
- if vt.is_initialized
1165
- vt = vt.get
1166
- else
1167
- vt = nil
1168
- end
1169
-
721
+
722
+ vt = if vt.is_initialized
723
+ vt.get
724
+ end
725
+
1170
726
  # Record the VT
1171
727
  construction_name_to_vt_map[construction_name] = vt
1172
728
 
1173
729
  else
1174
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.model.Model', 'Model has no sql file containing results, cannot lookup data.')
730
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Model has no sql file containing results, cannot lookup data.')
1175
731
  end
1176
732
 
1177
733
  end
1178
-
734
+
1179
735
  # Get the VT from the map
1180
736
  vt = construction_name_to_vt_map[construction_name]
1181
737
  if vt.nil?
1182
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "For #{self.name}, could not determine VLT for #{construction_name}, will not be included in skylight effective aperture caluclation.")
738
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "For #{name}, could not determine VLT for #{construction_name}, will not be included in skylight effective aperture caluclation.")
1183
739
  vt = 0
1184
740
  end
1185
741
 
1186
742
  sum_85pct_times_skylight_area_times_vt_times_wf += 0.85 * area_m2 * vt * wf
1187
-
1188
743
  end
1189
744
  end
1190
-
745
+
1191
746
  # Calculate the effective aperture
1192
- if sum_85pct_times_skylight_area_times_vt_times_wf == 0
747
+ if sum_85pct_times_skylight_area_times_vt_times_wf.zero?
1193
748
  skylight_effective_aperture = 9999
1194
749
  if num_sub_surfaces > 0
1195
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{self.name} has no skylights where VLT could be determined, skylight effective aperture will be higher than it should.")
750
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{name} has no skylights where VLT could be determined, skylight effective aperture will be higher than it should.")
1196
751
  end
1197
752
  else
1198
- skylight_effective_aperture = sum_85pct_times_skylight_area_times_vt_times_wf/toplighted_area
753
+ skylight_effective_aperture = sum_85pct_times_skylight_area_times_vt_times_wf / toplighted_area
1199
754
  end
1200
-
1201
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.Space', "#{self.name} skylight effective aperture = #{skylight_effective_aperture}.")
1202
-
755
+
756
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Space', "#{name} skylight effective aperture = #{skylight_effective_aperture}.")
757
+
1203
758
  return skylight_effective_aperture
1204
-
1205
759
  end
1206
-
1207
- # Adds daylighting controls (sidelighting and toplighting) per the standard
760
+
761
+ # Adds daylighting controls (sidelighting and toplighting) per the template
1208
762
  # @note This method is super complicated because of all the polygon/geometry math required.
1209
763
  # and therefore may not return perfect results. However, it works well in most tested
1210
764
  # situations. When it fails, it will log warnings/errors for users to see.
1211
765
  #
1212
- # @param vintage [String] standard to use. valid choices:
766
+ # @param template [String] template to use. valid choices:
1213
767
  # @param remove_existing_controls [Bool] if true, will remove existing controls then add new ones
1214
768
  # @param draw_daylight_areas_for_debugging [Bool] If this argument is set to true,
1215
769
  # daylight areas will be added to the model as surfaces for visual debugging.
1216
770
  # Yellow = toplighted area, Red = primary sidelighted area,
1217
- # Blue = secondary sidelighted area, Light Blue = floor
771
+ # Blue = secondary sidelighted area, Light Blue = floor
1218
772
  # @return [Hash] returns a hash of resulting areas (m^2).
1219
- # Hash keys are: 'toplighted_area', 'primary_sidelighted_area',
773
+ # Hash keys are: 'toplighted_area', 'primary_sidelighted_area',
1220
774
  # 'secondary_sidelighted_area', 'total_window_area', 'total_skylight_area'
1221
- # @todo add a list of valid choices for vintage argument
775
+ # @todo add a list of valid choices for template argument
1222
776
  # @todo add exception for retail spaces
1223
777
  # @todo add exception 2 for skylights with VT < 0.4
1224
778
  # @todo add exception 3 for CZ 8 where lighting < 200W
@@ -1226,256 +780,240 @@ class OpenStudio::Model::Space
1226
780
  # @todo stop skipping non-horizontal roofs
1227
781
  # @todo Determine the illuminance setpoint for the controls based on space type
1228
782
  # @todo rotate sensor to face window (only needed for glare calcs)
1229
- def addDaylightingControls(vintage, remove_existing_controls, draw_daylight_areas_for_debugging = false)
1230
-
1231
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "******For #{self.name}, adding daylight controls.")
783
+ def add_daylighting_controls(template, remove_existing_controls, draw_daylight_areas_for_debugging = false)
784
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "******For #{name}, adding daylight controls.")
1232
785
 
1233
786
  # Check for existing daylighting controls
1234
787
  # and remove if specified in the input
1235
- existing_daylighting_controls = self.daylightingControls
1236
- if existing_daylighting_controls.size > 0
788
+ existing_daylighting_controls = daylightingControls
789
+ unless existing_daylighting_controls.empty?
1237
790
  if remove_existing_controls
1238
- existing_daylighting_controls.each do |dc|
1239
- dc.remove
1240
- end
1241
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{self.name}, removed #{existing_daylighting_controls.size} existing daylight controls before adding new controls.")
791
+ existing_daylighting_controls.each(&:remove)
792
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, removed #{existing_daylighting_controls.size} existing daylight controls before adding new controls.")
1242
793
  else
1243
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{self.name}, daylight controls were already present, no additional controls added.")
794
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, daylight controls were already present, no additional controls added.")
1244
795
  return false
1245
796
  end
1246
797
  end
1247
798
 
799
+ # Skip this space if it has no exterior windows or skylights
800
+ ext_fen_area_m2 = 0
801
+ surfaces.each do |surface|
802
+ next unless surface.outsideBoundaryCondition == 'Outdoors'
803
+ surface.subSurfaces.each do |sub_surface|
804
+ next unless sub_surface.subSurfaceType == 'FixedWindow' || sub_surface.subSurfaceType == 'OperableWindow' || sub_surface.subSurfaceType == 'Skylight' || sub_surface.subSurfaceType == 'GlassDoor'
805
+ ext_fen_area_m2 += sub_surface.netArea
806
+ end
807
+ end
808
+ if ext_fen_area_m2.zero?
809
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, daylighting control not applicable because no exterior fenestration is present.")
810
+ return false
811
+ end
812
+
1248
813
  areas = nil
1249
-
814
+
1250
815
  req_top_ctrl = false
1251
816
  req_pri_ctrl = false
1252
817
  req_sec_ctrl = false
1253
-
818
+
1254
819
  # Get the area of the space
1255
- space_area_m2 = self.floorArea
1256
-
820
+ space_area_m2 = floorArea
821
+
1257
822
  # Get the LPD of the space
1258
- space_lpd_w_per_m2 = self.lightingPowerPerFloorArea
1259
-
823
+ space_lpd_w_per_m2 = lightingPowerPerFloorArea
824
+
1260
825
  # Determine the type of control required
1261
- case vintage
1262
- when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007'
1263
-
826
+ case template
827
+ when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007'
828
+
1264
829
  # Do nothing, no daylighting controls required
1265
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, daylighting control not required by this standard.")
830
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, daylighting control not required by this standard.")
1266
831
  return false
1267
-
832
+
1268
833
  when '90.1-2010'
1269
-
834
+
1270
835
  req_top_ctrl = true
1271
836
  req_pri_ctrl = true
1272
-
1273
- areas = self.daylighted_areas(vintage, draw_daylight_areas_for_debugging)
837
+
838
+ areas = daylighted_areas(template, draw_daylight_areas_for_debugging)
1274
839
  ###################
1275
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "primary_sidelighted_area = #{areas['primary_sidelighted_area']}")
840
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "primary_sidelighted_area = #{areas['primary_sidelighted_area']}")
1276
841
  ###################
1277
-
842
+
1278
843
  # Sidelighting
1279
844
  # Check if the primary sidelit area < 250 ft2
1280
845
  if areas['primary_sidelighted_area'] == 0.0
1281
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.4.")
846
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.4.")
1282
847
  req_pri_ctrl = false
1283
848
  elsif areas['primary_sidelighted_area'] < OpenStudio.convert(250, 'ft^2', 'm^2').get
1284
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because primary sidelighted area < 250ft2 per 9.4.1.4.")
849
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because primary sidelighted area < 250ft2 per 9.4.1.4.")
1285
850
  req_pri_ctrl = false
1286
- else
851
+ else
1287
852
  # Check effective sidelighted aperture
1288
- sidelighted_effective_aperture = self.sidelightingEffectiveAperture(areas['primary_sidelighted_area'])
853
+ sidelighted_effective_aperture = sidelighting_effective_aperture(areas['primary_sidelighted_area'])
1289
854
  ###################
1290
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "sidelighted_effective_aperture_pri = #{sidelighted_effective_aperture}")
855
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "sidelighted_effective_aperture_pri = #{sidelighted_effective_aperture}")
1291
856
  ###################
1292
857
  if sidelighted_effective_aperture < 0.1
1293
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because sidelighted effective aperture < 0.1 per 9.4.1.4 Exception b.")
858
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because sidelighted effective aperture < 0.1 per 9.4.1.4 Exception b.")
1294
859
  req_pri_ctrl = false
1295
- else
1296
- # TODO Check the space type
1297
- # if
1298
- # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{self.name}, primary sidelighting control not required because space type is retail per 9.4.1.4 Exception c.")
1299
- # req_pri_ctrl = false
1300
- # end
1301
860
  end
1302
861
  end
1303
-
862
+
1304
863
  ###################
1305
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "toplighted_area = #{areas['toplighted_area']}")
864
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "toplighted_area = #{areas['toplighted_area']}")
1306
865
  ###################
1307
866
  # Toplighting
1308
867
  # Check if the toplit area < 900 ft2
1309
868
  if areas['toplighted_area'] == 0.0
1310
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.5.")
869
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.5.")
1311
870
  req_top_ctrl = false
1312
871
  elsif areas['toplighted_area'] < OpenStudio.convert(900, 'ft^2', 'm^2').get
1313
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because toplighted area < 900ft2 per 9.4.1.5.")
872
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, toplighting control not required because toplighted area < 900ft2 per 9.4.1.5.")
1314
873
  req_top_ctrl = false
1315
- else
874
+ else
1316
875
  # Check effective sidelighted aperture
1317
- sidelighted_effective_aperture = self.skylightEffectiveAperture(areas['toplighted_area'])
876
+ sidelighted_effective_aperture = skylight_effective_aperture(areas['toplighted_area'])
1318
877
  ###################
1319
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "sidelighted_effective_aperture_top = #{sidelighted_effective_aperture}")
878
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "sidelighted_effective_aperture_top = #{sidelighted_effective_aperture}")
1320
879
  ###################
1321
880
  if sidelighted_effective_aperture < 0.006
1322
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because skylight effective aperture < 0.006 per 9.4.1.5 Exception b.")
881
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, toplighting control not required because skylight effective aperture < 0.006 per 9.4.1.5 Exception b.")
1323
882
  req_top_ctrl = false
1324
- else
1325
- # TODO Check the climate zone. Not required in CZ8 where toplit areas < 1500ft2
1326
- # if
1327
- # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{self.name}, toplighting control not required because space type is retail per 9.4.1.5 Exception c.")
1328
- # req_top_ctrl = false
1329
- # end
1330
883
  end
1331
884
  end
1332
-
885
+
1333
886
  when '90.1-2013'
1334
-
887
+
1335
888
  req_top_ctrl = true
1336
889
  req_pri_ctrl = true
1337
890
  req_sec_ctrl = true
1338
-
1339
- areas = self.daylighted_areas(vintage, draw_daylight_areas_for_debugging)
1340
-
891
+
892
+ areas = daylighted_areas(template, draw_daylight_areas_for_debugging)
893
+
1341
894
  # Primary Sidelighting
1342
895
  # Check if the primary sidelit area contains less than 150W of lighting
1343
896
  if areas['primary_sidelighted_area'] == 0.0
1344
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.1(e).")
897
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.1(e).")
1345
898
  req_pri_ctrl = false
1346
899
  elsif areas['primary_sidelighted_area'] * space_lpd_w_per_m2 < 150.0
1347
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because less than 150W of lighting are present in the primary daylighted area per 9.4.1.1(e).")
900
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because less than 150W of lighting are present in the primary daylighted area per 9.4.1.1(e).")
1348
901
  req_pri_ctrl = false
1349
902
  else
1350
903
  # Check the size of the windows
1351
- if areas['total_window_area'] < 20.0
1352
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because there are less than 20ft2 of window per 9.4.1.1(e) Exception 2.")
904
+ if areas['total_window_area'] < OpenStudio.convert(20.0, 'ft^2', 'm^2').get
905
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because there are less than 20ft2 of window per 9.4.1.1(e) Exception 2.")
1353
906
  req_pri_ctrl = false
1354
- else
1355
- # TODO Check the space type
1356
- # if
1357
- # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because space type is retail per 9.4.1.1(e) Exception c.")
1358
- # req_pri_ctrl = false
1359
- # end
1360
907
  end
1361
908
  end
1362
-
909
+
1363
910
  # Secondary Sidelighting
1364
911
  # Check if the primary and secondary sidelit areas contains less than 300W of lighting
1365
912
  if areas['secondary_sidelighted_area'] == 0.0
1366
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, secondary sidelighting control not required because secondary sidelighted area = 0ft2 per 9.4.1.1(e).")
1367
- req_pri_ctrl = false
913
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, secondary sidelighting control not required because secondary sidelighted area = 0ft2 per 9.4.1.1(e).")
914
+ req_sec_ctrl = false
1368
915
  elsif (areas['primary_sidelighted_area'] + areas['secondary_sidelighted_area']) * space_lpd_w_per_m2 < 300
1369
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, secondary sidelighting control not required because less than 300W of lighting are present in the combined primary and secondary daylighted areas per 9.4.1.1(e).")
916
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, secondary sidelighting control not required because less than 300W of lighting are present in the combined primary and secondary daylighted areas per 9.4.1.1(e).")
1370
917
  req_sec_ctrl = false
1371
918
  else
1372
919
  # Check the size of the windows
1373
- if areas['total_window_area'] < 20
1374
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, secondary sidelighting control not required because there are less than 20ft2 of window per 9.4.1.1(e) Exception 2.")
920
+ if areas['total_window_area'] < OpenStudio.convert(20.0, 'ft^2', 'm^2').get
921
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, secondary sidelighting control not required because there are less than 20ft2 of window per 9.4.1.1(e) Exception 2.")
1375
922
  req_sec_ctrl = false
1376
- else
1377
- # TODO Check the space type
1378
- # if
1379
- # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because space type is retail per 9.4.1.1(e) Exception c.")
1380
- # req_sec_ctrl = false
1381
- # end
1382
923
  end
1383
924
  end
1384
925
 
1385
926
  # Toplighting
1386
927
  # Check if the toplit area contains less than 150W of lighting
1387
928
  if areas['toplighted_area'] == 0.0
1388
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.1(f).")
1389
- req_pri_ctrl = false
929
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.1(f).")
930
+ req_top_ctrl = false
1390
931
  elsif areas['toplighted_area'] * space_lpd_w_per_m2 < 150
1391
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because less than 150W of lighting are present in the toplighted area per 9.4.1.1(f).")
1392
- req_sec_ctrl = false
1393
- else
1394
- # TODO exception 2 for skylights with VT < 0.4
1395
- # TODO exception 3 for CZ 8 where lighting < 200W
932
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, toplighting control not required because less than 150W of lighting are present in the toplighted area per 9.4.1.1(f).")
933
+ req_top_ctrl = false
1396
934
  end
1397
935
 
1398
936
  when 'AssetScore'
1399
-
937
+
1400
938
  # Same as 90.1-2010 but without effective aperture limits
1401
939
  # to avoid needing to perform run to get VLT for layered windows.
1402
-
940
+
1403
941
  req_top_ctrl = true
1404
942
  req_pri_ctrl = true
1405
-
1406
- areas = self.daylighted_areas(vintage, draw_daylight_areas_for_debugging)
1407
-
943
+
944
+ areas = daylighted_areas(template, draw_daylight_areas_for_debugging)
945
+
1408
946
  # Sidelighting
1409
947
  # Check if the primary sidelit area < 250 ft2
1410
948
  if areas['primary_sidelighted_area'] == 0.0
1411
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.4.")
949
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.4.")
1412
950
  req_pri_ctrl = false
1413
951
  elsif areas['primary_sidelighted_area'] < OpenStudio.convert(250, 'ft^2', 'm^2').get
1414
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control not required because primary sidelighted area < 250ft2 per 9.4.1.4.")
952
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, primary sidelighting control not required because primary sidelighted area < 250ft2 per 9.4.1.4.")
1415
953
  req_pri_ctrl = false
1416
954
  end
1417
-
955
+
1418
956
  # Toplighting
1419
957
  # Check if the toplit area < 900 ft2
1420
958
  if areas['toplighted_area'] == 0.0
1421
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.5.")
959
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.5.")
1422
960
  req_top_ctrl = false
1423
961
  elsif areas['toplighted_area'] < OpenStudio.convert(900, 'ft^2', 'm^2').get
1424
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control not required because toplighted area < 900ft2 per 9.4.1.5.")
1425
- req_top_ctrl = false
1426
- end
1427
-
1428
- end # End of vintage case statement
1429
-
962
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, toplighting control not required because toplighted area < 900ft2 per 9.4.1.5.")
963
+ req_top_ctrl = false
964
+ end
965
+
966
+ end # End of template case statement
967
+
1430
968
  # Output the daylight control requirements
1431
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{vintage} #{self.name}, toplighting control required = #{req_top_ctrl}")
1432
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{vintage} #{self.name}, primary sidelighting control required = #{req_pri_ctrl}")
1433
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{vintage} #{self.name}, secondary sidelighting control required = #{req_sec_ctrl}")
969
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, toplighting control required = #{req_top_ctrl}")
970
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, primary sidelighting control required = #{req_pri_ctrl}")
971
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, secondary sidelighting control required = #{req_sec_ctrl}")
1434
972
 
1435
973
  # Stop here if no lighting controls are required.
1436
974
  # Do not put daylighting control points into the space.
1437
975
  if !req_top_ctrl && !req_pri_ctrl && !req_sec_ctrl
1438
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{vintage} #{self.name}, no daylighting control is required.")
976
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, no daylighting control is required.")
1439
977
  return false
1440
978
  end
1441
-
979
+
1442
980
  # Record a floor in the space for later use
1443
981
  floor_surface = nil
1444
- self.surfaces.sort.each do |surface|
1445
- if surface.surfaceType == "Floor"
982
+ surfaces.sort.each do |surface|
983
+ if surface.surfaceType == 'Floor'
1446
984
  floor_surface = surface
1447
985
  break
1448
986
  end
1449
987
  end
1450
-
988
+
1451
989
  # Find all exterior windows/skylights in the space and record their azimuths and areas
1452
990
  windows = {}
1453
991
  skylights = {}
1454
- self.surfaces.sort.each do |surface|
1455
- next unless surface.outsideBoundaryCondition == "Outdoors" && (surface.surfaceType == "Wall" || surface.surfaceType == "RoofCeiling")
1456
-
992
+ surfaces.sort.each do |surface|
993
+ next unless surface.outsideBoundaryCondition == 'Outdoors' && (surface.surfaceType == 'Wall' || surface.surfaceType == 'RoofCeiling')
994
+
1457
995
  # Skip non-vertical walls and non-horizontal roofs
1458
996
  straight_upward = OpenStudio::Vector3d.new(0, 0, 1)
1459
997
  surface_normal = surface.outwardNormal
1460
- if surface.surfaceType == "Wall"
1461
- # TODO stop skipping non-vertical walls
1462
- unless surface_normal.z.abs < 0.001
1463
- if surface.subSurfaces.size > 0
1464
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Cannot currently handle non-vertical walls; skipping windows on #{surface.name} in #{self.name} for daylight sensor positioning.")
998
+ if surface.surfaceType == 'Wall'
999
+ # TODO: stop skipping non-vertical walls
1000
+ unless surface_normal.z.abs < 0.001
1001
+ unless surface.subSurfaces.empty?
1002
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Cannot currently handle non-vertical walls; skipping windows on #{surface.name} in #{name} for daylight sensor positioning.")
1465
1003
  next
1466
1004
  end
1467
1005
  end
1468
- elsif surface.surfaceType == "RoofCeiling"
1469
- # TODO stop skipping non-horizontal roofs
1006
+ elsif surface.surfaceType == 'RoofCeiling'
1007
+ # TODO: stop skipping non-horizontal roofs
1470
1008
  unless surface_normal.to_s == straight_upward.to_s
1471
- if surface.subSurfaces.size > 0
1472
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Cannot currently handle non-horizontal roofs; skipping skylights on #{surface.name} in #{self.name} for daylight sensor positioning.")
1473
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---Surface #{surface.name} has outward normal of #{surface_normal.to_s.gsub(/\[|\]/,'|')}; up is #{straight_upward.to_s.gsub(/\[|\]/,'|')}.")
1009
+ unless surface.subSurfaces.empty?
1010
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Cannot currently handle non-horizontal roofs; skipping skylights on #{surface.name} in #{name} for daylight sensor positioning.")
1011
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---Surface #{surface.name} has outward normal of #{surface_normal.to_s.gsub(/\[|\]/, '|')}; up is #{straight_upward.to_s.gsub(/\[|\]/, '|')}.")
1474
1012
  next
1475
1013
  end
1476
1014
  end
1477
1015
  end
1478
-
1016
+
1479
1017
  # Find the azimuth of the facade
1480
1018
  facade = nil
1481
1019
  group = surface.planarSurfaceGroup
@@ -1483,47 +1021,47 @@ class OpenStudio::Model::Space
1483
1021
  group = group.get
1484
1022
  site_transformation = group.buildingTransformation
1485
1023
  site_vertices = site_transformation * surface.vertices
1486
- site_outward_normal = OpenStudio::getOutwardNormal(site_vertices)
1024
+ site_outward_normal = OpenStudio.getOutwardNormal(site_vertices)
1487
1025
  if site_outward_normal.empty?
1488
- OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Could not compute outward normal for #{surface.name.get}")
1026
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Space', "Could not compute outward normal for #{surface.name.get}")
1489
1027
  next
1490
1028
  end
1491
1029
  site_outward_normal = site_outward_normal.get
1492
- north = OpenStudio::Vector3d.new(0.0,1.0,0.0)
1493
- if site_outward_normal.x < 0.0
1494
- azimuth = 360.0 - OpenStudio::radToDeg(OpenStudio::getAngle(site_outward_normal, north))
1495
- else
1496
- azimuth = OpenStudio::radToDeg(OpenStudio::getAngle(site_outward_normal, north))
1497
- end
1030
+ north = OpenStudio::Vector3d.new(0.0, 1.0, 0.0)
1031
+ azimuth = if site_outward_normal.x < 0.0
1032
+ 360.0 - OpenStudio.radToDeg(OpenStudio.getAngle(site_outward_normal, north))
1033
+ else
1034
+ OpenStudio.radToDeg(OpenStudio.getAngle(site_outward_normal, north))
1035
+ end
1498
1036
  else
1499
1037
  # The surface is not in a group; should not hit, since
1500
1038
  # called from Space.surfaces
1501
1039
  next
1502
1040
  end
1503
-
1504
- #TODO modify to work for buildings in the southern hemisphere?
1505
- if (azimuth >= 315.0 || azimuth < 45.0)
1506
- facade = "4-North"
1507
- elsif (azimuth >= 45.0 && azimuth < 135.0)
1508
- facade = "3-East"
1509
- elsif (azimuth >= 135.0 && azimuth < 225.0)
1510
- facade = "1-South"
1511
- elsif (azimuth >= 225.0 && azimuth < 315.0)
1512
- facade = "2-West"
1041
+
1042
+ # TODO: modify to work for buildings in the southern hemisphere?
1043
+ if azimuth >= 315.0 || azimuth < 45.0
1044
+ facade = '4-North'
1045
+ elsif azimuth >= 45.0 && azimuth < 135.0
1046
+ facade = '3-East'
1047
+ elsif azimuth >= 135.0 && azimuth < 225.0
1048
+ facade = '1-South'
1049
+ elsif azimuth >= 225.0 && azimuth < 315.0
1050
+ facade = '2-West'
1513
1051
  end
1514
-
1052
+
1515
1053
  # Label the facade as "Up" if it is a skylight
1516
1054
  if surface_normal.to_s == straight_upward.to_s
1517
- facade = "0-Up"
1055
+ facade = '0-Up'
1518
1056
  end
1519
-
1520
- # Loop through all subsurfaces and
1057
+
1058
+ # Loop through all subsurfaces and
1521
1059
  surface.subSurfaces.sort.each do |sub_surface|
1522
- next unless sub_surface.outsideBoundaryCondition == "Outdoors" && (sub_surface.subSurfaceType == "FixedWindow" || sub_surface.subSurfaceType == "OperableWindow" || sub_surface.subSurfaceType == "Skylight")
1060
+ next unless sub_surface.outsideBoundaryCondition == 'Outdoors' && (sub_surface.subSurfaceType == 'FixedWindow' || sub_surface.subSurfaceType == 'OperableWindow' || sub_surface.subSurfaceType == 'Skylight')
1523
1061
 
1524
1062
  # Find the area
1525
1063
  net_area_m2 = sub_surface.netArea
1526
-
1064
+
1527
1065
  # Find the head height and sill height of the window
1528
1066
  vertex_heights_above_floor = []
1529
1067
  sub_surface.vertices.each do |vertex|
@@ -1531,292 +1069,290 @@ class OpenStudio::Model::Space
1531
1069
  vertex_heights_above_floor << (vertex - vertex_on_floorplane).length
1532
1070
  end
1533
1071
  head_height_m = vertex_heights_above_floor.max
1534
- #OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "---head height = #{head_height_m}m, sill height = #{sill_height_m}m")
1535
-
1072
+ # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "---head height = #{head_height_m}m, sill height = #{sill_height_m}m")
1073
+
1536
1074
  # Log the window properties to use when creating daylight sensors
1537
- properties = {:facade => facade, :area_m2 => net_area_m2, :handle => sub_surface.handle, :head_height_m => head_height_m, :name => sub_surface.name.get.to_s}
1075
+ properties = { facade: facade, area_m2: net_area_m2, handle: sub_surface.handle, head_height_m: head_height_m, name: sub_surface.name.get.to_s }
1538
1076
  if facade == '0-Up'
1539
1077
  skylights[sub_surface] = properties
1540
1078
  else
1541
1079
  windows[sub_surface] = properties
1542
1080
  end
1543
-
1544
- end #next sub-surface
1545
- end #next surface
1546
-
1081
+ end # next sub-surface
1082
+ end # next surface
1083
+
1547
1084
  # Determine the illuminance setpoint for the controls based on space type
1548
1085
  # From IESNA Handbook 10th Edition - Applications
1549
1086
  daylight_stpt_lux = 300
1550
- =begin
1551
-
1552
- space_type = self.space_type
1553
- if space_type.empty?
1554
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Space #{space.name} is an unknown space type, assuming Office and 300 Lux daylight setpoint")
1555
- else
1556
- space_type = space_type.get
1557
- std_spc_type = space_type.standardsSpaceType
1558
- if std_spc_type.empty?
1559
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Space #{space.name} does not have a defined standards space type, assuming Office and 300 Lux daylight setpoint")
1560
- else
1561
- std_spc_type = std_spc_type.get
1562
- case std_spc_type
1563
- when
1564
- Storage = 50
1565
- Corridor = 50
1566
- Corridor2 = 50
1567
- when
1568
- PatCorridor = 100
1569
- 'Banquet = 100
1570
- Basement = 100
1571
- Cafe = 100
1572
- Lobby = 100
1573
- when
1574
- Dining = 150
1575
- GuestRoom = 150
1576
- GuestRoom2 = 150
1577
- GuestRoom3 = 150
1578
- GuestRoom4 = 150
1579
- when
1580
- Mechanical = 200
1581
- Retail = 200
1582
- Retail2 = 200
1583
- when
1584
- Laundry = 300
1585
- Office = 300
1586
- when
1587
- ER_NurseStn = 500
1588
- ICU_Open = 500
1589
- ICU_PatRm = 500
1590
- Kitchen = 500
1591
- Lab = 500
1592
- NurseStn = 500
1593
- ICU_NurseStn = 500
1594
- PatRoom = 500
1595
- PhysTherapy = 500
1596
- Radiology = 500
1597
- when
1598
- ER_Exam = 1000
1599
- ER_Trauma = 1000
1600
- ER_Triage = 1000
1601
- when
1602
- OR = 2000
1603
-
1604
- FullServiceRestaurant.Dining
1605
- FullServiceRestaurant
1606
-
1607
- Hospital.Corridor
1608
- Hospital.Dining
1609
- Hospital.ER_Exam
1610
- Hospital.ER_NurseStn
1611
- Hospital.ER_Trauma
1612
- Hospital.ER_Triage
1613
- Hospital.ICU_NurseStn
1614
- Hospital.ICU_Open
1615
- Hospital.ICU_PatRm
1616
- Hospital.Kitchen
1617
- Hospital.Lab
1618
- Hospital.Lobby
1619
- Hospital.NurseStn
1620
- Hospital.Office
1621
- Hospital.OR
1622
- Hospital.PatCorridor
1623
- Hospital.PatRoom
1624
- Hospital.PhysTherapy
1625
- Hospital.Radiology
1626
-
1627
- LargeHotel.Banquet
1628
- LargeHotel.Basement
1629
- LargeHotel.Cafe
1630
- LargeHotel.Corridor
1631
- LargeHotel.Corridor2
1632
- LargeHotel.GuestRoom
1633
- LargeHotel.GuestRoom2
1634
- LargeHotel.GuestRoom3
1635
- LargeHotel.GuestRoom4
1636
- LargeHotel.Kitchen
1637
- LargeHotel.Laundry
1638
- LargeHotel.Lobby
1639
- LargeHotel.Mechanical
1640
- LargeHotel.Retail
1641
- LargeHotel.Retail2
1642
- LargeHotel.Storage
1643
-
1644
- MidriseApartment.Apartment
1645
- MidriseApartment.Corridor
1646
- MidriseApartment.Office
1647
-
1648
- Office
1649
- Office.Attic
1650
- Office.BreakRoom
1651
- Office.ClosedOffice
1652
- Office.Conference
1653
- Office.Corridor
1654
- Office.Elec/MechRoom
1655
- Office.IT_Room
1656
- Office.Lobby
1657
- Office.OpenOffice
1658
- Office.PrintRoom
1659
- Office.Restroom
1660
- Office.Stair
1661
- Office.Storage
1662
- Office.Vending
1663
- Office.WholeBuilding - Lg Office
1664
- Office.WholeBuilding - Md Office
1665
- Office.WholeBuilding - Sm Office
1666
-
1667
- Outpatient.Anesthesia
1668
- Outpatient.BioHazard
1669
- Outpatient.Cafe
1670
- Outpatient.CleanWork
1671
- Outpatient.Conference
1672
- Outpatient.DressingRoom
1673
- Outpatient.Elec/MechRoom
1674
- Outpatient.Exam
1675
- Outpatient.Hall
1676
- Outpatient.IT_Room
1677
- Outpatient.Janitor
1678
- Outpatient.Lobby
1679
- Outpatient.LockerRoom
1680
- Outpatient.Lounge
1681
- Outpatient.MedGas
1682
- Outpatient.MRI
1683
- Outpatient.MRI_Control
1684
- Outpatient.NurseStation
1685
- Outpatient.Office
1686
- Outpatient.OR
1687
- Outpatient.PACU
1688
- Outpatient.PhysicalTherapy
1689
- Outpatient.PreOp
1690
- Outpatient.ProcedureRoom
1691
- Outpatient.Soil Work
1692
- Outpatient.Stair
1693
- Outpatient.Toilet
1694
- Outpatient.Xray
1695
-
1696
- PrimarySchool.Cafeteria
1697
- PrimarySchool.Classroom
1698
- PrimarySchool.Corridor
1699
- PrimarySchool.Gym
1700
- PrimarySchool.Kitchen
1701
- PrimarySchool.Library
1702
- PrimarySchool.Lobby
1703
- PrimarySchool.Mechanical
1704
- PrimarySchool.Office
1705
- PrimarySchool.Restroom
1706
-
1707
- QuickServiceRestaurant.Dining
1708
- QuickServiceRestaurant.Kitchen
1709
-
1710
- Retail.Back_Space
1711
- Retail.Entry
1712
- Retail.Point_of_Sale
1713
- Retail.Retail
1714
-
1715
- SecondarySchool.Auditorium
1716
- SecondarySchool.Cafeteria
1717
- SecondarySchool.Classroom
1718
- SecondarySchool.Corridor
1719
- SecondarySchool.Gym
1720
- SecondarySchool.Kitchen
1721
- SecondarySchool.Library
1722
- SecondarySchool.Lobby
1723
- SecondarySchool.Mechanical
1724
- SecondarySchool.Office
1725
- SecondarySchool.Restroom
1726
-
1727
- SmallHotel.Attic
1728
- SmallHotel.Corridor
1729
- SmallHotel.Corridor4
1730
- SmallHotel.Elec/MechRoom
1731
- SmallHotel.ElevatorCore
1732
- SmallHotel.ElevatorCore4
1733
- SmallHotel.Exercise
1734
- SmallHotel.GuestLounge
1735
- SmallHotel.GuestRoom
1736
- SmallHotel.GuestRoom123Occ
1737
- SmallHotel.GuestRoom123Vac
1738
- SmallHotel.GuestRoom4Occ
1739
- SmallHotel.GuestRoom4Vac
1740
- SmallHotel.Laundry
1741
- SmallHotel.Mechanical
1742
- SmallHotel.Meeting
1743
- SmallHotel.Office
1744
- SmallHotel.PublicRestroom
1745
- SmallHotel.StaffLounge
1746
- SmallHotel.Stair
1747
- SmallHotel.Stair4
1748
- SmallHotel.Storage
1749
- SmallHotel.Storage4
1750
-
1751
- StripMall.WholeBuilding
1752
-
1753
- SuperMarket.Deli/Bakery
1754
- SuperMarket.DryStorage
1755
- SuperMarket.Office
1756
- SuperMarket.Sales/Produce
1757
-
1758
- Warehouse.Bulk
1759
- Warehouse.Fine
1760
- Warehouse.Office
1761
-
1762
-
1763
- if std_spc_type.match(/post-office/i)# Post Office 500 Lux
1764
- daylight_stpt_lux = 500
1765
- elsif std_spc_type.match(/medical-office/i)# Medical Office 3000 Lux
1766
- daylight_stpt_lux = 3000
1767
- elsif std_spc_type.match(/office/i)# Office 500 Lux
1768
- daylight_stpt_lux = 500
1769
- elsif std_spc_type.match(/education/i)# School 500 Lux
1770
- daylight_stpt_lux = 500
1771
- elsif std_spc_type.match(/retail/i)# Retail 1000 Lux
1772
- daylight_stpt_lux = 1000
1773
- elsif std_spc_type.match(/warehouse/i)# Warehouse 200 Lux
1774
- daylight_stpt_lux = 200
1775
- elsif std_spc_type.match(/hotel/i)# Hotel 300 Lux
1776
- daylight_stpt_lux = 300
1777
- elsif std_spc_type.match(/multifamily/i)# Apartment 200 Lux
1778
- daylight_stpt_lux = 200
1779
- elsif std_spc_type.match(/courthouse/i)# Courthouse 300 Lux
1780
- daylight_stpt_lux = 300
1781
- elsif std_spc_type.match(/library/i)# Library 500 Lux
1782
- daylight_stpt_lux = 500
1783
- elsif std_spc_type.match(/community-center/i)# Community Center 300 Lux
1784
- daylight_stpt_lux = 300
1785
- elsif std_spc_type.match(/senior-center/i)# Senior Center 1000 Lux
1786
- daylight_stpt_lux = 1000
1787
- elsif std_spc_type.match(/city-hall/i)# City Hall 500 Lux
1788
- daylight_stpt_lux = 500
1789
- else
1790
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Space #{std_spc_type} is an unknown space type, assuming office and 300 Lux daylight setpoint")
1791
- daylight_stpt_lux = 300
1792
- end
1793
- end
1794
- end
1795
- =end
1796
-
1797
- # Get the zone that the space is in
1798
- zone = self.thermalZone
1799
- if zone.empty?
1800
- OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Space #{self.name.get} has no thermal zone")
1087
+ #
1088
+ #
1089
+ # space_type = self.space_type
1090
+ # if space_type.empty?
1091
+ # OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Space #{space.name} is an unknown space type, assuming Office and 300 Lux daylight setpoint")
1092
+ # else
1093
+ # space_type = space_type.get
1094
+ # std_spc_type = space_type.standardsSpaceType
1095
+ # if std_spc_type.empty?
1096
+ # OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Space #{space.name} does not have a defined standards space type, assuming Office and 300 Lux daylight setpoint")
1097
+ # else
1098
+ # std_spc_type = std_spc_type.get
1099
+ # case std_spc_type
1100
+ # when
1101
+ # Storage = 50
1102
+ # Corridor = 50
1103
+ # Corridor2 = 50
1104
+ # when
1105
+ # PatCorridor = 100
1106
+ # 'Banquet = 100
1107
+ # Basement = 100
1108
+ # Cafe = 100
1109
+ # Lobby = 100
1110
+ # when
1111
+ # Dining = 150
1112
+ # GuestRoom = 150
1113
+ # GuestRoom2 = 150
1114
+ # GuestRoom3 = 150
1115
+ # GuestRoom4 = 150
1116
+ # when
1117
+ # Mechanical = 200
1118
+ # Retail = 200
1119
+ # Retail2 = 200
1120
+ # when
1121
+ # Laundry = 300
1122
+ # Office = 300
1123
+ # when
1124
+ # ER_NurseStn = 500
1125
+ # ICU_Open = 500
1126
+ # ICU_PatRm = 500
1127
+ # Kitchen = 500
1128
+ # Lab = 500
1129
+ # NurseStn = 500
1130
+ # ICU_NurseStn = 500
1131
+ # PatRoom = 500
1132
+ # PhysTherapy = 500
1133
+ # Radiology = 500
1134
+ # when
1135
+ # ER_Exam = 1000
1136
+ # ER_Trauma = 1000
1137
+ # ER_Triage = 1000
1138
+ # when
1139
+ # OR = 2000
1140
+ #
1141
+ # FullServiceRestaurant.Dining
1142
+ # FullServiceRestaurant
1143
+ #
1144
+ # Hospital.Corridor
1145
+ # Hospital.Dining
1146
+ # Hospital.ER_Exam
1147
+ # Hospital.ER_NurseStn
1148
+ # Hospital.ER_Trauma
1149
+ # Hospital.ER_Triage
1150
+ # Hospital.ICU_NurseStn
1151
+ # Hospital.ICU_Open
1152
+ # Hospital.ICU_PatRm
1153
+ # Hospital.Kitchen
1154
+ # Hospital.Lab
1155
+ # Hospital.Lobby
1156
+ # Hospital.NurseStn
1157
+ # Hospital.Office
1158
+ # Hospital.OR
1159
+ # Hospital.PatCorridor
1160
+ # Hospital.PatRoom
1161
+ # Hospital.PhysTherapy
1162
+ # Hospital.Radiology
1163
+ #
1164
+ # LargeHotel.Banquet
1165
+ # LargeHotel.Basement
1166
+ # LargeHotel.Cafe
1167
+ # LargeHotel.Corridor
1168
+ # LargeHotel.Corridor2
1169
+ # LargeHotel.GuestRoom
1170
+ # LargeHotel.GuestRoom2
1171
+ # LargeHotel.GuestRoom3
1172
+ # LargeHotel.GuestRoom4
1173
+ # LargeHotel.Kitchen
1174
+ # LargeHotel.Laundry
1175
+ # LargeHotel.Lobby
1176
+ # LargeHotel.Mechanical
1177
+ # LargeHotel.Retail
1178
+ # LargeHotel.Retail2
1179
+ # LargeHotel.Storage
1180
+ #
1181
+ # MidriseApartment.Apartment
1182
+ # MidriseApartment.Corridor
1183
+ # MidriseApartment.Office
1184
+ #
1185
+ # Office
1186
+ # Office.Attic
1187
+ # Office.BreakRoom
1188
+ # Office.ClosedOffice
1189
+ # Office.Conference
1190
+ # Office.Corridor
1191
+ # Office.Elec/MechRoom
1192
+ # Office.IT_Room
1193
+ # Office.Lobby
1194
+ # Office.OpenOffice
1195
+ # Office.PrintRoom
1196
+ # Office.Restroom
1197
+ # Office.Stair
1198
+ # Office.Storage
1199
+ # Office.Vending
1200
+ # Office.WholeBuilding - Lg Office
1201
+ # Office.WholeBuilding - Md Office
1202
+ # Office.WholeBuilding - Sm Office
1203
+ #
1204
+ # Outpatient.Anesthesia
1205
+ # Outpatient.BioHazard
1206
+ # Outpatient.Cafe
1207
+ # Outpatient.CleanWork
1208
+ # Outpatient.Conference
1209
+ # Outpatient.DressingRoom
1210
+ # Outpatient.Elec/MechRoom
1211
+ # Outpatient.Exam
1212
+ # Outpatient.Hall
1213
+ # Outpatient.IT_Room
1214
+ # Outpatient.Janitor
1215
+ # Outpatient.Lobby
1216
+ # Outpatient.LockerRoom
1217
+ # Outpatient.Lounge
1218
+ # Outpatient.MedGas
1219
+ # Outpatient.MRI
1220
+ # Outpatient.MRI_Control
1221
+ # Outpatient.NurseStation
1222
+ # Outpatient.Office
1223
+ # Outpatient.OR
1224
+ # Outpatient.PACU
1225
+ # Outpatient.PhysicalTherapy
1226
+ # Outpatient.PreOp
1227
+ # Outpatient.ProcedureRoom
1228
+ # Outpatient.Soil Work
1229
+ # Outpatient.Stair
1230
+ # Outpatient.Toilet
1231
+ # Outpatient.Xray
1232
+ #
1233
+ # PrimarySchool.Cafeteria
1234
+ # PrimarySchool.Classroom
1235
+ # PrimarySchool.Corridor
1236
+ # PrimarySchool.Gym
1237
+ # PrimarySchool.Kitchen
1238
+ # PrimarySchool.Library
1239
+ # PrimarySchool.Lobby
1240
+ # PrimarySchool.Mechanical
1241
+ # PrimarySchool.Office
1242
+ # PrimarySchool.Restroom
1243
+ #
1244
+ # QuickServiceRestaurant.Dining
1245
+ # QuickServiceRestaurant.Kitchen
1246
+ #
1247
+ # Retail.Back_Space
1248
+ # Retail.Entry
1249
+ # Retail.Point_of_Sale
1250
+ # Retail.Retail
1251
+ #
1252
+ # SecondarySchool.Auditorium
1253
+ # SecondarySchool.Cafeteria
1254
+ # SecondarySchool.Classroom
1255
+ # SecondarySchool.Corridor
1256
+ # SecondarySchool.Gym
1257
+ # SecondarySchool.Kitchen
1258
+ # SecondarySchool.Library
1259
+ # SecondarySchool.Lobby
1260
+ # SecondarySchool.Mechanical
1261
+ # SecondarySchool.Office
1262
+ # SecondarySchool.Restroom
1263
+ #
1264
+ # SmallHotel.Attic
1265
+ # SmallHotel.Corridor
1266
+ # SmallHotel.Corridor4
1267
+ # SmallHotel.Elec/MechRoom
1268
+ # SmallHotel.ElevatorCore
1269
+ # SmallHotel.ElevatorCore4
1270
+ # SmallHotel.Exercise
1271
+ # SmallHotel.GuestLounge
1272
+ # SmallHotel.GuestRoom
1273
+ # SmallHotel.GuestRoom123Occ
1274
+ # SmallHotel.GuestRoom123Vac
1275
+ # SmallHotel.GuestRoom4Occ
1276
+ # SmallHotel.GuestRoom4Vac
1277
+ # SmallHotel.Laundry
1278
+ # SmallHotel.Mechanical
1279
+ # SmallHotel.Meeting
1280
+ # SmallHotel.Office
1281
+ # SmallHotel.PublicRestroom
1282
+ # SmallHotel.StaffLounge
1283
+ # SmallHotel.Stair
1284
+ # SmallHotel.Stair4
1285
+ # SmallHotel.Storage
1286
+ # SmallHotel.Storage4
1287
+ #
1288
+ # StripMall.WholeBuilding
1289
+ #
1290
+ # SuperMarket.Deli/Bakery
1291
+ # SuperMarket.DryStorage
1292
+ # SuperMarket.Office
1293
+ # SuperMarket.Sales/Produce
1294
+ #
1295
+ # Warehouse.Bulk
1296
+ # Warehouse.Fine
1297
+ # Warehouse.Office
1298
+ #
1299
+ #
1300
+ # if std_spc_type.match(/post-office/i)# Post Office 500 Lux
1301
+ # daylight_stpt_lux = 500
1302
+ # elsif std_spc_type.match(/medical-office/i)# Medical Office 3000 Lux
1303
+ # daylight_stpt_lux = 3000
1304
+ # elsif std_spc_type.match(/office/i)# Office 500 Lux
1305
+ # daylight_stpt_lux = 500
1306
+ # elsif std_spc_type.match(/education/i)# School 500 Lux
1307
+ # daylight_stpt_lux = 500
1308
+ # elsif std_spc_type.match(/retail/i)# Retail 1000 Lux
1309
+ # daylight_stpt_lux = 1000
1310
+ # elsif std_spc_type.match(/warehouse/i)# Warehouse 200 Lux
1311
+ # daylight_stpt_lux = 200
1312
+ # elsif std_spc_type.match(/hotel/i)# Hotel 300 Lux
1313
+ # daylight_stpt_lux = 300
1314
+ # elsif std_spc_type.match(/multifamily/i)# Apartment 200 Lux
1315
+ # daylight_stpt_lux = 200
1316
+ # elsif std_spc_type.match(/courthouse/i)# Courthouse 300 Lux
1317
+ # daylight_stpt_lux = 300
1318
+ # elsif std_spc_type.match(/library/i)# Library 500 Lux
1319
+ # daylight_stpt_lux = 500
1320
+ # elsif std_spc_type.match(/community-center/i)# Community Center 300 Lux
1321
+ # daylight_stpt_lux = 300
1322
+ # elsif std_spc_type.match(/senior-center/i)# Senior Center 1000 Lux
1323
+ # daylight_stpt_lux = 1000
1324
+ # elsif std_spc_type.match(/city-hall/i)# City Hall 500 Lux
1325
+ # daylight_stpt_lux = 500
1326
+ # else
1327
+ # OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.Space", "Space #{std_spc_type} is an unknown space type, assuming office and 300 Lux daylight setpoint")
1328
+ # daylight_stpt_lux = 300
1329
+ # end
1330
+ # end
1331
+ # end
1332
+
1333
+ # Get the zone that the space is in
1334
+ zone = thermalZone
1335
+ if zone.empty?
1336
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Space', "Space #{name.get} has no thermal zone")
1801
1337
  else
1802
1338
  zone = zone.get
1803
- end
1804
-
1805
- # Sort by priority; first by facade, then by area,
1339
+ end
1340
+
1341
+ # Sort by priority; first by facade, then by area,
1806
1342
  # then by name to ensure deterministic in case identical in other ways
1807
- sorted_windows = windows.sort_by { |window, vals| [vals[:facade], vals[:area], vals[:name]] }
1808
- sorted_skylights = skylights.sort_by { |skylight, vals| [vals[:facade], vals[:area], vals[:name]] }
1809
-
1343
+ sorted_windows = windows.sort_by { |_window, vals| [vals[:facade], vals[:area], vals[:name]] }
1344
+ sorted_skylights = skylights.sort_by { |_skylight, vals| [vals[:facade], vals[:area], vals[:name]] }
1345
+
1810
1346
  # Report out the sorted skylights for debugging
1811
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{vintage} #{self.name}, Skylights:")
1347
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, Skylights:")
1812
1348
  sorted_skylights.each do |sky, p|
1813
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{sky.name} #{p[:facade]}, area = #{p[:area_m2].round(2)} m^2")
1349
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{sky.name} #{p[:facade]}, area = #{p[:area_m2].round(2)} m^2")
1814
1350
  end
1815
-
1351
+
1816
1352
  # Report out the sorted windows for debugging
1817
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "For #{vintage} #{self.name}, Windows:")
1353
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{name}, Windows:")
1818
1354
  sorted_windows.each do |win, p|
1819
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.Space", "---#{win.name} #{p[:facade]}, area = #{p[:area_m2].round(2)} m^2")
1355
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{win.name} #{p[:facade]}, area = #{p[:area_m2].round(2)} m^2")
1820
1356
  end
1821
1357
 
1822
1358
  # Add the required controls
@@ -1824,167 +1360,178 @@ Warehouse.Office
1824
1360
  sensor_2_frac = 0.0
1825
1361
  sensor_1_window = nil
1826
1362
  sensor_2_window = nil
1827
-
1828
- case vintage
1829
- when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007'
1830
-
1363
+
1364
+ case template
1365
+ when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007'
1366
+
1831
1367
  # Do nothing, no daylighting controls required
1832
-
1368
+
1833
1369
  when '90.1-2010', 'AssetScore'
1834
-
1370
+
1835
1371
  if req_top_ctrl && req_pri_ctrl
1836
1372
  # Sensor 1 controls toplighted area
1837
- sensor_1_frac = areas['toplighted_area']/space_area_m2
1373
+ sensor_1_frac = areas['toplighted_area'] / space_area_m2
1838
1374
  sensor_1_window = sorted_skylights[0]
1839
1375
  # Sensor 2 controls primary area
1840
- sensor_2_frac = areas['primary_sidelighted_area']/space_area_m2
1376
+ sensor_2_frac = areas['primary_sidelighted_area'] / space_area_m2
1841
1377
  sensor_2_window = sorted_windows[0]
1842
1378
  elsif req_top_ctrl && !req_pri_ctrl
1843
1379
  # Sensor 1 controls toplighted area
1844
- sensor_1_frac = areas['toplighted_area']/space_area_m2
1380
+ sensor_1_frac = areas['toplighted_area'] / space_area_m2
1845
1381
  sensor_1_window = sorted_skylights[0]
1846
1382
  elsif !req_top_ctrl && req_pri_ctrl
1847
1383
  if sorted_windows.size == 1
1848
1384
  # Sensor 1 controls the whole primary area
1849
- sensor_1_frac = areas['primary_sidelighted_area']/space_area_m2
1385
+ sensor_1_frac = areas['primary_sidelighted_area'] / space_area_m2
1850
1386
  sensor_1_window = sorted_windows[0]
1851
1387
  else
1852
1388
  # Sensor 1 controls half the primary area
1853
- sensor_1_frac = (areas['primary_sidelighted_area']/space_area_m2)/2
1389
+ sensor_1_frac = (areas['primary_sidelighted_area'] / space_area_m2) / 2
1854
1390
  sensor_1_window = sorted_windows[0]
1855
1391
  # Sensor 2 controls the other half of primary area
1856
- sensor_2_frac = (areas['primary_sidelighted_area']/space_area_m2)/2
1392
+ sensor_2_frac = (areas['primary_sidelighted_area'] / space_area_m2) / 2
1857
1393
  sensor_2_window = sorted_windows[1]
1858
- end
1394
+ end
1859
1395
  end
1860
-
1396
+
1861
1397
  when '90.1-2013'
1862
-
1398
+
1863
1399
  if req_top_ctrl && req_pri_ctrl && req_sec_ctrl
1864
1400
  # Sensor 1 controls toplighted area
1865
- sensor_1_frac = areas['toplighted_area']/space_area_m2
1401
+ sensor_1_frac = areas['toplighted_area'] / space_area_m2
1866
1402
  sensor_1_window = sorted_skylights[0]
1867
1403
  # Sensor 2 controls primary + secondary area
1868
- sensor_2_frac = (areas['primary_sidelighted_area'] + areas['secondary_sidelighted_area'])/space_area_m2
1404
+ sensor_2_frac = (areas['primary_sidelighted_area'] + areas['secondary_sidelighted_area']) / space_area_m2
1869
1405
  sensor_2_window = sorted_windows[0]
1870
1406
  elsif !req_top_ctrl && req_pri_ctrl && req_sec_ctrl
1871
1407
  # Sensor 1 controls primary area
1872
- sensor_1_frac = areas['primary_sidelighted_area']/space_area_m2
1873
- sensor_1_window = sorted_windows[0]
1408
+ sensor_1_frac = areas['primary_sidelighted_area'] / space_area_m2
1409
+ sensor_1_window = sorted_windows[0]
1874
1410
  # Sensor 2 controls secondary area
1875
- sensor_2_frac = (areas['secondary_sidelighted_area']/space_area_m2)/2
1411
+ sensor_2_frac = (areas['secondary_sidelighted_area'] / space_area_m2)
1876
1412
  sensor_2_window = sorted_windows[0]
1877
1413
  elsif req_top_ctrl && !req_pri_ctrl && req_sec_ctrl
1878
1414
  # Sensor 1 controls toplighted area
1879
- sensor_1_frac = areas['toplighted_area']/space_area_m2
1415
+ sensor_1_frac = areas['toplighted_area'] / space_area_m2
1880
1416
  sensor_1_window = sorted_skylights[0]
1881
1417
  # Sensor 2 controls secondary area
1882
- sensor_2_frac = (areas['secondary_sidelighted_area']/space_area_m2)/2
1418
+ sensor_2_frac = (areas['secondary_sidelighted_area'] / space_area_m2)
1883
1419
  sensor_2_window = sorted_windows[0]
1884
1420
  elsif req_top_ctrl && !req_pri_ctrl && !req_sec_ctrl
1885
1421
  # Sensor 1 controls toplighted area
1886
- sensor_1_frac = areas['toplighted_area']/space_area_m2
1887
- sensor_1_window = sorted_skylights[0]
1422
+ sensor_1_frac = areas['toplighted_area'] / space_area_m2
1423
+ sensor_1_window = sorted_skylights[0]
1888
1424
  elsif !req_top_ctrl && req_pri_ctrl && !req_sec_ctrl
1889
1425
  # Sensor 1 controls primary area
1890
- sensor_1_frac = areas['primary_sidelighted_area']/space_area_m2
1891
- sensor_1_window = sorted_windows[0]
1426
+ sensor_1_frac = areas['primary_sidelighted_area'] / space_area_m2
1427
+ sensor_1_window = sorted_windows[0]
1892
1428
  elsif !req_top_ctrl && !req_pri_ctrl && req_sec_ctrl
1893
1429
  # Sensor 1 controls secondary area
1894
- sensor_1_frac = areas['secondary_sidelighted_area']/space_area_m2
1895
- sensor_1_window = sorted_windows[0]
1430
+ sensor_1_frac = areas['secondary_sidelighted_area'] / space_area_m2
1431
+ sensor_1_window = sorted_windows[0]
1896
1432
  end
1897
-
1898
- end # End of vintage case statement
1899
-
1433
+
1434
+ end # End of template case statement
1435
+
1900
1436
  # Place the sensors and set control fractions
1901
1437
  # get the zone that the space is in
1902
- zone = self.thermalZone
1438
+ zone = thermalZone
1903
1439
  if zone.empty?
1904
- OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Space #{self.name}, cannot determine daylighted areas.")
1440
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Space', "Space #{name}, cannot determine daylighted areas.")
1905
1441
  return false
1906
1442
  else
1907
- zone = self.thermalZone.get
1443
+ zone = thermalZone.get
1908
1444
  end
1909
-
1445
+
1446
+ # Ensure that total controlled fraction
1447
+ # is never set above 1 (100%)
1448
+ if sensor_1_frac + sensor_2_frac >= 1.0
1449
+ # Lower sensor_2_frac so that the total
1450
+ # is just slightly lower than 1.0
1451
+ sensor_2_frac = 1.0 - sensor_1_frac - 0.001
1452
+ end
1453
+
1910
1454
  # Sensors
1911
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, sensor 1 fraction = #{sensor_1_frac.round(2)}.")
1912
- OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{vintage} #{self.name}, sensor 2 fraction = #{sensor_2_frac.round(2)}.")
1913
-
1455
+ if sensor_1_frac > 0.0
1456
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, sensor 1 controls #{(sensor_1_frac * 100).round}% of the zone lighting.")
1457
+ end
1458
+ if sensor_2_frac > 0.0
1459
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{name}, sensor 2 controls #{(sensor_2_frac * 100).round}% of the zone lighting.")
1460
+ end
1461
+
1914
1462
  # First sensor
1915
1463
  if sensor_1_window
1916
1464
  # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{self.name}, calculating daylighted areas.")
1917
1465
  # runner.registerInfo("Daylight sensor 1 inside of #{sensor_1_frac.name}")
1918
1466
  sensor_1 = OpenStudio::Model::DaylightingControl.new(model)
1919
- sensor_1.setName("#{self.name} Daylt Sensor 1")
1467
+ sensor_1.setName("#{name} Daylt Sensor 1")
1920
1468
  sensor_1.setSpace(self)
1921
1469
  sensor_1.setIlluminanceSetpoint(daylight_stpt_lux)
1922
- sensor_1.setLightingControlType("Stepped")
1923
- sensor_1.setNumberofSteppedControlSteps(3) #all sensors 3-step per design
1470
+ sensor_1.setLightingControlType('Stepped')
1471
+ sensor_1.setNumberofSteppedControlSteps(3) # all sensors 3-step per design
1924
1472
  # Place sensor depending on skylight or window
1925
1473
  sensor_vertex = nil
1926
1474
  if sensor_1_window[1][:facade] == '0-Up'
1927
1475
  sub_surface = sensor_1_window[0]
1928
1476
  outward_normal = sub_surface.outwardNormal
1929
- centroid = OpenStudio::getCentroid(sub_surface.vertices).get
1930
- ht_above_flr = OpenStudio::convert(3.0, "ft", "m").get
1477
+ centroid = OpenStudio.getCentroid(sub_surface.vertices).get
1478
+ ht_above_flr = OpenStudio.convert(3.0, 'ft', 'm').get
1931
1479
  outward_normal.setLength(sensor_1_window[1][:head_height_m] - ht_above_flr)
1932
1480
  sensor_vertex = centroid + outward_normal.reverseVector
1933
1481
  else
1934
1482
  sub_surface = sensor_1_window[0]
1935
1483
  window_outward_normal = sub_surface.outwardNormal
1936
- window_centroid = OpenStudio::getCentroid(sub_surface.vertices).get
1484
+ window_centroid = OpenStudio.getCentroid(sub_surface.vertices).get
1937
1485
  window_outward_normal.setLength(sensor_1_window[1][:head_height_m])
1938
1486
  vertex = window_centroid + window_outward_normal.reverseVector
1939
1487
  vertex_on_floorplane = floor_surface.plane.project(vertex)
1940
1488
  floor_outward_normal = floor_surface.outwardNormal
1941
- floor_outward_normal.setLength(OpenStudio::convert(3.0, "ft", "m").get)
1489
+ floor_outward_normal.setLength(OpenStudio.convert(3.0, 'ft', 'm').get)
1942
1490
  sensor_vertex = vertex_on_floorplane + floor_outward_normal.reverseVector
1943
1491
  end
1944
1492
  sensor_1.setPosition(sensor_vertex)
1945
- #TODO rotate sensor to face window (only needed for glare calcs)
1493
+ # TODO: rotate sensor to face window (only needed for glare calcs)
1946
1494
  zone.setPrimaryDaylightingControl(sensor_1)
1947
1495
  zone.setFractionofZoneControlledbyPrimaryDaylightingControl(sensor_1_frac)
1948
1496
  end
1949
-
1497
+
1950
1498
  # Second sensor
1951
1499
  if sensor_2_window
1952
1500
  # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "For #{self.name}, calculating daylighted areas.")
1953
1501
  # runner.registerInfo("Daylight sensor 2 inside of #{sensor_2_frac.name}")
1954
1502
  sensor_2 = OpenStudio::Model::DaylightingControl.new(model)
1955
- sensor_2.setName("#{self.name} Daylt Sensor 2")
1503
+ sensor_2.setName("#{name} Daylt Sensor 2")
1956
1504
  sensor_2.setSpace(self)
1957
1505
  sensor_2.setIlluminanceSetpoint(daylight_stpt_lux)
1958
- sensor_2.setLightingControlType("Stepped")
1959
- sensor_2.setNumberofSteppedControlSteps(3) #all sensors 3-step per design
1506
+ sensor_2.setLightingControlType('Stepped')
1507
+ sensor_2.setNumberofSteppedControlSteps(3) # all sensors 3-step per design
1960
1508
  # Place sensor depending on skylight or window
1961
1509
  sensor_vertex = nil
1962
1510
  if sensor_2_window[1][:facade] == '0-Up'
1963
1511
  sub_surface = sensor_2_window[0]
1964
1512
  outward_normal = sub_surface.outwardNormal
1965
- centroid = OpenStudio::getCentroid(sub_surface.vertices).get
1966
- ht_above_flr = OpenStudio::convert(3.0, "ft", "m").get
1513
+ centroid = OpenStudio.getCentroid(sub_surface.vertices).get
1514
+ ht_above_flr = OpenStudio.convert(3.0, 'ft', 'm').get
1967
1515
  outward_normal.setLength(sensor_2_window[1][:head_height_m] - ht_above_flr)
1968
1516
  sensor_vertex = centroid + outward_normal.reverseVector
1969
1517
  else
1970
1518
  sub_surface = sensor_2_window[0]
1971
1519
  window_outward_normal = sub_surface.outwardNormal
1972
- window_centroid = OpenStudio::getCentroid(sub_surface.vertices).get
1520
+ window_centroid = OpenStudio.getCentroid(sub_surface.vertices).get
1973
1521
  window_outward_normal.setLength(sensor_2_window[1][:head_height_m])
1974
1522
  vertex = window_centroid + window_outward_normal.reverseVector
1975
1523
  vertex_on_floorplane = floor_surface.plane.project(vertex)
1976
1524
  floor_outward_normal = floor_surface.outwardNormal
1977
- floor_outward_normal.setLength(OpenStudio::convert(3.0, "ft", "m").get)
1525
+ floor_outward_normal.setLength(OpenStudio.convert(3.0, 'ft', 'm').get)
1978
1526
  sensor_vertex = vertex_on_floorplane + floor_outward_normal.reverseVector
1979
1527
  end
1980
1528
  sensor_2.setPosition(sensor_vertex)
1981
- #TODO rotate sensor to face window (only needed for glare calcs)
1529
+ # TODO: rotate sensor to face window (only needed for glare calcs)
1982
1530
  zone.setSecondaryDaylightingControl(sensor_2)
1983
1531
  zone.setFractionofZoneControlledbySecondaryDaylightingControl(sensor_2_frac)
1984
1532
  end
1985
-
1533
+
1986
1534
  return true
1987
-
1988
1535
  end
1989
1536
 
1990
1537
  # Set the infiltration rate for this space to include
@@ -1993,65 +1540,90 @@ Warehouse.Office
1993
1540
  # @param template [String] choices are 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
1994
1541
  # @return [Double] true if successful, false if not
1995
1542
  # @todo handle doors and vestibules
1996
- def set_infiltration_rate(template)
1997
-
1543
+ def apply_infiltration_rate(template)
1998
1544
  # Define the total building baseline infiltration rate
1999
1545
  basic_infil_rate_cfm_per_ft2 = nil
2000
1546
  infil_type = nil
2001
- case template
1547
+ case template
2002
1548
  when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
2003
- OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.Model", "For #{template}, infiltration rates are not defined using this method, no changes have been made to the model.")
1549
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, infiltration rates are not defined using this method, no changes have been made to the model.")
2004
1550
  return true
2005
1551
  when '90.1-2004', '90.1-2007'
2006
1552
  basic_infil_rate_cfm_per_ft2 = 1.8
2007
1553
  when '90.1-2010', '90.1-2013'
2008
1554
  basic_infil_rate_cfm_per_ft2 = 1.0
2009
- end
2010
-
1555
+ end
1556
+
2011
1557
  # Conversion factor
2012
1558
  # 1 m^3/s*m^2 = 196.85 cfm/ft2
2013
1559
  conv_fact = 196.85
2014
-
1560
+
2015
1561
  # Adjust the infiltration rate to the average pressure
2016
1562
  # for the prototype buildings.
2017
- adj_infil_rate_cfm_per_ft2 = adjust_infiltration_to_prototype_building_conditions(basic_infil_rate_cfm_per_ft2)
2018
- adj_infil_rate_m3_per_s_per_m2 = adj_infil_rate_cfm_per_ft2 / conv_fact
2019
-
2020
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}, infil = #{adj_infil_rate_cfm_per_ft2.round(8)} cfm/ft2.")
2021
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}, infil = #{adj_infil_rate_m3_per_s_per_m2.round(8)} m^3/s*m^2.")
2022
-
2023
- # Get the exterior wall area
2024
- exterior_wall_and_window_area_m2 = self.exterior_wall_and_window_area
2025
-
2026
- # Don't create an object if there is no exterior wall area
2027
- if exterior_wall_and_window_area_m2 <= 0.0
2028
- OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.Model", "For #{template}, no exterior wall area was found, no infiltration will be added.")
2029
- return true
1563
+ adj_infil_rate_m3_per_s_per_m2 = nil
1564
+ all_ext_infil_m3_per_s_per_m2 = nil
1565
+ case template
1566
+ when 'NECB 2011'
1567
+ # Remove infiltration rates set at the space type.
1568
+ unless spaceType.empty?
1569
+ spaceType.get.spaceInfiltrationDesignFlowRates.each(&:remove)
1570
+ end
1571
+ # Remove infiltration rates set at the space object.
1572
+ spaceInfiltrationDesignFlowRates.each(&:remove)
1573
+
1574
+ adj_infil_rate_m3_per_s_per_m2 = 0.25 * 0.001 # m3/s/m2
1575
+ exterior_wall_and_roof_and_subsurface_area = self.exterior_wall_and_roof_and_subsurface_area # To do
1576
+ # Don't create an object if there is no exterior wall area
1577
+ if exterior_wall_and_roof_and_subsurface_area <= 0.0
1578
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, no exterior wall area was found, no infiltration will be added.")
1579
+ return true
1580
+ end
1581
+ # Calculate the total infiltration, assuming
1582
+ # that it only occurs through exterior walls and roofs (not floors as
1583
+ # explicit stated in the NECB 2011 so overhang/cantilevered floors will
1584
+ # have no effective infiltration)
1585
+ tot_infil_m3_per_s = adj_infil_rate_m3_per_s_per_m2 * exterior_wall_and_roof_and_subsurface_area
1586
+ # Now spread the total infiltration rate over all
1587
+ # exterior surface area (for the E+ input field) this will include the exterior floor if present.
1588
+ all_ext_infil_m3_per_s_per_m2 = tot_infil_m3_per_s / exteriorArea
1589
+
1590
+ when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
1591
+ adj_infil_rate_cfm_per_ft2 = adjust_infiltration_to_prototype_building_conditions(basic_infil_rate_cfm_per_ft2)
1592
+ adj_infil_rate_m3_per_s_per_m2 = adj_infil_rate_cfm_per_ft2 / conv_fact
1593
+ # Get the exterior wall area
1594
+ exterior_wall_and_window_area_m2 = exterior_wall_and_window_area
1595
+
1596
+ # Don't create an object if there is no exterior wall area
1597
+ if exterior_wall_and_window_area_m2 <= 0.0
1598
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, no exterior wall area was found, no infiltration will be added.")
1599
+ return true
1600
+ end
1601
+
1602
+ # Calculate the total infiltration, assuming
1603
+ # that it only occurs through exterior walls
1604
+ tot_infil_m3_per_s = adj_infil_rate_m3_per_s_per_m2 * exterior_wall_and_window_area_m2
1605
+
1606
+ # Now spread the total infiltration rate over all
1607
+ # exterior surface areas (for the E+ input field)
1608
+ all_ext_infil_m3_per_s_per_m2 = tot_infil_m3_per_s / exteriorArea
1609
+
2030
1610
  end
2031
-
2032
- # Calculate the total infiltration, assuming
2033
- # that it only occurs through exterior walls
2034
- tot_infil_m3_per_s = adj_infil_rate_m3_per_s_per_m2 * exterior_wall_and_window_area_m2
2035
1611
 
2036
- # Now spread the total infiltration rate over all
2037
- # exterior surface area (for the E+ input field)
2038
- all_ext_infil_m3_per_s_per_m2 = tot_infil_m3_per_s / self.exteriorArea
2039
-
2040
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}, adj infil = #{all_ext_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.")
1612
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Space', "For #{name}, adj infil = #{all_ext_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.")
2041
1613
 
2042
1614
  # Get any infiltration schedule already assigned to this space or its space type
2043
1615
  # If not, the always on schedule will be applied.
2044
1616
  infil_sch = nil
2045
- if self.spaceInfiltrationDesignFlowRates.size > 0
2046
- old_infil = self.spaceInfiltrationDesignFlowRates[0]
1617
+ unless spaceInfiltrationDesignFlowRates.empty?
1618
+ old_infil = spaceInfiltrationDesignFlowRates[0]
2047
1619
  if old_infil.schedule.is_initialized
2048
1620
  infil_sch = old_infil.schedule.get
2049
1621
  end
2050
1622
  end
2051
1623
 
2052
- if infil_sch.nil? && self.spaceType.is_initialized
2053
- space_type = self.spaceType.get
2054
- if space_type.spaceInfiltrationDesignFlowRates.size > 0
1624
+ if infil_sch.nil? && spaceType.is_initialized
1625
+ space_type = spaceType.get
1626
+ unless space_type.spaceInfiltrationDesignFlowRates.empty?
2055
1627
  old_infil = space_type.spaceInfiltrationDesignFlowRates[0]
2056
1628
  if old_infil.schedule.is_initialized
2057
1629
  infil_sch = old_infil.schedule.get
@@ -2060,26 +1632,25 @@ Warehouse.Office
2060
1632
  end
2061
1633
 
2062
1634
  if infil_sch.nil?
2063
- infil_sch = self.model.alwaysOnDiscreteSchedule
1635
+ infil_sch = model.alwaysOnDiscreteSchedule
2064
1636
  end
2065
-
1637
+
2066
1638
  # Create an infiltration rate object for this space
2067
- infiltration = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(self.model)
2068
- infiltration.setName("#{self.name} Infiltration")
2069
- #infiltration.setFlowperExteriorWallArea(adj_infil_rate_m3_per_s_per_m2)
1639
+ infiltration = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(model)
1640
+ infiltration.setName("#{name} Infiltration")
1641
+ # infiltration.setFlowperExteriorWallArea(adj_infil_rate_m3_per_s_per_m2)
2070
1642
  infiltration.setFlowperExteriorSurfaceArea(all_ext_infil_m3_per_s_per_m2)
2071
1643
  infiltration.setSchedule(infil_sch)
2072
1644
  infiltration.setConstantTermCoefficient(0.0)
2073
- infiltration.setTemperatureTermCoefficient (0.0)
1645
+ infiltration.setTemperatureTermCoefficient 0.0
2074
1646
  infiltration.setVelocityTermCoefficient(0.224)
2075
- infiltration.setVelocitySquaredTermCoefficient(0.0)
2076
-
1647
+ infiltration.setVelocitySquaredTermCoefficient(0.0)
1648
+
2077
1649
  infiltration.setSpace(self)
2078
-
1650
+
2079
1651
  return true
2080
-
2081
1652
  end
2082
-
1653
+
2083
1654
  # Determine the component infiltration rate for this space
2084
1655
  #
2085
1656
  # @param template [String] choices are 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
@@ -2088,30 +1659,29 @@ Warehouse.Office
2088
1659
  # @todo handle floors over unconditioned spaces
2089
1660
  # @todo make subsurface infil rates part of Surface.component_infiltration_rate?
2090
1661
  def component_infiltration_rate(template)
2091
-
2092
1662
  # Define the total building baseline infiltration rate
2093
1663
  basic_infil_rate_cfm_per_ft2 = nil
2094
1664
  infil_type = nil
2095
- case template
1665
+ case template
2096
1666
  when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
2097
- OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.Model", "For #{template}, infiltration rates are not defined using this method, no changes have been made to the model.")
1667
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Standards.Model', "For #{template}, infiltration rates are not defined using this method, no changes have been made to the model.")
2098
1668
  return true
2099
1669
  when '90.1-2004', '90.1-2007'
2100
1670
  basic_infil_rate_cfm_per_ft2 = 1.8
2101
1671
  when '90.1-2010', '90.1-2013'
2102
1672
  basic_infil_rate_cfm_per_ft2 = 1.0
2103
- end
2104
-
1673
+ end
1674
+
2105
1675
  # Calculate the basic infiltration rate
2106
- ext_area_m2 = self.exteriorArea
2107
- ext_area_ft2 = OpenStudio.convert(ext_area_m2,'m^2','ft^2').get
1676
+ ext_area_m2 = exteriorArea
1677
+ ext_area_ft2 = OpenStudio.convert(ext_area_m2, 'm^2', 'ft^2').get
2108
1678
  basic_infil_cfm = basic_infil_rate_cfm_per_ft2 * ext_area_ft2
2109
- basic_infil_m3_per_s = OpenStudio.convert(basic_infil_cfm,'cfm','m^3/s').get
2110
-
1679
+ basic_infil_m3_per_s = OpenStudio.convert(basic_infil_cfm, 'cfm', 'm^3/s').get
1680
+
2111
1681
  # Calculate the baseline component infiltration rate
2112
1682
  infil_type = 'baseline'
2113
1683
  base_comp_infil_m3_per_s = 0.0
2114
- self.surfaces.sort.each do |surface|
1684
+ surfaces.sort.each do |surface|
2115
1685
  # This surface
2116
1686
  base_comp_infil_m3_per_s += surface.component_infiltration_rate(infil_type)
2117
1687
  # Subsurfaces in this surface
@@ -2120,12 +1690,12 @@ Warehouse.Office
2120
1690
  base_comp_infil_m3_per_s += subsurface.component_infiltration_rate(infil_type)
2121
1691
  end
2122
1692
  end
2123
- base_comp_infil_cfm = OpenStudio.convert(base_comp_infil_m3_per_s,'m^3/s','cfm').get
2124
-
1693
+ base_comp_infil_cfm = OpenStudio.convert(base_comp_infil_m3_per_s, 'm^3/s', 'cfm').get
1694
+
2125
1695
  # Calculate the advanced component infiltration rate
2126
1696
  infil_type = 'advanced'
2127
1697
  adv_comp_infil_m3_per_s = 0.0
2128
- self.surfaces.sort.each do |surface|
1698
+ surfaces.sort.each do |surface|
2129
1699
  # This surface
2130
1700
  adv_comp_infil_m3_per_s += surface.component_infiltration_rate(infil_type)
2131
1701
  # Subsurfaces in this surface
@@ -2134,38 +1704,36 @@ Warehouse.Office
2134
1704
  adv_comp_infil_m3_per_s += subsurface.component_infiltration_rate(infil_type)
2135
1705
  end
2136
1706
  end
2137
- adv_comp_infil_cfm = OpenStudio.convert(adv_comp_infil_m3_per_s,'m^3/s','cfm').get
1707
+ adv_comp_infil_cfm = OpenStudio.convert(adv_comp_infil_m3_per_s, 'm^3/s', 'cfm').get
2138
1708
 
2139
1709
  # Calculate the adjusted infiltration rate
2140
1710
  infil_m3_per_s = basic_infil_m3_per_s - base_comp_infil_m3_per_s + adv_comp_infil_m3_per_s
2141
-
1711
+
2142
1712
  # Adjust the infiltration from 75Pa to 4Pa
2143
1713
  intial_pressure_pa = 75.0
2144
1714
  final_pressure_pa = 4.0
2145
- adj_infil_m3_per_s = adjust_infiltration_to_lower_pressure(infil_m3_per_s, intial_pressure_pa, final_pressure_pa, )
2146
-
1715
+ adj_infil_m3_per_s = adjust_infiltration_to_lower_pressure(infil_m3_per_s, intial_pressure_pa, final_pressure_pa)
1716
+
2147
1717
  # Calculate the rate per exterior area
2148
1718
  adj_infil_m3_per_s_per_m2 = adj_infil_m3_per_s / ext_area_m2
2149
-
2150
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}, infil = #{adj_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.")
1719
+
1720
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.Standards.Space', "For #{name}, infil = #{adj_infil_m3_per_s_per_m2.round(8)} m^3/s*m^2.")
2151
1721
  #=> infil = #{comp_infil_rate_m3_per_s.round(2)} m^3/s, ext area = #{tot_ext_area_m2.round} m^2")
2152
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}, comp infil = #{comp_infil_rate_cfm_per_ft2.round(4)} cfm/ft2 => infil = #{comp_infil_rate_cfm.round(2)} cfm, ext area = #{tot_ext_area_ft2.round} ft2")
2153
- #OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}")
1722
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}, comp infil = #{comp_infil_rate_cfm_per_ft2.round(4)} cfm/ft2 => infil = #{comp_infil_rate_cfm.round(2)} cfm, ext area = #{tot_ext_area_ft2.round} ft2")
1723
+ # OpenStudio::logFree(OpenStudio::Debug, "openstudio.Standards.Space", "For #{self.name}")
2154
1724
 
2155
1725
  return adj_infil_m3_per_s
2156
-
2157
1726
  end
2158
-
1727
+
2159
1728
  # Calculate the area of the exterior walls,
2160
1729
  # including the area of the windows on these walls.
2161
1730
  #
2162
1731
  # @return [Double] area in m^2
2163
- def exterior_wall_and_window_area()
2164
-
1732
+ def exterior_wall_and_window_area
2165
1733
  area_m2 = 0.0
2166
-
1734
+
2167
1735
  # Loop through all surfaces in this space
2168
- self.surfaces.sort.each do |surface|
1736
+ surfaces.sort.each do |surface|
2169
1737
  # Skip non-outdoor surfaces
2170
1738
  next unless surface.outsideBoundaryCondition == 'Outdoors'
2171
1739
  # Skip non-walls
@@ -2179,101 +1747,716 @@ Warehouse.Office
2179
1747
  end
2180
1748
 
2181
1749
  return area_m2
2182
-
1750
+ end
1751
+
1752
+ # Calculate the area of the exterior walls,
1753
+ # including the area of the windows on these walls.
1754
+ #
1755
+ # @return [Double] area in m^2
1756
+ def exterior_wall_and_roof_and_subsurface_area
1757
+ area_m2 = 0.0
1758
+
1759
+ # Loop through all surfaces in this space
1760
+ surfaces.sort.each do |surface|
1761
+ # Skip non-outdoor surfaces
1762
+ next unless surface.outsideBoundaryCondition == 'Outdoors'
1763
+ # Skip non-walls
1764
+ next unless surface.surfaceType == 'Wall' || surface.surfaceType == 'RoofCeiling'
1765
+ # This surface
1766
+ area_m2 += surface.netArea
1767
+ # Subsurfaces in this surface
1768
+ surface.subSurfaces.sort.each do |subsurface|
1769
+ area_m2 += subsurface.netArea
1770
+ end
1771
+ end
1772
+
1773
+ return area_m2
2183
1774
  end
2184
1775
 
2185
1776
  # Determine if the space is a plenum.
2186
1777
  # Assume it is a plenum if it is a supply
2187
- # or return plenum for an AirLoop, or
2188
- # if it is not part of the total floor area.
1778
+ # or return plenum for an AirLoop,
1779
+ # if it is not part of the total floor area,
1780
+ # or if the space type name contains the
1781
+ # word plenum.
2189
1782
  #
2190
1783
  # return [Bool] returns true if plenum, false if not
2191
- def is_plenum
2192
-
1784
+ def plenum?
2193
1785
  plenum_status = false
2194
1786
 
2195
- # Check if it is part of a zone
2196
- # that is a supply/return plenum
2197
- zone = self.thermalZone
2198
- if zone.is_initialized
2199
- if zone.get.isPlenum
2200
- plenum_status = true
2201
- end
2202
- end
2203
-
2204
1787
  # Check if it is designated
2205
1788
  # as not part of the building
2206
- # floor area.
2207
- # todo - update to check if it has internal loads
2208
- unless self.partofTotalFloorArea
1789
+ # floor area. This method internally
1790
+ # also checks to see if the space's zone
1791
+ # is a supply or return plenum
1792
+ unless partofTotalFloorArea
2209
1793
  plenum_status = true
1794
+ return plenum_status
2210
1795
  end
2211
1796
 
2212
- return plenum_status
1797
+ # TODO: - update to check if it has internal loads
1798
+
1799
+ # Check if the space type name
1800
+ # contains the word plenum.
1801
+ space_type = spaceType
1802
+ if space_type.is_initialized
1803
+ space_type = space_type.get
1804
+ if space_type.name.get.to_s.downcase.include?('plenum')
1805
+ plenum_status = true
1806
+ return plenum_status
1807
+ end
1808
+ if space_type.standardsSpaceType.is_initialized
1809
+ if space_type.standardsSpaceType.get.downcase.include?('plenum')
1810
+ plenum_status = true
1811
+ return plenum_status
1812
+ end
1813
+ end
1814
+ end
2213
1815
 
1816
+ return plenum_status
2214
1817
  end
2215
-
1818
+
2216
1819
  # Determine if the space is residential based on the
2217
1820
  # space type properties for the space.
2218
1821
  # For spaces with no space type, assume nonresidential.
1822
+ # For spaces that are plenums, base the decision on the space
1823
+ # type of the space below the largest floor in the plenum.
2219
1824
  #
2220
1825
  # return [Bool] true if residential, false if nonresidential
2221
- def is_residential(standard)
2222
-
1826
+ def residential?(template)
2223
1827
  is_res = false
2224
-
2225
- space_type = self.spaceType
1828
+
1829
+ space_to_check = self
1830
+
1831
+ # If this space is a plenum, check the space type
1832
+ # of the space below the largest floor in the space
1833
+ if plenum?
1834
+ # Find the largest floor
1835
+ largest_floor_area = 0.0
1836
+ largest_surface = nil
1837
+ surfaces.each do |surface|
1838
+ next unless surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Surface'
1839
+ if surface.grossArea > largest_floor_area
1840
+ largest_floor_area = surface.grossArea
1841
+ largest_surface = surface
1842
+ end
1843
+ end
1844
+ if largest_surface.nil?
1845
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{name} is a plenum, but could not find a floor with a space below it to determine if plenum should be res or nonres. Assuming nonresidential.")
1846
+ return is_res
1847
+ end
1848
+ # Get the space on the other side of this floor
1849
+ if largest_surface.adjacentSurface.is_initialized
1850
+ adj_surface = largest_surface.adjacentSurface.get
1851
+ if adj_surface.space.is_initialized
1852
+ space_to_check = adj_surface.space.get
1853
+ else
1854
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{name} is a plenum, but could not find a space attached to the largest floor's adjacent surface #{adj_surface.name} to determine if plenum should be res or nonres. Assuming nonresidential.")
1855
+ return is_res
1856
+ end
1857
+ else
1858
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "#{name} is a plenum, but could not find a floor with a space below it to determine if plenum should be res or nonres. Assuming nonresidential.")
1859
+ return is_res
1860
+ end
1861
+ end
1862
+
1863
+ space_type = space_to_check.spaceType
2226
1864
  if space_type.is_initialized
2227
1865
  space_type = space_type.get
2228
1866
  # Get the space type data
2229
- space_type_properties = space_type.get_standards_data(standard)
1867
+ space_type_properties = space_type.get_standards_data(template)
2230
1868
  if space_type_properties.nil?
2231
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.Space', "Could not find space type properties for #{self.name}, assuming nonresidential.")
1869
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "Could not find space type properties for #{space_to_check.name}, assuming nonresidential.")
2232
1870
  is_res = false
2233
1871
  else
2234
- if space_type_properties['is_residential'] == "Yes"
2235
- is_res = true
2236
- else
2237
- is_res = false
2238
- end
1872
+ is_res = if space_type_properties['is_residential'] == 'Yes'
1873
+ true
1874
+ else
1875
+ false
1876
+ end
2239
1877
  end
2240
1878
  else
2241
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.Space', "Could not find a space type for #{self.name}, assuming nonresidential.")
1879
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Space', "Could not find a space type for #{space_to_check.name}, assuming nonresidential.")
2242
1880
  is_res = false
2243
- end
2244
-
1881
+ end
1882
+
2245
1883
  return is_res
2246
-
2247
- end
2248
-
2249
- # Determine if the space is a plenum.
2250
- # Assume it is a plenum if it is a supply
2251
- # or return plenum for an AirLoop, or
2252
- # if it is not part of the total floor area.
1884
+ end
1885
+
1886
+ # Determines whether the space is conditioned per 90.1,
1887
+ # which is based on heating and cooling loads.
2253
1888
  #
2254
- # return [Bool] returns true if plenum, false if not
2255
- def is_plenum
2256
-
2257
- plenum_status = false
2258
-
2259
- # Check if it is part of a zone
2260
- # that is a supply/return plenum
2261
- zone = self.thermalZone
2262
- if zone.is_initialized
2263
- if zone.get.isPlenum
2264
- plenum_status = true
1889
+ # @param climate_zone [String] climate zone
1890
+ # @return [String] NonResConditioned, ResConditioned, Semiheated, Unconditioned
1891
+ # @todo add logic to detect indirectly-conditioned spaces
1892
+ def conditioning_category(template, climate_zone)
1893
+ # Get the zone this space is inside
1894
+ zone = thermalZone
1895
+
1896
+ # Assume unconditioned if not assigned to a zone
1897
+ if zone.empty?
1898
+ return 'Unconditioned'
1899
+ end
1900
+
1901
+ # Get the category from the zone
1902
+ cond_cat = zone.get.conditioning_category(template, climate_zone)
1903
+
1904
+ return cond_cat
1905
+ end
1906
+
1907
+ # Determines heating status. If the space's
1908
+ # zone has a thermostat with a maximum heating
1909
+ # setpoint above 5C (41F), counts as heated.
1910
+ #
1911
+ # @author Andrew Parker, Julien Marrec
1912
+ # @return [Bool] true if heated, false if not
1913
+ def heated?
1914
+ # Get the zone this space is inside
1915
+ zone = thermalZone
1916
+
1917
+ # Assume unheated if not assigned to a zone
1918
+ if zone.empty?
1919
+ return false
1920
+ end
1921
+
1922
+ # Get the category from the zone
1923
+ htd = zone.get.heated?
1924
+
1925
+ return htd
1926
+ end
1927
+
1928
+ # Determines cooling status. If the space's
1929
+ # zone has a thermostat with a minimum cooling
1930
+ # setpoint above 33C (91F), counts as cooled.
1931
+ #
1932
+ # @author Andrew Parker, Julien Marrec
1933
+ # @return [Bool] true if cooled, false if not
1934
+ def cooled?
1935
+ # Get the zone this space is inside
1936
+ zone = thermalZone
1937
+
1938
+ # Assume uncooled if not assigned to a zone
1939
+ if zone.empty?
1940
+ return false
1941
+ end
1942
+
1943
+ # Get the category from the zone
1944
+ cld = zone.get.cooled?
1945
+
1946
+ return cld
1947
+ end
1948
+
1949
+ # Determine the design internal load (W) for
1950
+ # this space without space multipliers.
1951
+ # This include People, Lights, Electric Equipment,
1952
+ # and Gas Equipment. It assumes 100% of the wattage
1953
+ # is converted to heat, and that the design peak
1954
+ # schedule value is 1 (100%).
1955
+ #
1956
+ # @return [Double] the design internal load, in W
1957
+ def design_internal_load
1958
+ load_w = 0.0
1959
+
1960
+ # People
1961
+ people.each do |people|
1962
+ w_per_person = 125 # Initial assumption
1963
+ act_sch = people.activityLevelSchedule
1964
+ if act_sch.is_initialized
1965
+ if act_sch.get.to_ScheduleRuleset.is_initialized
1966
+ act_sch = act_sch.get.to_ScheduleRuleset.get
1967
+ w_per_person = act_sch.annual_min_max_value['max']
1968
+ else
1969
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "#{name} people activity schedule is not a Schedule:Ruleset. Assuming #{w_per_person}W/person.")
1970
+ end
1971
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "#{name} people activity schedule not found. Assuming #{w_per_person}W/person.")
2265
1972
  end
1973
+
1974
+ num_ppl = people.getNumberOfPeople(floorArea)
1975
+
1976
+ ppl_w = num_ppl * w_per_person
1977
+
1978
+ load_w += ppl_w
2266
1979
  end
2267
-
2268
- # Check if it is designated
2269
- # as not part of the building
2270
- # floor area.
2271
- unless self.partofTotalFloorArea
2272
- plenum_status = true
1980
+
1981
+ # Lights
1982
+ load_w += lightingPower
1983
+
1984
+ # Electric Equipment
1985
+ load_w += electricEquipmentPower
1986
+
1987
+ # Gas Equipment
1988
+ load_w += gasEquipmentPower
1989
+
1990
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "#{name} has #{load_w.round}W of design internal loads.")
1991
+
1992
+ return load_w
1993
+ end
1994
+
1995
+ # will return a sorted array of array of spaces and connected area (Descending)
1996
+ def get_adjacent_spaces_with_shared_wall_areas(same_floor = true)
1997
+ same_floor_spaces = []
1998
+ spaces = []
1999
+ surfaces.each do |surface|
2000
+ adj_surface = surface.adjacentSurface
2001
+ unless adj_surface.empty?
2002
+ model.getSpaces.each do |space|
2003
+ next if space == self
2004
+ space.surfaces.each do |surf|
2005
+ if surf == adj_surface.get
2006
+ spaces << space
2007
+ end
2008
+ end
2009
+ end
2010
+ end
2273
2011
  end
2274
-
2275
- return plenum_status
2276
-
2012
+ # If looking for only spaces adjacent on the same floor.
2013
+ if same_floor == true
2014
+ raise "Cannot get adjacent spaces of space #{name} since space not set to BuildingStory" if buildingStory.empty?
2015
+ spaces.each do |space|
2016
+ raise "One or more adjecent spaces to space #{name} is not assigned to a BuildingStory. Ensure all spaces are assigned." if space.buildingStory.empty?
2017
+ if space.buildingStory.get == buildingStory.get
2018
+ same_floor_spaces << space
2019
+ end
2020
+ end
2021
+ spaces = same_floor_spaces
2022
+ end
2023
+
2024
+ # now sort by areas.
2025
+ area_index = []
2026
+ array_hash = {}
2027
+ return nil if spaces.size.zero?
2028
+ # iterate through each surface in the space
2029
+ surfaces.each do |surface|
2030
+ # get the adjacent surface in another space.
2031
+ adj_surface = surface.adjacentSurface
2032
+ unless adj_surface.empty?
2033
+ # go through each of the adjeacent spaces to find the matching surface/space.
2034
+ spaces.each_with_index do |space, index|
2035
+ next if space == self
2036
+ space.surfaces.each do |surf|
2037
+ if surf == adj_surface.get
2038
+ # initialize array index to zero for first time so += will work.
2039
+ area_index[index] = 0 if area_index[index].nil?
2040
+ area_index[index] += surf.grossArea
2041
+ array_hash[space] = area_index[index]
2042
+ end
2043
+ end
2044
+ end
2045
+ end
2046
+ end
2047
+ sorted_spaces = array_hash.sort_by { |_key, value| value }.reverse
2048
+ return sorted_spaces
2049
+ end
2050
+
2051
+ def get_adjacent_space_with_most_shared_wall_area(same_floor = true)
2052
+ return get_adjacent_spaces_with_touching_area(same_floor)[0][0]
2053
+ end
2054
+
2055
+ private
2056
+
2057
+ # A series of private methods to modify polygons. Most are
2058
+ # wrappers of native OpenStudio methods, but with
2059
+ # workarounds for known issues or limitations.
2060
+
2061
+ # Check the z coordinates of a polygon
2062
+ # @api private
2063
+ def check_z_zero(polygons, name, space)
2064
+ fails = []
2065
+ errs = 0
2066
+ polygons.each do |polygon|
2067
+ # OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Checking z=0: #{name} => #{polygon.to_s.gsub(/\[|\]/,'|')}.")
2068
+ polygon.each do |vertex|
2069
+ # clsss << vertex.class
2070
+ unless vertex.z == 0.0
2071
+ errs += 1
2072
+ fails << vertex.z
2073
+ end
2074
+ end
2075
+ end
2076
+ # OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "Checking z=0: #{name} => #{clsss.uniq.to_s.gsub(/\[|\]/,'|')}.")
2077
+ if errs > 0
2078
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "***FAIL*** #{space} z=0 failed for #{errs} vertices in #{name}; #{fails.join(', ')}.")
2079
+ end
2080
+ end
2081
+
2082
+ # A method to convert an array of arrays to
2083
+ # an array of OpenStudio::Point3ds.
2084
+ # @api private
2085
+ def ruby_polygons_to_point3d_z_zero(ruby_polygons)
2086
+ # Convert the final polygons back to OpenStudio
2087
+ os_polygons = []
2088
+ ruby_polygons.each do |ruby_polygon|
2089
+ os_polygon = []
2090
+ ruby_polygon.each do |vertex|
2091
+ vertex = OpenStudio::Point3d.new(vertex[0], vertex[1], 0.0) # Set z to hard-zero instead of vertex[2]
2092
+ os_polygon << vertex
2093
+ end
2094
+ os_polygons << os_polygon
2095
+ end
2096
+
2097
+ return os_polygons
2098
+ end
2099
+
2100
+ # A method to zero-out the z vertex of an array of polygons
2101
+ # @api private
2102
+ def polygons_set_z(polygons, new_z)
2103
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "### #{polygons}")
2104
+
2105
+ # Convert the final polygons back to OpenStudio
2106
+ new_polygons = []
2107
+ polygons.each do |polygon|
2108
+ new_polygon = []
2109
+ polygon.each do |vertex|
2110
+ new_vertex = OpenStudio::Point3d.new(vertex.x, vertex.y, new_z) # Set z to hard-zero instead of vertex[2]
2111
+ new_polygon << new_vertex
2112
+ end
2113
+ new_polygons << new_polygon
2114
+ end
2115
+
2116
+ return new_polygons
2117
+ end
2118
+
2119
+ # A method to returns the number of duplicate vertices in a polygon.
2120
+ # TODO does not actually wor
2121
+ # @api private
2122
+ def find_duplicate_vertices(ruby_polygon, tol = 0.001)
2123
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '***')
2124
+ duplicates = []
2125
+
2126
+ combos = ruby_polygon.combination(2).to_a
2127
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "########{combos.size}")
2128
+ combos.each do |i, j|
2129
+ i_vertex = OpenStudio::Point3d.new(i[0], i[1], i[2])
2130
+ j_vertex = OpenStudio::Point3d.new(j[0], j[1], j[2])
2131
+
2132
+ distance = OpenStudio.getDistance(i_vertex, j_vertex)
2133
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "------- #{i} to #{j} = #{distance}")
2134
+ if distance < tol
2135
+ duplicates << i
2136
+ end
2137
+ end
2138
+
2139
+ return duplicates
2140
+ end
2141
+
2142
+ # Subtracts one array of polygons from the next,
2143
+ # returning an array of resulting polygons.
2144
+ # @api private
2145
+ def a_polygons_minus_b_polygons(a_polygons, b_polygons, a_name, b_name)
2146
+ final_polygons_ruby = []
2147
+
2148
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "#{a_polygons.size} #{a_name} minus #{b_polygons.size} #{b_name}")
2149
+
2150
+ # Don't try to subtract anything if either set is empty
2151
+ if a_polygons.size.zero?
2152
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{a_name} - #{b_name}: #{a_name} contains no polygons.")
2153
+ return polygons_set_z(a_polygons, 0.0)
2154
+ elsif b_polygons.size.zero?
2155
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{a_name} - #{b_name}: #{b_name} contains no polygons.")
2156
+ return polygons_set_z(a_polygons, 0.0)
2157
+ end
2158
+
2159
+ # Loop through all a polygons, and for each one,
2160
+ # subtract all the b polygons.
2161
+ a_polygons.each do |a_polygon|
2162
+ # Translate the polygon to plain arrays
2163
+ a_polygon_ruby = []
2164
+ a_polygon.each do |vertex|
2165
+ a_polygon_ruby << [vertex.x, vertex.y, vertex.z]
2166
+ end
2167
+
2168
+ # TODO: Skip really small polygons
2169
+ # reduced_b_polygons = []
2170
+ # b_polygons.each do |b_polygon|
2171
+ # next
2172
+ # end
2173
+
2174
+ # Perform the subtraction
2175
+ a_minus_b_polygons = OpenStudio.subtract(a_polygon, b_polygons, 0.01)
2176
+
2177
+ # Translate the resulting polygons to plain ruby arrays
2178
+ a_minus_b_polygons_ruby = []
2179
+ num_small_polygons = 0
2180
+ a_minus_b_polygons.each do |a_minus_b_polygon|
2181
+ # Drop any super small or zero-vertex polygons resulting from the subtraction
2182
+ area = OpenStudio.getArea(a_minus_b_polygon)
2183
+ if area.is_initialized
2184
+ if area.get < 0.5 # 5 square feet
2185
+ num_small_polygons += 1
2186
+ next
2187
+ end
2188
+ else
2189
+ num_small_polygons += 1
2190
+ next
2191
+ end
2192
+
2193
+ # Translate polygon to ruby array
2194
+ a_minus_b_polygon_ruby = []
2195
+ a_minus_b_polygon.each do |vertex|
2196
+ a_minus_b_polygon_ruby << [vertex.x, vertex.y, vertex.z]
2197
+ end
2198
+
2199
+ a_minus_b_polygons_ruby << a_minus_b_polygon_ruby
2200
+ end
2201
+
2202
+ if num_small_polygons > 0
2203
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---Dropped #{num_small_polygons} small or invalid polygons resulting from subtraction.")
2204
+ end
2205
+
2206
+ # Remove duplicate polygons
2207
+ unique_a_minus_b_polygons_ruby = a_minus_b_polygons_ruby.uniq
2208
+
2209
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---Remove duplicates: #{a_minus_b_polygons_ruby.size} ==> #{unique_a_minus_b_polygons_ruby.size}")
2210
+
2211
+ # TODO: bug workaround?
2212
+ # If the result includes the a polygon, the a polygon
2213
+ # was unchanged; only include that polgon and throw away the other junk?/bug? polygons.
2214
+ # If the result does not include the a polygon, the a polygon was
2215
+ # split into multiple pieces. Keep all those pieces.
2216
+ if unique_a_minus_b_polygons_ruby.include?(a_polygon_ruby)
2217
+ if unique_a_minus_b_polygons_ruby.size == 1
2218
+ final_polygons_ruby.concat([a_polygon_ruby])
2219
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '---includes only original polygon, keeping that one')
2220
+ else
2221
+ # Remove the original polygon
2222
+ unique_a_minus_b_polygons_ruby.delete(a_polygon_ruby)
2223
+ final_polygons_ruby.concat(unique_a_minus_b_polygons_ruby)
2224
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '---includes the original and others; keeping all other polygons')
2225
+ end
2226
+ else
2227
+ final_polygons_ruby.concat(unique_a_minus_b_polygons_ruby)
2228
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '---does not include original, keeping all resulting polygons')
2229
+ end
2230
+ end
2231
+
2232
+ # Remove duplicate polygons again
2233
+ unique_final_polygons_ruby = final_polygons_ruby.uniq
2234
+
2235
+ # TODO: remove this workaround
2236
+ # Split any polygons that are joined by a line into two separate
2237
+ # polygons. Do this by finding duplicate
2238
+ # unique_final_polygons_ruby.each do |unique_final_polygon_ruby|
2239
+ # next if unique_final_polygon_ruby.size == 4 # Don't check 4-sided polygons
2240
+ # dupes = find_duplicate_vertices(unique_final_polygon_ruby)
2241
+ # if dupes.size > 0
2242
+ # OpenStudio::logFree(OpenStudio::Error, "openstudio.model.Space", "---Two polygons attached by line = #{unique_final_polygon_ruby.to_s.gsub(/\[|\]/,'|')}")
2243
+ # end
2244
+ # end
2245
+
2246
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---Remove final duplicates: #{final_polygons_ruby.size} ==> #{unique_final_polygons_ruby.size}")
2247
+
2248
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{a_name} minus #{b_name} = #{unique_final_polygons_ruby.size} polygons.")
2249
+
2250
+ # Convert the final polygons back to OpenStudio
2251
+ unique_final_polygons = ruby_polygons_to_point3d_z_zero(unique_final_polygons_ruby)
2252
+
2253
+ return unique_final_polygons
2254
+ end
2255
+
2256
+ # Wrapper to catch errors in joinAll method
2257
+ # [utilities.geometry.joinAll] <1> Expected polygons to join together
2258
+ # @api private
2259
+ def join_polygons(polygons, tol, name)
2260
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "Joining #{name} from #{self.name}")
2261
+
2262
+ combined_polygons = []
2263
+
2264
+ # Don't try to combine an empty array of polygons
2265
+ if polygons.size.zero?
2266
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{name} contains no polygons, not combining.")
2267
+ return combined_polygons
2268
+ end
2269
+
2270
+ # Open a log
2271
+ msg_log = OpenStudio::StringStreamLogSink.new
2272
+ msg_log.setLogLevel(OpenStudio::Info)
2273
+
2274
+ # Combine the polygons
2275
+ combined_polygons = OpenStudio.joinAll(polygons, 0.01)
2276
+
2277
+ # Count logged errors
2278
+ join_errs = 0
2279
+ inner_loop_errs = 0
2280
+ msg_log.logMessages.each do |msg|
2281
+ if /utilities.geometry/ =~ msg.logChannel
2282
+ if msg.logMessage.include?('Expected polygons to join together')
2283
+ join_errs += 1
2284
+ elsif msg.logMessage.include?('Union has inner loops')
2285
+ inner_loop_errs += 1
2286
+ end
2287
+ end
2288
+ end
2289
+
2290
+ # Disable the log sink to prevent memory hogging
2291
+ msg_log.disable
2292
+
2293
+ # TODO: remove this workaround, which is tried if there
2294
+ # are any join errors. This handles the case of polygons
2295
+ # that make an inner loop, the most common case being
2296
+ # when all 4 sides of a space have windows.
2297
+ # If an error occurs, attempt to join n-1 polygons,
2298
+ # then subtract the
2299
+ if join_errs > 0 || inner_loop_errs > 0
2300
+
2301
+ # Open a log
2302
+ msg_log_2 = OpenStudio::StringStreamLogSink.new
2303
+ msg_log_2.setLogLevel(OpenStudio::Info)
2304
+
2305
+ first_polygon = polygons.first
2306
+ polygons = polygons.drop(1)
2307
+
2308
+ combined_polygons_2 = OpenStudio.joinAll(polygons, 0.01)
2309
+
2310
+ join_errs_2 = 0
2311
+ inner_loop_errs_2 = 0
2312
+ msg_log_2.logMessages.each do |msg|
2313
+ if /utilities.geometry/ =~ msg.logChannel
2314
+ if msg.logMessage.include?('Expected polygons to join together')
2315
+ join_errs_2 += 1
2316
+ elsif msg.logMessage.include?('Union has inner loops')
2317
+ inner_loop_errs_2 += 1
2318
+ end
2319
+ end
2320
+ end
2321
+
2322
+ # Disable the log sink to prevent memory hogging
2323
+ msg_log_2.disable
2324
+
2325
+ if join_errs_2 > 0 || inner_loop_errs_2 > 0
2326
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{self.name}, the workaround for joining polygons failed.")
2327
+ else
2328
+
2329
+ # First polygon minus the already combined polygons
2330
+ first_polygon_minus_combined = a_polygons_minus_b_polygons([first_polygon], combined_polygons_2, 'first_polygon', 'combined_polygons_2')
2331
+
2332
+ # Add the result back
2333
+ combined_polygons_2 += first_polygon_minus_combined
2334
+ combined_polygons = combined_polygons_2
2335
+ join_errs = 0
2336
+ inner_loop_errs = 0
2337
+
2338
+ end
2339
+ end
2340
+
2341
+ # Report logged errors to user
2342
+ if join_errs > 0
2343
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{self.name}, #{join_errs} of #{polygons.size} #{name} were not joined properly due to limitations of the geometry calculation methods. The resulting daylighted areas will be smaller than they should be.")
2344
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "For #{self.name}, the #{name.gsub('_polygons','')} daylight area calculations hit limitations. Double-check and possibly correct the fraction of lights controlled by each daylight sensor.")
2345
+ end
2346
+ if inner_loop_errs > 0
2347
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{self.name}, #{inner_loop_errs} of #{polygons.size} #{name} were not joined properly becasue the joined polygons have an internal hole. The resulting daylighted areas will be smaller than they should be.")
2348
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "For #{self.name}, the #{name.gsub('_polygons','')} daylight area calculations hit limitations. Double-check and possibly correct the fraction of lights controlled by each daylight sensor.")
2349
+ end
2350
+
2351
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---Joined #{polygons.size} #{name} into #{combined_polygons.size} polygons.")
2352
+
2353
+ return combined_polygons
2354
+ end
2355
+
2356
+ # Gets the total area of a series of polygons
2357
+ # @api private
2358
+ def total_area_of_polygons(polygons)
2359
+ total_area_m2 = 0
2360
+ polygons.each do |polygon|
2361
+ area_m2 = OpenStudio.getArea(polygon)
2362
+ if area_m2.is_initialized
2363
+ total_area_m2 += area_m2.get
2364
+ else
2365
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Could not get area for a polygon in #{name}, daylighted area calculation will not be accurate.")
2366
+ end
2367
+ end
2368
+
2369
+ return total_area_m2
2370
+ end
2371
+
2372
+ # Returns an array of resulting polygons.
2373
+ # Assumes that a_polygons don't overlap one another, and that b_polygons don't overlap one another
2374
+ # @api private
2375
+ def area_a_polygons_overlap_b_polygons(a_polygons, b_polygons, a_name, b_name)
2376
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "#{a_polygons.size} #{a_name} overlaps #{b_polygons.size} #{b_name}")
2377
+
2378
+ overlap_area = 0
2379
+
2380
+ # Don't try anything if either set is empty
2381
+ if a_polygons.size.zero?
2382
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{a_name} overlaps #{b_name}: #{a_name} contains no polygons.")
2383
+ return overlap_area
2384
+ elsif b_polygons.size.zero?
2385
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "---#{a_name} overlaps #{b_name}: #{b_name} contains no polygons.")
2386
+ return overlap_area
2387
+ end
2388
+
2389
+ # Loop through each base surface
2390
+ b_polygons.each do |b_polygon|
2391
+ # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "---b polygon = #{b_polygon_ruby.to_s.gsub(/\[|\]/,'|')}")
2392
+
2393
+ # Loop through each overlap surface and determine if it overlaps this base surface
2394
+ a_polygons.each do |a_polygon|
2395
+ # OpenStudio::logFree(OpenStudio::Info, "openstudio.model.Space", "------a polygon = #{a_polygon_ruby.to_s.gsub(/\[|\]/,'|')}")
2396
+
2397
+ # If the entire a polygon is within the b polygon, count 100% of the area
2398
+ # as overlapping and remove a polygon from the list
2399
+ if OpenStudio.within(a_polygon, b_polygon, 0.01)
2400
+
2401
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '---------a overlaps b ENTIRELY.')
2402
+
2403
+ area = OpenStudio.getArea(a_polygon)
2404
+ if area.is_initialized
2405
+ overlap_area += area.get
2406
+ next
2407
+ else
2408
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "Could not determine the area of #{a_polygon.to_s.gsub(/\[|\]/, '|')} in #{a_name}; #{a_name} overlaps #{b_name}.")
2409
+ end
2410
+
2411
+ # If part of a polygon overlaps b polygon, determine the
2412
+ # original area of polygon b, subtract polygon a from b,
2413
+ # then add the difference in area to the total.
2414
+ elsif OpenStudio.intersects(a_polygon, b_polygon, 0.01)
2415
+
2416
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '---------a overlaps b PARTIALLY.')
2417
+
2418
+ # Get the initial area
2419
+ area_initial = 0
2420
+ area = OpenStudio.getArea(b_polygon)
2421
+ if area.is_initialized
2422
+ area_initial = area.get
2423
+ else
2424
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "Could not determine the area of #{a_polygon.to_s.gsub(/\[|\]/, '|')} in #{a_name}; #{a_name} overlaps #{b_name}.")
2425
+ end
2426
+
2427
+ # Perform the subtraction
2428
+ b_minus_a_polygons = OpenStudio.subtract(b_polygon, [a_polygon], 0.01)
2429
+
2430
+ # Get the final area
2431
+ area_final = 0
2432
+ b_minus_a_polygons.each do |polygon|
2433
+ # Skip polygons that have no vertices
2434
+ # resulting from the subtraction.
2435
+ if polygon.size.zero?
2436
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "Zero-vertex polygon resulting from #{b_polygon.to_s.gsub(/\[|\]/, '|')} minus #{a_polygon.to_s.gsub(/\[|\]/, '|')}.")
2437
+ next
2438
+ end
2439
+ # Find the area of real polygons
2440
+ area = OpenStudio.getArea(polygon)
2441
+ if area.is_initialized
2442
+ area_final += area.get
2443
+ else
2444
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Space', "Could not determine the area of #{polygon.to_s.gsub(/\[|\]/, '|')} in #{a_name}; #{a_name} overlaps #{b_name}.")
2445
+ end
2446
+ end
2447
+
2448
+ # Add the diference to the total
2449
+ overlap_area += (area_initial - area_final)
2450
+
2451
+ # There is no overlap
2452
+ else
2453
+
2454
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', '---------a does not overlaps b at all.')
2455
+
2456
+ end
2457
+ end
2458
+ end
2459
+
2460
+ return overlap_area
2277
2461
  end
2278
-
2279
2462
  end