Class: AddOverhangsByProjectionFactor

Inherits:
OpenStudio::Measure::ModelMeasure
  • Object
show all
Defined in:
lib/measures/AddOverhangsByProjectionFactor/measure.rb

Overview

start the measure

Instance Method Summary collapse

Instance Method Details

#arguments(model) ⇒ Object

define the arguments that the user will input



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 26

def arguments(model)
  args = OpenStudio::Measure::OSArgumentVector.new

  # make an argument for projection factor
  projection_factor = OpenStudio::Measure::OSArgument.makeDoubleArgument('projection_factor', true)
  projection_factor.setDisplayName('Projection Factor')
  projection_factor.setUnits('overhang depth / window height')
  projection_factor.setDefaultValue(0.5)
  args << projection_factor

  # make choice argument for facade
  choices = OpenStudio::StringVector.new
  choices << 'North'
  choices << 'East'
  choices << 'South'
  choices << 'West'
  facade = OpenStudio::Measure::OSArgument.makeChoiceArgument('facade', choices)
  facade.setDisplayName('Cardinal Direction')
  facade.setDefaultValue('South')
  args << facade

  # make an argument for deleting all existing space shading in the model
  remove_ext_space_shading = OpenStudio::Measure::OSArgument.makeBoolArgument('remove_ext_space_shading', true)
  remove_ext_space_shading.setDisplayName('Remove Existing Space Shading Surfaces From the Model')
  remove_ext_space_shading.setDefaultValue(false)
  args << remove_ext_space_shading

  # populate choice argument for constructions that are applied to surfaces in the model
  construction_handles = OpenStudio::StringVector.new
  construction_display_names = OpenStudio::StringVector.new

  # putting space types and names into hash
  construction_args = model.getConstructions
  construction_args_hash = {}
  construction_args.each do |construction_arg|
    construction_args_hash[construction_arg.name.to_s] = construction_arg
  end

  # looping through sorted hash of constructions
  construction_args_hash.sort.map do |key, value|
    # only include if construction is not used on surface
    if !value.isFenestration
      construction_handles << value.handle.to_s
      construction_display_names << key
    end
  end

  # make an argument for construction
  construction = OpenStudio::Measure::OSArgument.makeChoiceArgument('construction', construction_handles, construction_display_names, false)
  construction.setDisplayName('Optionally Choose a Construction for the Overhangs')
  args << construction

  return args
end

#descriptionObject

human readable description



16
17
18
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 16

def description
  return 'Add overhangs by projection factor to specified windows. The projection factor is the overhang depth divided by the window height. This can be applied to windows by the closest cardinal direction. If baseline model contains overhangs made by this measure, they will be replaced. Optionally the measure can delete any pre-existing space shading surfaces.'
end

#get_total_costs_for_objects(objects) ⇒ Object

helper that loops through lifecycle costs getting total costs under “Construction” or “Salvage” category and add to counter if occurs during year 0



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 146

def get_total_costs_for_objects(objects)
  counter = 0
  objects.each do |object|
    object_LCCs = object.lifeCycleCosts
    object_LCCs.each do |object_LCC|
      if (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
        if object_LCC.yearsFromStart == 0
          counter += object_LCC.totalCost
        end
      end
    end
  end
  return counter
end

#modeler_descriptionObject

human readable description of modeling approach



21
22
23
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 21

def modeler_description
  return "If requested then delete existing space shading surfaces. Then loop through exterior windows. If the requested cardinal direction is the closest to the window, then add the overhang. Name the shading surface the same as the window but append with '-Overhang'.  If a space shading surface of that name already exists, then delete it before making the new one. This measure has no life cycle cost arguments. You can see the economic impact of the measure by costing the construction used for the overhangs."
end

#nameObject

define the name that a user will see



11
12
13
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 11

def name
  return 'Add Overhangs by Projection Factor'
end

#neat_numbers(number, roundto = 2) ⇒ Object

helper to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure.



130
131
132
133
134
135
136
137
138
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 130

def neat_numbers(number, roundto = 2) # round to 0 or 2)
  if roundto == 2
    number = format '%.2f', number
  else
    number = number.round
  end
  # regex to add commas
  number.to_s.reverse.gsub(/([0-9]{3}(?=([0-9])))/, '\\1,').reverse
end

#run(model, runner, user_arguments) ⇒ Object

define what happens when the measure is run



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 82

def run(model, runner, user_arguments)
  super(model, runner, user_arguments)

  # use the built-in error checking
  if !runner.validateUserArguments(arguments(model), user_arguments)
    return false
  end

  # assign the user inputs to variables
  projection_factor = runner.getDoubleArgumentValue('projection_factor', user_arguments)
  facade = runner.getStringArgumentValue('facade', user_arguments)
  remove_ext_space_shading = runner.getBoolArgumentValue('remove_ext_space_shading', user_arguments)
  construction = runner.getOptionalWorkspaceObjectChoiceValue('construction', user_arguments, model)

  # check reasonableness of fraction
  projection_factor_too_small = false
  if projection_factor < 0
    runner.registerError('Please enter a positive number for the projection factor.')
    return false
  elsif projection_factor < 0.1
    runner.registerWarning("The requested projection factor of #{projection_factor} seems unusually small, no overhangs will be added.")
    projection_factor_too_small = true
  elsif projection_factor > 5
    runner.registerWarning("The requested projection factor of #{projection_factor} seems unusually large.")
  end

  # check the construction for reasonableness
  construction_chosen = true
  if construction.empty?
    handle = runner.getOptionalStringArgumentValue('construction', user_arguments)
    if handle.empty?
      runner.registerInfo('No construction was chosen.')
      construction_chosen = false
    else
      runner.registerError("The selected construction with handle '#{handle}' was not found in the model. It may have been removed by another measure.")
      return false
    end

  else
    if !construction.get.to_Construction.empty?
      construction = construction.get.to_Construction.get
    else
      runner.registerError('Script Error - argument not showing up as construction.')
      return false
    end
  end

  # helper to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure.
  def neat_numbers(number, roundto = 2) # round to 0 or 2)
    if roundto == 2
      number = format '%.2f', number
    else
      number = number.round
    end
    # regex to add commas
    number.to_s.reverse.gsub(/([0-9]{3}(?=([0-9])))/, '\\1,').reverse
  end

  # helper to make it easier to do unit conversions on the fly.  The definition be called through this measure.
  def unit_helper(number, from_unit_string, to_unit_string)
    converted_number = OpenStudio.convert(OpenStudio::Quantity.new(number, OpenStudio.createUnit(from_unit_string).get), OpenStudio.createUnit(to_unit_string).get).get.value
  end

  # helper that loops through lifecycle costs getting total costs under "Construction" or "Salvage" category and add to counter if occurs during year 0
  def get_total_costs_for_objects(objects)
    counter = 0
    objects.each do |object|
      object_LCCs = object.lifeCycleCosts
      object_LCCs.each do |object_LCC|
        if (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
          if object_LCC.yearsFromStart == 0
            counter += object_LCC.totalCost
          end
        end
      end
    end
    return counter
  end

  # counter for year 0 capital costs
  yr0_capital_totalCosts = 0

  # get initial construction costs and multiply by -1
  yr0_capital_totalCosts += get_total_costs_for_objects(model.getConstructions) * -1

  # reporting initial condition of model
  number_of_exist_space_shading_surf = 0
  shading_groups = model.getShadingSurfaceGroups
  shading_groups.each do |shading_group|
    if shading_group.shadingSurfaceType == 'Space'
      number_of_exist_space_shading_surf += shading_group.shadingSurfaces.size
    end
  end
  runner.registerInitialCondition("The initial building had #{number_of_exist_space_shading_surf} space shading surfaces.")

  # delete all space shading groups if requested
  if remove_ext_space_shading && (number_of_exist_space_shading_surf > 0)
    num_removed = 0
    shading_groups.each do |shading_group|
      if shading_group.shadingSurfaceType == 'Space'
        shading_group.remove
        num_removed += 1
      end
    end
    runner.registerInfo("Removed all #{num_removed} space shading surface groups from the model.")
  end

  # flag for not applicable
  overhang_added = false

  # loop through surfaces finding exterior walls with proper orientation
  sub_surfaces = model.getSubSurfaces
  sub_surfaces.each do |s|
    next if s.outsideBoundaryCondition != 'Outdoors'
    next if s.subSurfaceType == 'Skylight'
    next if s.subSurfaceType == 'Door'
    next if s.subSurfaceType == 'GlassDoor'
    next if s.subSurfaceType == 'OverheadDoor'
    next if s.subSurfaceType == 'TubularDaylightDome'
    next if s.subSurfaceType == 'TubularDaylightDiffuser'

    # get the absoluteAzimuth for the surface so we can categorize it
    absoluteAzimuth = OpenStudio.convert(s.azimuth, 'rad', 'deg').get + s.space.get.directionofRelativeNorth + model.getBuilding.northAxis
    absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0

    if facade == 'North'
      next if !((absoluteAzimuth >= 315.0) || (absoluteAzimuth < 45.0))
    elsif facade == 'East'
      next if !((absoluteAzimuth >= 45.0) && (absoluteAzimuth < 135.0))
    elsif facade == 'South'
      next if !((absoluteAzimuth >= 135.0) && (absoluteAzimuth < 225.0))
    elsif facade == 'West'
      next if !((absoluteAzimuth >= 225.0) && (absoluteAzimuth < 315.0))
    else
      runner.registerError('Unexpected value of facade: ' + facade + '.')
      return false
    end

    # delete existing overhang for this window if it exists from previously run measure
    shading_groups.each do |shading_group|
      shading_s = shading_group.shadingSurfaces
      shading_s.each do |ss|
        if ss.name.to_s == "#{s.name} - Overhang"
          ss.remove
          runner.registerWarning("Removed pre-existing window shade named '#{ss.name}'.")
        end
      end
    end

    if projection_factor_too_small
      # new overhang would be too small and would cause errors in OpenStudio
      # don't actually add it, but from the measure's perspective this worked as requested
      overhang_added = true
    else
      # add the overhang
      new_overhang = s.addOverhangByProjectionFactor(projection_factor, 0)
      if new_overhang.empty?
        ok = runner.registerWarning('Unable to add overhang to ' + s.briefDescription +
                 ' with projection factor ' + projection_factor.to_s + ' and offset ' + offset.to_s + '.')
        return false if !ok
      else
        new_overhang.get.setName("#{s.name} - Overhang")
        runner.registerInfo('Added overhang ' + new_overhang.get.briefDescription + ' to ' +
            s.briefDescription + ' with projection factor ' + projection_factor.to_s +
            ' and offset ' + '0' + '.')
        if construction_chosen
          if !construction.to_Construction.empty?
            new_overhang.get.setConstruction(construction)
          end
        end
        overhang_added = true
      end
    end
  end

  if !overhang_added
    runner.registerAsNotApplicable("The model has exterior #{facade.downcase} walls, but no windows were found to add overhangs to.")
    return true
  end

  # get final construction costs and multiply
  yr0_capital_totalCosts += get_total_costs_for_objects(model.getConstructions)

  # reporting initial condition of model
  number_of_final_space_shading_surf = 0
  final_shading_groups = model.getShadingSurfaceGroups
  final_shading_groups.each do |shading_group|
    number_of_final_space_shading_surf += shading_group.shadingSurfaces.size
  end
  runner.registerFinalCondition("The final building has #{number_of_final_space_shading_surf} space shading surfaces. Initial capital costs associated with the improvements are $#{neat_numbers(yr0_capital_totalCosts, 0)}.")

  return true
end

#unit_helper(number, from_unit_string, to_unit_string) ⇒ Object

helper to make it easier to do unit conversions on the fly. The definition be called through this measure.



141
142
143
# File 'lib/measures/AddOverhangsByProjectionFactor/measure.rb', line 141

def unit_helper(number, from_unit_string, to_unit_string)
  converted_number = OpenStudio.convert(OpenStudio::Quantity.new(number, OpenStudio.createUnit(from_unit_string).get), OpenStudio.createUnit(to_unit_string).get).get.value
end