Class: ReplaceExteriorWindowConstruction
- Inherits:
-
OpenStudio::Measure::ModelMeasure
- Object
- OpenStudio::Measure::ModelMeasure
- ReplaceExteriorWindowConstruction
- Defined in:
- lib/measures/ReplaceExteriorWindowConstruction/measure.rb
Overview
start the measure
Instance Method Summary collapse
-
#arguments(model) ⇒ Object
define the arguments that the user will input.
-
#name ⇒ Object
define the name that a user will see.
-
#neat_numbers(number, roundto = 2) ⇒ Object
short def to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001).
-
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run.
Instance Method Details
#arguments(model) ⇒ Object
define the arguments that the user will input
14 15 16 17 18 19 20 21 22 23 24 25 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/measures/ReplaceExteriorWindowConstruction/measure.rb', line 14 def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # make a choice argument for constructions that are appropriate for windows 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 a valid fenestration construction if value.isFenestration construction_handles << value.handle.to_s construction_display_names << key end end # make a choice argument for fixed windows construction = OpenStudio::Measure::OSArgument.makeChoiceArgument('construction', construction_handles, construction_display_names, true) construction.setDisplayName('Pick a Window Construction From the Model to Replace Existing Window Constructions.') args << construction # make a bool argument for fixed windows change_fixed_windows = OpenStudio::Measure::OSArgument.makeBoolArgument('change_fixed_windows', true) change_fixed_windows.setDisplayName('Change Fixed Windows?') change_fixed_windows.setDefaultValue(true) args << change_fixed_windows # make a bool argument for operable windows change_operable_windows = OpenStudio::Measure::OSArgument.makeBoolArgument('change_operable_windows', true) change_operable_windows.setDisplayName('Change Operable Windows?') change_operable_windows.setDefaultValue(true) args << change_operable_windows # make an argument to remove existing costs remove_costs = OpenStudio::Measure::OSArgument.makeBoolArgument('remove_costs', true) remove_costs.setDisplayName('Remove Existing Costs?') remove_costs.setDefaultValue(true) args << remove_costs # make an argument for material and installation cost material_cost_ip = OpenStudio::Measure::OSArgument.makeDoubleArgument('material_cost_ip', true) material_cost_ip.setDisplayName('Material and Installation Costs for Construction per Area Used ($/ft^2).') material_cost_ip.setDefaultValue(0.0) args << material_cost_ip # make an argument for demolition cost demolition_cost_ip = OpenStudio::Measure::OSArgument.makeDoubleArgument('demolition_cost_ip', true) demolition_cost_ip.setDisplayName('Demolition Costs for Construction per Area Used ($/ft^2).') demolition_cost_ip.setDefaultValue(0.0) args << demolition_cost_ip # make an argument for duration in years until costs start years_until_costs_start = OpenStudio::Measure::OSArgument.makeIntegerArgument('years_until_costs_start', true) years_until_costs_start.setDisplayName('Years Until Costs Start (whole years).') years_until_costs_start.setDefaultValue(0) args << years_until_costs_start # make an argument to determine if demolition costs should be included in initial construction demo_cost_initial_const = OpenStudio::Measure::OSArgument.makeBoolArgument('demo_cost_initial_const', true) demo_cost_initial_const.setDisplayName('Demolition Costs Occur During Initial Construction?') demo_cost_initial_const.setDefaultValue(false) args << demo_cost_initial_const # make an argument for expected life expected_life = OpenStudio::Measure::OSArgument.makeIntegerArgument('expected_life', true) expected_life.setDisplayName('Expected Life (whole years).') expected_life.setDefaultValue(20) args << expected_life # make an argument for o&m cost om_cost_ip = OpenStudio::Measure::OSArgument.makeDoubleArgument('om_cost_ip', true) om_cost_ip.setDisplayName('O & M Costs for Construction per Area Used ($/ft^2).') om_cost_ip.setDefaultValue(0.0) args << om_cost_ip # make an argument for o&m frequency om_frequency = OpenStudio::Measure::OSArgument.makeIntegerArgument('om_frequency', true) om_frequency.setDisplayName('O & M Frequency (whole years).') om_frequency.setDefaultValue(1) args << om_frequency return args end |
#name ⇒ Object
define the name that a user will see
9 10 11 |
# File 'lib/measures/ReplaceExteriorWindowConstruction/measure.rb', line 9 def name return 'Replace Exterior Window Constructions with a Different Construction from the Model.' end |
#neat_numbers(number, roundto = 2) ⇒ Object
short def to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure
172 173 174 175 176 177 178 179 180 |
# File 'lib/measures/ReplaceExteriorWindowConstruction/measure.rb', line 172 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
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 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/measures/ReplaceExteriorWindowConstruction/measure.rb', line 106 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 construction = runner.getOptionalWorkspaceObjectChoiceValue('construction', user_arguments, model) change_fixed_windows = runner.getBoolArgumentValue('change_fixed_windows', user_arguments) change_operable_windows = runner.getBoolArgumentValue('change_operable_windows', user_arguments) remove_costs = runner.getBoolArgumentValue('remove_costs', user_arguments) material_cost_ip = runner.getDoubleArgumentValue('material_cost_ip', user_arguments) demolition_cost_ip = runner.getDoubleArgumentValue('demolition_cost_ip', user_arguments) years_until_costs_start = runner.getIntegerArgumentValue('years_until_costs_start', user_arguments) demo_cost_initial_const = runner.getBoolArgumentValue('demo_cost_initial_const', user_arguments) expected_life = runner.getIntegerArgumentValue('expected_life', user_arguments) om_cost_ip = runner.getDoubleArgumentValue('om_cost_ip', user_arguments) om_frequency = runner.getIntegerArgumentValue('om_frequency', user_arguments) # check the construction for reasonableness if construction.empty? handle = runner.getStringArgumentValue('construction', user_arguments) if handle.empty? runner.registerError('No construction was chosen.') else runner.registerError("The selected construction with handle '#{handle}' was not found in the model. It may have been removed by another measure.") end return false 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 # set flags and counters to use later costs_requested = false costs_removed = false # Later will add hard sized $ cost to this each time I swap a construction surfaces. # If demo_cost_initial_const is true then will be applied once in the lifecycle. Future replacements use the demo cost of the new construction. demo_costs_of_baseline_objects = 0 # check costs for reasonableness if material_cost_ip.abs + demolition_cost_ip.abs + om_cost_ip.abs == 0 runner.registerInfo("No costs were requested for #{construction.name}.") else costs_requested = true end # check lifecycle arguments for reasonableness if (years_until_costs_start < 0) && (years_until_costs_start > expected_life) runner.registerError('Years until costs start should be a non-negative integer less than Expected Life.') end if (expected_life < 1) && (expected_life > 100) runner.registerError('Choose an integer greater than 0 and less than or equal to 100 for Expected Life.') end if om_frequency < 1 runner.registerError('Choose an integer greater than 0 for O & M Frequency.') end # short def 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 # clone construction to get proper area for measure economics, in case it is used elsewhere in the building new_object = construction.clone(model) if !new_object.to_Construction.empty? construction = new_object.to_Construction.get end # remove any component cost line items associated with the construction. if !construction.lifeCycleCosts.empty? && (remove_costs == true) runner.registerInfo("Removing existing lifecycle cost objects associated with #{construction.name}") removed_costs = construction.removeLifeCycleCosts costs_removed = !removed_costs.empty? end removed_costs = construction.removeLifeCycleCosts costs_removed = !removed_costs.empty? # add lifeCycleCost objects if there is a non-zero value in one of the cost arguments if costs_requested == true # converting doubles to si values from ip material_cost_si = OpenStudio.convert(OpenStudio::Quantity.new(material_cost_ip, OpenStudio.createUnit('1/ft^2').get), OpenStudio.createUnit('1/m^2').get).get.value demolition_cost_si = OpenStudio.convert(OpenStudio::Quantity.new(demolition_cost_ip, OpenStudio.createUnit('1/ft^2').get), OpenStudio.createUnit('1/m^2').get).get.value om_cost_si = OpenStudio.convert(OpenStudio::Quantity.new(om_cost_ip, OpenStudio.createUnit('1/ft^2').get), OpenStudio.createUnit('1/m^2').get).get.value # adding new cost items lcc_mat = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Mat-#{construction.name}", construction, material_cost_si, 'CostPerArea', 'Construction', expected_life, years_until_costs_start) # if demo_cost_initial_const is true then later will add one time demo costs using removed baseline objects. Cost will occur at year specified by years_until_costs_start lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo-#{construction.name}", construction, demolition_cost_si, 'CostPerArea', 'Salvage', expected_life, years_until_costs_start + expected_life) lcc_om = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_OM-#{construction.name}", construction, om_cost_si, 'CostPerArea', 'Maintenance', om_frequency, 0) end # loop through sub surfaces starting_exterior_windows_constructions = [] sub_surfaces_to_change = [] sub_surfaces = model.getSubSurfaces sub_surfaces.each do |sub_surface| if (sub_surface.outsideBoundaryCondition == 'Outdoors') && (sub_surface.subSurfaceType == 'FixedWindow') && (change_fixed_windows == true) sub_surfaces_to_change << sub_surface sub_surface_const = sub_surface.construction if !sub_surface_const.empty? if starting_exterior_windows_constructions.empty? starting_exterior_windows_constructions << sub_surface_const.get.name.to_s else starting_exterior_windows_constructions << sub_surface_const.get.name.to_s end end elsif (sub_surface.outsideBoundaryCondition == 'Outdoors') && (sub_surface.subSurfaceType == 'OperableWindow') && (change_operable_windows == true) sub_surfaces_to_change << sub_surface sub_surface_const = sub_surface.construction if !sub_surface_const.empty? if starting_exterior_windows_constructions.empty? starting_exterior_windows_constructions << sub_surface_const.get.name.to_s else starting_exterior_windows_constructions << sub_surface_const.get.name.to_s end end end end # create array of constructions for sub_surfaces to change, before construction is replaced constructions_to_change = [] sub_surfaces_to_change.each do |sub_surface| if !sub_surface.construction.empty? constructions_to_change << sub_surface.construction.get end end # getting cost of all existing windows before constructions are swapped. This will create demo cost if all windows were removed. Will adjust later for windows left in place constructions_to_change.uniq.each do |construction_to_change| # loop through lifecycle costs getting total costs under "Salvage" category demo_LCCs = construction_to_change.lifeCycleCosts demo_LCCs.each do |demo_LCC| if demo_LCC.category == 'Salvage' demo_costs_of_baseline_objects += demo_LCC.totalCost end end end if (change_fixed_windows == false) && (change_operable_windows == false) runner.registerAsNotApplicable('Fixed and operable windows are both set not to change.') return true # no need to waste time with the measure if we know it isn't applicable elsif sub_surfaces_to_change.empty? runner.registerAsNotApplicable('There are no appropriate exterior windows to change in the model.') return true # no need to waste time with the measure if we know it isn't applicable end # report initial condition runner.registerInitialCondition("The building had #{starting_exterior_windows_constructions.uniq.size} window constructions: #{starting_exterior_windows_constructions.uniq.sort.join(', ')}.") # loop through construction sets used in the model default_construction_sets = model.getDefaultConstructionSets default_construction_sets.each do |default_construction_set| if default_construction_set.directUseCount > 0 default_sub_surface_const_set = default_construction_set.defaultExteriorSubSurfaceConstructions if !default_sub_surface_const_set.empty? starting_construction = default_sub_surface_const_set.get.fixedWindowConstruction # creating new default construction set new_default_construction_set = default_construction_set.clone(model) new_default_construction_set = new_default_construction_set.to_DefaultConstructionSet.get # create new sub_surface set new_default_sub_surface_const_set = default_sub_surface_const_set.get.clone(model) new_default_sub_surface_const_set = new_default_sub_surface_const_set.to_DefaultSubSurfaceConstructions.get if change_fixed_windows == true # assign selected construction sub_surface set new_default_sub_surface_const_set.setFixedWindowConstruction(construction) end if change_operable_windows == true # assign selected construction sub_surface set new_default_sub_surface_const_set.setOperableWindowConstruction(construction) end # link new subset to new set new_default_construction_set.setDefaultExteriorSubSurfaceConstructions(new_default_sub_surface_const_set) # swap all uses of the old construction set for the new construction_set_sources = default_construction_set.sources construction_set_sources.each do |construction_set_source| building_source = construction_set_source.to_Building if !building_source.empty? building_source = building_source.get building_source.setDefaultConstructionSet(new_default_construction_set) next end # add SpaceType, BuildingStory, and Space if statements end end end end # loop through appropriate sub surfaces and change where there is a hard assigned construction sub_surfaces_to_change.each do |sub_surface| if !sub_surface.isConstructionDefaulted sub_surface.setConstruction(construction) end end # loop through lifecycle costs getting total costs under "Salvage" category constructions_to_change.uniq.each do |construction_to_change| demo_LCCs = construction_to_change.lifeCycleCosts demo_LCCs.each do |demo_LCC| if demo_LCC.category == 'Salvage' demo_costs_of_baseline_objects += demo_LCC.totalCost * -1 # this is to adjust demo cost down for original windows that were not changed end end end # loop through lifecycle costs getting total costs under "Construction" or "Salvage" category and add to counter if occurs during year 0 const_LCCs = construction.lifeCycleCosts yr0_capital_totalCosts = 0 const_LCCs.each do |const_LCC| if (const_LCC.category == 'Construction') || (const_LCC.category == 'Salvage') if const_LCC.yearsFromStart == 0 yr0_capital_totalCosts += const_LCC.totalCost end end end # add one time demo cost of removed windows if appropriate if demo_cost_initial_const == true building = model.getBuilding lcc_baseline_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost('LCC_baseline_demo', building, demo_costs_of_baseline_objects, 'CostPerEach', 'Salvage', 0, years_until_costs_start).get # using 0 for repeat period since one time cost. runner.registerInfo("Adding one time cost of $#{neat_numbers(lcc_baseline_demo.totalCost, 0)} related to demolition of baseline objects.") # if demo occurs on year 0 then add to initial capital cost counter if lcc_baseline_demo.yearsFromStart == 0 yr0_capital_totalCosts += lcc_baseline_demo.totalCost end end # ip construction area for reporting const_area_ip = OpenStudio.convert(OpenStudio::Quantity.new(construction.getNetArea, OpenStudio.createUnit('m^2').get), OpenStudio.createUnit('ft^2').get).get.value # get names from constructions to change const_names = [] if !constructions_to_change.empty? constructions_to_change.uniq.sort.each do |const_name| const_names << const_name.name end end # need to format better. At first I did each do, but seems initial condition only reports the first one. runner.registerFinalCondition("#{neat_numbers(const_area_ip, 0)} (ft^2) of existing windows of the types: #{const_names.join(', ')} were replaced by new #{construction.name} windows. Initial capital costs associated with the new windows are $#{neat_numbers(yr0_capital_totalCosts, 0)}.") return true end |