Class: SetWindowToWallRatioByFacade
- Inherits:
-
OpenStudio::Measure::ModelMeasure
- Object
- OpenStudio::Measure::ModelMeasure
- SetWindowToWallRatioByFacade
- Defined in:
- lib/measures/SetWindowToWallRatioByFacade/measure.rb
Instance Method Summary collapse
-
#arguments(model) ⇒ Object
return a vector of arguments.
-
#name ⇒ Object
override name to return the name of your script.
-
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run.
Instance Method Details
#arguments(model) ⇒ Object
return a vector of arguments
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 |
# File 'lib/measures/SetWindowToWallRatioByFacade/measure.rb', line 15 def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # make double argument for wwr wwr = OpenStudio::Measure::OSArgument.makeDoubleArgument('wwr', true) wwr.setDisplayName('Window to Wall Ratio (fraction).') wwr.setDefaultValue(0.4) args << wwr # make double argument for sillHeight sillHeight = OpenStudio::Measure::OSArgument.makeDoubleArgument('sillHeight', true) sillHeight.setDisplayName('Sill Height (in).') sillHeight.setDefaultValue(30.0) args << sillHeight # make choice argument for facade choices = OpenStudio::StringVector.new choices << 'North' choices << 'East' choices << 'South' choices << 'West' choices << 'All' facade = OpenStudio::Measure::OSArgument.makeChoiceArgument('facade', choices, true) facade.setDisplayName('Cardinal Direction.') facade.setDefaultValue('South') args << facade # bool to not apply windows to spaces that are not included in the building floor area exl_spaces_not_incl_fl_area = OpenStudio::Measure::OSArgument.makeBoolArgument('exl_spaces_not_incl_fl_area', true) exl_spaces_not_incl_fl_area.setDisplayName("Don't alter spaces that are not included in the building floor area") exl_spaces_not_incl_fl_area.setDefaultValue(true) args << exl_spaces_not_incl_fl_area # make an argument for splitting base surfaces at doors choices = OpenStudio::StringVector.new choices << 'Do nothing to Doors' choices << 'Split Walls at Doors' choices << 'Remove Doors' split_at_doors = OpenStudio::Measure::OSArgument.makeChoiceArgument('split_at_doors', choices, true) split_at_doors.setDisplayName('Exterior Door Logic') split_at_doors.setDescription('This will only impact exterior surfaces with specified orientation. Can do nothing, split all, or remove doors.') split_at_doors.setDefaultValue('Split Walls at Doors') args << split_at_doors # bool to create inset windows for triangular base surfaces inset_tri_sub = OpenStudio::Measure::OSArgument.makeBoolArgument('inset_tri_sub', true) inset_tri_sub.setDisplayName('Inset windows for triangular surfaces') inset_tri_sub.setDefaultValue(true) args << inset_tri_sub # triangulate non rectangular base surfaces triangulate = OpenStudio::Measure::OSArgument.makeBoolArgument('triangulate', true) triangulate.setDisplayName('Triangulate non-Rectangular surfaces') triangulate.setDescription('This will only impact exterior surfaces with specified orientation') triangulate.setDefaultValue(true) args << triangulate # triangulation minimum area triangulation_min_area = OpenStudio::Measure::OSArgument.makeDoubleArgument('triangulation_min_area', true) triangulation_min_area.setDisplayName('Triangulation Minimum Area (m^2)') triangulation_min_area.setDescription('Triangulated surfaces less than this will not be created.') triangulation_min_area.setDefaultValue(0.001) # Two vertices < 0.01 meters apart are coincident in EnergyPlus (SurfaceGeometry.cc). args << triangulation_min_area return args end |
#name ⇒ Object
override name to return the name of your script
10 11 12 |
# File 'lib/measures/SetWindowToWallRatioByFacade/measure.rb', line 10 def name return 'Set Window to Wall Ratio by Facade' end |
#run(model, runner, user_arguments) ⇒ Object
define what happens when the measure is run
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 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 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'lib/measures/SetWindowToWallRatioByFacade/measure.rb', line 83 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 wwr = runner.getDoubleArgumentValue('wwr', user_arguments) sillHeight = runner.getDoubleArgumentValue('sillHeight', user_arguments) facade = runner.getStringArgumentValue('facade', user_arguments) exl_spaces_not_incl_fl_area = runner.getBoolArgumentValue('exl_spaces_not_incl_fl_area', user_arguments) split_at_doors = runner.getStringArgumentValue('split_at_doors', user_arguments) inset_tri_sub = runner.getBoolArgumentValue('inset_tri_sub', user_arguments) triangulate = runner.getBoolArgumentValue('triangulate', user_arguments) triangulation_min_area = runner.getDoubleArgumentValue('triangulation_min_area', user_arguments) # check reasonableness of fraction if wwr == 0 runner.registerInfo('Target window to wall ratio is 0. Windows for selected surfaces will be removed, no new windows will be added.') elsif (wwr < 0) || (wwr >= 1) runner.registerError('Window to Wall Ratio must be greater than or equal to 0 and less than 1.') return false end # check reasonableness of fraction if sillHeight <= 0 runner.registerError('Sill height must be > 0.') return false elsif sillHeight > 360 runner.registerWarning("#{sillHeight} inches seems like an unusually high sill height.") elsif sillHeight > 9999 runner.registerError("#{sillHeight} inches is above the measure limit for sill height.") return false end # setup OpenStudio units that we will need unit_sillHeight_ip = OpenStudio.createUnit('ft').get unit_sillHeight_si = OpenStudio.createUnit('m').get unit_area_ip = OpenStudio.createUnit('ft^2').get unit_area_si = OpenStudio.createUnit('m^2').get unit_cost_per_area_ip = OpenStudio.createUnit('1/ft^2').get # $/ft^2 does not work unit_cost_per_area_si = OpenStudio.createUnit('1/m^2').get # define starting units sillHeight_ip = OpenStudio::Quantity.new(sillHeight / 12, unit_sillHeight_ip) # unit conversion sillHeight_si = OpenStudio.convert(sillHeight_ip, unit_sillHeight_si).get # hold data for initial condition starting_gross_ext_wall_area = 0.0 # includes windows and doors starting_ext_window_area = 0.0 # hold data for final condition final_gross_ext_wall_area = 0.0 # includes windows and doors final_ext_window_area = 0.0 # flag for not applicable exterior_walls = false window_confirmed = false # flag to track notifications of zone multipliers space_warning_issued = [] # flag to track warning for new windows without construction facade_const_warning = false bldg_const_warning = false empty_const_warning = false # flag for catchall glazing to be made only once catchall_glazing_const = nil # calculate initial envelope cost as negative value envelope_cost = 0 constructions = model.getConstructions.sort constructions.each do |construction| const_llcs = construction.lifeCycleCosts const_llcs.each do |const_llc| if const_llc.category == 'Construction' envelope_cost += const_llc.totalCost * -1 end end end # loop through surfaces finding exterior walls with proper orientation if exl_spaces_not_incl_fl_area # loop through spaces to gather surfaces. surfaces = [] model.getSpaces.sort.each do |space| next if !space.partofTotalFloorArea space.surfaces.sort.each do |surface| surfaces << surface end end else surfaces = model.getSurfaces.sort end # used for new sub surfaces to find target construction subsurfaces_by_facade = Functions.get_surfaces_or_subsurfaces_by_facade(model.getSubSurfaces, facade) orig_sub_surf_const_for_target_facade = Functions.get_orig_sub_surf_const_for_target(subsurfaces_by_facade) orig_sub_surf_const_for_target_all_ext = Functions.get_orig_sub_surf_const_for_target(model.getSubSurfaces) # hash for sub surfaces removed from non rectangular surfaces non_rect_parent = {} Functions.get_surfaces_or_subsurfaces_by_facade(model.getSurfaces, facade).sort.each do |s| exterior_walls = true # get surface area adjusting for zone multiplier space = s.space if !space.empty? zone = space.get.thermalZone end if !zone.empty? zone_multiplier = zone.get.multiplier if (zone_multiplier > 1) && !space_warning_issued.include?(space.get.name.to_s) runner.registerInfo("Space #{space.get.name} in thermal zone #{zone.get.name} has a zone multiplier of #{zone_multiplier}. Adjusting area calculations.") space_warning_issued << space.get.name.to_s end else zone_multiplier = 1 # space is not in a thermal zone runner.registerWarning("Space #{space.get.name} is not in a thermal zone and won't be included in in the simulation. Windows will still be altered with an assumed zone multiplier of 1") end surface_gross_area = s.grossArea * zone_multiplier # loop through sub surfaces and add area including multiplier ext_window_area = 0 has_doors = false s.subSurfaces.sort.each do |subSurface| # stop if non window or glass door if subSurface.subSurfaceType == 'Door' || subSurface.subSurfaceType == 'OverheadDoor' if split_at_doors == 'Remove Doors' subSurface.remove else has_doors = true end next end ext_window_area += subSurface.grossArea * subSurface.multiplier * zone_multiplier if subSurface.multiplier > 1 runner.registerInfo("Sub-surface #{subSurface.name} in space #{space.get.name} has a sub-surface multiplier of #{subSurface.multiplier}. Adjusting area calculations.") end end starting_gross_ext_wall_area += surface_gross_area starting_ext_window_area += ext_window_area all_surfaces = [s] if split_at_doors == 'Split Walls at Doors' && has_doors # remove windows before split at doors s.subSurfaces.each do |sub_surface| next if ['Door', 'OverheadDoor'].include? sub_surface.subSurfaceType sub_surface.remove end # split base surfaces at doors to create multiple base surfaces split_surfaces = s.splitSurfaceForSubSurfaces.to_a # frozen array # add original surface to new surfaces split_surfaces.sort.each do |ss| all_surfaces << ss end end if wwr > 0 && triangulate all_surfaces2 = [] all_surfaces.sort.each do |ss| # see if surface is rectangular (only checking non rotated on vertical wall) # todo - add in more robust rectangle check that can look for rotate and tilted rectangles rect_tri = Functions.rectangle?(ss) has_doors = false ss.subSurfaces.sort.each do |subSurface| if subSurface.subSurfaceType == 'Door' || subSurface.subSurfaceType == 'OverheadDoor' has_doors = true end end if has_doors || rect_tri all_surfaces2 << ss next end # add triangulated surfaces # todo - bring in more attributes # get construction from sub-surfaces and then delete them pre_tri_sub_const = {} ss.subSurfaces.sort.each do |subSurface| if subSurface.construction.is_initialized && !subSurface.isConstructionDefaulted if pre_tri_sub_const.key?(subSurface.construction.get) pre_tri_sub_const[subSurface.construction.get] = subSurface.grossArea else pre_tri_sub_const[subSurface.construction.get] = + subSurface.grossArea end end subSurface.remove end # triangulate surface ss.triangulation.each do |tri| new_surface = OpenStudio::Model::Surface.new(tri, model) if new_surface.grossArea < triangulation_min_area runner.registerWarning("triangulation produced a surface with area less than minimum for surface = #{ss.name}") new_surface.remove next end new_surface.setSpace(ss.space.get) if ss.construction.is_initialized && !ss.isConstructionDefaulted new_surface.setConstruction(ss.construction.get) end if !pre_tri_sub_const.empty? non_rect_parent[new_surface] = pre_tri_sub_const.key(pre_tri_sub_const.values.max) end all_surfaces2 << new_surface end # remove orig surface ss.remove end else all_surfaces2 = all_surfaces end # add windows all_surfaces2.sort.each do |ss| orig_sub_surf_constructions = {} ss.subSurfaces.sort.each do |sub_surf| next if sub_surf.subSurfaceType == 'Door' || sub_surf.subSurfaceType == 'OverheadDoor' if sub_surf.construction.is_initialized if orig_sub_surf_constructions.key?(sub_surf.construction.get) orig_sub_surf_constructions[sub_surf.construction.get] += 1 else orig_sub_surf_constructions[sub_surf.construction.get] = 1 end end end # remove windows if ratio 0 or add in other cases if wwr == 0 # remove all sub surfaces ss.subSurfaces.sort.each(&:remove) new_window = [] window_confirmed = true else new_window = ss.setWindowToWallRatio(wwr, sillHeight_si.value, true) window_confirmed = false end if wwr > 0 && new_window.empty? # if new window is empty then inset base surface to add window (check may need to skip on base surfaces with doors) # skip of surface already has sub-surfaces or if not triangle if inset_tri_sub && (ss.subSurfaces.empty? && ss.vertices.size <= 3) # get centroid vertices = ss.vertices centroid = OpenStudio.getCentroid(vertices).get x_cent = centroid.x y_cent = centroid.y z_cent = centroid.z # reduce vertices towards centroid scale = Math.sqrt(wwr) new_vertices = OpenStudio::Point3dVector.new vertices.each do |vertex| x = (vertex.x * scale + x_cent * (1.0 - scale)) y = (vertex.y * scale + y_cent * (1.0 - scale)) z = (vertex.z * scale + z_cent * (1.0 - scale)) new_vertices << OpenStudio::Point3d.new(x, y, z) end # create inset window new_window = OpenStudio::Model::SubSurface.new(new_vertices, model) new_window.setSurface(ss) new_window.setSubSurfaceType('FixedWindow') if non_rect_parent.key?(ss) new_window.setConstruction(non_rect_parent[ss]) end window_confirmed = true end else if wwr > 0 new_window = new_window.get end window_confirmed = true end if !window_confirmed case ss.vertices.size when 3 if !inset_tri_sub runner.registerWarning("window could not be added because the surface has 3 sides, but the inset_tri_sub argument is false = #{ss.name}") end when 4 case Functions.rectangle?(ss) when true # if Functions.requested_window_area_greater_than_max?(ss, wwr) # runner.registerWarning("window could not be added because the surface has 4 sides and is rectangular, but the WWR exceeds the maximum = #{ss.name}") # end # HACK until requested_window_area_greater_than_max? works. reduce by 0.01 to find the "maximum". (1..(wwr / 0.1).floor).each do |i| wwr_adj = wwr - (i / 100.0) new_window = ss.setWindowToWallRatio(wwr_adj, sillHeight_si.value, true) unless new_window.empty? new_window = new_window.get window_confirmed = true runner.registerWarning("window-to-wall ratio exceeds maximum and was reduced to #{wwr_adj.round(3)} for surface = #{ss.name}") break end end when false if !triangulate runner.registerWarning("window could not be added because the surface has 4 sides, but is not rectangular and the triangulate argument is false = #{ss.name}") end end else if !triangulate runner.registerWarning("window could not be added because the surface has more than 4 sides and the triangulate argument is false = #{ss.name}") end end end # warn user if resulting window doesn't have a construction, as it will result in failed simulation. In the future may use logic from starting windows to apply construction to new window. if wwr > 0 && window_confirmed && new_window.construction.empty? # construction search order (orig window on this base surface, window in this orientation, andy window in building) if !orig_sub_surf_constructions.empty? new_window.setConstruction(orig_sub_surf_constructions.key(orig_sub_surf_constructions.values.max)) elsif !orig_sub_surf_const_for_target_facade.empty? new_window.setConstruction(orig_sub_surf_const_for_target_facade.key(orig_sub_surf_const_for_target_facade.values.max)) facade_const_warning = true elsif !orig_sub_surf_const_for_target_all_ext.empty? new_window.setConstruction(orig_sub_surf_const_for_target_all_ext.key(orig_sub_surf_const_for_target_all_ext.values.max)) bldg_const_warning = true else empty_const_warning = true if catchall_glazing_const.nil? material = OpenStudio::Model::SimpleGlazing.new(model) material.setUFactor(2.556) material.setSolarHeatGainCoefficient(0.764) material.setVisibleTransmittance(0.812) catchall_glazing_const = OpenStudio::Model::Construction.new(model) catchall_glazing_const.insertLayer(0, material) catchall_glazing_const.setName('Dbl Clr 3mm/13mm Air') # from E+ dataset end new_window.setConstruction(catchall_glazing_const) end end end end # warn if some constructions do not have sub-surfaces if facade_const_warning runner.registerInfo('One or more new sub-surfaces did not have construction, using most commonly used construction for this facade.') end if bldg_const_warning runner.registerInfo('One or more new sub-surfaces did not have construction, using most commonly used construction across the entire building.') end if empty_const_warning # TODO: - add in catchall like something equiv to double glazed of glass with new simple glazing construction runner.registerWarning("Could not find existing window with construction as guide for new windows. Using a catchall glazing of #{catchall_glazing_const.name}.") end # report initial condition wwr # the initial and final ratios does not currently account for either sub-surface or zone multipliers. starting_wwr = format('%.02f', (starting_ext_window_area / starting_gross_ext_wall_area)) runner.registerInitialCondition("The model's initial window to wall ratio for #{facade} facing exterior walls was #{starting_wwr}.") if !exterior_walls runner.registerAsNotApplicable("The model has no exterior #{facade.downcase} walls and was not altered") return true elsif !window_confirmed runner.registerAsNotApplicable("The model has exterior #{facade.downcase} walls, but no windows could be added with the requested window to wall ratio") return true end # data for final condition wwr Functions.get_surfaces_or_subsurfaces_by_facade(model.getSurfaces, facade).sort.each do |s| # get surface area adjusting for zone multiplier space = s.space if !space.empty? zone = space.get.thermalZone end if !zone.empty? zone_multiplier = zone.get.multiplier if zone_multiplier > 1 end else zone_multiplier = 1 # space is not in a thermal zone end surface_gross_area = s.grossArea * zone_multiplier # loop through sub surfaces and add area including multiplier ext_window_area = 0 s.subSurfaces.sort.each do |subSurface| # onlky one and should have multiplier of 1 ext_window_area += subSurface.grossArea * subSurface.multiplier * zone_multiplier end final_gross_ext_wall_area += surface_gross_area final_ext_window_area += ext_window_area end # get delta in ft^2 for final - starting window area increase_window_area_si = OpenStudio::Quantity.new(final_ext_window_area - starting_ext_window_area, unit_area_si) increase_window_area_ip = OpenStudio.convert(increase_window_area_si, unit_area_ip).get # calculate final envelope cost as positive value constructions = model.getConstructions.sort constructions.each do |construction| const_llcs = construction.lifeCycleCosts const_llcs.sort.each do |const_llc| if const_llc.category == 'Construction' envelope_cost += const_llc.totalCost end end end # report final condition final_wwr = format('%.02f', (final_ext_window_area / final_gross_ext_wall_area)) runner.registerFinalCondition("The model's final window to wall ratio for #{facade} facing exterior walls is #{final_wwr}. Window area increased by #{OpenStudio.toNeatString(increase_window_area_ip.value, 0)} (ft^2). The material and construction costs increased by $#{OpenStudio.toNeatString(envelope_cost, 0)}.") return true end |