module OpenstudioStandards::Geometry

The Geometry module provides methods to create, modify, and get information about model geometry

The Geometry module provides methods to create, modify, and get information about model geometry

The Geometry module provides methods to create, modify, and get information about model geometry

The Geometry module provides methods to create, modify, and get information about model geometry

The Geometry module provides methods to create, modify, and get information about model geometry

The Geometry module provides methods to create, modify, and get information about model geometry

Public Class Methods

aspect_ratio(area, perimeter) click to toggle source

calculate aspect ratio from area and perimeter

@param area [Double] area @param perimeter [Double] perimeter @return [Double] aspect ratio

# File lib/openstudio-standards/geometry/information.rb, line 13
def self.aspect_ratio(area, perimeter)
  length = 0.25 * (perimeter + Math.sqrt((perimeter**2) - (16 * area)))
  width = 0.25 * (perimeter - Math.sqrt((perimeter**2) - (16 * area)))
  aspect_ratio = length / width

  return aspect_ratio
end
bar_hash_setup_run(model, args, length, width, floor_height, center_of_footprint, space_types_hash, num_stories) click to toggle source

give info messages bar hash for create_bar method

@param model [OpenStudio::Model::Model] OpenStudio model object @param args [Hash] user arguments @param length [Double] length of building in meters @param width [Double] width of building in meters @param floor_height [Double] floor height in meters @param center_of_footprint [OpenStudio::Point3d] center of footprint @param space_types_hash [Hash] space type hash @param num_stories [Double] number of stories @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/create_bar.rb, line 969
def self.bar_hash_setup_run(model, args, length, width, floor_height, center_of_footprint, space_types_hash, num_stories)
  # create envelope
  # populate bar_hash and create envelope with data from envelope_data_hash and user arguments
  bar_hash = {}
  bar_hash[:length] = length
  bar_hash[:width] = width
  bar_hash[:num_stories_below_grade] = args[:num_stories_below_grade]
  bar_hash[:num_stories_above_grade] = args[:num_stories_above_grade]
  bar_hash[:floor_height] = floor_height
  bar_hash[:center_of_footprint] = center_of_footprint
  bar_hash[:bar_division_method] = args[:bar_division_method]
  bar_hash[:story_multiplier_method] = args[:story_multiplier_method]
  bar_hash[:make_mid_story_surfaces_adiabatic] = args[:make_mid_story_surfaces_adiabatic]
  bar_hash[:space_types] = space_types_hash
  bar_hash[:building_wwr_n] = args[:wwr]
  bar_hash[:building_wwr_s] = args[:wwr]
  bar_hash[:building_wwr_e] = args[:wwr]
  bar_hash[:building_wwr_w] = args[:wwr]

  # round up non integer stoires to next integer
  num_stories_round_up = num_stories.ceil
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Making bar with length of #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft and width of #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft")

  # party_walls_array to be used by orientation specific or fractional party wall values
  party_walls_array = [] # this is an array of arrays, where each entry is effective building story with array of directions

  if args[:party_wall_stories_north] + args[:party_wall_stories_south] + args[:party_wall_stories_east] + args[:party_wall_stories_west] > 0

    # loop through effective number of stories add orientation specific party walls per user arguments
    num_stories_round_up.times do |i|
      test_value = i + 1 - bar_hash[:num_stories_below_grade]

      array = []
      if args[:party_wall_stories_north] >= test_value
        array << 'north'
      end
      if args[:party_wall_stories_south] >= test_value
        array << 'south'
      end
      if args[:party_wall_stories_east] >= test_value
        array << 'east'
      end
      if args[:party_wall_stories_west] >= test_value
        array << 'west'
      end

      # populate party_wall_array for this story
      party_walls_array << array
    end
  end

  # calculate party walls if using party_wall_fraction method
  if args[:party_wall_fraction] > 0 && !party_walls_array.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Both orientation and fractional party wall values arguments were populated, will ignore fractional party wall input')
  elsif args[:party_wall_fraction] > 0
    # orientation of long and short side of building will vary based on building rotation

    # full story ext wall area
    typical_length_facade_area = length * floor_height
    typical_width_facade_area = width * floor_height

    # top story ext wall area, may be partial story
    partial_story_multiplier = (1.0 - args[:num_stories_above_grade].ceil + args[:num_stories_above_grade])
    area_multiplier = partial_story_multiplier
    edge_multiplier = Math.sqrt(area_multiplier)
    top_story_length = length * edge_multiplier
    top_story_width = width * edge_multiplier
    top_story_length_facade_area = top_story_length * floor_height
    top_story_width_facade_area = top_story_width * floor_height

    total_exterior_wall_area = (2 * (length + width) * (args[:num_stories_above_grade].ceil - 1.0) * floor_height) + (2 * (top_story_length + top_story_width) * floor_height)
    target_party_wall_area = total_exterior_wall_area * args[:party_wall_fraction]

    width_counter = 0
    width_area = 0.0
    facade_area = typical_width_facade_area
    until (width_area + facade_area >= target_party_wall_area) || (width_counter == (args[:num_stories_above_grade].ceil * 2))
      # update facade area for top story
      if (width_counter == (args[:num_stories_above_grade].ceil - 1)) || (width_counter == ((args[:num_stories_above_grade].ceil * 2) - 1))
        facade_area = top_story_width_facade_area
      else
        facade_area = typical_width_facade_area
      end

      width_counter += 1
      width_area += facade_area

    end
    width_area_remainder = target_party_wall_area - width_area

    length_counter = 0
    length_area = 0.0
    facade_area = typical_length_facade_area
    until (length_area + facade_area >= target_party_wall_area) || (length_counter == args[:num_stories_above_grade].ceil * 2)
      # update facade area for top story
      if (length_counter == (args[:num_stories_above_grade].ceil - 1)) || (length_counter == ((args[:num_stories_above_grade].ceil * 2) - 1))
        facade_area = top_story_length_facade_area
      else
        facade_area = typical_length_facade_area
      end

      length_counter += 1
      length_area += facade_area
    end
    length_area_remainder = target_party_wall_area - length_area

    # get rotation and best fit to adjust orientation for fraction party wall
    rotation = args[:building_rotation] % 360.0 # should result in value between 0 and 360
    card_dir_array = [0.0, 90.0, 180.0, 270.0, 360.0]
    # reverse array to properly handle 45, 135, 225, and 315
    best_fit = card_dir_array.reverse.min_by { |x| (x.to_f - rotation).abs }

    if ![90.0, 270.0].include? best_fit
      width_card_dir = ['east', 'west']
      length_card_dir = ['north', 'south']
    else # if rotation is closest to 90 or 270 then reverse which orientation is used for length and width
      width_card_dir = ['north', 'south']
      length_card_dir = ['east', 'west']
    end

    # if dont' find enough on short sides
    if width_area_remainder <= typical_length_facade_area

      num_stories_round_up.times do |i|
        if i + 1 <= args[:num_stories_below_grade]
          party_walls_array << []
          next
        end
        if i + 1 - args[:num_stories_below_grade] <= width_counter
          if i + 1 - args[:num_stories_below_grade] <= width_counter - args[:num_stories_above_grade]
            party_walls_array << width_card_dir
          else
            party_walls_array << [width_card_dir.first]
          end
        else
          party_walls_array << []
        end
      end

    else
      # use long sides instead
      num_stories_round_up.times do |i|
        if i + 1 <= args[:num_stories_below_grade]
          party_walls_array << []
          next
        end
        if i + 1 - args[:num_stories_below_grade] <= length_counter
          if i + 1 - args[:num_stories_below_grade] <= length_counter - args[:num_stories_above_grade]
            party_walls_array << length_card_dir
          else
            party_walls_array << [length_card_dir.first]
          end
        else
          party_walls_array << []
        end
      end

    end
    # @todo currently won't go past making two opposing sets of walls party walls. Info and registerValue are after create_bar in measure.rb
  end

  # populate bar hash with story information
  bar_hash[:stories] = {}
  num_stories_round_up.times do |i|
    if party_walls_array.empty?
      party_walls = []
    else
      party_walls = party_walls_array[i]
    end

    # add below_partial_story
    if num_stories.ceil > num_stories && i == num_stories_round_up - 2
      below_partial_story = true
    else
      below_partial_story = false
    end

    # bottom_story_ground_exposed_floor and top_story_exterior_exposed_roof already setup as bool
    bar_hash[:stories]["key #{i}"] = { story_party_walls: party_walls, story_min_multiplier: 1, story_included_in_building_area: true, below_partial_story: below_partial_story, bottom_story_ground_exposed_floor: args[:bottom_story_ground_exposed_floor], top_story_exterior_exposed_roof: args[:top_story_exterior_exposed_roof] }
  end

  # create bar
  new_spaces = create_bar(model, bar_hash)

  # check expect roof and wall area
  target_footprint = bar_hash[:length] * bar_hash[:width]
  ground_floor_area = 0.0
  roof_area = 0.0
  new_spaces.each do |space|
    space.surfaces.each do |surface|
      if surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground'
        ground_floor_area += surface.netArea
      elsif surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Outdoors'
        roof_area += surface.netArea
      end
    end
  end
  # @todo extend to address when top and or bottom story are not exposed via argument
  if ground_floor_area > target_footprint + 0.001 || roof_area > target_footprint + 0.001
    # OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.")
    # return false

    # not providing adiabatic work around when top story is partial story.
    if args[:num_stories_above_grade].to_i != args[:num_stories_above_grade].ceil
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.')
      return false
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error, altering impacted surfaces boundary condition to be adiabatic.')
      match_error = true
    end
  else
    match_error = false
  end

  # @todo should be able to remove this fix after OpenStudio intersection issue is fixed. At that time turn the above message into an error with return false after it
  return true unless match_error

  # identify z value of top and bottom story
  bottom_story = nil
  top_story = nil
  new_spaces.each do |space|
    story = space.buildingStory.get
    nom_z = story.nominalZCoordinate.get
    if bottom_story.nil?
      bottom_story = nom_z
    elsif bottom_story > nom_z
      bottom_story = nom_z
    end
    if top_story.nil?
      top_story = nom_z
    elsif top_story < nom_z
      top_story = nom_z
    end
  end

  # change boundary condition and intersection as needed.
  new_spaces.each do |space|
    if space.buildingStory.get.nominalZCoordinate.get > bottom_story
      # change floors
      space.surfaces.each do |surface|
        next if !(surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground')

        surface.setOutsideBoundaryCondition('Adiabatic')
      end
    end
    if space.buildingStory.get.nominalZCoordinate.get < top_story
      # change ceilings
      space.surfaces.each do |surface|
        next if !(surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Outdoors')

        surface.setOutsideBoundaryCondition('Adiabatic')
      end
    end
  end
end
bar_reduced_bounding_box(envelope_data_hash) click to toggle source

get length and width of rectangle matching bounding box aspect ratio will maintaining proper floor area

@param envelope_data_hash [Hash] Hash of envelope data @return [Hash] hash of bar length and width

# File lib/openstudio-standards/geometry/create_bar.rb, line 387
def self.bar_reduced_bounding_box(envelope_data_hash)
  bar = {}

  bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
  bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
  bounding_area = bounding_length * bounding_width
  footprint_area = envelope_data_hash[:building_floor_area] / (envelope_data_hash[:effective_num_stories_above_grade] + envelope_data_hash[:effective_num_stories_below_grade].to_f)
  area_multiplier = footprint_area / bounding_area
  edge_multiplier = Math.sqrt(area_multiplier)
  bar[:length] = bounding_length * edge_multiplier
  bar[:width] = bounding_width * edge_multiplier

  return bar
end
bar_reduced_width(envelope_data_hash) click to toggle source

get length and width of rectangle matching longer of two edges, and reducing the other way until floor area matches

@param envelope_data_hash [Hash] Hash of envelope data @return [Hash] hash of bar length and width

# File lib/openstudio-standards/geometry/create_bar.rb, line 406
def self.bar_reduced_width(envelope_data_hash)
  bar = {}

  bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
  bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
  footprint_area = envelope_data_hash[:building_floor_area] / (envelope_data_hash[:effective_num_stories_above_grade] + envelope_data_hash[:effective_num_stories_below_grade].to_f)

  if bounding_length >= bounding_width
    bar[:length] = bounding_length
    bar[:width] = footprint_area / bounding_length
  else
    bar[:width] = bounding_width
    bar[:length] = footprint_area / bounding_width
  end

  return bar
end
bar_stretched(envelope_data_hash) click to toggle source

get length and width of rectangle by stretching it until both floor area and exterior wall area or perimeter match

@param envelope_data_hash [Hash] Hash of envelope data including @return [Hash] hash of bar length and width

# File lib/openstudio-standards/geometry/create_bar.rb, line 428
def self.bar_stretched(envelope_data_hash)
  bar = {}

  bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
  bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
  a = envelope_data_hash[:building_floor_area] / (envelope_data_hash[:effective_num_stories_above_grade] + envelope_data_hash[:effective_num_stories_below_grade].to_f)
  p = envelope_data_hash[:building_perimeter]

  if bounding_length >= bounding_width
    bar[:length] = 0.25 * (p + Math.sqrt((p**2) - (16 * a)))
    bar[:width] = 0.25 * (p - Math.sqrt((p**2) - (16 * a)))
  else
    bar[:length] = 0.25 * (p - Math.sqrt((p**2) - (16 * a)))
    bar[:width] = 0.25 * (p + Math.sqrt((p**2) - (16 * a)))
  end

  return bar
end
building_form_defaults(building_type) click to toggle source

Building Form Defaults from Table 4.2 in Achieving the 30% Goal: Energy and Cost Savings Analysis of ASHRAE Standard 90.1-2010 aspect ratio for NA replaced with floor area to perimeter ratio from prototype model currently no reason to split apart doe and deer inputs here

@param building_type [String] standard building type @return [Hash] Hash of aspect_ratio, wwr, typical_story, and perim_mult

# File lib/openstudio-standards/geometry/create_bar.rb, line 13
def self.building_form_defaults(building_type)
  hash = {}

  # DOE Prototypes

  # calculate aspect ratios not represented on Table 4.2
  primary_footprint = 73958.0
  primary_p = 619.0 # wrote measure using calculate_perimeter method in os_lib_geometry
  primary_ns_ew_ratio = 2.829268293 # estimated from ratio of ns/ew total wall area
  primary_width = Math.sqrt(primary_footprint / primary_ns_ew_ratio)
  primary_p_min = 2 * (primary_width + (primary_width / primary_footprint))
  primary_p_mult = primary_p / primary_p_min

  secondary_footprint = 210887.0 / 2.0 # floor area divided by area instead of true footprint 128112.0)
  secondary_p = 708.0 # wrote measure using calculate_perimeter method in os_lib_geometry
  secondary_ns_ew_ratio = 2.069230769 # estimated from ratio of ns/ew total wall area
  secondary_width = Math.sqrt(secondary_footprint / secondary_ns_ew_ratio)
  secondary_p_min = 2 * (secondary_width + (secondary_width / secondary_footprint))
  secondary_p_mult = secondary_p / secondary_p_min

  outpatient_footprint = 40946.0 / 3.0 # floor area divided by area instead of true footprint 17872.0)
  outpatient_p = 537.0 # wrote measure using calculate_perimeter method in os_lib_geometry
  outpatient_ns_ew_ratio = 1.56448737 # estimated from ratio of ns/ew total wall area
  outpatient_width = Math.sqrt(outpatient_footprint / outpatient_ns_ew_ratio)
  outpatient_p_min = 2 * (outpatient_width + (outpatient_footprint / outpatient_width))
  outpatient_p_mult = outpatient_p / outpatient_p_min

  # primary_aspet_ratio = OpenstudioStandards::Geometry.aspect_ratio(73958.0, 2060.0)
  # secondary_aspet_ratio = OpenstudioStandards::Geometry.aspect_ratio(128112.0, 2447.0)
  # outpatient_aspet_ratio = OpenstudioStandards::Geometry.aspect_ratio(14782.0, 588.0)
  supermarket_a = 45001.0
  supermarket_p = 866.0
  supermarket_wwr = 1880.0 / (supermarket_p * 20.0)
  supermarket_aspect_ratio = OpenstudioStandards::Geometry.aspect_ratio(supermarket_a, supermarket_p)

  hash['SmallOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
  hash['MediumOffice'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 13.0, perim_mult: 1.0 }
  hash['LargeOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 13.0, perim_mult: 1.0 }
  hash['RetailStandalone'] = { aspect_ratio: 1.28, wwr: 0.07, typical_story: 20.0, perim_mult: 1.0 }
  hash['RetailStripmall'] = { aspect_ratio: 4.0, wwr: 0.11, typical_story: 17.0, perim_mult: 1.0 }
  hash['PrimarySchool'] = { aspect_ratio: primary_ns_ew_ratio.round(1), wwr: 0.35, typical_story: 13.0, perim_mult: primary_p_mult.round(3) }
  hash['SecondarySchool'] = { aspect_ratio: secondary_ns_ew_ratio.round(1), wwr: 0.33, typical_story: 13.0, perim_mult: secondary_p_mult.round(3) }
  hash['Outpatient'] = { aspect_ratio: outpatient_ns_ew_ratio.round(1), wwr: 0.20, typical_story: 10.0, perim_mult: outpatient_p_mult.round(3) }
  hash['Hospital'] = { aspect_ratio: 1.33, wwr: 0.16, typical_story: 14.0, perim_mult: 1.0 }
  hash['SmallHotel'] = { aspect_ratio: 3.0, wwr: 0.11, typical_story: 9.0, first_story: 11.0, perim_mult: 1.0 }
  hash['LargeHotel'] = { aspect_ratio: 5.1, wwr: 0.27, typical_story: 10.0, first_story: 13.0, perim_mult: 1.0 }

  # code in get_space_types_from_building_type is used to override building wwr with space type specific wwr
  hash['Warehouse'] = { aspect_ratio: 2.2, wwr: 0.0, typical_story: 28.0, perim_mult: 1.0 }

  hash['FullServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
  hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
  hash['MidriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
  hash['HighriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
  # SuperMarket inputs come from prototype model
  hash['SuperMarket'] = { aspect_ratio: supermarket_aspect_ratio.round(1), wwr: supermarket_wwr.round(2), typical_story: 20.0, perim_mult: 1.0 }

  # Add Laboratory and Data Centers
  hash['Laboratory'] = { aspect_ratio: 1.33, wwr: 0.12, typical_story: 10.0, perim_mult: 1.0 }
  hash['LargeDataCenterLowITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
  hash['LargeDataCenterHighITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
  hash['SmallDataCenterLowITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
  hash['SmallDataCenterHighITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }

  # Add Courthouse and Education
  hash['Courthouse'] = { aspect_ratio: 2.06, wwr: 0.18, typical_story: 16.0, perim_mult: 1.0 }
  hash['College'] = { aspect_ratio: 2.5, wwr: 0.037, typical_story: 13.0, perim_mult: 1.0 }

  # DEER Prototypes
  hash['Asm'] = { aspect_ratio: 1.0, wwr: 0.19, typical_story: 15.0 }
  hash['ECC'] = { aspect_ratio: 4.0, wwr: 0.25, typical_story: 13.0 }
  hash['EPr'] = { aspect_ratio: 2.0, wwr: 0.16, typical_story: 12.0 }
  hash['ERC'] = { aspect_ratio: 1.7, wwr: 0.03, typical_story: 12.0 }
  hash['ESe'] = { aspect_ratio: 1.0, wwr: 0.15, typical_story: 13.0 }
  hash['EUn'] = { aspect_ratio: 2.5, wwr: 0.3, typical_story: 14.0 }
  hash['Gro'] = { aspect_ratio: 1.0, wwr: 0.07, typical_story: 25.0 }
  hash['Hsp'] = { aspect_ratio: 1.5, wwr: 0.11, typical_story: 13.0 }
  hash['Htl'] = { aspect_ratio: 3.0, wwr: 0.23, typical_story: 9.5, first_story: 12.0 }
  hash['MBT'] = { aspect_ratio: 10.7, wwr: 0.12, typical_story: 15.0 }
  hash['MFm'] = { aspect_ratio: 1.4, wwr: 0.24, typical_story: 9.5 }
  hash['MLI'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 35.0 }
  hash['Mtl'] = { aspect_ratio: 5.1, wwr: 0.41, typical_story: 9.0 }
  hash['Nrs'] = { aspect_ratio: 10.3, wwr: 0.2, typical_story: 13.0 }
  hash['OfL'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
  hash['OfS'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
  hash['RFF'] = { aspect_ratio: 1.0, wwr: 0.25, typical_story: 13.0 }
  hash['RSD'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 13.0 }
  hash['Rt3'] = { aspect_ratio: 1.0, wwr: 0.02, typical_story: 20.8 }
  hash['RtL'] = { aspect_ratio: 1.0, wwr: 0.03, typical_story: 20.5 }
  hash['RtS'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 12.0 }
  hash['SCn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
  hash['SUn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
  hash['WRf'] = { aspect_ratio: 1.6, wwr: 0.0, typical_story: 32.0 }

  return hash[building_type]
end
building_story_get_exterior_wall_perimeter(story, multiplier_adjustment: nil, exterior_boundary_conditions: ['Outdoors', 'Ground'], bounding_box: nil) click to toggle source

Calculate the story exterior wall perimeter. Selected story should have above grade walls. If not perimeter may return zero.

@param story [OpenStudio::Model::BuildingStory] @param multiplier_adjustment [Double] Adjust the calculated perimeter to account for zone multipliers. The value represents the story_multiplier which reduces the adjustment by that factor over the full zone multiplier. @param exterior_boundary_conditions [Array<String>] Array of strings of exterior boundary conditions. @param bounding_box [OpenStudio::BoundingBox] bounding box to determine which spaces are included @todo this doesn’t catch walls that are split that sit above floor surfaces that are not (e.g. main corridoor in secondary school model) @todo also odd with multi-height spaces

# File lib/openstudio-standards/geometry/information.rb, line 695
def self.building_story_get_exterior_wall_perimeter(story,
                                                    multiplier_adjustment: nil,
                                                    exterior_boundary_conditions: ['Outdoors', 'Ground'],
                                                    bounding_box: nil)
  perimeter = 0
  party_walls = []
  story.spaces.each do |space|
    # counter to use later
    edge_hash = {}
    edge_counter = 0
    space.surfaces.each do |surface|
      # get vertices
      vertex_hash = {}
      vertex_counter = 0
      surface.vertices.each do |vertex|
        vertex_counter += 1
        vertex_hash[vertex_counter] = [vertex.x, vertex.y, vertex.z]
      end
      # make edges
      counter = 0
      vertex_hash.each do |k, v|
        edge_counter += 1
        counter += 1
        if vertex_hash.size == counter
          # different code for wrap around vertex
          edge_hash[edge_counter] = [v, vertex_hash[1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
        else
          edge_hash[edge_counter] = [v, vertex_hash[counter + 1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
        end
      end
    end

    # check edges for matches (need opposite vertices and proper boundary conditions)
    edge_hash.each do |k1, v1|
      # apply to any floor boundary condition. This supports used in floors above basements
      next if v1[4] != 'Floor'

      edge_hash.each do |k2, v2|
        test_boundary_cond = false
        next if !exterior_boundary_conditions.include?(v2[3]) # method arg takes multiple conditions
        next if v2[4] != 'Wall'

        # see if edges have same geometry

        # found cases where the two lines below removed edges and resulted in lower than actual perimeter. Added new code with tolerance.
        # next if not v1[0] == v2[1] # next if not same geometry reversed
        # next if not v1[1] == v2[0]

        # these are three item array's add in tolerance for each array entry
        tolerance = 0.0001
        test_a = true
        test_b = true
        3.times.each do |i|
          if (v1[0][i] - v2[1][i]).abs > tolerance
            test_a = false
          end
          if (v1[1][i] - v2[0][i]).abs > tolerance
            test_b = false
          end
        end

        next if test_a != true
        next if test_b != true

        # edge_bounding_box = OpenStudio::BoundingBox.new
        # edge_bounding_box.addPoints(space.transformation() * v2[2].vertices)
        # if not edge_bounding_box.intersects(bounding_box) doesn't seem to work reliably, writing custom code to check

        point_one = OpenStudio::Point3d.new(v2[0][0], v2[0][1], v2[0][2])
        point_one = (space.transformation * point_one)
        point_two = OpenStudio::Point3d.new(v2[1][0], v2[1][1], v2[1][2])
        point_two = (space.transformation * point_two)

        if !bounding_box.nil? && (v2[3] == 'Adiabatic')

          on_bounding_box = false
          if ((bounding_box.minX.to_f - point_one.x).abs < tolerance) && ((bounding_box.minX.to_f - point_two.x).abs < tolerance)
            on_bounding_box = true
          elsif ((bounding_box.maxX.to_f - point_one.x).abs < tolerance) && ((bounding_box.maxX.to_f - point_two.x).abs < tolerance)
            on_bounding_box = true
          elsif ((bounding_box.minY.to_f - point_one.y).abs < tolerance) && ((bounding_box.minY.to_f - point_two.y).abs < tolerance)
            on_bounding_box = true
          elsif ((bounding_box.maxY.to_f - point_one.y).abs < tolerance) && ((bounding_box.maxY.to_f - point_two.y).abs < tolerance)
            on_bounding_box = true
          end

          # if not edge_bounding_box.intersects(bounding_box) doesn't seem to work reliably, writing custom code to check
          # todo - this is basic check for adiabatic party walls and won't catch all situations. Can be made more robust in the future
          if on_bounding_box == true
            length = OpenStudio::Vector3d.new(point_one - point_two).length
            party_walls << v2[2]
            length_ip_display = OpenStudio.convert(length, 'm', 'ft').get.round(2)
            OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Information', " * #{v2[2].name} has an adiabatic boundary condition and sits in plane with the building bounding box. Adding #{length_ip_display} (ft) to perimeter length of #{story.name} for this surface, assuming it is a party wall.")
          elsif space.multiplier == 1
            length = OpenStudio::Vector3d.new(point_one - point_two).length
            party_walls << v2[2]
            length_ip_display = OpenStudio.convert(length, 'm', 'ft').get.round(2)
            OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Information', " * #{v2[2].name} has an adiabatic boundary condition and is in a zone with a multiplier of 1. Adding #{length_ip_display} (ft) to perimeter length of #{story.name} for this surface, assuming it is a party wall.")
          else
            length = 0
          end

        else
          length = OpenStudio::Vector3d.new(point_one - point_two).length
        end

        if multiplier_adjustment.nil?
          perimeter += length
        else
          # adjust for multiplier
          non_story_multiplier = space.multiplier / multiplier_adjustment.to_f
          perimeter += length * non_story_multiplier
        end
      end
    end
  end

  return { perimeter: perimeter, party_walls: party_walls }
end
building_story_get_floor_multiplier(building_story) click to toggle source

Checks all spaces on this story that are part of the total floor area to see if they have the same multiplier. If they do, assume that the multipliers are being used as a floor multiplier.

@param building_story [OpenStudio::Model::BuildingStory] OpenStudio BuildingStory object @return [Integer] return the floor multiplier for this story, returning 1 if no floor multiplier.

# File lib/openstudio-standards/geometry/information.rb, line 820
def self.building_story_get_floor_multiplier(building_story)
  floor_multiplier = 1

  # Determine the multipliers for all spaces
  multipliers = []
  building_story.spaces.each do |space|
    # Ignore spaces that aren't part of the total floor area
    next unless space.partofTotalFloorArea

    multipliers << space.multiplier
  end

  # If there are no spaces on this story, assume
  # a multiplier of 1
  if multipliers.empty?
    return floor_multiplier
  end

  # Calculate the average multiplier and
  # then convert to integer.
  avg_multiplier = (multipliers.inject { |a, e| a + e }.to_f / multipliers.size).to_i

  # If the multiplier is greater than 1, report this
  if avg_multiplier > 1
    floor_multiplier = avg_multiplier
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Information', "Story #{building_story.name} has a multiplier of #{floor_multiplier}.")
  end

  return floor_multiplier
end
building_story_get_minimum_height(building_story) click to toggle source

Gets the minimum height of the building story. This is considered to be the minimum z value of any vertex of any surface of any space on the story, with the exception of plenum spaces.

@param building_story [OpenStudio::Model::BuildingStory] OpenStudio BuildingStory object @return [Double] the minimum height in meters

# File lib/openstudio-standards/geometry/information.rb, line 856
def self.building_story_get_minimum_height(building_story)
  z_heights = []
  building_story.spaces.each do |space|
    # Skip plenum spaces
    next if OpenstudioStandards::Space.space_plenum?(space)

    # Get the z value of the space, which
    # vertices in space surfaces are relative to.
    z_origin = space.zOrigin

    # loop through space surfaces to find min z value
    space.surfaces.each do |surface|
      surface.vertices.each do |vertex|
        z_heights << (vertex.z + z_origin)
      end
    end
  end

  # Error if no z heights were found
  z = 999.9
  if z_heights.empty?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Information', "For #{building_story.name} could not find the minimum_z_value, which means the story has no spaces assigned or the spaces have no surfaces.")
  else
    z = z_heights.min
  end

  return z
end
building_story_get_thermal_zones(building_story) click to toggle source

Get an array of OpenStudio ThermalZone objects for an OpenStudio BuildingStory

@param building_story [OpenStudio::Model::BuildingStory] OpenStudio BuildingStory object @return [Array<OpenStudio::Model::ThermalZone>] Array of OpenStudio ThermalZone objects, empty array if none

# File lib/openstudio-standards/geometry/information.rb, line 889
def self.building_story_get_thermal_zones(building_story)
  zones = []
  building_story.spaces.sort.each do |space|
    zones << space.thermalZone.get if space.thermalZone.is_initialized
  end
  zones = zones.uniq

  return zones
end
create_bar(model, bar_hash) click to toggle source

create_bar creates spaces based on a set of geometric characteristics

@param model [OpenStudio::Model::Model] OpenStudio model object @param bar_hash [Hash] A hash object of bar characteristics @return [Array<OpenStudio::Model::Space>] An array of OpenStudio Space objects

# File lib/openstudio-standards/geometry/create_bar.rb, line 452
def self.create_bar(model, bar_hash)
  # make custom story hash when number of stories below grade > 0
  # @todo update this so have option basements are not below 0? (useful for simplifying existing model and maintaining z position relative to site shading)
  story_hash = {}
  eff_below = bar_hash[:num_stories_below_grade]
  eff_above = bar_hash[:num_stories_above_grade]
  footprint_origin_point = bar_hash[:center_of_footprint]
  typical_story_height = bar_hash[:floor_height]

  # warn about site shading
  if !model.getSite.shadingSurfaceGroups.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'The model has one or more site shading surfaces. New geometry may not be positioned where expected, it will be centered over the center of the original geometry.')
  end

  # flatten story_hash out to individual stories included in building area
  stories_flat = []
  stories_flat_counter = 0
  bar_hash[:stories].each_with_index do |(k, v), i|
    # k is invalid in some cases, old story object that has been removed, should be from low to high including basement
    # skip if source story insn't included in building area
    if v[:story_included_in_building_area].nil? || (v[:story_included_in_building_area] == true)

      # add to counter
      stories_flat_counter += v[:story_min_multiplier]

      flat_hash = {}
      flat_hash[:story_party_walls] = v[:story_party_walls]
      flat_hash[:below_partial_story] = v[:below_partial_story]
      flat_hash[:bottom_story_ground_exposed_floor] = v[:bottom_story_ground_exposed_floor]
      flat_hash[:top_story_exterior_exposed_roof] = v[:top_story_exterior_exposed_roof]
      if i < eff_below
        flat_hash[:story_type] = 'b'
        flat_hash[:multiplier] = 1
      elsif i == eff_below
        flat_hash[:story_type] = 'ground'
        flat_hash[:multiplier] = 1
      elsif stories_flat_counter == eff_below + eff_above.ceil
        flat_hash[:story_type] = 'top'
        flat_hash[:multiplier] = 1
      else
        flat_hash[:story_type] = 'mid'
        flat_hash[:multiplier] = v[:story_min_multiplier]
      end

      compare_hash = {}
      if !stories_flat.empty?
        stories_flat.last.each { |s, m| compare_hash[s] = flat_hash[s] if flat_hash[s] != m }
      end
      if (bar_hash[:story_multiplier_method] != 'None' && stories_flat.last == flat_hash) || (bar_hash[:story_multiplier_method] != 'None' && compare_hash.size == 1 && compare_hash.include?(:multiplier))
        stories_flat.last[:multiplier] += v[:story_min_multiplier]
      else
        stories_flat << flat_hash
      end
    end
  end

  if bar_hash[:num_stories_below_grade] > 0

    # add in below grade levels (may want to add below grade multipliers at some point if we start running deep basements)
    eff_below.times do |i|
      story_hash["B#{i + 1}"] = { space_origin_z: footprint_origin_point.z - (typical_story_height * (i + 1)), space_height: typical_story_height, multiplier: 1 }
    end
  end

  # add in above grade levels
  if eff_above > 2
    story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }

    footprint_counter = 0
    effective_stories_counter = 1
    stories_flat.each do |hash|
      next if hash[:story_type] != 'mid'

      if footprint_counter == 0
        string = 'mid'
      else
        string = "mid#{footprint_counter + 1}"
      end
      story_hash[string] = { space_origin_z: footprint_origin_point.z + (typical_story_height * effective_stories_counter) + (typical_story_height * (hash[:multiplier] - 1) / 2.0), space_height: typical_story_height, multiplier: hash[:multiplier] }
      footprint_counter += 1
      effective_stories_counter += hash[:multiplier]
    end

    story_hash['top'] = { space_origin_z: footprint_origin_point.z + (typical_story_height * (eff_above.ceil - 1)), space_height: typical_story_height, multiplier: 1 }
  elsif eff_above > 1
    story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
    story_hash['top'] = { space_origin_z: footprint_origin_point.z + (typical_story_height * (eff_above.ceil - 1)), space_height: typical_story_height, multiplier: 1 }
  else # one story only
    story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
  end

  # create footprints
  if bar_hash[:bar_division_method] == 'Multiple Space Types - Simple Sliced'
    footprints = []
    story_hash.size.times do |i|
      # adjust size of bar of top story is not a full story
      if i + 1 == story_hash.size
        area_multiplier = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
        edge_multiplier = Math.sqrt(area_multiplier)
        length = bar_hash[:length] * edge_multiplier
        width = bar_hash[:width] * edge_multiplier
      else
        length = bar_hash[:length]
        width = bar_hash[:width]
      end
      footprints << OpenstudioStandards::Geometry.create_sliced_bar_simple_polygons(bar_hash[:space_types], length, width, bar_hash[:center_of_footprint])
    end

  elsif bar_hash[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'

    # update story_hash for partial_story_above
    story_hash.each_with_index do |(k, v), i|
      # adjust size of bar of top story is not a full story
      if i + 1 == story_hash.size
        story_hash[k][:partial_story_multiplier] = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
      end
    end

    footprints = OpenstudioStandards::Geometry.create_sliced_bar_multi_polygons(bar_hash[:space_types], bar_hash[:length], bar_hash[:width], bar_hash[:center_of_footprint], story_hash)

  else
    footprints = []
    story_hash.size.times do |i|
      # adjust size of bar of top story is not a full story
      if i + 1 == story_hash.size
        area_multiplier = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
        edge_multiplier = Math.sqrt(area_multiplier)
        length = bar_hash[:length] * edge_multiplier
        width = bar_hash[:width] * edge_multiplier
      else
        length = bar_hash[:length]
        width = bar_hash[:width]
      end
      # perimeter defaults to 15 ft
      footprints << OpenstudioStandards::Geometry.create_core_and_perimeter_polygons(length, width, bar_hash[:center_of_footprint])
    end

    # set primary space type to building default space type
    space_types = bar_hash[:space_types].sort_by { |k, v| v[:floor_area] }
    if space_types.last.first.class.to_s == 'OpenStudio::Model::SpaceType'
      model.getBuilding.setSpaceType(space_types.last.first)
    end

  end

  # make spaces from polygons
  new_spaces = OpenstudioStandards::Geometry.create_spaces_from_polygons(model, footprints, bar_hash[:floor_height], bar_hash[:num_stories], bar_hash[:center_of_footprint], story_hash)

  # put all of the spaces in the model into a vector for intersection and surface matching
  spaces = OpenStudio::Model::SpaceVector.new
  model.getSpaces.sort.each do |space|
    spaces << space
  end

  # flag for intersection and matching type
  diagnostic_intersect = true

  # only intersect if make_mid_story_surfaces_adiabatic false
  if diagnostic_intersect

    model.getPlanarSurfaces.sort.each do |surface|
      array = []
      vertices = surface.vertices
      fixed = false
      vertices.each do |vertex|
        next if fixed

        if array.include?(vertex)
          # create a new set of vertices
          new_vertices = OpenStudio::Point3dVector.new
          array_b = []
          surface.vertices.each do |vertex_b|
            next if array_b.include?(vertex_b)

            new_vertices << vertex_b
            array_b << vertex_b
          end
          surface.setVertices(new_vertices)
          num_removed = vertices.size - surface.vertices.size
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{surface.name} has duplicate vertices. Started with #{vertices.size} vertices, removed #{num_removed}.")
          fixed = true
        else
          array << vertex
        end
      end
    end

    # remove collinear points in a surface
    model.getPlanarSurfaces.sort.each do |surface|
      new_vertices = OpenStudio.removeCollinear(surface.vertices)
      starting_count = surface.vertices.size
      final_count = new_vertices.size
      if final_count < starting_count
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Removing #{starting_count - final_count} collinear vertices from #{surface.name}.")
        surface.setVertices(new_vertices)
      end
    end

    # remove duplicate surfaces in a space (should be done after remove duplicate and collinear points)
    model.getSpaces.sort.each do |space|
      # secondary array to compare against
      surfaces_b = space.surfaces.sort

      space.surfaces.sort.each do |surface_a|
        # delete from secondary array
        surfaces_b.delete(surface_a)

        surfaces_b.each do |surface_b|
          next if surface_a == surface_b # dont' test against same surface

          if surface_a.equalVertices(surface_b)
            OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{surface_a.name} and #{surface_b.name} in #{space.name} have duplicate geometry, removing #{surface_b.name}.")
            surface_b.remove
          elsif surface_a.reverseEqualVertices(surface_b)
            # @todo add logic to determine which face naormal is reversed and which is correct
            OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{surface_a.name} and #{surface_b.name} in #{space.name} have reversed geometry, removing #{surface_b.name}.")
            surface_b.remove
          end
        end
      end
    end

    if (bar_hash[:make_mid_story_surfaces_adiabatic])
      # elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
      model.getBuilding.buildingStories.sort.each do |story|
        # intersect and surface match two pair by pair
        spaces_b = story.spaces.sort
        # looping through vector of each space
        story.spaces.sort.each do |space_a|
          spaces_b.delete(space_a)
          spaces_b.each do |space_b|
            spaces_temp = OpenStudio::Model::SpaceVector.new
            spaces_temp << space_a
            spaces_temp << space_b

            # intersect and sort
            OpenStudio::Model.intersectSurfaces(spaces_temp)
            OpenStudio::Model.matchSurfaces(spaces_temp)
          end
        end
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
      end
    else
      # intersect and surface match two pair by pair
      spaces_b = model.getSpaces.sort
      # looping through vector of each space
      model.getSpaces.sort.each do |space_a|
        spaces_b.delete(space_a)
        spaces_b.each do |space_b|
          # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Intersecting and matching surfaces between #{space_a.name} and #{space.name}")
          spaces_temp = OpenStudio::Model::SpaceVector.new
          spaces_temp << space_a
          spaces_temp << space_b
          # intersect and sort
          OpenStudio::Model.intersectSurfaces(spaces_temp)
          OpenStudio::Model.matchSurfaces(spaces_temp)
        end
      end
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Intersecting and matching surfaces in model, this will create additional geometry.')
    end
  else
    if (bar_hash[:make_mid_story_surfaces_adiabatic])
      # elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
      model.getBuilding.buildingStories.sort.each do |story|
        story_spaces = OpenStudio::Model::SpaceVector.new
        story.spaces.sort.each do |space|
          story_spaces << space
        end

        # intersect and sort
        OpenStudio::Model.intersectSurfaces(story_spaces)
        OpenStudio::Model.matchSurfaces(story_spaces)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
      end
    else
      # intersect surfaces
      # (when bottom floor has many space types and one above doesn't will end up with heavily subdivided floor. Maybe use adiabatic and don't intersect floor/ceilings)
      intersect_surfaces = true
      if intersect_surfaces
        OpenStudio::Model.intersectSurfaces(spaces)
        OpenStudio::Model.matchSurfaces(spaces)
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Intersecting and matching surfaces in model, this will create additional geometry.')
      end
    end
  end

  # set boundary conditions if not already set when geometry was created
  # @todo update this to use space original z value vs. story name
  if bar_hash[:num_stories_below_grade] > 0
    model.getBuildingStorys.sort.each do |story|
      next if !story.name.to_s.include?('Story B')

      story.spaces.sort.each do |space|
        next if !new_spaces.include?(space)

        space.surfaces.sort.each do |surface|
          next if surface.surfaceType != 'Wall'
          next if surface.outsideBoundaryCondition != 'Outdoors'

          surface.setOutsideBoundaryCondition('Ground')
        end
      end
    end
  end

  # set wall boundary condtions to adiabatic if using make_mid_story_surfaces_adiabatic prior to windows being made
  if bar_hash[:make_mid_story_surfaces_adiabatic]

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Finding non-exterior walls and setting boundary condition to adiabatic')

    # need to organize by story incase top story is partial story
    # should also be only for a single bar
    story_bounding = {}
    missed_match_count = 0

    # gather new spaces by story
    new_spaces.each do |space|
      story = space.buildingStory.get
      if story_bounding.key?(story)
        story_bounding[story][:spaces] << space
      else
        story_bounding[story] = { spaces: [space] }
      end
    end

    # get bounding box for each story
    story_bounding.each do |story, v|
      # get bounding_box
      bounding_box = OpenStudio::BoundingBox.new
      v[:spaces].each do |space|
        space.surfaces.each do |space_surface|
          bounding_box.addPoints(space.transformation * space_surface.vertices)
        end
      end
      min_x = bounding_box.minX.get
      min_y = bounding_box.minY.get
      max_x = bounding_box.maxX.get
      max_y = bounding_box.maxY.get
      ext_wall_toll = 0.01

      # check surfaces again against min/max and change to adiabatic if not fully on one min or max x or y
      # todo - may need to look at aidiabiatc constructions in downstream measure. Some may be exterior party wall others may be interior walls
      v[:spaces].each do |space|
        space.surfaces.each do |space_surface|
          next if space_surface.surfaceType != 'Wall'
          next if space_surface.outsideBoundaryCondition == 'Surface' # if if found a match leave it alone, don't change to adiabiatc

          surface_bounding_box = OpenStudio::BoundingBox.new
          surface_bounding_box.addPoints(space.transformation * space_surface.vertices)
          surface_on_outside = false
          # check xmin
          if (surface_bounding_box.minX.get - min_x).abs < ext_wall_toll && (surface_bounding_box.maxX.get - min_x).abs < ext_wall_toll then surface_on_outside = true end
          # check xmax
          if (surface_bounding_box.minX.get - max_x).abs < ext_wall_toll && (surface_bounding_box.maxX.get - max_x).abs < ext_wall_toll then surface_on_outside = true end
          # check ymin
          if (surface_bounding_box.minY.get - min_y).abs < ext_wall_toll && (surface_bounding_box.maxY.get - min_y).abs < ext_wall_toll then surface_on_outside = true end
          # check ymax
          if (surface_bounding_box.minY.get - max_y).abs < ext_wall_toll && (surface_bounding_box.maxY.get - max_y).abs < ext_wall_toll then surface_on_outside = true end

          # change if not exterior
          if !surface_on_outside
            space_surface.setOutsideBoundaryCondition('Adiabatic')
            missed_match_count += 1
          end
        end
      end
    end

    if missed_match_count > 0
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "#{missed_match_count} surfaces that were exterior appear to be interior walls and had boundary condition chagned to adiabiatic.")
    end
  end

  # sort stories (by name for now but need better way)
  sorted_stories = {}
  new_spaces.each do |space|
    next if !space.buildingStory.is_initialized

    story = space.buildingStory.get
    if !sorted_stories.key?(name.to_s)
      sorted_stories[story.name.to_s] = story
    end
  end

  # flag space types that have wwr overrides
  space_type_wwr_overrides = {}

  # loop through building stories, spaces, and surfaces
  sorted_stories.sort.each_with_index do |(key, story), i|
    # flag for adiabatic floor if building doesn't have ground exposed floor
    if stories_flat[i][:bottom_story_ground_exposed_floor] == false
      adiabatic_floor = true
    end
    # flag for adiabatic roof if building doesn't have exterior exposed roof
    if stories_flat[i][:top_story_exterior_exposed_roof] == false
      adiabatic_ceiling = true
    end

    # make all mid story floor and ceilings adiabatic if requested
    if bar_hash[:make_mid_story_surfaces_adiabatic]
      if i > 0
        adiabatic_floor = true
      end
      if i < sorted_stories.size - 1
        adiabatic_ceiling = true
      end
    end

    # flag orientations for this story to recieve party walls
    party_wall_facades = stories_flat[i][:story_party_walls]

    story.spaces.each do |space|
      next if !new_spaces.include?(space)

      space.surfaces.each do |surface|
        # set floor to adiabatic if requited
        if (adiabatic_floor && surface.surfaceType == 'Floor') || (adiabatic_ceiling && surface.surfaceType == 'RoofCeiling')
          surface.setOutsideBoundaryCondition('Adiabatic')
        end

        # skip of not exterior wall
        next if surface.surfaceType != 'Wall'
        next if surface.outsideBoundaryCondition != 'Outdoors'

        # get the absolute azimuth for the surface so we can categorize it
        absolute_azimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
        absolute_azimuth = absolute_azimuth % 360.0 # should result in value between 0 and 360
        absolute_azimuth = absolute_azimuth.round(5) # this was creating issues at 45 deg angles with opposing facades

        # target wwr values that may be changed for specific space types
        wwr_n = bar_hash[:building_wwr_n]
        wwr_e = bar_hash[:building_wwr_e]
        wwr_s = bar_hash[:building_wwr_s]
        wwr_w = bar_hash[:building_wwr_w]

        # look for space type specific wwr values
        if surface.space.is_initialized && surface.space.get.spaceType.is_initialized
          space_type = surface.space.get.spaceType.get

          # see if space type has wwr value
          bar_hash[:space_types].each do |k, v|
            if v.key?(:space_type) && space_type == v[:space_type] && v.key?(:wwr)
              # if matching space type specifies a wwr then override the orientation specific recommendations for this surface.
              wwr_n = v[:wwr]
              wwr_e = v[:wwr]
              wwr_s = v[:wwr]
              wwr_w = v[:wwr]
              space_type_wwr_overrides[space_type] = v[:wwr]
            end
          end
        end

        # add fenestration (wwr for now, maybe overhang and overhead doors later)
        if (absolute_azimuth >= 315.0) || (absolute_azimuth < 45.0)
          if party_wall_facades.include?('north')
            surface.setOutsideBoundaryCondition('Adiabatic')
          else
            surface.setWindowToWallRatio(wwr_n)
          end
        elsif (absolute_azimuth >= 45.0) && (absolute_azimuth < 135.0)
          if party_wall_facades.include?('east')
            surface.setOutsideBoundaryCondition('Adiabatic')
          else
            surface.setWindowToWallRatio(wwr_e)
          end
        elsif (absolute_azimuth >= 135.0) && (absolute_azimuth < 225.0)
          if party_wall_facades.include?('south')
            surface.setOutsideBoundaryCondition('Adiabatic')
          else
            surface.setWindowToWallRatio(wwr_s)
          end
        elsif (absolute_azimuth >= 225.0) && (absolute_azimuth < 315.0)
          if party_wall_facades.include?('west')
            surface.setOutsideBoundaryCondition('Adiabatic')
          else
            surface.setWindowToWallRatio(wwr_w)
          end
        else
          OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Unexpected value of facade: #{absolute_azimuth}.")
          return false
        end
      end
    end
  end

  # report space types with custom wwr values
  space_type_wwr_overrides.each do |space_type, wwr|
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "For #{space_type.name} the default building wwr was replaced with a space type specfic value of #{wwr}")
  end

  new_floor_area_si = 0.0
  new_spaces.each do |space|
    new_floor_area_si += space.floorArea * space.multiplier
  end
  new_floor_area_ip = OpenStudio.convert(new_floor_area_si, 'm^2', 'ft^2').get

  final_floor_area_ip = OpenStudio.convert(model.getBuilding.floorArea, 'm^2', 'ft^2').get
  if new_floor_area_ip == final_floor_area_ip
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2.")
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2. Total building area is #{OpenStudio.toNeatString(final_floor_area_ip, 0, true)} ft^2.")
  end

  return new_spaces
end
create_bar_from_args_and_building_type_hash(model, args, building_type_hash) click to toggle source

create bar from arguments and building type hash

@param args [Hash] user arguments @option args [Double] :single_floor_area (0.0) Single floor area in ft^2. Non-zero value will fix the single floor area, overriding a user entry for total_bldg_floor_area @option args [Double] :total_bldg_floor_area (10000.0) Total building floor area in ft^2 @option args [Double] :floor_height (0.0) Typical floor to floor height. Selecting a typical floor height of 0 will trigger a smart building type default. @option args [Boolean] :custom_height_bar (true) This is argument value is only relevant when smart default floor to floor height is used for a building type that has spaces with custom heights. @option args [Integer] :num_stories_above_grade (1) Number of stories above grade @option args [Integer] :num_stories_below_grade (0) Number of stories below grade @option args [Double] :building_rotation (0.0) Building rotation. Set Building Rotation off of North (positive value is clockwise). Rotation applied after geometry generation. Values greater than +/- 45 will result in aspect ratio and party wall orientations that do not match cardinal directions of the inputs. @option args [Double] :ns_to_ew_ratio (0.0) Ratio of North/South facade length relative to east/west facade length. Selecting an aspect ratio of 0 will trigger a smart building type default. Aspect ratios less than one are not recommended for sliced bar geometry, instead rotate building and use a greater than 1 aspect ratio. @option args [Double] :perim_mult (0.0) Perimeter multiplier. Selecting a value of 0 will trigger a smart building type default. This represents a multiplier for the building perimeter relative to the perimeter of a rectangular building that meets the area and aspect ratio inputs. Other than the smart default of 0.0 this argument should have a value of 1.0 or higher and is only applicable Multiple Space Types - Individual Stories Sliced division method. @option args [Double] :bar_width (0.0) Bar Width. Non-zero value will fix the building width, overriding user entry for Perimeter Multiplier. NS/EW Aspect Ratio may be limited based on target width. @option args [Double] :bar_sep_dist_mult (10.0) Bar separation distance multiplier. Multiplier of separation between bar elements relative to building height. @option args [Double] :wwr (0.0) Window to wall ratio. Selecting a window to wall ratio of 0 will trigger a smart building type default. @option args [Double] :party_wall_fraction (0.0) fraction of exterior wall area with an adjacent structure @option args [Integer] :party_wall_stories_north (0) Number of North facing stories with party wall @option args [Integer] :party_wall_stories_south (0) Number of South facing stories with party wall @option args [Integer] :party_wall_stories_east (0) Number of East facing stories with party wall @option args [Integer] :party_wall_stories_west (0) Number of West facing stories with party wall @option args [Boolean] :bottom_story_ground_exposed_floor (true) Is the bottom story exposed to the ground @option args [Boolean] :top_story_exterior_exposed_roof (true) Is the top story an exterior roof @option args [String] :story_multiplier_method (‘Basements Ground Mid Top’) Calculation method for story multiplier. Options are ‘None’ and ‘Basements Ground Mid Top’ @option args [Boolean] :make_mid_story_surfaces_adiabatic (true) Make mid story floor surfaces adiabatic. If set to true, this will skip surface intersection and make mid story floors and celings adiabatic, not just at multiplied gaps. @option args [String] :bar_division_method (‘Multiple Space Types - Individual Stories Sliced’) Division method for bar space types. Options are ‘Multiple Space Types - Simple Sliced’, ‘Multiple Space Types - Individual Stories Sliced’, ‘Single Space Type - Core and Perimeter’ @option args [String] :double_loaded_corridor (‘Primary Space Type’) Method for double loaded corridor. Add double loaded corridor for building types that have a defined circulation space type, to the selected space types. Options are ‘None’ and ‘Primary Space Type’ @option args [String] :space_type_sort_logic (‘Building Type > Size’) Space type sorting method. Options are ‘Size’ and ‘Building Type > Size’ @option args [String] :template (‘90.1-2013’) target standard @param building_type_hash [Array<Hash>] array of building type hashes @option building_type_hash [Double] :frac_bldg_area fraction of building area @option building_type_hash [Hash] :space_types hash of space types data @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/create_bar.rb, line 1257
def self.create_bar_from_args_and_building_type_hash(model, args, building_type_hash)
  # set argument defaults if not present
  args[:single_floor_area] = args.fetch(:single_floor_area, 0.0)
  args[:total_bldg_floor_area] = args.fetch(:total_bldg_floor_area, 10000.0)
  args[:floor_height] = args.fetch(:floor_height, 0.0)
  args[:custom_height_bar] = args.fetch(:custom_height_bar, true)
  args[:num_stories_above_grade] = args.fetch(:num_stories_above_grade, 1)
  args[:num_stories_below_grade] = args.fetch(:num_stories_below_grade, 0)
  args[:building_rotation] = args.fetch(:building_rotation, 0.0)
  args[:ns_to_ew_ratio] = args.fetch(:ns_to_ew_ratio, 0.0)
  args[:perim_mult] = args.fetch(:perim_mult, 0.0)
  args[:bar_width] = args.fetch(:bar_width, 0.0)
  args[:bar_sep_dist_mult] = args.fetch(:bar_sep_dist_mult, 10.0)
  args[:wwr] = args.fetch(:wwr, 0.0)
  args[:party_wall_fraction] = args.fetch(:party_wall_fraction, 0.0)
  args[:party_wall_stories_north] = args.fetch(:party_wall_stories_north, 0)
  args[:party_wall_stories_south] = args.fetch(:party_wall_stories_south, 0)
  args[:party_wall_stories_east] = args.fetch(:party_wall_stories_east, 0)
  args[:party_wall_stories_west] = args.fetch(:party_wall_stories_west, 0)
  args[:bottom_story_ground_exposed_floor] = args.fetch(:bottom_story_ground_exposed_floor, true)
  args[:top_story_exterior_exposed_roof] = args.fetch(:top_story_exterior_exposed_roof, true)
  args[:story_multiplier_method] = args.fetch(:story_multiplier_method, 'Basements Ground Mid Top')
  args[:make_mid_story_surfaces_adiabatic] = args.fetch(:make_mid_story_surfaces_adiabatic, true)
  args[:bar_division_method] = args.fetch(:bar_division_method, 'Multiple Space Types - Individual Stories Sliced')
  args[:double_loaded_corridor] = args.fetch(:double_loaded_corridor, 'Primary Space Type')
  args[:space_type_sort_logic] = args.fetch(:space_type_sort_logic, 'Building Type > Size')
  args[:template] = args.fetch(:template, '90.1-2013')

  # get defaults for the primary building type
  primary_building_type = args[:primary_building_type]
  building_form_defaults = OpenstudioStandards::Geometry.building_form_defaults(primary_building_type)

  # if aspect ratio, story height or wwr have argument value of 0 then use smart building type defaults
  # store list of defaulted items
  defaulted_args = []

  if args[:ns_to_ew_ratio].abs < 0.01
    args[:ns_to_ew_ratio] = building_form_defaults[:aspect_ratio]
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for aspect ratio will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:aspect_ratio]}.")
  end

  if args[:perim_mult].abs < 0.01
    # if this is not defined then use default of 1.0
    if !building_form_defaults.key?(:perim_mult)
      args[:perim_mult] = 1.0
    else
      args[:perim_mult] = building_form_defaults[:perim_mult]
    end
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for minimum perimeter multiplier will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:perim_mult]}.")
  elsif args[:perim_mult] < 1.0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Other than the smart default value of 0, the minimum perimeter multiplier should be equal to 1.0 or greater.')
    return false
  end

  if args[:floor_height].abs < 0.01
    args[:floor_height] = building_form_defaults[:typical_story]
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for floor height will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:typical_story]}.")
    defaulted_args << 'floor_height'
  end
  # because of this can't set wwr to 0.0. If that is desired then we can change this to check for 1.0 instead of 0.0
  if args[:wwr].abs < 0.01
    args[:wwr] = building_form_defaults[:wwr]
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for window to wall ratio will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:wwr]}.")
  end

  # report initial condition of model
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "The building started with #{model.getSpaces.size} spaces.")

  # determine of ns_ew needs to be mirrored
  mirror_ns_ew = false
  rotation = model.getBuilding.northAxis
  if rotation > 45.0 && rotation < 135.0
    mirror_ns_ew = true
  elsif rotation > 45.0 && rotation < 135.0
    mirror_ns_ew = true
  end

  # remove non-resource objects not removed by removing the building
  # remove_non_resource_objects(model)

  # creating space types for requested building types
  building_type_hash.each do |building_type, building_type_hash|
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating Space Types for #{building_type}.")

    # mapping building_type name is needed for a few methods
    temp_standard = Standard.build(args[:template])
    building_type = temp_standard.model_get_lookup_name(building_type)

    # create space_type_map from array
    sum_of_ratios = 0.0
    building_type_hash[:space_types] = building_type_hash[:space_types].sort_by { |k, v| v[:ratio] }.to_h
    building_type_hash[:space_types].each do |space_type_name, hash|
      next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.

      # create space type
      space_type = OpenStudio::Model::SpaceType.new(model)
      space_type.setStandardsBuildingType(building_type)
      space_type.setStandardsSpaceType(space_type_name)
      space_type.setName("#{building_type} #{space_type_name}")

      # set color
      test = temp_standard.space_type_apply_rendering_color(space_type)
      if !test
        # @todo once fixed in standards un-comment this
        # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Could not find color for #{space_type.name}")
      end

      # extend hash to hold new space type object
      hash[:space_type] = space_type

      # add to sum_of_ratios counter for adjustment multiplier
      sum_of_ratios += hash[:ratio]
    end

    # store multiplier needed to adjust sum of ratios to equal 1.0
    building_type_hash[:ratio_adjustment_multiplier] = 1.0 / sum_of_ratios
  end

  # calculate length and with of bar
  total_bldg_floor_area_si = OpenStudio.convert(args[:total_bldg_floor_area], 'ft^2', 'm^2').get
  single_floor_area_si = OpenStudio.convert(args[:single_floor_area], 'ft^2', 'm^2').get

  # store number of stories
  num_stories = args[:num_stories_below_grade] + args[:num_stories_above_grade]

  # handle user-assigned single floor plate size condition
  if args[:single_floor_area] > 0.0
    footprint_si = single_floor_area_si
    total_bldg_floor_area_si = footprint_si * num_stories.to_f
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'User-defined single floor area was used for calculation of total building floor area')
    # add warning if custom_height_bar is true and applicable building type is selected
    if args[:custom_height_bar]
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Cannot use custom height bar with single floor area method, will not create custom height bar.')
      args[:custom_height_bar] = false
    end
  else
    footprint_si = nil
  end

  # populate space_types_hash
  space_types_hash = {}
  multi_height_space_types_hash = {}
  custom_story_heights = []
  if args[:space_type_sort_logic] == 'Building Type > Size'
    building_type_hash = building_type_hash.sort_by { |k, v| v[:frac_bldg_area] }
  end
  building_type_hash.each do |building_type, building_type_hash|
    if args[:double_loaded_corridor] == 'Primary Space Type'

      # see if building type has circulation space type, if so then merge that along with default space type into hash key in place of space type
      default_st = nil
      circ_st = nil
      building_type_hash[:space_types].each do |space_type_name, hash|
        if hash[:default] then default_st = space_type_name end
        if hash[:circ] then circ_st = space_type_name end
      end

      # update building hash
      if !default_st.nil? && !circ_st.nil?
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Combining #{default_st} and #{circ_st} into a group representing a double loaded corridor")

        # add new item
        building_type_hash[:space_types]['Double Loaded Corridor'] = {}
        double_loaded_st = building_type_hash[:space_types]['Double Loaded Corridor']
        double_loaded_st[:ratio] = building_type_hash[:space_types][default_st][:ratio] + building_type_hash[:space_types][circ_st][:ratio]
        double_loaded_st[:double_loaded_corridor] = true
        double_loaded_st[:space_type] = model.getBuilding
        double_loaded_st[:children] = {}
        building_type_hash[:space_types][default_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][default_st][:ratio]
        building_type_hash[:space_types][circ_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][circ_st][:ratio]
        building_type_hash[:space_types][default_st][:name] = default_st
        building_type_hash[:space_types][circ_st][:name] = circ_st
        double_loaded_st[:children][:default] = building_type_hash[:space_types][default_st]
        double_loaded_st[:children][:circ] = building_type_hash[:space_types][circ_st]
        double_loaded_st[:orig_ratio] = 0.0

        # zero out ratios from old item (don't delete because I still want the space types made)
        building_type_hash[:space_types][default_st][:ratio] = 0.0
        building_type_hash[:space_types][circ_st][:ratio] = 0.0
      end
    end

    building_type_hash[:space_types].each do |space_type_name, hash|
      next if hash[:space_type_gen] == false

      space_type = hash[:space_type]
      ratio_of_bldg_total = hash[:ratio] * building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area]
      final_floor_area = ratio_of_bldg_total * total_bldg_floor_area_si # I think I can just pass ratio but passing in area is cleaner

      # only add custom height space if 0 is used for floor_height
      if defaulted_args.include?('floor_height') && hash.key?(:story_height) && args[:custom_height_bar]
        multi_height_space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type, story_height: hash[:story_height] }
        if hash.key?(:orig_ratio) then multi_height_space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
        custom_story_heights << hash[:story_height]
        if args[:wwr] == 0 && hash.key?(:wwr)
          multi_height_space_types_hash[space_type][:wwr] = hash[:wwr]
        end
      else
        # only add wwr if 0 used for wwr arg and if space type has wwr as key
        space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type }
        if hash.key?(:orig_ratio) then space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
        if args[:wwr] == 0 && hash.key?(:wwr)
          space_types_hash[space_type][:wwr] = hash[:wwr]
        end
        if hash[:double_loaded_corridor]
          space_types_hash[space_type][:children] = hash[:children]
        end
      end
    end
  end

  # resort if not sorted by building type
  if args[:space_type_sort_logic] == 'Size'
    # added code to convert to hash. I use sort_by 3 other times, but those seem to be working fine as is now.
    space_types_hash = Hash[space_types_hash.sort_by { |k, v| v[:floor_area] }]
  end

  # calculate targets for testing
  target_areas = {} # used for checks
  target_areas_cust_height = 0.0
  space_types_hash.each do |k, v|
    if v.key?(:orig_ratio)
      target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
    else
      target_areas[k] = v[:floor_area]
    end
  end
  multi_height_space_types_hash.each do |k, v|
    if v.key?(:orig_ratio)
      target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
      target_areas_cust_height += v[:orig_ratio] * total_bldg_floor_area_si
    else
      target_areas[k] = v[:floor_area]
      target_areas_cust_height += v[:floor_area]
    end
  end

  # gather inputs
  if footprint_si.nil?
    footprint_si = (total_bldg_floor_area_si - target_areas_cust_height) / num_stories.to_f
  end
  floor_height = OpenStudio.convert(args[:floor_height], 'ft', 'm').get
  min_allow_size = OpenStudio.convert(15.0, 'ft', 'm').get
  specified_bar_width_si = OpenStudio.convert(args[:bar_width], 'ft', 'm').get

  # set custom width
  if specified_bar_width_si > 0
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Ignoring perimeter multiplier argument when non zero width argument is used')
    if footprint_si / specified_bar_width_si >= min_allow_size
      width = specified_bar_width_si
      length = footprint_si / width
    else
      length = min_allow_size
      width = footprint_si / length
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'User specified width results in a length that is too short, adjusting width to be narrower than specified.')
    end
    width_cust_height = specified_bar_width_si
  else
    width = Math.sqrt(footprint_si / args[:ns_to_ew_ratio])
    length = footprint_si / width
    width_cust_height = Math.sqrt(target_areas_cust_height / args[:ns_to_ew_ratio])
  end
  length_cust_height = target_areas_cust_height / width_cust_height
  if args[:perim_mult] > 1.0 && target_areas_cust_height > 0.0
    # @todo update tests that hit this warning
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Ignoring perimeter multiplier for bar that represents custom height spaces.')
  end

  # check if dual bar is needed
  dual_bar = false
  if specified_bar_width_si > 0.0 && args[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'
    if length / width != args[:ns_to_ew_ratio]

      if args[:ns_to_ew_ratio] >= 1.0 && args[:ns_to_ew_ratio] > length / width
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Can't meet target aspect ratio of #{args[:ns_to_ew_ratio]}, Lowering it to #{length / width} ")
        args[:ns_to_ew_ratio] = length / width
      elsif args[:ns_to_ew_ratio] < 1.0 && args[:ns_to_ew_ratio] > length / width
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Can't meet target aspect ratio of #{args[:ns_to_ew_ratio]}, Increasing it to #{length / width} ")
        args[:ns_to_ew_ratio] = length / width
      else
        # check if each bar would be longer then 15 feet, then set as dual bar and override perimeter multiplier
        length_alt1 = (((args[:ns_to_ew_ratio] * footprint_si) / width) + ((2 * (args[:ns_to_ew_ratio] * width)) - (2 * width))) / (1 + args[:ns_to_ew_ratio])
        length_alt2 = length - length_alt1
        if [length_alt1, length_alt2].min >= min_allow_size
          dual_bar = true
        else
          OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Second bar would be below minimum length, will model as single bar')
          # swap length and width if single bar and aspect ratio less than 1
          if args[:ns_to_ew_ratio] < 1.0
            width = length
            length = specified_bar_width_si
          end
        end
      end
    end
  elsif args[:perim_mult] > 1.0 && args[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'You selected a perimeter multiplier greater than 1.0 for a supported bar division method. This will result in two detached rectangular buildings if secondary bar meets minimum size requirements.')
    dual_bar = true
  elsif args[:perim_mult] > 1.0
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "You selected a perimeter multiplier greater than 1.0 but didn't select a bar division method that supports this. The value for this argument will be ignored by the measure")
  end

  # calculations for dual bar, which later will be setup to run create_bar twice
  if dual_bar
    min_perim = (2 * width) + (2 * length)
    target_area = footprint_si
    target_perim = min_perim * args[:perim_mult]
    tol_testing = 0.00001
    dual_bar_calc_approach = nil # stretched, adiabatic_ends_bar_b, dual_bar
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Minimum rectangle is #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft with an area of #{OpenStudio.toNeatString(OpenStudio.convert(length * width, 'm^2', 'ft^2').get, 0, true)} ft^2. Perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(min_perim, 'm', 'ft').get, 0, true)} ft.")
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Target dual bar perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(target_perim, 'm', 'ft').get, 0, true)} ft.")

    # determine which of the three paths to hit target perimeter multiplier are possible
    # A use dual bar non adiabatic
    # B use dual bar adiabatic
    # C use stretched bar (requires model to miss ns/ew ratio)

    # custom quadratic equation to solve two bars with common width 2l^2 - p*l + 4a = 0
    if ((target_perim**2) - (32 * footprint_si)) > 0
      if specified_bar_width_si > 0
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Ignoring perimeter multiplier argument and using use specified bar width.')
        dual_double_end_width = specified_bar_width_si
        dual_double_end_length = footprint_si / dual_double_end_width
      else
        dual_double_end_length = 0.25 * (target_perim + Math.sqrt((target_perim**2) - (32 * footprint_si)))
        dual_double_end_width = footprint_si / dual_double_end_length
      end

      # now that stretched  bar is made, determine where to split it and rotate
      bar_a_length = ((args[:ns_to_ew_ratio] * (dual_double_end_length + dual_double_end_width)) - dual_double_end_width) / (1 + args[:ns_to_ew_ratio])
      bar_b_length = dual_double_end_length - bar_a_length
      area_a = bar_a_length * dual_double_end_width
      area_b = bar_b_length * dual_double_end_width
    else
      # this will throw it to adiabatic ends test
      bar_a_length = 0
      bar_b_length = 0
    end

    if bar_a_length >= min_allow_size && bar_b_length >= min_allow_size
      dual_bar_calc_approach = 'dual_bar'
    else
      # adiabatic bar input calcs
      if ((target_perim**2) - (16 * footprint_si)) > 0
        adiabatic_dual_double_end_length = 0.25 * (target_perim + Math.sqrt((target_perim**2) - (16 * footprint_si)))
        adiabatic_dual_double_end_width = footprint_si / adiabatic_dual_double_end_length
        # test for unexpected
        unexpected = false
        if (target_area - (adiabatic_dual_double_end_length * adiabatic_dual_double_end_width)).abs > tol_testing then unexpected = true end
        if specified_bar_width_si == 0
          if (target_perim - ((adiabatic_dual_double_end_length * 2) + (adiabatic_dual_double_end_width * 2))).abs > tol_testing then unexpected = true end
        end
        if unexpected
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for dual rectangle adiabatic ends bar b.')
        end
        # now that stretched  bar is made, determine where to split it and rotate
        adiabatic_bar_a_length = (args[:ns_to_ew_ratio] * (adiabatic_dual_double_end_length + adiabatic_dual_double_end_width)) / (1 + args[:ns_to_ew_ratio])
        adiabatic_bar_b_length = adiabatic_dual_double_end_length - adiabatic_bar_a_length
        adiabatic_area_a = adiabatic_bar_a_length * adiabatic_dual_double_end_width
        adiabatic_area_b = adiabatic_bar_b_length * adiabatic_dual_double_end_width
      else
        # this will throw it stretched single bar
        adiabatic_bar_a_length = 0
        adiabatic_bar_b_length = 0
      end
      if adiabatic_bar_a_length >= min_allow_size && adiabatic_bar_b_length >= min_allow_size
        dual_bar_calc_approach = 'adiabatic_ends_bar_b'
      else
        dual_bar_calc_approach = 'stretched'
      end
    end

    # apply prescribed approach for stretched or dual bar
    if dual_bar_calc_approach == 'dual_bar'
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Stretched  #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_width, 'm', 'ft').get, 0, true)} ft rectangle has an area of #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * dual_double_end_width, 'm^2', 'ft^2').get, 0, true)} ft^2. When split in two the perimeter will be #{OpenStudio.toNeatString(OpenStudio.convert((dual_double_end_length * 2) + (dual_double_end_width * 4), 'm', 'ft').get, 0, true)} ft")
      if (target_area - (dual_double_end_length * dual_double_end_width)).abs > tol_testing || (target_perim - ((dual_double_end_length * 2) + (dual_double_end_width * 4))).abs > tol_testing
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for dual rectangle.')
      end

      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "For stretched split bar, to match target ns/ew aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be horizontal, with #{OpenStudio.toNeatString(OpenStudio.convert(bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(area_a + area_b, 'm^2', 'ft^2').get, 0, true)} ft^2. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert((bar_a_length * 2) + (bar_b_length * 2) + (dual_double_end_width * 4), 'm', 'ft').get, 0, true)} ft")
      if (target_area - (area_a + area_b)).abs > tol_testing || (target_perim - ((bar_a_length * 2) + (bar_b_length * 2) + (dual_double_end_width * 4))).abs > tol_testing
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for rotated dual rectangle')
      end
    elsif dual_bar_calc_approach == 'adiabatic_ends_bar_b'
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Can't hit target perimeter with two rectangles, need to make two ends adiabatic")

      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "For dual bar with adiabatic ends on bar b, to reach target aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be north/south, with #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_area_a + adiabatic_area_b, 'm^2', 'ft^2').get, 0, true)} ft^2}. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert((adiabatic_bar_a_length * 2) + (adiabatic_bar_b_length * 2) + (adiabatic_dual_double_end_width * 2), 'm', 'ft').get, 0, true)} ft")
      if (target_area - (adiabatic_area_a + adiabatic_area_b)).abs > tol_testing || (target_perim - ((adiabatic_bar_a_length * 2) + (adiabatic_bar_b_length * 2) + (adiabatic_dual_double_end_width * 2))).abs > tol_testing
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for rotated dual rectangle adiabatic ends bar b')
      end
    else
      # stretched bar
      dual_bar = false

      stretched_length = 0.25 * (target_perim + Math.sqrt((target_perim**2) - (16 * footprint_si)))
      stretched_width = footprint_si / stretched_length
      if (target_area - (stretched_length * stretched_width)).abs > tol_testing || (target_perim - ((stretched_length + stretched_width) * 2)) > tol_testing
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for single stretched')
      end

      width = stretched_width
      length = stretched_length
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating a dual bar to match the target minimum perimeter multiplier at the given aspect ratio would result in a bar with edge shorter than #{OpenStudio.toNeatString(OpenStudio.convert(min_allow_size, 'm', 'ft').get, 0, true)} ft. Will create a single stretched bar instead that hits the target perimeter with a slightly different ns/ew aspect ratio.")
    end
  end

  bars = {}
  bars['primary'] = {}
  if dual_bar
    if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
      bars['primary'][:length] = dual_double_end_width
      bars['primary'][:width] = bar_a_length
    elsif dual_bar_calc_approach == 'dual_bar'
      bars['primary'][:length] = bar_a_length
      bars['primary'][:width] = dual_double_end_width
    elsif mirror_ns_ew
      bars['primary'][:length] = adiabatic_dual_double_end_width
      bars['primary'][:width] = adiabatic_bar_a_length
    else
      bars['primary'][:length] = adiabatic_bar_a_length
      bars['primary'][:width] = adiabatic_dual_double_end_width
    end
  else
    if mirror_ns_ew
      bars['primary'][:length] = width
      bars['primary'][:width] = length
    else
      bars['primary'][:length] = length
      bars['primary'][:width] = width
    end
  end
  bars['primary'][:floor_height] = floor_height # can make use of this when breaking out multi-height spaces
  bars['primary'][:num_stories] = num_stories
  bars['primary'][:center_of_footprint] = OpenStudio::Point3d.new(0.0, 0.0, 0.0)
  space_types_hash_secondary = {}
  if dual_bar
    # loop through each story and move portion for other bar to its own hash
    primary_footprint = bars['primary'][:length] * bars['primary'][:width]
    secondary_footprint = target_area - primary_footprint
    footprint_counter = primary_footprint
    secondary_footprint_counter = secondary_footprint
    story_counter = 0
    pri_sec_tol = 0.0001 # m^2
    pri_sec_min_area = 0.0001 # m^2
    space_types_hash.each do |k, v|
      space_type_left = v[:floor_area]

      # do not go to next space type until this one is evaulate, which may span stories
      until (space_type_left.abs < 0.01) || (story_counter >= num_stories)

        # use secondary footprint if any left
        if secondary_footprint_counter > 0.0
          hash_area = [space_type_left, secondary_footprint_counter].min

          # confirm that the part of space type use or what is left is greater than min allowed value
          projected_space_type_left = space_type_left - hash_area
          test_a = hash_area >= pri_sec_min_area
          test_b = (projected_space_type_left >= pri_sec_min_area) || (projected_space_type_left.abs < 0.01)
          test_c = k == space_types_hash.keys.last # if last space type accept sliver, no other space to infil
          if (test_a && test_b) || test_c
            if space_types_hash_secondary.key?(k)
              # add to what was added for previous story
              space_types_hash_secondary[k][:floor_area] += hash_area
            else
              # add new space type to hash
              if v.key?(:children)
                space_types_hash_secondary[k] = { floor_area: hash_area, space_type: v[:space_type], children: v[:children] }
              else
                space_types_hash_secondary[k] = { floor_area: hash_area, space_type: v[:space_type] }
              end
            end
            space_types_hash[k][:floor_area] -= hash_area
            secondary_footprint_counter -= hash_area
            space_type_left -= hash_area
          else
            OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Shifting space types between bars to avoid sliver of #{k.name}.")
          end
        end

        # remove space if entirely used up by secondary bar
        if space_types_hash[k][:floor_area] <= pri_sec_tol
          space_types_hash.delete(k)
          space_type_left = 0.0
        else
          # then look at primary bar
          hash_area_pri = [space_type_left, footprint_counter].min
          footprint_counter -= hash_area_pri
          space_type_left -= hash_area_pri
        end

        # reset counter when full
        if footprint_counter <= pri_sec_tol && secondary_footprint_counter <= pri_sec_tol
          # check if this is partial top floor
          story_counter += 1
          if num_stories < story_counter + 1
            footprint_counter = primary_footprint * (num_stories - story_counter)
            secondary_footprint_counter = secondary_footprint * (num_stories - story_counter)
          else
            footprint_counter = primary_footprint
            secondary_footprint_counter = secondary_footprint
          end
        end
      end
    end
  end

  # setup bar_hash and run create_bar
  bars['primary'][:space_types_hash] = space_types_hash
  bars['primary'][:args] = args
  v = bars['primary']
  OpenstudioStandards::Geometry.bar_hash_setup_run(model, v[:args], v[:length], v[:width], v[:floor_height], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])

  # store offset value for multiple bars
  if args.key?(:bar_sep_dist_mult) && args[:bar_sep_dist_mult] > 0.0
    offset_val = num_stories.ceil * floor_height * args[:bar_sep_dist_mult]
  elsif args.key?(:bar_sep_dist_mult)
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Positive value is required for bar_sep_dist_mult, ignoring input and using value of 0.1')
    offset_val = num_stories.ceil * floor_height * 0.1
  else
    offset_val = num_stories.ceil * floor_height * 10.0
  end

  if dual_bar
    args2 = args.clone
    bars['secondary'] = {}
    if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
      bars['secondary'][:length] = bar_b_length
      bars['secondary'][:width] = dual_double_end_width
    elsif dual_bar_calc_approach == 'dual_bar'
      bars['secondary'][:length] = dual_double_end_width
      bars['secondary'][:width] = bar_b_length
    elsif mirror_ns_ew
      bars['secondary'][:length] = adiabatic_bar_b_length
      bars['secondary'][:width] = adiabatic_dual_double_end_width
      args2[:party_wall_stories_east] = num_stories.ceil
      args2[:party_wall_stories_west] = num_stories.ceil
    else
      bars['secondary'][:length] = adiabatic_dual_double_end_width
      bars['secondary'][:width] = adiabatic_bar_b_length
      args2[:party_wall_stories_south] = num_stories.ceil
      args2[:party_wall_stories_north] = num_stories.ceil
    end
    bars['secondary'][:floor_height] = floor_height # can make use of this when breaking out multi-height spaces
    bars['secondary'][:num_stories] = num_stories
    bars['secondary'][:space_types_hash] = space_types_hash_secondary
    if dual_bar_calc_approach == 'adiabatic_ends_bar_b'
      # warn that combination of dual bar with low perimeter multiplier and use of party wall may result in discrepency between target and actual adiabatic walls
      if args[:party_wall_fraction] > 0 || args[:party_wall_stories_north] > 0 || args[:party_wall_stories_south] > 0 || args[:party_wall_stories_east] > 0 || args[:party_wall_stories_west] > 0
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'The combination of low perimeter multiplier and use of non zero party wall inputs may result in discrepency between target and actual adiabatic walls. This is due to the need to create adiabatic walls on secondary bar to maintian target building perimeter.')
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Adiabatic ends added to secondary bar because target perimeter multiplier could not be met with two full rectangular footprints.')
      end
      bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new((adiabatic_bar_a_length * 0.5) + (adiabatic_dual_double_end_width * 0.5) + offset_val, (adiabatic_bar_b_length * 0.5) + (adiabatic_dual_double_end_width * 0.5) + offset_val, 0.0)
    else
      bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new((bar_a_length * 0.5) + (dual_double_end_width * 0.5) + offset_val, (bar_b_length * 0.5) + (dual_double_end_width * 0.5) + offset_val, 0.0)
    end
    bars['secondary'][:args] = args2

    # setup bar_hash and run create_bar
    v = bars['secondary']
    OpenstudioStandards::Geometry.bar_hash_setup_run(model, v[:args], v[:length], v[:width], v[:floor_height], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
  end

  # future development (up against primary bar run intersection and surface matching after add all bars, avoid interior windows)
  # I could loop through each space type and give them unique height but for now will just take largest height and make bar of that height, which is fine for prototypes
  if !multi_height_space_types_hash.empty?
    args3 = args.clone
    bars['custom_height'] = {}
    if mirror_ns_ew
      bars['custom_height'][:length] = width_cust_height
      bars['custom_height'][:width] = length_cust_height
    else
      bars['custom_height'][:length] = length_cust_height
      bars['custom_height'][:width] = width_cust_height
    end
    if args[:party_wall_stories_east] + args[:party_wall_stories_west] + args[:party_wall_stories_south] + args[:party_wall_stories_north] > 0.0
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Ignorning party wall inputs for custom height bar')
    end

    # disable party walls
    args3[:party_wall_stories_east] = 0
    args3[:party_wall_stories_west] = 0
    args3[:party_wall_stories_south] = 0
    args3[:party_wall_stories_north] = 0

    # setup stories
    args3[:num_stories_below_grade] = 0
    args3[:num_stories_above_grade] = 1

    # can make use of this when breaking out multi-height spaces
    bars['custom_height'][:floor_height] = floor_height
    bars['custom_height'][:num_stories] = num_stories
    bars['custom_height'][:center_of_footprint] = OpenStudio::Point3d.new((bars['primary'][:length] * -0.5) - (length_cust_height * 0.5) - offset_val, 0.0, 0.0)
    bars['custom_height'][:floor_height] = OpenStudio.convert(custom_story_heights.max, 'ft', 'm').get
    bars['custom_height'][:num_stories] = 1
    bars['custom_height'][:space_types_hash] = multi_height_space_types_hash
    bars['custom_height'][:args] = args3

    v = bars['custom_height']
    OpenstudioStandards::Geometry.bar_hash_setup_run(model, v[:args], v[:length], v[:width], v[:floor_height], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
  end

  # diagnostic log
  sum_actual = 0.0
  sum_target = 0.0
  throw_error = false

  # check expected floor areas against actual
  model.getSpaceTypes.sort.each do |space_type|
    next if !target_areas.key? space_type # space type in model not part of building type(s), maybe issue warning

    # convert to IP
    actual_ip = OpenStudio.convert(space_type.floorArea, 'm^2', 'ft^2').get
    target_ip = OpenStudio.convert(target_areas[space_type], 'm^2', 'ft^2').get
    sum_actual += actual_ip
    sum_target += target_ip

    if (space_type.floorArea - target_areas[space_type]).abs >= 1.0

      if !args[:bar_division_method].include? 'Single Space Type'
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
        throw_error = true
      else
        # will see this if use Single Space type division method on multi-use building or single building type without whole building space type
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
      end

    end
  end

  # report summary then throw error
  if throw_error
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Sum of actual floor area is #{sum_actual} ft^2, sum of target floor area is #{sum_target}.")
    return false
  end

  # check party wall fraction by looping through surfaces
  if args[:party_wall_fraction] > 0
    actual_ext_wall_area = model.getBuilding.exteriorWallArea
    actual_party_wall_area = 0.0
    model.getSurfaces.sort.each do |surface|
      next if surface.outsideBoundaryCondition != 'Adiabatic'
      next if surface.surfaceType != 'Wall'

      actual_party_wall_area += surface.grossArea * surface.space.get.multiplier
    end
    actual_party_wall_fraction = actual_party_wall_area / (actual_party_wall_area + actual_ext_wall_area)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Target party wall fraction is #{args[:party_wall_fraction]}. Realized fraction is #{actual_party_wall_fraction.round(2)}")
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "party_wall_fraction_actual: #{actual_party_wall_fraction}")
  end

  # check ns/ew aspect ratio (harder to check when party walls are added)
  wall_and_window_by_orientation = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)
  wall_ns = (wall_and_window_by_orientation['north_wall'] + wall_and_window_by_orientation['south_wall'])
  wall_ew = wall_and_window_by_orientation['east_wall'] + wall_and_window_by_orientation['west_wall']
  wall_ns_ip = OpenStudio.convert(wall_ns, 'm^2', 'ft^2').get
  wall_ew_ip = OpenStudio.convert(wall_ew, 'm^2', 'ft^2').get
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "wall_area_ip: #{wall_ns_ip + wall_ew_ip} ft^2")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "ns_wall_area_ip: #{wall_ns_ip} ft^2")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "ew_wall_area_ip: #{wall_ew_ip} ft^2")
  # for now using perimeter of ground floor and average story area (building area / num_stories)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "floor_area_to_perim_ratio: #{model.getBuilding.floorArea / (OpenstudioStandards::Geometry.model_get_perimeter(model) * num_stories)}")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "bar_width: #{OpenStudio.convert(bars['primary'][:width], 'm', 'ft').get} ft")

  if args[:party_wall_fraction] > 0 || args[:party_wall_stories_north] > 0 || args[:party_wall_stories_south] > 0 || args[:party_wall_stories_east] > 0 || args[:party_wall_stories_west] > 0
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when party walls are applied')
  elsif args[:num_stories_above_grade] != args[:num_stories_above_grade].ceil
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when partial top story is used')
  elsif dual_bar_calc_approach == 'stretched'
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when single stretched bar has to be used to meet target minimum perimeter multiplier')
  elsif defaulted_args.include?('floor_height') && args[:custom_height_bar] && !multi_height_space_types_hash.empty?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when a dedicated bar is added for space types with custom heights')
  elsif args[:bar_width] > 0
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when a dedicated custom bar width is defined')
  else

    # adjust length versus width based on building rotation
    if mirror_ns_ew
      wall_target_ns_ip = 2 * OpenStudio.convert(width, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
      wall_target_ew_ip = 2 * OpenStudio.convert(length, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
    else
      wall_target_ns_ip = 2 * OpenStudio.convert(length, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
      wall_target_ew_ip = 2 * OpenStudio.convert(width, 'm', 'ft').get  * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
    end
    flag_error = false
    if (wall_target_ns_ip - wall_ns_ip).abs > 0.1
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "North/South walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ns_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ns_ip, 4, true)} ft^2)")
      flag_error = true
    end
    if (wall_target_ew_ip - wall_ew_ip).abs > 0.1
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "East/West walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ew_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ew_ip, 4, true)} ft^2)")
      flag_error = true
    end
    if flag_error
      return false
    end
  end

  # test for excessive exterior roof area (indication of problem with intersection and or surface matching)
  ext_roof_area = model.getBuilding.exteriorSurfaceArea - model.getBuilding.exteriorWallArea
  expected_roof_area = args[:total_bldg_floor_area] / (args[:num_stories_above_grade] + args[:num_stories_below_grade]).to_f
  if ext_roof_area > expected_roof_area && (single_floor_area_si.abs < 0.01) # only test if using whole-building area input
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Roof area larger than expected, may indicate problem with inter-floor surface intersection or matching.')
    return false
  end

  # set building rotation
  initial_rotation = model.getBuilding.northAxis
  if args[:building_rotation] != initial_rotation
    model.getBuilding.setNorthAxis(args[:building_rotation])
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Set Building Rotation to #{model.getBuilding.northAxis}. Rotation altered after geometry generation is completed, as a result party wall orientation and aspect ratio may not reflect input values.")
  end

  # report final condition of model
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "The building finished with #{model.getSpaces.size} spaces.")

  return true
end
create_bar_from_building_type_ratios(model, args) click to toggle source

create bar from building type ratios arguments are passed through to lower level methods. See create_bar_from_args_and_building_type_hash for additional argument options.

@param model [OpenStudio::Model::Model] OpenStudio model object @param args [Hash] user arguments @option args [String] :bldg_type_a (‘SmallOffice’) primary building type @option args [String] :bldg_type_b (nil) building type b @option args [String] :bldg_type_c (nil) building type c @option args [String] :bldg_type_d (nil) building type d @option args [String] :bldg_subtype_a (‘NA’) primary building subtype @option args [String] :bldg_subtype_b (‘NA’) building type b subtype @option args [String] :bldg_subtype_c (‘NA’) building type c subtype @option args [String] :bldg_subtype_d (‘NA’) building type d subtype @option args [String] :bldg_type_a_fract_bldg_area (1.0) building type a area fraction of total floor area @option args [String] :bldg_type_b_fract_bldg_area (0.0) building type b area fraction of total floor area @option args [String] :bldg_type_c_fract_bldg_area (0.0) building type c area fraction of total floor area @option args [String] :bldg_type_d_fract_bldg_area (0.0) building type d area fraction of total floor area @option args [String] :template (‘90.1-2013’) target standard @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/create_bar.rb, line 1996
def self.create_bar_from_building_type_ratios(model, args)
  # convert arguments to symbols
  args = args.transform_keys(&:to_sym)
  bldg_type_a = args.fetch(:bldg_type_a, 'SmallOffice')
  bldg_type_b = args.fetch(:bldg_type_b, nil)
  bldg_type_c = args.fetch(:bldg_type_c, nil)
  bldg_type_d = args.fetch(:bldg_type_d, nil)
  bldg_subtype_a = args.fetch(:bldg_subtype_a, 'NA')
  bldg_subtype_b = args.fetch(:bldg_subtype_b, 'NA')
  bldg_subtype_c = args.fetch(:bldg_subtype_c, 'NA')
  bldg_subtype_d = args.fetch(:bldg_subtype_d, 'NA')
  bldg_type_a_fract_bldg_area = args.fetch(:bldg_type_a_fract_bldg_area, 1.0)
  bldg_type_b_fract_bldg_area = args.fetch(:bldg_type_b_fract_bldg_area, 0.0)
  bldg_type_c_fract_bldg_area = args.fetch(:bldg_type_c_fract_bldg_area, 0.0)
  bldg_type_d_fract_bldg_area = args.fetch(:bldg_type_d_fract_bldg_area, 0.0)
  template = args.fetch(:template, '90.1-2013')

  # If DOE building type is supplied with a DEER template, map to the nearest DEER building type
  if template.include?('DEER')
    unless bldg_type_a.nil?
      bldg_type_a_deer = OpenstudioStandards::CreateTypical.doe_to_deer_building_type(bldg_type_a)
      if bldg_type_a_deer.nil?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Could not find a DEER equivalent to #{bldg_type_a} to align with template #{template}.")
        return false
      elsif bldg_type_a == bldg_type_a_deer
        # Already using a DEER building type with a DEER template, no need to change
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Mapped input of DOE building type #{bldg_type_a} to DEER building type #{bldg_type_a_deer} to match template #{template}")
        bldg_type_a = bldg_type_a_deer
      end
    end

    unless bldg_type_b.nil?
      bldg_type_b_deer = OpenstudioStandards::CreateTypical.doe_to_deer_building_type(bldg_type_b)
      if bldg_type_b_deer.nil?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Could not find a DEER equivalent to #{bldg_type_b} to align with template #{template}.")
        return false
      elsif bldg_type_b == bldg_type_b_deer
        # Already using a DEER building type with a DEER template, no need to change
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Mapped input of DOE building type #{bldg_type_b} to DEER building type #{bldg_type_b_deer} to match template #{template}")
        bldg_type_b = bldg_type_b_deer
      end
    end

    unless bldg_type_c.nil?
      bldg_type_c_deer = OpenstudioStandards::CreateTypical.doe_to_deer_building_type(bldg_type_c)
      if bldg_type_c_deer.nil?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Could not find a DEER equivalent to #{bldg_type_c} to align with template #{template}.")
        return false
      elsif bldg_type_c == bldg_type_c_deer
        # Already using a DEER building type with a DEER template, no need to change
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Mapped input of DOE building type #{bldg_type_c} to DEER building type #{bldg_type_c_deer} to match template #{template}")
        bldg_type_c = bldg_type_c_deer
      end
    end

    unless bldg_type_d.nil?
      bldg_type_d_deer = OpenstudioStandards::CreateTypical.doe_to_deer_building_type(bldg_type_d)
      if bldg_type_d_deer.nil?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Could not find a DEER equivalent to #{bldg_type_d} to align with template #{template}.")
        return false
      elsif bldg_type_d == bldg_type_d_deer
        # Already using a DEER building type with a DEER template, no need to change
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Mapped input of DOE building type #{bldg_type_d} to DEER building type #{bldg_type_d_deer} to match template #{template}")
        bldg_type_d = bldg_type_d_deer
      end
    end
  end

  # check that sum of fractions for b,c, and d is less than 1.0 (so something is left for primary building type)
  bldg_type_a_fract_bldg_area = 1.0 - bldg_type_b_fract_bldg_area - bldg_type_c_fract_bldg_area - bldg_type_d_fract_bldg_area
  if bldg_type_a_fract_bldg_area <= 0.0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Primary Building Type fraction of floor area must be greater than 0. Please lower one or more of the fractions for Building Type B-D.')
    return false
  end

  # report initial condition of model
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "The building started with #{model.getSpaces.size} spaces.")

  # determine of ns_ew needs to be mirrored
  mirror_ns_ew = false
  rotation = model.getBuilding.northAxis
  if rotation > 45.0 && rotation < 135.0
    mirror_ns_ew = true
  elsif rotation > 45.0 && rotation < 135.0
    mirror_ns_ew = true
  end

  # remove non-resource objects not removed by removing the building
  # remove_non_resource_objects(model)

  # rename building to infer template in downstream measure
  name_array = [template, bldg_type_a]
  if bldg_type_b_fract_bldg_area > 0 then name_array << bldg_type_b end
  if bldg_type_c_fract_bldg_area > 0 then name_array << bldg_type_c end
  if bldg_type_d_fract_bldg_area > 0 then name_array << bldg_type_d end
  model.getBuilding.setName(name_array.join('|').to_s)

  # hash to whole building type data
  building_type_hash = {}

  # gather data for bldg_type_a
  building_type_hash[bldg_type_a] = {}
  building_type_hash[bldg_type_a][:frac_bldg_area] = bldg_type_a_fract_bldg_area
  building_type_hash[bldg_type_a][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_a, building_subtype: bldg_subtype_a, template: template, whole_building: true)

  # gather data for bldg_type_b
  if bldg_type_b_fract_bldg_area > 0
    building_type_hash[bldg_type_b] = {}
    building_type_hash[bldg_type_b][:frac_bldg_area] = bldg_type_b_fract_bldg_area
    building_type_hash[bldg_type_b][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_b, building_subtype: bldg_subtype_b, template: template, whole_building: true)
  end

  # gather data for bldg_type_c
  if bldg_type_c_fract_bldg_area > 0
    building_type_hash[bldg_type_c] = {}
    building_type_hash[bldg_type_c][:frac_bldg_area] = bldg_type_c_fract_bldg_area
    building_type_hash[bldg_type_c][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_c, building_subtype: bldg_subtype_c, template: template, whole_building: true)
  end

  # gather data for bldg_type_d
  if bldg_type_d_fract_bldg_area > 0
    building_type_hash[bldg_type_d] = {}
    building_type_hash[bldg_type_d][:frac_bldg_area] = bldg_type_d_fract_bldg_area
    building_type_hash[bldg_type_d][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_d, building_subtype: bldg_subtype_d, template: template, whole_building: true)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating bar based on space ratios from #{bldg_type_a} for building form defaults.")

  # call create_bar_from_args_and_building_type_hash to generate bar
  args[:primary_building_type] = bldg_type_a
  OpenstudioStandards::Geometry.create_bar_from_args_and_building_type_hash(model, args, building_type_hash)

  # rename all surfaces and subsurfaces
  OpenstudioStandards::Geometry.model_rename_surfaces_and_subsurfaces(model)

  return true
end
create_bar_from_space_type_ratios(model, args) click to toggle source

create bar from space type ratios arguments are passed through to lower level methods. See create_bar_from_args_and_building_type_hash for additional argument options.

@param args [Hash] user arguments @option args [String] :space_type_hash_string Space types ratio string in the form ‘BuildingType | SpaceType => 0.75, BuildingType | SpaceType => 0.25’. Fractions should add up to 1. All space types should come from the selected OpenStudio Standards template. @option args [String] :template (‘90.1-2013’) target standard @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/create_bar.rb, line 2146
def self.create_bar_from_space_type_ratios(model, args)
  if args[:space_type_hash_string].empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'args hash passed to create_bar_from_space_type_ratios must include a non-empty :space_type_hash_string')
    return false
  end
  template = args.fetch(:template, '90.1-2013')

  # process arg into hash
  space_type_hash_name = {}
  args[:space_type_hash_string][0..-1].split(/, /) do |entry|
    entry_map = entry.split(/=>/)
    value_str = entry_map[1]
    space_type_hash_name[entry_map[0].strip[0..-1].to_s] = value_str.nil? ? '' : value_str.strip[0..-1].to_f
  end

  # create building type hash from space type ratios
  building_type_hash = {}
  building_type_fraction_of_building = 0.0
  space_type_hash_name.each do |building_space_type, ratio|
    building_type = building_space_type.split('|')[0].strip
    space_type = building_space_type.split('|')[1].strip

    # harvest height and circ info from get_space_types_from_building_type(building_type, template, whole_building = true)
    building_type_lookup_info = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(building_type, template: template)
    if building_type_lookup_info.empty?
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{building_type} looks like an invalid building type for #{template}")
    end
    space_type_info_hash = {}
    if building_type_lookup_info.key?(space_type)
      if building_type_lookup_info[space_type].key?(:story_height)
        space_type_info_hash[:story_height] = building_type_lookup_info[space_type][:story_height]
      end
      if building_type_lookup_info[space_type].key?(:default)
        space_type_info_hash[:default] = building_type_lookup_info[space_type][:default]
      end
      if building_type_lookup_info[space_type].key?(:circ)
        space_type_info_hash[:circ] = building_type_lookup_info[space_type][:circ]
      end
    else
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space_type} looks like an invalid space type for #{building_type}")
    end

    # extend harvested data with custom ratios from space type ratio string argument.
    if building_type_hash.key?(building_type)
      building_type_hash[building_type][:frac_bldg_area] += ratio
      space_type_info_hash[:ratio] = ratio
      building_type_hash[building_type][:space_types][space_type] = space_type_info_hash
    else
      building_type_hash[building_type] = {}
      building_type_hash[building_type][:frac_bldg_area] = ratio
      space_type_info_hash[:ratio] = ratio
      space_types = {}
      space_types[space_type] = space_type_info_hash
      building_type_hash[building_type][:space_types] = space_types
    end
    building_type_fraction_of_building += ratio
  end

  # @todo confirm if this will get normalized up/down later of if I should fix or stop here instead of just a warning
  if building_type_fraction_of_building > 1.0
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Sum of Space Type Ratio of #{building_type_fraction_of_building} is greater than the expected value of 1.0")
  elsif building_type_fraction_of_building < 1.0
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Sum of Space Type Ratio of #{building_type_fraction_of_building} is less than the expected value of 1.0")
  end

  # identify primary building type for building form defaults
  # update to choose building with highest ratio
  primary_building_type = building_type_hash.keys.first

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating bar based space type ratios provided. Using building type #{primary_building_type} from the first ratio as the primary building type. This determines the building form defaults.")

  # call create_bar_from_args_and_building_type_hash to generate bar
  args[:primary_building_type] = primary_building_type
  OpenstudioStandards::Geometry.create_bar_from_args_and_building_type_hash(model, args, building_type_hash)

  return true
end
create_core_and_perimeter_polygons(length, width, footprint_origin_point = OpenStudio::Point3d.new(0.0, 0.0, 0.0), perimeter_zone_depth = OpenStudio.convert(15.0, 'ft', 'm').get) click to toggle source

create core and perimeter polygons from length width and origin

@param length [Double] length of building in meters @param width [Double] width of building in meters @param footprint_origin_point [OpenStudio::Point3d] Optional OpenStudio Point3d object for the new origin @param perimeter_zone_depth [Double] Optional perimeter zone depth in meters @return [Hash] Hash of point vectors that define the space geometry for each direction

# File lib/openstudio-standards/geometry/create.rb, line 80
def self.create_core_and_perimeter_polygons(length, width,
                                            footprint_origin_point = OpenStudio::Point3d.new(0.0, 0.0, 0.0),
                                            perimeter_zone_depth = OpenStudio.convert(15.0, 'ft', 'm').get)
  # key is name, value is a hash, one item of which is polygon. Another could be space type.
  hash_of_point_vectors = {}

  # determine if core and perimeter zoning can be used
  if !(length > perimeter_zone_depth * 2.5 && width > perimeter_zone_depth * 2.5)
    # if any size is to small then just model floor as single zone, issue warning
    perimeter_zone_depth = 0.0
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Due to the size of the building modeling each floor as a single zone.')
  end

  x_delta = footprint_origin_point.x - (length / 2.0)
  y_delta = footprint_origin_point.y - (width / 2.0)
  z = 0
  nw_point = OpenStudio::Point3d.new(x_delta, y_delta + width, z)
  ne_point = OpenStudio::Point3d.new(x_delta + length, y_delta + width, z)
  se_point = OpenStudio::Point3d.new(x_delta + length, y_delta, z)
  sw_point = OpenStudio::Point3d.new(x_delta, y_delta, z)

  # Define polygons for a rectangular building
  if perimeter_zone_depth > 0
    perimeter_nw_point = nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
    perimeter_ne_point = ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
    perimeter_se_point = se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
    perimeter_sw_point = sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)

    west_polygon = OpenStudio::Point3dVector.new
    west_polygon << sw_point
    west_polygon << nw_point
    west_polygon << perimeter_nw_point
    west_polygon << perimeter_sw_point
    hash_of_point_vectors['West Perimeter Space'] = {}
    hash_of_point_vectors['West Perimeter Space'][:space_type] = nil # other methods being used by makeSpacesFromPolygons may have space types associated with each polygon but this doesn't.
    hash_of_point_vectors['West Perimeter Space'][:polygon] = west_polygon

    north_polygon = OpenStudio::Point3dVector.new
    north_polygon << nw_point
    north_polygon << ne_point
    north_polygon << perimeter_ne_point
    north_polygon << perimeter_nw_point
    hash_of_point_vectors['North Perimeter Space'] = {}
    hash_of_point_vectors['North Perimeter Space'][:space_type] = nil
    hash_of_point_vectors['North Perimeter Space'][:polygon] = north_polygon

    east_polygon = OpenStudio::Point3dVector.new
    east_polygon << ne_point
    east_polygon << se_point
    east_polygon << perimeter_se_point
    east_polygon << perimeter_ne_point
    hash_of_point_vectors['East Perimeter Space'] = {}
    hash_of_point_vectors['East Perimeter Space'][:space_type] = nil
    hash_of_point_vectors['East Perimeter Space'][:polygon] = east_polygon

    south_polygon = OpenStudio::Point3dVector.new
    south_polygon << se_point
    south_polygon << sw_point
    south_polygon << perimeter_sw_point
    south_polygon << perimeter_se_point
    hash_of_point_vectors['South Perimeter Space'] = {}
    hash_of_point_vectors['South Perimeter Space'][:space_type] = nil
    hash_of_point_vectors['South Perimeter Space'][:polygon] = south_polygon

    core_polygon = OpenStudio::Point3dVector.new
    core_polygon << perimeter_sw_point
    core_polygon << perimeter_nw_point
    core_polygon << perimeter_ne_point
    core_polygon << perimeter_se_point
    hash_of_point_vectors['Core Space'] = {}
    hash_of_point_vectors['Core Space'][:space_type] = nil
    hash_of_point_vectors['Core Space'][:polygon] = core_polygon

    # Minimal zones
  else
    whole_story_polygon = OpenStudio::Point3dVector.new
    whole_story_polygon << sw_point
    whole_story_polygon << nw_point
    whole_story_polygon << ne_point
    whole_story_polygon << se_point
    hash_of_point_vectors['Whole Story Space'] = {}
    hash_of_point_vectors['Whole Story Space'][:space_type] = nil
    hash_of_point_vectors['Whole Story Space'][:polygon] = whole_story_polygon
  end

  return hash_of_point_vectors
end
create_shape_aspect_ratio(model, aspect_ratio = 0.5, floor_area = 1000.0, rotation = 0.0, num_floors = 3, floor_to_floor_height = 3.8, plenum_height = 1.0, perimeter_zone_depth = 4.57) click to toggle source

Create a Rectangle shape in a model based on a given aspect ratio

@param model [OpenStudio::Model::Model] OpenStudio model object @param aspect_ratio [Double] Aspect ratio @param floor_area [Double] Building floor area in m2 @param rotation [Double] Building rotation in degrees from North @param num_floors [Integer] Number of floors @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 199
def self.create_shape_aspect_ratio(model,
                                   aspect_ratio = 0.5,
                                   floor_area = 1000.0,
                                   rotation = 0.0,
                                   num_floors = 3,
                                   floor_to_floor_height = 3.8,
                                   plenum_height = 1.0,
                                   perimeter_zone_depth = 4.57)
  # determine length and width
  length = Math.sqrt((floor_area / (num_floors * 1.0)) / aspect_ratio)
  width = Math.sqrt((floor_area / (num_floors * 1.0)) * aspect_ratio)
  OpenstudioStandards::Geometry.create_shape_rectangle(model,
                                                       length = length,
                                                       width = width,
                                                       above_ground_storys = num_floors,
                                                       under_ground_storys = 0,
                                                       floor_to_floor_height = floor_to_floor_height,
                                                       plenum_height = plenum_height,
                                                       perimeter_zone_depth = perimeter_zone_depth)
  BTAP::Geometry.rotate_model(model, rotation)

  return model
end
create_shape_courtyard(model, length = 50.0, width = 30.0, courtyard_length = 15.0, courtyard_width = 5.0, num_floors = 3, floor_to_floor_height = 3.8, plenum_height = 1.0, perimeter_zone_depth = 4.57) click to toggle source

Create a Courtyard shape in a model

@param model [OpenStudio::Model::Model] OpenStudio model object @param length [Double] Building length in meters @param width [Double] Building width in meters @param courtyard_length [Double] Courtyard depth in meters @param courtyard_width [Double] Courtyard width in meters @param num_floors [Integer] Number of floors @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 235
def self.create_shape_courtyard(model,
                                length = 50.0,
                                width = 30.0,
                                courtyard_length = 15.0,
                                courtyard_width = 5.0,
                                num_floors = 3,
                                floor_to_floor_height = 3.8,
                                plenum_height = 1.0,
                                perimeter_zone_depth = 4.57)
  if length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Length must be greater than 0.')
    return nil
  end

  if width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Width must be greater than 0.')
    return nil
  end

  if courtyard_length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Courtyard length must be greater than 0.')
    return nil
  end

  if courtyard_width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Courtyard width must be greater than 0.')
    return nil
  end

  if num_floors <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Number of floors must be greater than 0.')
    return nil
  end

  if floor_to_floor_height <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Floor to floor height must be greater than 0.')
    return nil
  end

  if plenum_height < 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Plenum height must be greater than 0.')
    return nil
  end

  shortest_side = [length, width].min
  if perimeter_zone_depth < 0 || 4 * perimeter_zone_depth >= (shortest_side - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Perimeter zone depth must be greater than or equal to 0 and less than #{shortest_side / 4.0}m.")
    return nil
  end

  if courtyard_length >= (length - (4 * perimeter_zone_depth) - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Courtyard length must be less than #{length - (4.0 * perimeter_zone_depth)}m.")
    return nil
  end

  if courtyard_width >= (width - (4 * perimeter_zone_depth) - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Courtyard width must be less than #{width - (4.0 * perimeter_zone_depth)}m.")
    return nil
  end

  # Loop through the number of floors
  for floor in (0..num_floors - 1)
    z = floor_to_floor_height * floor

    # Create a new story within the building
    story = OpenStudio::Model::BuildingStory.new(model)
    story.setNominalFloortoFloorHeight(floor_to_floor_height)
    story.setName("Story #{floor + 1}")

    nw_point = OpenStudio::Point3d.new(0.0, width, z)
    ne_point = OpenStudio::Point3d.new(length, width, z)
    se_point = OpenStudio::Point3d.new(length, 0.0, z)
    sw_point = OpenStudio::Point3d.new(0.0, 0.0, z)

    courtyard_nw_point = OpenStudio::Point3d.new((length - courtyard_length) / 2.0, ((width - courtyard_width) / 2.0) + courtyard_width, z)
    courtyard_ne_point = OpenStudio::Point3d.new(((length - courtyard_length) / 2.0) + courtyard_length, ((width - courtyard_width) / 2.0) + courtyard_width, z)
    courtyard_se_point = OpenStudio::Point3d.new(((length - courtyard_length) / 2.0) + courtyard_length, (width - courtyard_width) / 2.0, z)
    courtyard_sw_point = OpenStudio::Point3d.new((length - courtyard_length) / 2.0, (width - courtyard_width) / 2.0, z)

    # Identity matrix for setting space origins
    m = OpenStudio::Matrix.new(4, 4, 0.0)
    m[0, 0] = 1.0
    m[1, 1] = 1.0
    m[2, 2] = 1.0
    m[3, 3] = 1.0

    # Define polygons for a building with a courtyard
    if perimeter_zone_depth > 0
      outer_perimeter_nw_point = nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0.0)
      outer_perimeter_ne_point = ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0.0)
      outer_perimeter_se_point = se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0.0)
      outer_perimeter_sw_point = sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0.0)
      inner_perimeter_nw_point = courtyard_nw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0.0)
      inner_perimeter_ne_point = courtyard_ne_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0.0)
      inner_perimeter_se_point = courtyard_se_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0.0)
      inner_perimeter_sw_point = courtyard_sw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0.0)

      west_outer_perimeter_polygon = OpenStudio::Point3dVector.new
      west_outer_perimeter_polygon << sw_point
      west_outer_perimeter_polygon << nw_point
      west_outer_perimeter_polygon << outer_perimeter_nw_point
      west_outer_perimeter_polygon << outer_perimeter_sw_point
      west_outer_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_outer_perimeter_polygon, floor_to_floor_height, model)
      west_outer_perimeter_space = west_outer_perimeter_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      west_outer_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_outer_perimeter_space.setBuildingStory(story)
      west_outer_perimeter_space.setName("Story #{floor + 1} West Outer Perimeter Space")

      north_outer_perimeter_polygon = OpenStudio::Point3dVector.new
      north_outer_perimeter_polygon << nw_point
      north_outer_perimeter_polygon << ne_point
      north_outer_perimeter_polygon << outer_perimeter_ne_point
      north_outer_perimeter_polygon << outer_perimeter_nw_point
      north_outer_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_outer_perimeter_polygon, floor_to_floor_height, model)
      north_outer_perimeter_space = north_outer_perimeter_space.get
      m[0, 3] = outer_perimeter_nw_point.x
      m[1, 3] = outer_perimeter_nw_point.y
      m[2, 3] = outer_perimeter_nw_point.z
      north_outer_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_outer_perimeter_space.setBuildingStory(story)
      north_outer_perimeter_space.setName("Story #{floor + 1} North Outer Perimeter Space")

      east_outer_perimeter_polygon = OpenStudio::Point3dVector.new
      east_outer_perimeter_polygon << ne_point
      east_outer_perimeter_polygon << se_point
      east_outer_perimeter_polygon << outer_perimeter_se_point
      east_outer_perimeter_polygon << outer_perimeter_ne_point
      east_outer_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_outer_perimeter_polygon, floor_to_floor_height, model)
      east_outer_perimeter_space = east_outer_perimeter_space.get
      m[0, 3] = outer_perimeter_se_point.x
      m[1, 3] = outer_perimeter_se_point.y
      m[2, 3] = outer_perimeter_se_point.z
      east_outer_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_outer_perimeter_space.setBuildingStory(story)
      east_outer_perimeter_space.setName("Story #{floor + 1} East Outer Perimeter Space")

      south_outer_perimeter_polygon = OpenStudio::Point3dVector.new
      south_outer_perimeter_polygon << se_point
      south_outer_perimeter_polygon << sw_point
      south_outer_perimeter_polygon << outer_perimeter_sw_point
      south_outer_perimeter_polygon << outer_perimeter_se_point
      south_outer_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_outer_perimeter_polygon, floor_to_floor_height, model)
      south_outer_perimeter_space = south_outer_perimeter_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      south_outer_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_outer_perimeter_space.setBuildingStory(story)
      south_outer_perimeter_space.setName("Story #{floor + 1} South Outer Perimeter Space")

      west_core_polygon = OpenStudio::Point3dVector.new
      west_core_polygon << outer_perimeter_sw_point
      west_core_polygon << outer_perimeter_nw_point
      west_core_polygon << inner_perimeter_nw_point
      west_core_polygon << inner_perimeter_sw_point
      west_core_space = OpenStudio::Model::Space.fromFloorPrint(west_core_polygon, floor_to_floor_height, model)
      west_core_space = west_core_space.get
      m[0, 3] = outer_perimeter_sw_point.x
      m[1, 3] = outer_perimeter_sw_point.y
      m[2, 3] = outer_perimeter_sw_point.z
      west_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_core_space.setBuildingStory(story)
      west_core_space.setName("Story #{floor + 1} West Core Space")

      north_core_polygon = OpenStudio::Point3dVector.new
      north_core_polygon << outer_perimeter_nw_point
      north_core_polygon << outer_perimeter_ne_point
      north_core_polygon << inner_perimeter_ne_point
      north_core_polygon << inner_perimeter_nw_point
      north_core_space = OpenStudio::Model::Space.fromFloorPrint(north_core_polygon, floor_to_floor_height, model)
      north_core_space = north_core_space.get
      m[0, 3] = inner_perimeter_nw_point.x
      m[1, 3] = inner_perimeter_nw_point.y
      m[2, 3] = inner_perimeter_nw_point.z
      north_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_core_space.setBuildingStory(story)
      north_core_space.setName("Story #{floor + 1} North Core Space")

      east_core_polygon = OpenStudio::Point3dVector.new
      east_core_polygon << outer_perimeter_ne_point
      east_core_polygon << outer_perimeter_se_point
      east_core_polygon << inner_perimeter_se_point
      east_core_polygon << inner_perimeter_ne_point
      east_core_space = OpenStudio::Model::Space.fromFloorPrint(east_core_polygon, floor_to_floor_height, model)
      east_core_space = east_core_space.get
      m[0, 3] = inner_perimeter_se_point.x
      m[1, 3] = inner_perimeter_se_point.y
      m[2, 3] = inner_perimeter_se_point.z
      east_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_core_space.setBuildingStory(story)
      east_core_space.setName("Story #{floor + 1} East Core Space")

      south_core_polygon = OpenStudio::Point3dVector.new
      south_core_polygon << outer_perimeter_se_point
      south_core_polygon << outer_perimeter_sw_point
      south_core_polygon << inner_perimeter_sw_point
      south_core_polygon << inner_perimeter_se_point
      south_core_space = OpenStudio::Model::Space.fromFloorPrint(south_core_polygon, floor_to_floor_height, model)
      south_core_space = south_core_space.get
      m[0, 3] = outer_perimeter_sw_point.x
      m[1, 3] = outer_perimeter_sw_point.y
      m[2, 3] = outer_perimeter_sw_point.z
      south_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_core_space.setBuildingStory(story)
      south_core_space.setName("Story #{floor + 1} South Core Space")

      west_inner_perimeter_polygon = OpenStudio::Point3dVector.new
      west_inner_perimeter_polygon << inner_perimeter_sw_point
      west_inner_perimeter_polygon << inner_perimeter_nw_point
      west_inner_perimeter_polygon << courtyard_nw_point
      west_inner_perimeter_polygon << courtyard_sw_point
      west_inner_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_inner_perimeter_polygon, floor_to_floor_height, model)
      west_inner_perimeter_space = west_inner_perimeter_space.get
      m[0, 3] = inner_perimeter_sw_point.x
      m[1, 3] = inner_perimeter_sw_point.y
      m[2, 3] = inner_perimeter_sw_point.z
      west_inner_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_inner_perimeter_space.setBuildingStory(story)
      west_inner_perimeter_space.setName("Story #{floor + 1} West Inner Perimeter Space")

      north_inner_perimeter_polygon = OpenStudio::Point3dVector.new
      north_inner_perimeter_polygon << inner_perimeter_nw_point
      north_inner_perimeter_polygon << inner_perimeter_ne_point
      north_inner_perimeter_polygon << courtyard_ne_point
      north_inner_perimeter_polygon << courtyard_nw_point
      north_inner_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_inner_perimeter_polygon, floor_to_floor_height, model)
      north_inner_perimeter_space = north_inner_perimeter_space.get
      m[0, 3] = courtyard_nw_point.x
      m[1, 3] = courtyard_nw_point.y
      m[2, 3] = courtyard_nw_point.z
      north_inner_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_inner_perimeter_space.setBuildingStory(story)
      north_inner_perimeter_space.setName("Story #{floor + 1} North Inner Perimeter Space")

      east_inner_perimeter_polygon = OpenStudio::Point3dVector.new
      east_inner_perimeter_polygon << inner_perimeter_ne_point
      east_inner_perimeter_polygon << inner_perimeter_se_point
      east_inner_perimeter_polygon << courtyard_se_point
      east_inner_perimeter_polygon << courtyard_ne_point
      east_inner_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_inner_perimeter_polygon, floor_to_floor_height, model)
      east_inner_perimeter_space = east_inner_perimeter_space.get
      m[0, 3] = courtyard_se_point.x
      m[1, 3] = courtyard_se_point.y
      m[2, 3] = courtyard_se_point.z
      east_inner_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_inner_perimeter_space.setBuildingStory(story)
      east_inner_perimeter_space.setName("Story #{floor + 1} East Inner Perimeter Space")

      south_inner_perimeter_polygon = OpenStudio::Point3dVector.new
      south_inner_perimeter_polygon << inner_perimeter_se_point
      south_inner_perimeter_polygon << inner_perimeter_sw_point
      south_inner_perimeter_polygon << courtyard_sw_point
      south_inner_perimeter_polygon << courtyard_se_point
      south_inner_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_inner_perimeter_polygon, floor_to_floor_height, model)
      south_inner_perimeter_space = south_inner_perimeter_space.get
      m[0, 3] = inner_perimeter_sw_point.x
      m[1, 3] = inner_perimeter_sw_point.y
      m[2, 3] = inner_perimeter_sw_point.z
      south_inner_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_inner_perimeter_space.setBuildingStory(story)
      south_inner_perimeter_space.setName("Story #{floor + 1} South Inner Perimeter Space")
    else
      # Minimal zones
      west_polygon = OpenStudio::Point3dVector.new
      west_polygon << sw_point
      west_polygon << nw_point
      west_polygon << courtyard_nw_point
      west_polygon << courtyard_sw_point
      west_space = OpenStudio::Model::Space.fromFloorPrint(west_polygon, floor_to_floor_height, model)
      west_space = west_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      west_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_space.setBuildingStory(story)
      west_space.setName("Story #{floor + 1} West Space")

      north_polygon = OpenStudio::Point3dVector.new
      north_polygon << nw_point
      north_polygon << ne_point
      north_polygon << courtyard_ne_point
      north_polygon << courtyard_nw_point
      north_space = OpenStudio::Model::Space.fromFloorPrint(north_polygon, floor_to_floor_height, model)
      north_space = north_space.get
      m[0, 3] = courtyard_nw_point.x
      m[1, 3] = courtyard_nw_point.y
      m[2, 3] = courtyard_nw_point.z
      north_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_space.setBuildingStory(story)
      north_space.setName("Story #{floor + 1} North Space")

      east_polygon = OpenStudio::Point3dVector.new
      east_polygon << ne_point
      east_polygon << se_point
      east_polygon << courtyard_se_point
      east_polygon << courtyard_ne_point
      east_space = OpenStudio::Model::Space.fromFloorPrint(east_polygon, floor_to_floor_height, model)
      east_space = east_space.get
      m[0, 3] = courtyard_se_point.x
      m[1, 3] = courtyard_se_point.y
      m[2, 3] = courtyard_se_point.z
      east_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_space.setBuildingStory(story)
      east_space.setName("Story #{floor + 1} East Space")

      south_polygon = OpenStudio::Point3dVector.new
      south_polygon << se_point
      south_polygon << sw_point
      south_polygon << courtyard_sw_point
      south_polygon << courtyard_se_point
      south_space = OpenStudio::Model::Space.fromFloorPrint(south_polygon, floor_to_floor_height, model)
      south_space = south_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      south_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_space.setBuildingStory(story)
      south_space.setName("Story #{floor + 1} South Space")
    end
    # Set vertical story position
    story.setNominalZCoordinate(z)
  end
  BTAP::Geometry.match_surfaces(model)

  return model
end
create_shape_h(model, length = 40.0, left_width = 40.0, center_width = 10.0, right_width = 40.0, left_end_length = 15.0, right_end_length = 15.0, left_upper_end_offset = 15.0, right_upper_end_offset = 15.0, num_floors = 3, floor_to_floor_height = 3.8, plenum_height = 1, perimeter_zone_depth = 4.57) click to toggle source

Create an H shape in a model

@param model [OpenStudio::Model::Model] OpenStudio model object @param length [Double] Building length in meters @param left_width [Double] Left width in meters @param center_width [Double] Center width in meters @param right_width [Double] Right width in meters @param left_end_length [Double] Left end length in meters @param right_end_length [Double] Right end length in meters @param left_upper_end_offset [Double] Left upper end offset in meters @param right_upper_end_offset [Double] Right upper end offset in meters @param num_floors [Integer] Number of floors @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 581
def self.create_shape_h(model,
                        length = 40.0,
                        left_width = 40.0,
                        center_width = 10.0,
                        right_width = 40.0,
                        left_end_length = 15.0,
                        right_end_length = 15.0,
                        left_upper_end_offset = 15.0,
                        right_upper_end_offset = 15.0,
                        num_floors = 3,
                        floor_to_floor_height = 3.8,
                        plenum_height = 1,
                        perimeter_zone_depth = 4.57)
  if length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Length must be greater than 0.')
    return nil
  end

  if left_width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Left width must be greater than 0.')
    return nil
  end

  if right_width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Right width must be greater than 0.')
    return nil
  end

  if center_width <= 1e-4 || center_width >= ([left_width, right_width].min - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Center width must be greater than 0 and less than #{[left_width, right_width].min}m.")
    return nil
  end

  if left_end_length <= 1e-4 || left_end_length >= (length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Left end length must be greater than 0 and less than #{length}m.")
    return nil
  end

  if right_end_length <= 1e-4 || right_end_length >= (length - left_end_length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Right end length must be greater than 0 and less than #{length - left_end_length}m.")
    return nil
  end

  if left_upper_end_offset <= 1e-4 || left_upper_end_offset >= (left_width - center_width - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Left upper end offset must be greater than 0 and less than #{left_width - center_width}m.")
    return nil
  end

  if right_upper_end_offset <= 1e-4 || right_upper_end_offset >= (right_width - center_width - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Right upper end offset must be greater than 0 and less than #{right_width - center_width}m.")
    return nil
  end

  if num_floors <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Number of floors must be greater than 0.')
    return nil
  end

  if floor_to_floor_height <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Floor to floor height must be greater than 0.')
    return nil
  end

  if plenum_height < 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Plenum height must be greater than 0.')
    return nil
  end

  shortest_side = [length / 2, left_width, center_width, right_width, left_end_length, right_end_length].min
  if perimeter_zone_depth < 0 || 2 * perimeter_zone_depth >= (shortest_side - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Perimeter zone depth must be greater than or equal to 0 and less than #{shortest_side / 2}m.")
    return nil
  end

  # Loop through the number of floors
  for floor in (0..num_floors - 1)
    z = floor_to_floor_height * floor

    # Create a new story within the building
    story = OpenStudio::Model::BuildingStory.new(model)
    story.setNominalFloortoFloorHeight(floor_to_floor_height)
    story.setName("Story #{floor + 1}")

    left_origin = (right_width - right_upper_end_offset) > (left_width - left_upper_end_offset) ? (right_width - right_upper_end_offset) - (left_width - left_upper_end_offset) : 0
    left_nw_point = OpenStudio::Point3d.new(0, left_width + left_origin, z)
    left_ne_point = OpenStudio::Point3d.new(left_end_length, left_width + left_origin, z)
    left_se_point = OpenStudio::Point3d.new(left_end_length, left_origin, z)
    left_sw_point = OpenStudio::Point3d.new(0, left_origin, z)
    center_nw_point = OpenStudio::Point3d.new(left_end_length, left_ne_point.y - left_upper_end_offset, z)
    center_ne_point = OpenStudio::Point3d.new(length - right_end_length, center_nw_point.y, z)
    center_se_point = OpenStudio::Point3d.new(length - right_end_length, center_nw_point.y - center_width, z)
    center_sw_point = OpenStudio::Point3d.new(left_end_length, center_se_point.y, z)
    right_nw_point = OpenStudio::Point3d.new(length - right_end_length, center_ne_point.y + right_upper_end_offset, z)
    right_ne_point = OpenStudio::Point3d.new(length, right_nw_point.y, z)
    right_se_point = OpenStudio::Point3d.new(length, right_ne_point.y - right_width, z)
    right_sw_point = OpenStudio::Point3d.new(length - right_end_length, right_se_point.y, z)

    # Identity matrix for setting space origins
    m = OpenStudio::Matrix.new(4, 4, 0)
    m[0, 0] = 1
    m[1, 1] = 1
    m[2, 2] = 1
    m[3, 3] = 1

    # Define polygons for an H-shape building with perimeter core zoning
    if perimeter_zone_depth > 0
      perimeter_left_nw_point = left_nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_left_ne_point = left_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_left_se_point = left_se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_left_sw_point = left_sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_center_nw_point = center_nw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_center_ne_point = center_ne_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_center_se_point = center_se_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_center_sw_point = center_sw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_right_nw_point = right_nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_right_ne_point = right_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_right_se_point = right_se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_right_sw_point = right_sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)

      west_left_perimeter_polygon = OpenStudio::Point3dVector.new
      west_left_perimeter_polygon << left_sw_point
      west_left_perimeter_polygon << left_nw_point
      west_left_perimeter_polygon << perimeter_left_nw_point
      west_left_perimeter_polygon << perimeter_left_sw_point
      west_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_left_perimeter_polygon, floor_to_floor_height, model)
      west_left_perimeter_space = west_left_perimeter_space.get
      m[0, 3] = left_sw_point.x
      m[1, 3] = left_sw_point.y
      m[2, 3] = left_sw_point.z
      west_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_left_perimeter_space.setBuildingStory(story)
      west_left_perimeter_space.setName("Story #{floor + 1} West Left Perimeter Space")

      north_left_perimeter_polygon = OpenStudio::Point3dVector.new
      north_left_perimeter_polygon << left_nw_point
      north_left_perimeter_polygon << left_ne_point
      north_left_perimeter_polygon << perimeter_left_ne_point
      north_left_perimeter_polygon << perimeter_left_nw_point
      north_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_left_perimeter_polygon, floor_to_floor_height, model)
      north_left_perimeter_space = north_left_perimeter_space.get
      m[0, 3] = perimeter_left_nw_point.x
      m[1, 3] = perimeter_left_nw_point.y
      m[2, 3] = perimeter_left_nw_point.z
      north_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_left_perimeter_space.setBuildingStory(story)
      north_left_perimeter_space.setName("Story #{floor + 1} North Left Perimeter Space")

      east_upper_left_perimeter_polygon = OpenStudio::Point3dVector.new
      east_upper_left_perimeter_polygon << left_ne_point
      east_upper_left_perimeter_polygon << center_nw_point
      east_upper_left_perimeter_polygon << perimeter_center_nw_point
      east_upper_left_perimeter_polygon << perimeter_left_ne_point
      east_upper_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_upper_left_perimeter_polygon, floor_to_floor_height, model)
      east_upper_left_perimeter_space = east_upper_left_perimeter_space.get
      m[0, 3] = perimeter_center_nw_point.x
      m[1, 3] = perimeter_center_nw_point.y
      m[2, 3] = perimeter_center_nw_point.z
      east_upper_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_upper_left_perimeter_space.setBuildingStory(story)
      east_upper_left_perimeter_space.setName("Story #{floor + 1} East Upper Left Perimeter Space")

      north_center_perimeter_polygon = OpenStudio::Point3dVector.new
      north_center_perimeter_polygon << center_nw_point
      north_center_perimeter_polygon << center_ne_point
      north_center_perimeter_polygon << perimeter_center_ne_point
      north_center_perimeter_polygon << perimeter_center_nw_point
      north_center_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_center_perimeter_polygon, floor_to_floor_height, model)
      north_center_perimeter_space = north_center_perimeter_space.get
      m[0, 3] = perimeter_center_nw_point.x
      m[1, 3] = perimeter_center_nw_point.y
      m[2, 3] = perimeter_center_nw_point.z
      north_center_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_center_perimeter_space.setBuildingStory(story)
      north_center_perimeter_space.setName("Story #{floor + 1} North Center Perimeter Space")

      west_upper_right_perimeter_polygon = OpenStudio::Point3dVector.new
      west_upper_right_perimeter_polygon << center_ne_point
      west_upper_right_perimeter_polygon << right_nw_point
      west_upper_right_perimeter_polygon << perimeter_right_nw_point
      west_upper_right_perimeter_polygon << perimeter_center_ne_point
      west_upper_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_upper_right_perimeter_polygon, floor_to_floor_height, model)
      west_upper_right_perimeter_space = west_upper_right_perimeter_space.get
      m[0, 3] = center_ne_point.x
      m[1, 3] = center_ne_point.y
      m[2, 3] = center_ne_point.z
      west_upper_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_upper_right_perimeter_space.setBuildingStory(story)
      west_upper_right_perimeter_space.setName("Story #{floor + 1} West Upper Right Perimeter Space")

      north_right_perimeter_polygon = OpenStudio::Point3dVector.new
      north_right_perimeter_polygon << right_nw_point
      north_right_perimeter_polygon << right_ne_point
      north_right_perimeter_polygon << perimeter_right_ne_point
      north_right_perimeter_polygon << perimeter_right_nw_point
      north_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_right_perimeter_polygon, floor_to_floor_height, model)
      north_right_perimeter_space = north_right_perimeter_space.get
      m[0, 3] = perimeter_right_nw_point.x
      m[1, 3] = perimeter_right_nw_point.y
      m[2, 3] = perimeter_right_nw_point.z
      north_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_right_perimeter_space.setBuildingStory(story)
      north_right_perimeter_space.setName("Story #{floor + 1} North Right Perimeter Space")

      east_right_perimeter_polygon = OpenStudio::Point3dVector.new
      east_right_perimeter_polygon << right_ne_point
      east_right_perimeter_polygon << right_se_point
      east_right_perimeter_polygon << perimeter_right_se_point
      east_right_perimeter_polygon << perimeter_right_ne_point
      east_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_right_perimeter_polygon, floor_to_floor_height, model)
      east_right_perimeter_space = east_right_perimeter_space.get
      m[0, 3] = perimeter_right_se_point.x
      m[1, 3] = perimeter_right_se_point.y
      m[2, 3] = perimeter_right_se_point.z
      east_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_right_perimeter_space.setBuildingStory(story)
      east_right_perimeter_space.setName("Story #{floor + 1} East Right Perimeter Space")

      south_right_perimeter_polygon = OpenStudio::Point3dVector.new
      south_right_perimeter_polygon << right_se_point
      south_right_perimeter_polygon << right_sw_point
      south_right_perimeter_polygon << perimeter_right_sw_point
      south_right_perimeter_polygon << perimeter_right_se_point
      south_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_right_perimeter_polygon, floor_to_floor_height, model)
      south_right_perimeter_space = south_right_perimeter_space.get
      m[0, 3] = right_sw_point.x
      m[1, 3] = right_sw_point.y
      m[2, 3] = right_sw_point.z
      south_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_right_perimeter_space.setBuildingStory(story)
      south_right_perimeter_space.setName("Story #{floor + 1} South Right Perimeter Space")

      west_lower_right_perimeter_polygon = OpenStudio::Point3dVector.new
      west_lower_right_perimeter_polygon << right_sw_point
      west_lower_right_perimeter_polygon << center_se_point
      west_lower_right_perimeter_polygon << perimeter_center_se_point
      west_lower_right_perimeter_polygon << perimeter_right_sw_point
      west_lower_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_lower_right_perimeter_polygon, floor_to_floor_height, model)
      west_lower_right_perimeter_space = west_lower_right_perimeter_space.get
      m[0, 3] = right_sw_point.x
      m[1, 3] = right_sw_point.y
      m[2, 3] = right_sw_point.z
      west_lower_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_lower_right_perimeter_space.setBuildingStory(story)
      west_lower_right_perimeter_space.setName("Story #{floor + 1} West Lower Right Perimeter Space")

      south_center_perimeter_polygon = OpenStudio::Point3dVector.new
      south_center_perimeter_polygon << center_se_point
      south_center_perimeter_polygon << center_sw_point
      south_center_perimeter_polygon << perimeter_center_sw_point
      south_center_perimeter_polygon << perimeter_center_se_point
      south_center_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_center_perimeter_polygon, floor_to_floor_height, model)
      south_center_perimeter_space = south_center_perimeter_space.get
      m[0, 3] = center_sw_point.x
      m[1, 3] = center_sw_point.y
      m[2, 3] = center_sw_point.z
      south_center_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_center_perimeter_space.setBuildingStory(story)
      south_center_perimeter_space.setName("Story #{floor + 1} South Center Perimeter Space")

      east_lower_left_perimeter_polygon = OpenStudio::Point3dVector.new
      east_lower_left_perimeter_polygon << center_sw_point
      east_lower_left_perimeter_polygon << left_se_point
      east_lower_left_perimeter_polygon << perimeter_left_se_point
      east_lower_left_perimeter_polygon << perimeter_center_sw_point
      east_lower_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_lower_left_perimeter_polygon, floor_to_floor_height, model)
      east_lower_left_perimeter_space = east_lower_left_perimeter_space.get
      m[0, 3] = perimeter_left_se_point.x
      m[1, 3] = perimeter_left_se_point.y
      m[2, 3] = perimeter_left_se_point.z
      east_lower_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_lower_left_perimeter_space.setBuildingStory(story)
      east_lower_left_perimeter_space.setName("Story #{floor + 1} East Lower Left Perimeter Space")

      south_left_perimeter_polygon = OpenStudio::Point3dVector.new
      south_left_perimeter_polygon << left_se_point
      south_left_perimeter_polygon << left_sw_point
      south_left_perimeter_polygon << perimeter_left_sw_point
      south_left_perimeter_polygon << perimeter_left_se_point
      south_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_left_perimeter_polygon, floor_to_floor_height, model)
      south_left_perimeter_space = south_left_perimeter_space.get
      m[0, 3] = left_sw_point.x
      m[1, 3] = left_sw_point.y
      m[2, 3] = left_sw_point.z
      south_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_left_perimeter_space.setBuildingStory(story)
      south_left_perimeter_space.setName("Story #{floor + 1} South Left Perimeter Space")

      west_core_polygon = OpenStudio::Point3dVector.new
      west_core_polygon << perimeter_left_sw_point
      west_core_polygon << perimeter_left_nw_point
      west_core_polygon << perimeter_left_ne_point
      west_core_polygon << perimeter_center_nw_point
      west_core_polygon << perimeter_center_sw_point
      west_core_polygon << perimeter_left_se_point
      west_core_space = OpenStudio::Model::Space.fromFloorPrint(west_core_polygon, floor_to_floor_height, model)
      west_core_space = west_core_space.get
      m[0, 3] = perimeter_left_sw_point.x
      m[1, 3] = perimeter_left_sw_point.y
      m[2, 3] = perimeter_left_sw_point.z
      west_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_core_space.setBuildingStory(story)
      west_core_space.setName("Story #{floor + 1} West Core Space")

      center_core_polygon = OpenStudio::Point3dVector.new
      center_core_polygon << perimeter_center_sw_point
      center_core_polygon << perimeter_center_nw_point
      center_core_polygon << perimeter_center_ne_point
      center_core_polygon << perimeter_center_se_point
      center_core_space = OpenStudio::Model::Space.fromFloorPrint(center_core_polygon, floor_to_floor_height, model)
      center_core_space = center_core_space.get
      m[0, 3] = perimeter_center_sw_point.x
      m[1, 3] = perimeter_center_sw_point.y
      m[2, 3] = perimeter_center_sw_point.z
      center_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      center_core_space.setBuildingStory(story)
      center_core_space.setName("Story #{floor + 1} Center Core Space")

      east_core_polygon = OpenStudio::Point3dVector.new
      east_core_polygon << perimeter_right_sw_point
      east_core_polygon << perimeter_center_se_point
      east_core_polygon << perimeter_center_ne_point
      east_core_polygon << perimeter_right_nw_point
      east_core_polygon << perimeter_right_ne_point
      east_core_polygon << perimeter_right_se_point
      east_core_space = OpenStudio::Model::Space.fromFloorPrint(east_core_polygon, floor_to_floor_height, model)
      east_core_space = east_core_space.get
      m[0, 3] = perimeter_right_sw_point.x
      m[1, 3] = perimeter_right_sw_point.y
      m[2, 3] = perimeter_right_sw_point.z
      east_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_core_space.setBuildingStory(story)
      east_core_space.setName("Story #{floor + 1} East Core Space")
    else
      # Minimal zones
      west_polygon = OpenStudio::Point3dVector.new
      west_polygon << left_sw_point
      west_polygon << left_nw_point
      west_polygon << left_ne_point
      west_polygon << center_nw_point
      west_polygon << center_sw_point
      west_polygon << left_se_point
      west_space = OpenStudio::Model::Space.fromFloorPrint(west_polygon, floor_to_floor_height, model)
      west_space = west_space.get
      m[0, 3] = left_sw_point.x
      m[1, 3] = left_sw_point.y
      m[2, 3] = left_sw_point.z
      west_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_space.setBuildingStory(story)
      west_space.setName("Story #{floor + 1} West Space")

      center_polygon = OpenStudio::Point3dVector.new
      center_polygon << center_sw_point
      center_polygon << center_nw_point
      center_polygon << center_ne_point
      center_polygon << center_se_point
      center_space = OpenStudio::Model::Space.fromFloorPrint(center_polygon, floor_to_floor_height, model)
      center_space = center_space.get
      m[0, 3] = center_sw_point.x
      m[1, 3] = center_sw_point.y
      m[2, 3] = center_sw_point.z
      center_space.changeTransformation(OpenStudio::Transformation.new(m))
      center_space.setBuildingStory(story)
      center_space.setName("Story #{floor + 1} Center Space")

      east_polygon = OpenStudio::Point3dVector.new
      east_polygon << right_sw_point
      east_polygon << center_se_point
      east_polygon << center_ne_point
      east_polygon << right_nw_point
      east_polygon << right_ne_point
      east_polygon << right_se_point
      east_space = OpenStudio::Model::Space.fromFloorPrint(east_polygon, floor_to_floor_height, model)
      east_space = east_space.get
      m[0, 3] = right_sw_point.x
      m[1, 3] = right_sw_point.y
      m[2, 3] = right_sw_point.z
      east_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_space.setBuildingStory(story)
      east_space.setName("Story #{floor + 1} East Space")
    end
    # Set vertical story position
    story.setNominalZCoordinate(z)

  end
  BTAP::Geometry.match_surfaces(model)

  return model
end
create_shape_l(model, length = 40.0, width = 40.0, lower_end_width = 20.0, upper_end_length = 20.0, num_floors = 3, floor_to_floor_height = 3.8, plenum_height = 1.0, perimeter_zone_depth = 4.57) click to toggle source

Create an L shape in a model

@param model [OpenStudio::Model::Model] OpenStudio model object @param length [Double] Building length in meters @param width [Double] Building width in meters @param lower_end_width [Double] Lower end width in meters @param upper_end_length [Double] Upper end width in meters @param num_floors [Integer] Number of floors @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 982
def self.create_shape_l(model,
                        length = 40.0,
                        width = 40.0,
                        lower_end_width = 20.0,
                        upper_end_length = 20.0,
                        num_floors = 3,
                        floor_to_floor_height = 3.8,
                        plenum_height = 1.0,
                        perimeter_zone_depth = 4.57)
  if length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Length must be greater than 0.')
    return nil
  end

  if width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Width must be greater than 0.')
    return nil
  end

  if lower_end_width <= 1e-4 || lower_end_width >= (width - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Lower end width must be greater than 0 and less than #{width}m.")
    return nil
  end

  if upper_end_length <= 1e-4 || upper_end_length >= (length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Upper end length must be greater than 0 and less than #{length}m.")
    return nil
  end

  if num_floors <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Number of floors must be greater than 0.')
    return nil
  end

  if floor_to_floor_height <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Floor to floor height must be greater than 0.')
    return nil
  end

  if plenum_height < 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Plenum height must be greater than 0.')
    return nil
  end

  shortest_side = [lower_end_width, upper_end_length].min
  if perimeter_zone_depth < 0 || 2 * perimeter_zone_depth >= (shortest_side - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Perimeter zone depth must be greater than or equal to 0 and less than #{shortest_side / 2}m.")
    return nil
  end

  # Loop through the number of floors
  for floor in (0..num_floors - 1)
    z = floor_to_floor_height * floor

    # Create a new story within the building
    story = OpenStudio::Model::BuildingStory.new(model)
    story.setNominalFloortoFloorHeight(floor_to_floor_height)
    story.setName("Story #{floor + 1}")

    nw_point = OpenStudio::Point3d.new(0, width, z)
    upper_ne_point = OpenStudio::Point3d.new(upper_end_length, width, z)
    upper_sw_point = OpenStudio::Point3d.new(upper_end_length, lower_end_width, z)
    lower_ne_point = OpenStudio::Point3d.new(length, lower_end_width, z)
    se_point = OpenStudio::Point3d.new(length, 0, z)
    sw_point = OpenStudio::Point3d.new(0, 0, z)

    # Identity matrix for setting space origins
    m = OpenStudio::Matrix.new(4, 4, 0)
    m[0, 0] = 1
    m[1, 1] = 1
    m[2, 2] = 1
    m[3, 3] = 1

    # Define polygons for a L-shape building with perimeter core zoning
    if perimeter_zone_depth > 0
      perimeter_nw_point = nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_upper_ne_point = upper_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_upper_sw_point = upper_sw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_lower_ne_point = lower_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_se_point = se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_lower_sw_point = sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)

      west_perimeter_polygon = OpenStudio::Point3dVector.new
      west_perimeter_polygon << sw_point
      west_perimeter_polygon << nw_point
      west_perimeter_polygon << perimeter_nw_point
      west_perimeter_polygon << perimeter_lower_sw_point
      west_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_perimeter_polygon, floor_to_floor_height, model)
      west_perimeter_space = west_perimeter_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      west_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_perimeter_space.setBuildingStory(story)
      west_perimeter_space.setName("Story #{floor + 1} West Perimeter Space")

      north_upper_perimeter_polygon = OpenStudio::Point3dVector.new
      north_upper_perimeter_polygon << nw_point
      north_upper_perimeter_polygon << upper_ne_point
      north_upper_perimeter_polygon << perimeter_upper_ne_point
      north_upper_perimeter_polygon << perimeter_nw_point
      north_upper_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_upper_perimeter_polygon, floor_to_floor_height, model)
      north_upper_perimeter_space = north_upper_perimeter_space.get
      m[0, 3] = perimeter_nw_point.x
      m[1, 3] = perimeter_nw_point.y
      m[2, 3] = perimeter_nw_point.z
      north_upper_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_upper_perimeter_space.setBuildingStory(story)
      north_upper_perimeter_space.setName("Story #{floor + 1} North Upper Perimeter Space")

      east_upper_perimeter_polygon = OpenStudio::Point3dVector.new
      east_upper_perimeter_polygon << upper_ne_point
      east_upper_perimeter_polygon << upper_sw_point
      east_upper_perimeter_polygon << perimeter_upper_sw_point
      east_upper_perimeter_polygon << perimeter_upper_ne_point
      east_upper_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_upper_perimeter_polygon, floor_to_floor_height, model)
      east_upper_perimeter_space = east_upper_perimeter_space.get
      m[0, 3] = perimeter_upper_sw_point.x
      m[1, 3] = perimeter_upper_sw_point.y
      m[2, 3] = perimeter_upper_sw_point.z
      east_upper_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_upper_perimeter_space.setBuildingStory(story)
      east_upper_perimeter_space.setName("Story #{floor + 1} East Upper Perimeter Space")

      north_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      north_lower_perimeter_polygon << upper_sw_point
      north_lower_perimeter_polygon << lower_ne_point
      north_lower_perimeter_polygon << perimeter_lower_ne_point
      north_lower_perimeter_polygon << perimeter_upper_sw_point
      north_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_lower_perimeter_polygon, floor_to_floor_height, model)
      north_lower_perimeter_space = north_lower_perimeter_space.get
      m[0, 3] = perimeter_upper_sw_point.x
      m[1, 3] = perimeter_upper_sw_point.y
      m[2, 3] = perimeter_upper_sw_point.z
      north_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_lower_perimeter_space.setBuildingStory(story)
      north_lower_perimeter_space.setName("Story #{floor + 1} North Lower Perimeter Space")

      east_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      east_lower_perimeter_polygon << lower_ne_point
      east_lower_perimeter_polygon << se_point
      east_lower_perimeter_polygon << perimeter_se_point
      east_lower_perimeter_polygon << perimeter_lower_ne_point
      east_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_lower_perimeter_polygon, floor_to_floor_height, model)
      east_lower_perimeter_space = east_lower_perimeter_space.get
      m[0, 3] = perimeter_se_point.x
      m[1, 3] = perimeter_se_point.y
      m[2, 3] = perimeter_se_point.z
      east_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_lower_perimeter_space.setBuildingStory(story)
      east_lower_perimeter_space.setName("Story #{floor + 1} East Lower Perimeter Space")

      south_perimeter_polygon = OpenStudio::Point3dVector.new
      south_perimeter_polygon << se_point
      south_perimeter_polygon << sw_point
      south_perimeter_polygon << perimeter_lower_sw_point
      south_perimeter_polygon << perimeter_se_point
      south_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_perimeter_polygon, floor_to_floor_height, model)
      south_perimeter_space = south_perimeter_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      south_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_perimeter_space.setBuildingStory(story)
      south_perimeter_space.setName("Story #{floor + 1} South Perimeter Space")

      west_core_polygon = OpenStudio::Point3dVector.new
      west_core_polygon << perimeter_lower_sw_point
      west_core_polygon << perimeter_nw_point
      west_core_polygon << perimeter_upper_ne_point
      west_core_polygon << perimeter_upper_sw_point
      west_core_space = OpenStudio::Model::Space.fromFloorPrint(west_core_polygon, floor_to_floor_height, model)
      west_core_space = west_core_space.get
      m[0, 3] = perimeter_lower_sw_point.x
      m[1, 3] = perimeter_lower_sw_point.y
      m[2, 3] = perimeter_lower_sw_point.z
      west_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_core_space.setBuildingStory(story)
      west_core_space.setName("Story #{floor + 1} West Core Space")

      east_core_polygon = OpenStudio::Point3dVector.new
      east_core_polygon << perimeter_upper_sw_point
      east_core_polygon << perimeter_lower_ne_point
      east_core_polygon << perimeter_se_point
      east_core_polygon << perimeter_lower_sw_point
      east_core_space = OpenStudio::Model::Space.fromFloorPrint(east_core_polygon, floor_to_floor_height, model)
      east_core_space = east_core_space.get
      m[0, 3] = perimeter_lower_sw_point.x
      m[1, 3] = perimeter_lower_sw_point.y
      m[2, 3] = perimeter_lower_sw_point.z
      east_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_core_space.setBuildingStory(story)
      east_core_space.setName("Story #{floor + 1} East Core Space")
    else
      # Minimal zones
      west_polygon = OpenStudio::Point3dVector.new
      west_polygon << sw_point
      west_polygon << nw_point
      west_polygon << upper_ne_point
      west_polygon << upper_sw_point
      west_space = OpenStudio::Model::Space.fromFloorPrint(west_polygon, floor_to_floor_height, model)
      west_space = west_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      west_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_space.setBuildingStory(story)
      west_space.setName("Story #{floor + 1} West Space")

      east_polygon = OpenStudio::Point3dVector.new
      east_polygon << sw_point
      east_polygon << upper_sw_point
      east_polygon << lower_ne_point
      east_polygon << se_point
      east_space = OpenStudio::Model::Space.fromFloorPrint(east_polygon, floor_to_floor_height, model)
      east_space = east_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      east_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_space.setBuildingStory(story)
      east_space.setName("Story #{floor + 1} East Space")
    end
    # Set vertical story position
    story.setNominalZCoordinate(z)
  end
  BTAP::Geometry.match_surfaces(model)

  return model
end
create_shape_rectangle(model, length = 100.0, width = 100.0, above_ground_storys = 3, under_ground_storys = 1, floor_to_floor_height = 3.8, plenum_height = 1.0, perimeter_zone_depth = 4.57, initial_height = 0.0) click to toggle source

Create a Rectangle shape in a model

@param model [OpenStudio::Model::Model] OpenStudio model object @param length [Double] Building length in meters @param width [Double] Building width in meters @param above_ground_storys [Integer] Number of above ground stories @param under_ground_storys [Integer] Number of below ground stories @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @param initial_height [Double] Initial height in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 20
def self.create_shape_rectangle(model,
                                length = 100.0,
                                width = 100.0,
                                above_ground_storys = 3,
                                under_ground_storys = 1,
                                floor_to_floor_height = 3.8,
                                plenum_height = 1.0,
                                perimeter_zone_depth = 4.57,
                                initial_height = 0.0)
  if length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Length must be greater than 0.')
    return nil
  end

  if width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Width must be greater than 0.')
    return nil
  end

  if (above_ground_storys + under_ground_storys) <= 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Number of floors must be greater than 0.')
    return nil
  end

  if floor_to_floor_height <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Floor to floor height must be greater than 0.')
    return nil
  end

  if plenum_height < 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Plenum height must be greater than 0.')
    return nil
  end

  shortest_side = [length, width].min
  if perimeter_zone_depth < 0 || 2 * perimeter_zone_depth >= (shortest_side - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Perimeter zone depth must be greater than or equal to 0 and less than half of the smaller of length and width, #{(shortest_side / 2).round(2)}m")
    return nil
  end

  # Loop through the number of floors
  building_stories = []
  for floor in ((under_ground_storys * -1)..above_ground_storys - 1)
    z = (floor_to_floor_height * floor) + initial_height

    # Create a new story within the building
    story = OpenStudio::Model::BuildingStory.new(model)
    story.setNominalFloortoFloorHeight(floor_to_floor_height)
    story.setName("Story #{floor + 1}")
    building_stories << story

    nw_point = OpenStudio::Point3d.new(0, width, z)
    ne_point = OpenStudio::Point3d.new(length, width, z)
    se_point = OpenStudio::Point3d.new(length, 0, z)
    sw_point = OpenStudio::Point3d.new(0, 0, z)

    # Identity matrix for setting space origins
    m = OpenStudio::Matrix.new(4, 4, 0)
    m[0, 0] = 1
    m[1, 1] = 1
    m[2, 2] = 1
    m[3, 3] = 1

    # Define polygons for a rectangular building
    if perimeter_zone_depth > 0
      perimeter_nw_point = nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_ne_point = ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_se_point = se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_sw_point = sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)

      west_polygon = OpenStudio::Point3dVector.new
      west_polygon << sw_point
      west_polygon << nw_point
      west_polygon << perimeter_nw_point
      west_polygon << perimeter_sw_point
      west_space = OpenStudio::Model::Space.fromFloorPrint(west_polygon, floor_to_floor_height, model)
      west_space = west_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      west_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_space.setBuildingStory(story)
      west_space.setName("Story #{floor + 1} West Perimeter Space")

      north_polygon = OpenStudio::Point3dVector.new
      north_polygon << nw_point
      north_polygon << ne_point
      north_polygon << perimeter_ne_point
      north_polygon << perimeter_nw_point
      north_space = OpenStudio::Model::Space.fromFloorPrint(north_polygon, floor_to_floor_height, model)
      north_space = north_space.get
      m[0, 3] = perimeter_nw_point.x
      m[1, 3] = perimeter_nw_point.y
      m[2, 3] = perimeter_nw_point.z
      north_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_space.setBuildingStory(story)
      north_space.setName("Story #{floor + 1} North Perimeter Space")

      east_polygon = OpenStudio::Point3dVector.new
      east_polygon << ne_point
      east_polygon << se_point
      east_polygon << perimeter_se_point
      east_polygon << perimeter_ne_point
      east_space = OpenStudio::Model::Space.fromFloorPrint(east_polygon, floor_to_floor_height, model)
      east_space = east_space.get
      m[0, 3] = perimeter_se_point.x
      m[1, 3] = perimeter_se_point.y
      m[2, 3] = perimeter_se_point.z
      east_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_space.setBuildingStory(story)
      east_space.setName("Story #{floor + 1} East Perimeter Space")

      south_polygon = OpenStudio::Point3dVector.new
      south_polygon << se_point
      south_polygon << sw_point
      south_polygon << perimeter_sw_point
      south_polygon << perimeter_se_point
      south_space = OpenStudio::Model::Space.fromFloorPrint(south_polygon, floor_to_floor_height, model)
      south_space = south_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      south_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_space.setBuildingStory(story)
      south_space.setName("Story #{floor + 1} South Perimeter Space")

      core_polygon = OpenStudio::Point3dVector.new
      core_polygon << perimeter_sw_point
      core_polygon << perimeter_nw_point
      core_polygon << perimeter_ne_point
      core_polygon << perimeter_se_point
      core_space = OpenStudio::Model::Space.fromFloorPrint(core_polygon, floor_to_floor_height, model)
      core_space = core_space.get
      m[0, 3] = perimeter_sw_point.x
      m[1, 3] = perimeter_sw_point.y
      m[2, 3] = perimeter_sw_point.z
      core_space.changeTransformation(OpenStudio::Transformation.new(m))
      core_space.setBuildingStory(story)
      core_space.setName("Story #{floor + 1} Core Space")
    else
      # Minimal zones
      core_polygon = OpenStudio::Point3dVector.new
      core_polygon << sw_point
      core_polygon << nw_point
      core_polygon << ne_point
      core_polygon << se_point
      core_space = OpenStudio::Model::Space.fromFloorPrint(core_polygon, floor_to_floor_height, model)
      core_space = core_space.get
      m[0, 3] = sw_point.x
      m[1, 3] = sw_point.y
      m[2, 3] = sw_point.z
      core_space.changeTransformation(OpenStudio::Transformation.new(m))
      core_space.setBuildingStory(story)
      core_space.setName("Story #{floor + 1} Core Space")
    end
    # Set vertical story position
    story.setNominalZCoordinate(z)

    # Ensure that underground stories (when z<0 have Ground set as Boundary conditions).
    # Apply the Ground BC to all surfaces, the top ceiling will be corrected below when the surface matching algorithm is called.
    underground_surfaces = story.spaces.flat_map(&:surfaces)
    BTAP::Geometry::Surfaces.set_surfaces_boundary_condition(model, underground_surfaces, 'Ground') if z < 0
  end

  BTAP::Geometry.match_surfaces(model)
  return model
end
create_shape_t(model, length = 40.0, width = 40.0, upper_end_width = 20.0, lower_end_length = 20.0, left_end_offset = 10.0, num_floors = 3, floor_to_floor_height = 3.8, plenum_height = 1.0, perimeter_zone_depth = 4.57) click to toggle source

Create a T shape in a model

@param model [OpenStudio::Model::Model] OpenStudio model object @param length [Double] Building length in meters @param width [Double] Building width in meters @param upper_end_width [Double] Upper end width in meters @param lower_end_length [Double] Lower end length in meters @param left_end_offset [Double] Left end offset in meters @param num_floors [Integer] Number of floors @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 1226
def self.create_shape_t(model,
                        length = 40.0,
                        width = 40.0,
                        upper_end_width = 20.0,
                        lower_end_length = 20.0,
                        left_end_offset = 10.0,
                        num_floors = 3,
                        floor_to_floor_height = 3.8,
                        plenum_height = 1.0,
                        perimeter_zone_depth = 4.57)
  if length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Length must be greater than 0.')
    return nil
  end

  if width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Width must be greater than 0.')
    return nil
  end

  if upper_end_width <= 1e-4 || upper_end_width >= (width - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Upper end width must be greater than 0 and less than #{width}m.")
    return nil
  end

  if lower_end_length <= 1e-4 || lower_end_length >= (length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Lower end length must be greater than 0 and less than #{length}m.")
    return nil
  end

  if left_end_offset <= 1e-4 || left_end_offset >= (length - lower_end_length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Left end offset must be greater than 0 and less than #{length - lower_end_length}m.")
    return nil
  end

  if num_floors <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Number of floors must be greater than 0.')
    return nil
  end

  if floor_to_floor_height <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Floor to floor height must be greater than 0.')
    return nil
  end

  if plenum_height < 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Plenum height must be greater than 0.')
    return nil
  end

  shortest_side = [length, width, upper_end_width, lower_end_length].min
  if perimeter_zone_depth < 0 || 2 * perimeter_zone_depth >= (shortest_side - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Perimeter zone depth must be greater than or equal to 0 and less than #{shortest_side / 2}m.")
    return nil
  end

  # Loop through the number of floors
  for floor in (0..num_floors - 1)
    z = floor_to_floor_height * floor

    # Create a new story within the building
    story = OpenStudio::Model::BuildingStory.new(model)
    story.setNominalFloortoFloorHeight(floor_to_floor_height)
    story.setName("Story #{floor + 1}")

    lower_ne_point = OpenStudio::Point3d.new(left_end_offset, width - upper_end_width, z)
    upper_sw_point = OpenStudio::Point3d.new(0, width - upper_end_width, z)
    upper_nw_point = OpenStudio::Point3d.new(0, width, z)
    upper_ne_point = OpenStudio::Point3d.new(length, width, z)
    upper_se_point = OpenStudio::Point3d.new(length, width - upper_end_width, z)
    lower_nw_point = OpenStudio::Point3d.new(left_end_offset + lower_end_length, width - upper_end_width, z)
    lower_se_point = OpenStudio::Point3d.new(left_end_offset + lower_end_length, 0, z)
    lower_sw_point = OpenStudio::Point3d.new(left_end_offset, 0, z)

    # Identity matrix for setting space origins
    m = OpenStudio::Matrix.new(4, 4, 0)
    m[0, 0] = 1
    m[1, 1] = 1
    m[2, 2] = 1
    m[3, 3] = 1

    # Define polygons for a T-shape building with perimeter core zoning
    if perimeter_zone_depth > 0
      perimeter_lower_ne_point = lower_ne_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_upper_sw_point = upper_sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_upper_nw_point = upper_nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_upper_ne_point = upper_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_upper_se_point = upper_se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_lower_nw_point = lower_nw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_lower_se_point = lower_se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_lower_sw_point = lower_sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)

      west_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      west_lower_perimeter_polygon << lower_sw_point
      west_lower_perimeter_polygon << lower_ne_point
      west_lower_perimeter_polygon << perimeter_lower_ne_point
      west_lower_perimeter_polygon << perimeter_lower_sw_point
      west_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_lower_perimeter_polygon, floor_to_floor_height, model)
      west_lower_perimeter_space = west_lower_perimeter_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      west_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_lower_perimeter_space.setBuildingStory(story)
      west_lower_perimeter_space.setName("Story #{floor + 1} West Lower Perimeter Space")

      south_upper_left_perimeter_polygon = OpenStudio::Point3dVector.new
      south_upper_left_perimeter_polygon << lower_ne_point
      south_upper_left_perimeter_polygon << upper_sw_point
      south_upper_left_perimeter_polygon << perimeter_upper_sw_point
      south_upper_left_perimeter_polygon << perimeter_lower_ne_point
      south_upper_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_upper_left_perimeter_polygon, floor_to_floor_height, model)
      south_upper_left_perimeter_space = south_upper_left_perimeter_space.get
      m[0, 3] = upper_sw_point.x
      m[1, 3] = upper_sw_point.y
      m[2, 3] = upper_sw_point.z
      south_upper_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_upper_left_perimeter_space.setBuildingStory(story)
      south_upper_left_perimeter_space.setName("Story #{floor + 1} South Upper Left Perimeter Space")

      west_upper_perimeter_polygon = OpenStudio::Point3dVector.new
      west_upper_perimeter_polygon << upper_sw_point
      west_upper_perimeter_polygon << upper_nw_point
      west_upper_perimeter_polygon << perimeter_upper_nw_point
      west_upper_perimeter_polygon << perimeter_upper_sw_point
      west_upper_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_upper_perimeter_polygon, floor_to_floor_height, model)
      west_upper_perimeter_space = west_upper_perimeter_space.get
      m[0, 3] = upper_sw_point.x
      m[1, 3] = upper_sw_point.y
      m[2, 3] = upper_sw_point.z
      west_upper_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_upper_perimeter_space.setBuildingStory(story)
      west_upper_perimeter_space.setName("Story #{floor + 1} West Upper Perimeter Space")

      north_perimeter_polygon = OpenStudio::Point3dVector.new
      north_perimeter_polygon << upper_nw_point
      north_perimeter_polygon << upper_ne_point
      north_perimeter_polygon << perimeter_upper_ne_point
      north_perimeter_polygon << perimeter_upper_nw_point
      north_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_perimeter_polygon, floor_to_floor_height, model)
      north_perimeter_space = north_perimeter_space.get
      m[0, 3] = perimeter_upper_nw_point.x
      m[1, 3] = perimeter_upper_nw_point.y
      m[2, 3] = perimeter_upper_nw_point.z
      north_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_perimeter_space.setBuildingStory(story)
      north_perimeter_space.setName("Story #{floor + 1} North Perimeter Space")

      east_upper_perimeter_polygon = OpenStudio::Point3dVector.new
      east_upper_perimeter_polygon << upper_ne_point
      east_upper_perimeter_polygon << upper_se_point
      east_upper_perimeter_polygon << perimeter_upper_se_point
      east_upper_perimeter_polygon << perimeter_upper_ne_point
      east_upper_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_upper_perimeter_polygon, floor_to_floor_height, model)
      east_upper_perimeter_space = east_upper_perimeter_space.get
      m[0, 3] = perimeter_upper_se_point.x
      m[1, 3] = perimeter_upper_se_point.y
      m[2, 3] = perimeter_upper_se_point.z
      east_upper_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_upper_perimeter_space.setBuildingStory(story)
      east_upper_perimeter_space.setName("Story #{floor + 1} East Upper Perimeter Space")

      south_upper_right_perimeter_polygon = OpenStudio::Point3dVector.new
      south_upper_right_perimeter_polygon << upper_se_point
      south_upper_right_perimeter_polygon << lower_nw_point
      south_upper_right_perimeter_polygon << perimeter_lower_nw_point
      south_upper_right_perimeter_polygon << perimeter_upper_se_point
      south_upper_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_upper_right_perimeter_polygon, floor_to_floor_height, model)
      south_upper_right_perimeter_space = south_upper_right_perimeter_space.get
      m[0, 3] = lower_nw_point.x
      m[1, 3] = lower_nw_point.y
      m[2, 3] = lower_nw_point.z
      south_upper_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_upper_right_perimeter_space.setBuildingStory(story)
      south_upper_right_perimeter_space.setName("Story #{floor + 1} South Upper Left Perimeter Space")

      east_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      east_lower_perimeter_polygon << lower_nw_point
      east_lower_perimeter_polygon << lower_se_point
      east_lower_perimeter_polygon << perimeter_lower_se_point
      east_lower_perimeter_polygon << perimeter_lower_nw_point
      east_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_lower_perimeter_polygon, floor_to_floor_height, model)
      east_lower_perimeter_space = east_lower_perimeter_space.get
      m[0, 3] = perimeter_lower_se_point.x
      m[1, 3] = perimeter_lower_se_point.y
      m[2, 3] = perimeter_lower_se_point.z
      east_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_lower_perimeter_space.setBuildingStory(story)
      east_lower_perimeter_space.setName("Story #{floor + 1} East Lower Perimeter Space")

      south_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      south_lower_perimeter_polygon << lower_se_point
      south_lower_perimeter_polygon << lower_sw_point
      south_lower_perimeter_polygon << perimeter_lower_sw_point
      south_lower_perimeter_polygon << perimeter_lower_se_point
      south_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_lower_perimeter_polygon, floor_to_floor_height, model)
      south_lower_perimeter_space = south_lower_perimeter_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      south_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_lower_perimeter_space.setBuildingStory(story)
      south_lower_perimeter_space.setName("Story #{floor + 1} South Lower Perimeter Space")

      north_core_polygon = OpenStudio::Point3dVector.new
      north_core_polygon << perimeter_upper_sw_point
      north_core_polygon << perimeter_upper_nw_point
      north_core_polygon << perimeter_upper_ne_point
      north_core_polygon << perimeter_upper_se_point
      north_core_polygon << perimeter_lower_nw_point
      north_core_polygon << perimeter_lower_ne_point
      north_core_space = OpenStudio::Model::Space.fromFloorPrint(north_core_polygon, floor_to_floor_height, model)
      north_core_space = north_core_space.get
      m[0, 3] = perimeter_upper_sw_point.x
      m[1, 3] = perimeter_upper_sw_point.y
      m[2, 3] = perimeter_upper_sw_point.z
      north_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_core_space.setBuildingStory(story)
      north_core_space.setName("Story #{floor + 1} North Core Space")

      south_core_polygon = OpenStudio::Point3dVector.new
      south_core_polygon << perimeter_lower_sw_point
      south_core_polygon << perimeter_lower_ne_point
      south_core_polygon << perimeter_lower_nw_point
      south_core_polygon << perimeter_lower_se_point
      south_core_space = OpenStudio::Model::Space.fromFloorPrint(south_core_polygon, floor_to_floor_height, model)
      south_core_space = south_core_space.get
      m[0, 3] = perimeter_lower_sw_point.x
      m[1, 3] = perimeter_lower_sw_point.y
      m[2, 3] = perimeter_lower_sw_point.z
      south_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_core_space.setBuildingStory(story)
      south_core_space.setName("Story #{floor + 1} South Core Space")
    else
      # Minimal zones
      north_polygon = OpenStudio::Point3dVector.new
      north_polygon << upper_sw_point
      north_polygon << upper_nw_point
      north_polygon << upper_ne_point
      north_polygon << upper_se_point
      north_polygon << lower_nw_point
      north_polygon << lower_ne_point
      north_space = OpenStudio::Model::Space.fromFloorPrint(north_polygon, floor_to_floor_height, model)
      north_space = north_space.get
      m[0, 3] = upper_sw_point.x
      m[1, 3] = upper_sw_point.y
      m[2, 3] = upper_sw_point.z
      north_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_space.setBuildingStory(story)
      north_space.setName("Story #{floor + 1} North Space")

      south_polygon = OpenStudio::Point3dVector.new
      south_polygon << lower_sw_point
      south_polygon << lower_ne_point
      south_polygon << lower_nw_point
      south_polygon << lower_se_point
      south_space = OpenStudio::Model::Space.fromFloorPrint(south_polygon, floor_to_floor_height, model)
      south_space = south_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      south_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_space.setBuildingStory(story)
      south_space.setName("Story #{floor + 1} South Space")
    end
    # Set vertical story position
    story.setNominalZCoordinate(z)
  end
  BTAP::Geometry.match_surfaces(model)

  return model
end
create_shape_u(model, length = 40.0, left_width = 40.0, right_width = 40.0, left_end_length = 15.0, right_end_length = 15.0, left_end_offset = 25.0, num_floors = 3.0, floor_to_floor_height = 3.8, plenum_height = 1.0, perimeter_zone_depth = 4.57) click to toggle source

Create a U shape in a model

@param model [OpenStudio::Model::Model] OpenStudio model object @param length [Double] Building length in meters @param left_width [Double] Left width in meters @param right_width [Double] Right width in meters @param left_end_length [Double] Left end length in meters @param right_end_length [Double] Right end length in meters @param left_end_offset [Double] Left end offset in meters @param num_floors [Integer] Number of floors @param floor_to_floor_height [Double] Floor to floor height in meters @param plenum_height [Double] Plenum height in meters @param perimeter_zone_depth [Double] Perimeter zone depth in meters @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/geometry/create_shape.rb, line 1513
def self.create_shape_u(model,
                        length = 40.0,
                        left_width = 40.0,
                        right_width = 40.0,
                        left_end_length = 15.0,
                        right_end_length = 15.0,
                        left_end_offset = 25.0,
                        num_floors = 3.0,
                        floor_to_floor_height = 3.8,
                        plenum_height = 1.0,
                        perimeter_zone_depth = 4.57)
  if length <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Length must be greater than 0.')
    return nil
  end

  if left_width <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Left width must be greater than 0.')
    return nil
  end

  if left_end_length <= 1e-4 || left_end_length >= (length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Left end length must be greater than 0 and less than #{length}m.")
    return nil
  end

  if right_end_length <= 1e-4 || right_end_length >= (length - left_end_length - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Right end length must be greater than 0 and less than #{length - left_end_length}m.")
    return nil
  end

  if left_end_offset <= 1e-4 || left_end_offset >= (left_width - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Left end offset must be greater than 0 and less than #{left_width}m.")
    return nil
  end

  if right_width <= (left_width - left_end_offset - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Right width must be greater than #{left_width - left_end_offset}m.")
    return nil
  end

  if num_floors <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Number of floors must be greater than 0.')
    return nil
  end

  if floor_to_floor_height <= 1e-4
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Floor to floor height must be greater than 0.')
    return nil
  end

  if plenum_height < 0
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', 'Plenum height must be greater than 0.')
    return nil
  end

  shortest_side = [length / 2, left_width, right_width, left_end_length, right_end_length, left_width - left_end_offset].min
  if perimeter_zone_depth < 0 || 2 * perimeter_zone_depth >= (shortest_side - 1e-4)
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create.Shape', "Perimeter zone depth must be greater than or equal to 0 and less than #{shortest_side / 2}m.")
    return nil
  end

  # Loop through the number of floors
  for floor in (0..num_floors - 1)
    z = floor_to_floor_height * floor

    # Create a new story within the building
    story = OpenStudio::Model::BuildingStory.new(model)
    story.setNominalFloortoFloorHeight(floor_to_floor_height)
    story.setName("Story #{floor + 1}")

    left_nw_point = OpenStudio::Point3d.new(0, left_width, z)
    left_ne_point = OpenStudio::Point3d.new(left_end_length, left_width, z)
    upper_sw_point = OpenStudio::Point3d.new(left_end_length, left_width - left_end_offset, z)
    upper_se_point = OpenStudio::Point3d.new(length - right_end_length, left_width - left_end_offset, z)
    right_nw_point = OpenStudio::Point3d.new(length - right_end_length, right_width, z)
    right_ne_point = OpenStudio::Point3d.new(length, right_width, z)
    lower_se_point = OpenStudio::Point3d.new(length, 0, z)
    lower_sw_point = OpenStudio::Point3d.new(0, 0, z)

    # Identity matrix for setting space origins
    m = OpenStudio::Matrix.new(4, 4, 0)
    m[0, 0] = 1
    m[1, 1] = 1
    m[2, 2] = 1
    m[3, 3] = 1

    # Define polygons for a U-shape building with perimeter core zoning
    if perimeter_zone_depth > 0
      perimeter_left_nw_point = left_nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_left_ne_point = left_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_upper_sw_point = upper_sw_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_upper_se_point = upper_se_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_right_nw_point = right_nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_right_ne_point = right_ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
      perimeter_lower_se_point = lower_se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
      perimeter_lower_sw_point = lower_sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)

      west_left_perimeter_polygon = OpenStudio::Point3dVector.new
      west_left_perimeter_polygon << lower_sw_point
      west_left_perimeter_polygon << left_nw_point
      west_left_perimeter_polygon << perimeter_left_nw_point
      west_left_perimeter_polygon << perimeter_lower_sw_point
      west_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_left_perimeter_polygon, floor_to_floor_height, model)
      west_left_perimeter_space = west_left_perimeter_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      west_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_left_perimeter_space.setBuildingStory(story)
      west_left_perimeter_space.setName("Story #{floor + 1} West Left Perimeter Space")

      north_left_perimeter_polygon = OpenStudio::Point3dVector.new
      north_left_perimeter_polygon << left_nw_point
      north_left_perimeter_polygon << left_ne_point
      north_left_perimeter_polygon << perimeter_left_ne_point
      north_left_perimeter_polygon << perimeter_left_nw_point
      north_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_left_perimeter_polygon, floor_to_floor_height, model)
      north_left_perimeter_space = north_left_perimeter_space.get
      m[0, 3] = perimeter_left_nw_point.x
      m[1, 3] = perimeter_left_nw_point.y
      m[2, 3] = perimeter_left_nw_point.z
      north_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_left_perimeter_space.setBuildingStory(story)
      north_left_perimeter_space.setName("Story #{floor + 1} North Left Perimeter Space")

      east_left_perimeter_polygon = OpenStudio::Point3dVector.new
      east_left_perimeter_polygon << left_ne_point
      east_left_perimeter_polygon << upper_sw_point
      east_left_perimeter_polygon << perimeter_upper_sw_point
      east_left_perimeter_polygon << perimeter_left_ne_point
      east_left_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_left_perimeter_polygon, floor_to_floor_height, model)
      east_left_perimeter_space = east_left_perimeter_space.get
      m[0, 3] = perimeter_upper_sw_point.x
      m[1, 3] = perimeter_upper_sw_point.y
      m[2, 3] = perimeter_upper_sw_point.z
      east_left_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_left_perimeter_space.setBuildingStory(story)
      east_left_perimeter_space.setName("Story #{floor + 1} East Left Perimeter Space")

      north_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      north_lower_perimeter_polygon << upper_sw_point
      north_lower_perimeter_polygon << upper_se_point
      north_lower_perimeter_polygon << perimeter_upper_se_point
      north_lower_perimeter_polygon << perimeter_upper_sw_point
      north_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_lower_perimeter_polygon, floor_to_floor_height, model)
      north_lower_perimeter_space = north_lower_perimeter_space.get
      m[0, 3] = perimeter_upper_sw_point.x
      m[1, 3] = perimeter_upper_sw_point.y
      m[2, 3] = perimeter_upper_sw_point.z
      north_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_lower_perimeter_space.setBuildingStory(story)
      north_lower_perimeter_space.setName("Story #{floor + 1} North Lower Perimeter Space")

      west_right_perimeter_polygon = OpenStudio::Point3dVector.new
      west_right_perimeter_polygon << upper_se_point
      west_right_perimeter_polygon << right_nw_point
      west_right_perimeter_polygon << perimeter_right_nw_point
      west_right_perimeter_polygon << perimeter_upper_se_point
      west_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(west_right_perimeter_polygon, floor_to_floor_height, model)
      west_right_perimeter_space = west_right_perimeter_space.get
      m[0, 3] = upper_se_point.x
      m[1, 3] = upper_se_point.y
      m[2, 3] = upper_se_point.z
      west_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_right_perimeter_space.setBuildingStory(story)
      west_right_perimeter_space.setName("Story #{floor + 1} West Right Perimeter Space")

      north_right_perimeter_polygon = OpenStudio::Point3dVector.new
      north_right_perimeter_polygon << right_nw_point
      north_right_perimeter_polygon << right_ne_point
      north_right_perimeter_polygon << perimeter_right_ne_point
      north_right_perimeter_polygon << perimeter_right_nw_point
      north_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(north_right_perimeter_polygon, floor_to_floor_height, model)
      north_right_perimeter_space = north_right_perimeter_space.get
      m[0, 3] = perimeter_right_nw_point.x
      m[1, 3] = perimeter_right_nw_point.y
      m[2, 3] = perimeter_right_nw_point.z
      north_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      north_right_perimeter_space.setBuildingStory(story)
      north_right_perimeter_space.setName("Story #{floor + 1} North Right Perimeter Space")

      east_right_perimeter_polygon = OpenStudio::Point3dVector.new
      east_right_perimeter_polygon << right_ne_point
      east_right_perimeter_polygon << lower_se_point
      east_right_perimeter_polygon << perimeter_lower_se_point
      east_right_perimeter_polygon << perimeter_right_ne_point
      east_right_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(east_right_perimeter_polygon, floor_to_floor_height, model)
      east_right_perimeter_space = east_right_perimeter_space.get
      m[0, 3] = perimeter_lower_se_point.x
      m[1, 3] = perimeter_lower_se_point.y
      m[2, 3] = perimeter_lower_se_point.z
      east_right_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_right_perimeter_space.setBuildingStory(story)
      east_right_perimeter_space.setName("Story #{floor + 1} East Right Perimeter Space")

      south_lower_perimeter_polygon = OpenStudio::Point3dVector.new
      south_lower_perimeter_polygon << lower_se_point
      south_lower_perimeter_polygon << lower_sw_point
      south_lower_perimeter_polygon << perimeter_lower_sw_point
      south_lower_perimeter_polygon << perimeter_lower_se_point
      south_lower_perimeter_space = OpenStudio::Model::Space.fromFloorPrint(south_lower_perimeter_polygon, floor_to_floor_height, model)
      south_lower_perimeter_space = south_lower_perimeter_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      south_lower_perimeter_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_lower_perimeter_space.setBuildingStory(story)
      south_lower_perimeter_space.setName("Story #{floor + 1} South Lower Perimeter Space")

      west_core_polygon = OpenStudio::Point3dVector.new
      west_core_polygon << perimeter_lower_sw_point
      west_core_polygon << perimeter_left_nw_point
      west_core_polygon << perimeter_left_ne_point
      west_core_polygon << perimeter_upper_sw_point
      west_core_space = OpenStudio::Model::Space.fromFloorPrint(west_core_polygon, floor_to_floor_height, model)
      west_core_space = west_core_space.get
      m[0, 3] = perimeter_lower_sw_point.x
      m[1, 3] = perimeter_lower_sw_point.y
      m[2, 3] = perimeter_lower_sw_point.z
      west_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_core_space.setBuildingStory(story)
      west_core_space.setName("Story #{floor + 1} West Core Space")

      south_core_polygon = OpenStudio::Point3dVector.new
      south_core_polygon << perimeter_upper_sw_point
      south_core_polygon << perimeter_upper_se_point
      south_core_polygon << perimeter_lower_se_point
      south_core_polygon << perimeter_lower_sw_point
      south_core_space = OpenStudio::Model::Space.fromFloorPrint(south_core_polygon, floor_to_floor_height, model)
      south_core_space = south_core_space.get
      m[0, 3] = perimeter_lower_sw_point.x
      m[1, 3] = perimeter_lower_sw_point.y
      m[2, 3] = perimeter_lower_sw_point.z
      south_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_core_space.setBuildingStory(story)
      south_core_space.setName("Story #{floor + 1} South Core Space")

      east_core_polygon = OpenStudio::Point3dVector.new
      east_core_polygon << perimeter_upper_se_point
      east_core_polygon << perimeter_right_nw_point
      east_core_polygon << perimeter_right_ne_point
      east_core_polygon << perimeter_lower_se_point
      east_core_space = OpenStudio::Model::Space.fromFloorPrint(east_core_polygon, floor_to_floor_height, model)
      east_core_space = east_core_space.get
      m[0, 3] = perimeter_upper_se_point.x
      m[1, 3] = perimeter_upper_se_point.y
      m[2, 3] = perimeter_upper_se_point.z
      east_core_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_core_space.setBuildingStory(story)
      east_core_space.setName("Story #{floor + 1} East Core Space")
    else
      # Minimal zones
      west_polygon = OpenStudio::Point3dVector.new
      west_polygon << lower_sw_point
      west_polygon << left_nw_point
      west_polygon << left_ne_point
      west_polygon << upper_sw_point
      west_space = OpenStudio::Model::Space.fromFloorPrint(west_polygon, floor_to_floor_height, model)
      west_space = west_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      west_space.changeTransformation(OpenStudio::Transformation.new(m))
      west_space.setBuildingStory(story)
      west_space.setName("Story #{floor + 1} West Space")

      south_polygon = OpenStudio::Point3dVector.new
      south_polygon << lower_sw_point
      south_polygon << upper_sw_point
      south_polygon << upper_se_point
      south_polygon << lower_se_point
      south_space = OpenStudio::Model::Space.fromFloorPrint(south_polygon, floor_to_floor_height, model)
      south_space = south_space.get
      m[0, 3] = lower_sw_point.x
      m[1, 3] = lower_sw_point.y
      m[2, 3] = lower_sw_point.z
      south_space.changeTransformation(OpenStudio::Transformation.new(m))
      south_space.setBuildingStory(story)
      south_space.setName("Story #{floor + 1} South Space")

      east_polygon = OpenStudio::Point3dVector.new
      east_polygon << upper_se_point
      east_polygon << right_nw_point
      east_polygon << right_ne_point
      east_polygon << lower_se_point
      east_space = OpenStudio::Model::Space.fromFloorPrint(east_polygon, floor_to_floor_height, model)
      east_space = east_space.get
      m[0, 3] = upper_se_point.x
      m[1, 3] = upper_se_point.y
      m[2, 3] = upper_se_point.z
      east_space.changeTransformation(OpenStudio::Transformation.new(m))
      east_space.setBuildingStory(story)
      east_space.setName("Story #{floor + 1} East Space")
    end
    # Set vertical story position
    story.setNominalZCoordinate(z)
  end
  BTAP::Geometry.match_surfaces(model)

  return model
end
create_sliced_bar_multi_polygons(space_types, length, width, footprint_origin_point, story_hash) click to toggle source

sliced bar multi creates and array of multiple sliced bar simple hashes

@param space_types [Array<Hash>] Array of hashes with the space type and floor area @param length [Double] length of building in meters @param width [Double] width of building in meters @param footprint_origin_point [OpenStudio::Point3d] OpenStudio Point3d object for the new origin @param story_hash [Hash] A hash of building story information including space origin z value and space height @return [Hash] Hash of point vectors that define the space geometry for each direction

# File lib/openstudio-standards/geometry/create.rb, line 176
def self.create_sliced_bar_multi_polygons(space_types, length, width, footprint_origin_point, story_hash)
  # total building floor area to calculate ratios from space type floor areas
  total_floor_area = 0.0
  target_per_space_type = {}
  space_types.each do |space_type, space_type_hash|
    total_floor_area += space_type_hash[:floor_area]
    target_per_space_type[space_type] = space_type_hash[:floor_area]
  end

  # sort array by floor area, this hash will be altered to reduce floor area for each space type to 0
  space_types_running_count = space_types.sort_by { |k, v| v[:floor_area] }

  # array entry for each story
  footprints = []

  # variables for sliver check
  # re-evaluate what the default should be
  valid_bar_width_min_m = OpenStudio.convert(3.0, 'ft', 'm').get
  # building width
  bar_length = width
  valid_bar_area_min_m2 = valid_bar_width_min_m * bar_length

  # loop through stories to populate footprints
  story_hash.each_with_index do |(k, v), i|
    # update the length and width for partial floors
    if i + 1 == story_hash.size
      area_multiplier = v[:partial_story_multiplier]
      edge_multiplier = Math.sqrt(area_multiplier)
      length *= edge_multiplier
      width *= edge_multiplier
    end

    # this will be populated for each building story
    target_footprint_area = v[:multiplier] * length * width
    current_footprint_area = 0.0
    space_types_local_count = {}

    space_types_running_count.each do |space_type, space_type_hash|
      # next if floor area is full or space type is empty

      tol_value = 0.0001
      next if current_footprint_area + tol_value >= target_footprint_area
      next if space_type_hash[:floor_area] <= tol_value

      # special test for when total floor area is smaller than valid_bar_area_min_m2, just make bar smaller that valid min and warn user
      if target_per_space_type[space_type] < valid_bar_area_min_m2
        sliver_override = true
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Floor area of #{space_type.name} results in a bar with smaller than target minimum width.")
      else
        sliver_override = false
      end

      # add entry for space type if it doesn't have one yet
      if !space_types_local_count.key?(space_type)
        if space_type_hash.key?(:children)
          space_type = space_type_hash[:children][:default][:space_type] # will re-using space type create issue
          space_types_local_count[space_type] = { floor_area: 0.0 }
          space_types_local_count[space_type][:children] = space_type_hash[:children]
        else
          space_types_local_count[space_type] = { floor_area: 0.0 }
        end
      end

      # if there is enough of this space type to fill rest of floor area
      remaining_in_footprint = target_footprint_area - current_footprint_area
      raw_footprint_area_used = [space_type_hash[:floor_area], remaining_in_footprint].min

      # add to local hash
      space_types_local_count[space_type][:floor_area] = raw_footprint_area_used / v[:multiplier].to_f

      # adjust balance ot running and local counts
      current_footprint_area += raw_footprint_area_used
      space_type_hash[:floor_area] -= raw_footprint_area_used

      # test if think sliver left on current floor.
      # fix by moving smallest space type to next floor and and the same amount more of the sliver space type to this story
      raw_footprint_area_used < valid_bar_area_min_m2 && sliver_override == false ? (test_a = true) : (test_a = false)

      # test if what would be left of the current space type would result in a sliver on the next story.
      # fix by removing some of this space type so their is enough left for the next story, and replace the removed amount with the largest space type in the model
      (space_type_hash[:floor_area] < valid_bar_area_min_m2) && (space_type_hash[:floor_area] > tol_value) ? (test_b = true) : (test_b = false)

      # identify very small slices and re-arrange spaces to different stories to avoid this
      if test_a

        # get first/smallest space type to move to another story
        first_space = space_types_local_count.first

        # adjustments running counter for space type being removed from this story
        space_types_running_count.each do |k2, v2|
          next if k2 != first_space[0]

          v2[:floor_area] += first_space[1][:floor_area] * v[:multiplier]
        end

        # adjust running count for current space type
        space_type_hash[:floor_area] -= first_space[1][:floor_area] * v[:multiplier]

        # add to local count for current space type
        space_types_local_count[space_type][:floor_area] += first_space[1][:floor_area]

        # remove from local count for removed space type
        space_types_local_count.shift

      elsif test_b

        # swap size
        swap_size = valid_bar_area_min_m2 * 5.0 # currently equal to default perimeter zone depth of 15'
        # this prevents too much area from being swapped resulting in a negative number for floor area
        if swap_size > space_types_local_count[space_type][:floor_area] * v[:multiplier].to_f
          swap_size = space_types_local_count[space_type][:floor_area] * v[:multiplier].to_f
        end

        # adjust running count for current space type
        space_type_hash[:floor_area] += swap_size

        # remove from local count for current space type
        space_types_local_count[space_type][:floor_area] -= swap_size / v[:multiplier].to_f

        # adjust footprint used
        current_footprint_area -= swap_size

        # the next larger space type will be brought down to fill out the footprint without any additional code
      end
    end

    # creating footprint for story
    footprints << OpenstudioStandards::Geometry.create_sliced_bar_simple_polygons(space_types_local_count, length, width, footprint_origin_point)
  end
  return footprints
end
create_sliced_bar_simple_polygons(space_types, length, width, footprint_origin_point = OpenStudio::Point3d.new(0.0, 0.0, 0.0), perimeter_zone_depth = OpenStudio.convert(15.0, 'ft', 'm').get) click to toggle source

sliced bar simple creates a single sliced bar for space types passed in look at length and width to adjust slicing direction

@param space_types [Array<Hash>] Array of hashes with the space type and floor area @param length [Double] length of building in meters @param width [Double] width of building in meters @param footprint_origin_point [OpenStudio::Point3d] Optional OpenStudio Point3d object for the new origin @param perimeter_zone_depth [Double] Optional perimeter zone depth in meters @return [Hash] Hash of point vectors that define the space geometry for each direction

# File lib/openstudio-standards/geometry/create.rb, line 317
def self.create_sliced_bar_simple_polygons(space_types, length, width,
                                           footprint_origin_point = OpenStudio::Point3d.new(0.0, 0.0, 0.0),
                                           perimeter_zone_depth = OpenStudio.convert(15.0, 'ft', 'm').get)
  hash_of_point_vectors = {} # key is name, value is a hash, one item of which is polygon. Another could be space type

  reverse_slice = false
  if length < width
    reverse_slice = true
    # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Reverse typical slice direction for bar because of aspect ratio less than 1.0.")
  end

  # determine if core and perimeter zoning can be used
  if !([length, width].min > perimeter_zone_depth * 2.5 && [length, width].min > perimeter_zone_depth * 2.5)
    perimeter_zone_depth = 0 # if any size is to small then just model floor as single zone, issue warning
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Not modeling core and perimeter zones for some portion of the model.')
  end

  x_delta = footprint_origin_point.x - (length / 2.0)
  y_delta = footprint_origin_point.y - (width / 2.0)
  z = 0.0
  # this represents the entire bar, not individual space type slices
  nw_point = OpenStudio::Point3d.new(x_delta, y_delta + width, z)
  sw_point = OpenStudio::Point3d.new(x_delta, y_delta, z)
  # used when length is less than width
  se_point = OpenStudio::Point3d.new(x_delta + length, y_delta, z)

  # total building floor area to calculate ratios from space type floor areas
  total_floor_area = 0.0
  space_types.each do |space_type, space_type_hash|
    total_floor_area += space_type_hash[:floor_area]
  end

  # sort array by floor area but shift largest object to front
  space_types = space_types.sort_by { |k, v| v[:floor_area] }
  space_types.insert(0, space_types.delete_at(space_types.size - 1)) # .to_h

  # min and max bar end values
  min_bar_end_multiplier = 0.75
  max_bar_end_multiplier = 1.5

  # sort_by results in arrays with two items , first is key, second is hash value
  re_apply_largest_space_type_at_end = false
  max_reduction = nil # used when looping through section_hash_for_space_type if first space type needs to also be at far end of bar
  space_types.each do |space_type, space_type_hash|
    # setup end perimeter zones if needed
    start_perimeter_width_deduction = 0.0
    end_perimeter_width_deduction = 0.0
    if space_type == space_types.first[0]
      if [length, width].max * space_type_hash[:floor_area] / total_floor_area > max_bar_end_multiplier * perimeter_zone_depth
        start_perimeter_width_deduction = perimeter_zone_depth
      end
      # see if last space type is too small for perimeter. If it is then save some of this space type
      if [length, width].max * space_types.last[1][:floor_area] / total_floor_area < perimeter_zone_depth * min_bar_end_multiplier
        re_apply_largest_space_type_at_end = true
      end
    end
    if space_type == space_types.last[0]
      if [length, width].max * space_type_hash[:floor_area] / total_floor_area > max_bar_end_multiplier * perimeter_zone_depth
        end_perimeter_width_deduction = perimeter_zone_depth
      end
    end
    non_end_adjusted_width = ([length, width].max * space_type_hash[:floor_area] / total_floor_area) - start_perimeter_width_deduction - end_perimeter_width_deduction

    # adjustment of end space type is too small and is replaced with largest space type
    if (space_type == space_types.first[0]) && re_apply_largest_space_type_at_end
      max_reduction = [perimeter_zone_depth, non_end_adjusted_width].min
      non_end_adjusted_width -= max_reduction
    end
    if (space_type == space_types.last[0]) && re_apply_largest_space_type_at_end
      end_perimeter_width_deduction = space_types.first[0]
      end_b_flag = true
    else
      end_b_flag = false
    end

    # populate data for core and perimeter of slice
    section_hash_for_space_type = {}
    section_hash_for_space_type['end_a'] = start_perimeter_width_deduction
    section_hash_for_space_type[''] = non_end_adjusted_width
    section_hash_for_space_type['end_b'] = end_perimeter_width_deduction

    # determine if this space+type is double loaded corridor, and if so what the perimeter zone depth should be based on building width
    # look at reverse_slice to see if length or width should be used to determine perimeter depth
    if space_type_hash.key?(:children)
      core_ratio = space_type_hash[:children][:circ][:orig_ratio]
      perim_ratio = space_type_hash[:children][:default][:orig_ratio]
      core_ratio_adj = core_ratio / (core_ratio + perim_ratio)
      perim_ratio_adj = perim_ratio / (core_ratio + perim_ratio)
      core_space_type = space_type_hash[:children][:circ][:space_type]
      perim_space_type = space_type_hash[:children][:default][:space_type]
      if reverse_slice
        custom_cor_val = length * core_ratio_adj
        custom_perim_val = (length - custom_cor_val) / 2.0
      else
        custom_cor_val = width * core_ratio_adj
        custom_perim_val = (width - custom_cor_val) / 2.0
      end
      # use perimeter zone depth if the custom perimeter value is within 1 milimeter
      if (custom_perim_val - perimeter_zone_depth).abs < 0.001
        actual_perim = perimeter_zone_depth
      else
        actual_perim = custom_perim_val
      end

      double_loaded_corridor = true
    else
      actual_perim = perimeter_zone_depth
      double_loaded_corridor = false
    end

    # may overwrite
    first_space_type_hash = space_types.first[1]
    if end_b_flag && first_space_type_hash.key?(:children)
      end_b_core_ratio = first_space_type_hash[:children][:circ][:orig_ratio]
      end_b_perim_ratio = first_space_type_hash[:children][:default][:orig_ratio]
      end_b_core_ratio_adj = end_b_core_ratio / (end_b_core_ratio + end_b_perim_ratio)
      end_b_perim_ratio_adj = end_b_perim_ratio / (end_b_core_ratio + end_b_perim_ratio)
      end_b_core_space_type = first_space_type_hash[:children][:circ][:space_type]
      end_b_perim_space_type = first_space_type_hash[:children][:default][:space_type]
      if reverse_slice
        end_b_custom_cor_val = length * end_b_core_ratio_adj
        end_b_custom_perim_val = (length - end_b_custom_cor_val) / 2.0
      else
        end_b_custom_cor_val = width * end_b_core_ratio_adj
        end_b_custom_perim_val = (width - end_b_custom_cor_val) / 2.0
      end
      end_b_actual_perim = end_b_custom_perim_val
      end_b_double_loaded_corridor = true
    else
      end_b_actual_perim = perimeter_zone_depth
      end_b_double_loaded_corridor = false
    end

    # loop through sections for space type (main and possibly one or two end perimeter sections)
    section_hash_for_space_type.each do |k, slice|
      # need to use different space type for end_b
      if end_b_flag && k == 'end_b' && space_types.first[1].key?(:children)
        slice = space_types.first[0]
        actual_perim = end_b_actual_perim
        double_loaded_corridor = end_b_double_loaded_corridor
        core_ratio = end_b_core_ratio
        perim_ratio = end_b_perim_ratio
        core_ratio_adj = end_b_core_ratio_adj
        perim_ratio_adj = end_b_perim_ratio_adj
        core_space_type = end_b_core_space_type
        perim_space_type = end_b_perim_space_type
      end

      if slice.class.to_s == 'OpenStudio::Model::SpaceType' || slice.class.to_s == 'OpenStudio::Model::Building'
        space_type = slice
        max_reduction = [perimeter_zone_depth, max_reduction].min
        slice = max_reduction
      end
      if slice == 0
        next
      end

      if reverse_slice
        # create_bar at 90 degrees if aspect ration is less than 1.0
        # typical order (sw,nw,ne,se)
        # order used here (se,sw,nw,ne)
        nw_point = (sw_point + OpenStudio::Vector3d.new(0, slice, 0))
        ne_point = (se_point + OpenStudio::Vector3d.new(0, slice, 0))

        if actual_perim > 0 && (actual_perim * 2.0) < length
          polygon_a = OpenStudio::Point3dVector.new
          polygon_a << se_point
          polygon_a << (se_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0))
          polygon_a << (ne_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0))
          polygon_a << ne_point
          if double_loaded_corridor
            hash_of_point_vectors["#{perim_space_type.name} A #{k}"] = {}
            hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:space_type] = perim_space_type
            hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:polygon] = polygon_a
          else
            hash_of_point_vectors["#{space_type.name} A #{k}"] = {}
            hash_of_point_vectors["#{space_type.name} A #{k}"][:space_type] = space_type
            hash_of_point_vectors["#{space_type.name} A #{k}"][:polygon] = polygon_a
          end

          polygon_b = OpenStudio::Point3dVector.new
          polygon_b << (se_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0))
          polygon_b << (sw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0))
          polygon_b << (nw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0))
          polygon_b << (ne_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0))
          if double_loaded_corridor
            hash_of_point_vectors["#{core_space_type.name} B #{k}"] = {}
            hash_of_point_vectors["#{core_space_type.name} B #{k}"][:space_type] = core_space_type
            hash_of_point_vectors["#{core_space_type.name} B #{k}"][:polygon] = polygon_b
          else
            hash_of_point_vectors["#{space_type.name} B #{k}"] = {}
            hash_of_point_vectors["#{space_type.name} B #{k}"][:space_type] = space_type
            hash_of_point_vectors["#{space_type.name} B #{k}"][:polygon] = polygon_b
          end

          polygon_c = OpenStudio::Point3dVector.new
          polygon_c << (sw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0))
          polygon_c << sw_point
          polygon_c << nw_point
          polygon_c << (nw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0))
          if double_loaded_corridor
            hash_of_point_vectors["#{perim_space_type.name} C #{k}"] = {}
            hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:space_type] = perim_space_type
            hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:polygon] = polygon_c
          else
            hash_of_point_vectors["#{space_type.name} C #{k}"] = {}
            hash_of_point_vectors["#{space_type.name} C #{k}"][:space_type] = space_type
            hash_of_point_vectors["#{space_type.name} C #{k}"][:polygon] = polygon_c
          end
        else
          polygon_a = OpenStudio::Point3dVector.new
          polygon_a << se_point
          polygon_a << sw_point
          polygon_a << nw_point
          polygon_a << ne_point
          hash_of_point_vectors["#{space_type.name} #{k}"] = {}
          hash_of_point_vectors["#{space_type.name} #{k}"][:space_type] = space_type
          hash_of_point_vectors["#{space_type.name} #{k}"][:polygon] = polygon_a
        end

        # update west points
        sw_point = nw_point
        se_point = ne_point
      else
        ne_point = nw_point + OpenStudio::Vector3d.new(slice, 0, 0)
        se_point = sw_point + OpenStudio::Vector3d.new(slice, 0, 0)

        if actual_perim > 0 && (actual_perim * 2.0) < width
          polygon_a = OpenStudio::Point3dVector.new
          polygon_a << sw_point
          polygon_a << (sw_point + OpenStudio::Vector3d.new(0, actual_perim, 0))
          polygon_a << (se_point + OpenStudio::Vector3d.new(0, actual_perim, 0))
          polygon_a << se_point
          if double_loaded_corridor
            hash_of_point_vectors["#{perim_space_type.name} A #{k}"] = {}
            hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:space_type] = perim_space_type
            hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:polygon] = polygon_a
          else
            hash_of_point_vectors["#{space_type.name} A #{k}"] = {}
            hash_of_point_vectors["#{space_type.name} A #{k}"][:space_type] = space_type
            hash_of_point_vectors["#{space_type.name} A #{k}"][:polygon] = polygon_a
          end

          polygon_b = OpenStudio::Point3dVector.new
          polygon_b << (sw_point + OpenStudio::Vector3d.new(0, actual_perim, 0))
          polygon_b << (nw_point + OpenStudio::Vector3d.new(0, - actual_perim, 0))
          polygon_b << (ne_point + OpenStudio::Vector3d.new(0, - actual_perim, 0))
          polygon_b << (se_point + OpenStudio::Vector3d.new(0, actual_perim, 0))
          if double_loaded_corridor
            hash_of_point_vectors["#{core_space_type.name} B #{k}"] = {}
            hash_of_point_vectors["#{core_space_type.name} B #{k}"][:space_type] = core_space_type
            hash_of_point_vectors["#{core_space_type.name} B #{k}"][:polygon] = polygon_b
          else
            hash_of_point_vectors["#{space_type.name} B #{k}"] = {}
            hash_of_point_vectors["#{space_type.name} B #{k}"][:space_type] = space_type
            hash_of_point_vectors["#{space_type.name} B #{k}"][:polygon] = polygon_b
          end

          polygon_c = OpenStudio::Point3dVector.new
          polygon_c << (nw_point + OpenStudio::Vector3d.new(0, - actual_perim, 0))
          polygon_c << nw_point
          polygon_c << ne_point
          polygon_c << (ne_point + OpenStudio::Vector3d.new(0, - actual_perim, 0))
          if double_loaded_corridor
            hash_of_point_vectors["#{perim_space_type.name} C #{k}"] = {}
            hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:space_type] = perim_space_type
            hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:polygon] = polygon_c
          else
            hash_of_point_vectors["#{space_type.name} C #{k}"] = {}
            hash_of_point_vectors["#{space_type.name} C #{k}"][:space_type] = space_type
            hash_of_point_vectors["#{space_type.name} C #{k}"][:polygon] = polygon_c
          end
        else
          polygon_a = OpenStudio::Point3dVector.new
          polygon_a << sw_point
          polygon_a << nw_point
          polygon_a << ne_point
          polygon_a << se_point
          hash_of_point_vectors["#{space_type.name} #{k}"] = {}
          hash_of_point_vectors["#{space_type.name} #{k}"][:space_type] = space_type
          hash_of_point_vectors["#{space_type.name} #{k}"][:polygon] = polygon_a
        end

        # update west points
        nw_point = ne_point
        sw_point = se_point
      end
    end
  end

  return hash_of_point_vectors
end
create_space_from_polygon(model, space_origin, point_3d_vector, options = {}) click to toggle source

add def to create a space from input, optionally take a name, space type, story and thermal zone.

@param model [OpenStudio::Model::Model] OpenStudio model object describing the space footprint polygon @param space_origin [OpenStudio::Point3d] origin point @param point_3d_vector [OpenStudio::Point3dVector] OpenStudio Point3dVector defining the space footprint @param options [Hash] Hash of options for additional arguments @option options [String] :name name of the space @option options [OpenStudio::Model::SpaceType] :space_type OpenStudio SpaceType object @option options [String] :story name name of the building story @option options [Boolean] :make_thermal_zone set to true to make an thermal zone object, defaults to true. @option options [OpenStudio::Model::ThermalZone] :thermal_zone attach a specific ThermalZone object to the space @option options [Integer] :thermal_zone_multiplier the thermal zone multiplier, defaults to 1. @option options [Double] :floor_to_floor_height floor to floor height in meters, defaults to 10 ft. @return [OpenStudio::Model::Space] OpenStudio Space object

# File lib/openstudio-standards/geometry/create.rb, line 752
def self.create_space_from_polygon(model, space_origin, point_3d_vector, options = {})
  # set defaults to use if user inputs not passed in
  defaults = {
    'name' => nil,
    'space_type' => nil,
    'story' => nil,
    'make_thermal_zone' => nil,
    'thermal_zone' => nil,
    'thermal_zone_multiplier' => 1,
    'floor_to_floor_height' => OpenStudio.convert(10.0, 'ft', 'm').get
  }

  # merge user inputs with defaults
  options = defaults.merge(options)

  # Identity matrix for setting space origins
  m = OpenStudio::Matrix.new(4, 4, 0)
  m[0, 0] = 1
  m[1, 1] = 1
  m[2, 2] = 1
  m[3, 3] = 1

  # make space from floor print
  space = OpenStudio::Model::Space.fromFloorPrint(point_3d_vector, options['floor_to_floor_height'], model)
  space = space.get
  m[0, 3] = space_origin.x
  m[1, 3] = space_origin.y
  m[2, 3] = space_origin.z
  space.changeTransformation(OpenStudio::Transformation.new(m))
  space.setBuildingStory(options['story'])
  if !options['name'].nil?
    space.setName(options['name'])
  end

  if !options['space_type'].nil? && options['space_type'].class.to_s == 'OpenStudio::Model::SpaceType'
    space.setSpaceType(options['space_type'])
  end

  # create thermal zone if requested and assign
  if options['make_thermal_zone']
    new_zone = OpenStudio::Model::ThermalZone.new(model)
    new_zone.setMultiplier(options['thermal_zone_multiplier'])
    space.setThermalZone(new_zone)
    new_zone.setName("Zone #{space.name}")
  else
    if !options['thermal_zone'].nil? then space.setThermalZone(options['thermal_zone']) end
  end

  return space
end
create_spaces_from_polygons(model, footprints, typical_story_height, effective_num_stories, footprint_origin_point = OpenStudio::Point3d.new(0.0, 0.0, 0.0), story_hash = {}) click to toggle source

take diagram made by create_core_and_perimeter_polygons and make multi-story building @todo add option to create shading surfaces when using multiplier. Mainly important for non rectangular buildings where self shading would be an issue.

@param model [OpenStudio::Model::Model] OpenStudio model object @param footprints [Hash] Array of footprint polygons that make up the spaces @param typical_story_height [Double] typical story height in meters @param effective_num_stories [Double] effective number of stories @param footprint_origin_point [OpenStudio::Point3d] Optional OpenStudio Point3d object for the new origin @param story_hash [Hash] A hash of building story information including space origin z value and space height

If blank, this method will default to using information in the story_hash.

@return [Array<OpenStudio::Model::Space>] Array of OpenStudio Space objects

# File lib/openstudio-standards/geometry/create.rb, line 621
def self.create_spaces_from_polygons(model, footprints, typical_story_height, effective_num_stories,
                                     footprint_origin_point = OpenStudio::Point3d.new(0.0, 0.0, 0.0),
                                     story_hash = {})
  # default story hash is for three stories with mid-story multiplier, but user can pass in custom versions
  if story_hash.empty?
    if effective_num_stories > 2
      story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
      story_hash['mid'] = { space_origin_z: footprint_origin_point.z + typical_story_height + (typical_story_height * (effective_num_stories.ceil - 3) / 2.0), space_height: typical_story_height, multiplier: effective_num_stories - 2 }
      story_hash['top'] = { space_origin_z: footprint_origin_point.z + (typical_story_height * (effective_num_stories.ceil - 1)), space_height: typical_story_height, multiplier: 1 }
    elsif effective_num_stories > 1
      story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
      story_hash['top'] = { space_origin_z: footprint_origin_point.z + (typical_story_height * (effective_num_stories.ceil - 1)), space_height: typical_story_height, multiplier: 1 }
    else
      # one story only
      story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
    end
  end

  # hash of new spaces (only change boundary conditions for these)
  new_spaces = []

  # loop through story_hash and polygons to generate all of the spaces
  story_hash.each_with_index do |(story_name, story_data), index|
    # make new story unless story at requested height already exists.
    story = nil
    model.getBuildingStorys.sort.each do |ext_story|
      if (ext_story.nominalZCoordinate.to_f - story_data[:space_origin_z].to_f).abs < 0.01
        story = ext_story
      end
    end
    if story.nil?
      story = OpenStudio::Model::BuildingStory.new(model)
      # not used for anything
      story.setNominalFloortoFloorHeight(story_data[:space_height])
      # not used for anything
      story.setNominalZCoordinate(story_data[:space_origin_z])
      story.setName("Story #{story_name}")
    end

    # multiplier values for adjacent stories to be altered below as needed
    multiplier_story_above = 1
    multiplier_story_below = 1

    if index == 0 # bottom floor, only check above
      if story_hash.size > 1
        multiplier_story_above = story_hash.values[index + 1][:multiplier]
      end
    elsif index == story_hash.size - 1 # top floor, check only below
      multiplier_story_below = story_hash.values[index + -1][:multiplier]
    else # mid floor, check above and below
      multiplier_story_above = story_hash.values[index + 1][:multiplier]
      multiplier_story_below = story_hash.values[index + -1][:multiplier]
    end

    # if adjacent story has multiplier > 1 then make appropriate surfaces adiabatic
    adiabatic_ceilings = false
    adiabatic_floors = false
    if story_data[:multiplier] > 1
      adiabatic_ceilings = true
      adiabatic_floors = true
    elsif multiplier_story_above > 1
      adiabatic_ceilings = true
    elsif multiplier_story_below > 1
      adiabatic_floors = true
    end

    # get the right collection of polygons to make up footprint for each building story
    if index > footprints.size - 1
      # use last footprint
      target_footprint = footprints.last
    else
      target_footprint = footprints[index]
    end
    target_footprint.each do |name, space_data|
      # gather options
      options = {
        'name' => "#{name} - #{story.name}",
        'space_type' => space_data[:space_type],
        'story' => story,
        'make_thermal_zone' => true,
        'thermal_zone_multiplier' => story_data[:multiplier],
        'floor_to_floor_height' => story_data[:space_height]
      }

      # make space
      space = OpenstudioStandards::Geometry.create_space_from_polygon(model, space_data[:polygon].first, space_data[:polygon], options)
      new_spaces << space

      # set z origin to proper position
      space.setZOrigin(story_data[:space_origin_z])

      # loop through celings and floors to hard asssign constructions and set boundary condition
      if adiabatic_ceilings || adiabatic_floors
        space.surfaces.each do |surface|
          if adiabatic_floors && (surface.surfaceType == 'Floor')
            if surface.construction.is_initialized
              surface.setConstruction(surface.construction.get)
            end
            surface.setOutsideBoundaryCondition('Adiabatic')
          end
          if adiabatic_ceilings && (surface.surfaceType == 'RoofCeiling')
            if surface.construction.is_initialized
              surface.setConstruction(surface.construction.get)
            end
            surface.setOutsideBoundaryCondition('Adiabatic')
          end
        end
      end
    end

    # @tofo in future add code to include plenums or raised floor to each/any story.
  end
  # any changes to wall boundary conditions will be handled by same code that calls this method.
  # this method doesn't need to know about basements and party walls.
  return new_spaces
end
model_assign_spaces_to_building_stories(model) click to toggle source

Assign each space in the model to a building story based on common z (height) values. If no story object is found for a particular height, create a new one and assign it to the space. Does not assign a story to plenum spaces.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/modify.rb, line 139
def self.model_assign_spaces_to_building_stories(model)
  # Make hash of spaces and min z values
  sorted_spaces = {}
  model.getSpaces.sort.each do |space|
    # Skip plenum spaces
    next if OpenstudioStandards::Space.space_plenum?(space)

    # loop through space surfaces to find min z value
    z_points = []
    space.surfaces.each do |surface|
      surface.vertices.each do |vertex|
        z_points << vertex.z
      end
    end
    min_z = z_points.min + space.zOrigin
    sorted_spaces[space] = min_z
  end

  # Pre-sort spaces
  sorted_spaces = sorted_spaces.sort_by { |a| a[1] }

  # Take the sorted list and assign/make stories
  sorted_spaces.each do |space|
    space_obj = space[0]
    space_min_z = space[1]
    if space_obj.buildingStory.empty?
      tolerance = 0.3
      story = OpenstudioStandards::Geometry.model_get_building_story_for_nominal_height(model, space_min_z, tolerance: tolerance)
      if story.nil?
        story = OpenStudio::Model::BuildingStory.new(model)
        story.setNominalZCoordinate(space_min_z)
        story.setName("Building Story #{space_min_z.round(1)}m")
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "No story with a min z value of #{space_min_z.round(2)} m +/- #{tolerance} m was found, so a new story called #{story.name} was created.")
      end
      space_obj.setBuildingStory(story)
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Space #{space[0].name} was not assigned to a story by the user.  It has been assigned to #{story.name}.")
    end
  end

  return true
end
model_envelope_data(model) click to toggle source

gather envelope data for envelope simplification

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Hash] A hash of envelope data used by other methods @todo full list of hash returns aren’t documented yet

# File lib/openstudio-standards/geometry/create_bar.rb, line 137
def self.model_envelope_data(model)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Gathering envelope data.')

  # hash to contain envelope data
  envelope_data_hash = {}

  # used for overhang and party wall orientation catigorization
  facade_options = {
    'north_east' => 45.0,
    'south_east' => 125.0,
    'south_west' => 225.0,
    'north_west' => 315.0
  }

  # get building level inputs
  envelope_data_hash[:north_axis] = model.getBuilding.northAxis
  envelope_data_hash[:building_floor_area] = model.getBuilding.floorArea
  envelope_data_hash[:building_exterior_surface_area] = model.getBuilding.exteriorSurfaceArea
  envelope_data_hash[:building_exterior_wall_area] = model.getBuilding.exteriorWallArea
  envelope_data_hash[:building_exterior_roof_area] = envelope_data_hash[:building_exterior_surface_area] - envelope_data_hash[:building_exterior_wall_area]
  envelope_data_hash[:building_air_volume] = model.getBuilding.airVolume
  envelope_data_hash[:building_perimeter] = nil # will be applied for first story without ground walls

  # get bounding_box
  bounding_box = OpenStudio::BoundingBox.new
  model.getSpaces.sort.each do |space|
    space.surfaces.sort.each do |space_surface|
      bounding_box.addPoints(space.transformation * space_surface.vertices)
    end
  end
  min_x = bounding_box.minX.get
  min_y = bounding_box.minY.get
  min_z = bounding_box.minZ.get
  max_x = bounding_box.maxX.get
  max_y = bounding_box.maxY.get
  max_z = bounding_box.maxZ.get
  envelope_data_hash[:building_min_xyz] = [min_x, min_y, min_z]
  envelope_data_hash[:building_max_xyz] = [max_x, max_y, max_z]

  # add orientation specific wwr
  ext_surfaces_hash = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)
  envelope_data_hash[:building_wwr_n] = ext_surfaces_hash['north_window'] / ext_surfaces_hash['north_wall']
  envelope_data_hash[:building_wwr_s] = ext_surfaces_hash['south_window'] / ext_surfaces_hash['south_wall']
  envelope_data_hash[:building_wwr_e] = ext_surfaces_hash['east_window'] / ext_surfaces_hash['east_wall']
  envelope_data_hash[:building_wwr_w] = ext_surfaces_hash['west_window'] / ext_surfaces_hash['west_wall']
  envelope_data_hash[:stories] = {} # each entry will be hash with buildingStory as key and attributes has values
  envelope_data_hash[:space_types] = {} # each entry will be hash with spaceType as key and attributes has values

  # as rough estimate overhang area / glazing area should be close to projection factor assuming overhang is same width as windows
  # will only add building shading surfaces assoicated with a sub-surface.
  building_overhang_area_n = 0.0
  building_overhang_area_s = 0.0
  building_overhang_area_e = 0.0
  building_overhang_area_w = 0.0

  # loop through stories based on mine z height of surfaces.
  sorted_stories = OpenstudioStandards::Geometry.model_sort_building_stories_and_get_min_multiplier(model).sort_by { |k, v| v }
  sorted_stories.each do |story, story_min_z|
    story_min_multiplier = nil
    story_footprint = nil
    story_multiplied_floor_area = OpenstudioStandards::Geometry.spaces_get_floor_area(story.spaces)
    # goal of footprint calc is to count multiplier for hotel room on facade,but not to count what is intended as a story multiplier
    story_multiplied_exterior_surface_area = OpenstudioStandards::Geometry.spaces_get_exterior_area(story.spaces)
    story_multiplied_exterior_wall_area = OpenstudioStandards::Geometry.spaces_get_exterior_wall_area(story.spaces)
    story_multiplied_exterior_roof_area = story_multiplied_exterior_surface_area - story_multiplied_exterior_wall_area
    story_has_ground_walls = []
    story_has_adiabatic_walls = []
    story_included_in_building_area = false # will be true if any spaces on story are inclued in building area
    story_max_z = nil

    # loop through spaces for story gathering information
    story.spaces.each do |space|
      # get min multiplier value
      multiplier = space.multiplier
      if story_min_multiplier.nil? || (story_min_multiplier > multiplier)
        story_min_multiplier = multiplier
      end

      # calculate footprint
      story_footprint = story_multiplied_floor_area / story_min_multiplier

      # see if part of floor area
      if space.partofTotalFloorArea
        story_included_in_building_area = true

        # add to space type ratio hash when space is included in building floor area
        if space.spaceType.is_initialized
          space_type = space.spaceType.get
          space_floor_area = space.floorArea * space.multiplier
          if envelope_data_hash[:space_types].key?(space_type)
            envelope_data_hash[:space_types][space_type][:floor_area] += space_floor_area
          else
            envelope_data_hash[:space_types][space_type] = {}
            envelope_data_hash[:space_types][space_type][:floor_area] = space_floor_area

            # make hash for heating and cooling setpoints
            envelope_data_hash[:space_types][space_type][:htg_setpoint] = {}
            envelope_data_hash[:space_types][space_type][:clg_setpoint] = {}

          end

          # add heating and cooling setpoints
          if space.thermalZone.is_initialized && space.thermalZone.get.thermostatSetpointDualSetpoint.is_initialized
            thermostat = space.thermalZone.get.thermostatSetpointDualSetpoint.get

            # log heating schedule
            if thermostat.heatingSetpointTemperatureSchedule.is_initialized
              htg_sch = thermostat.heatingSetpointTemperatureSchedule.get
              if envelope_data_hash[:space_types][space_type][:htg_setpoint].key?(htg_sch)
                envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] += space_floor_area
              else
                envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] = space_floor_area
              end
            else
              OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
            end

            # log cooling schedule
            if thermostat.coolingSetpointTemperatureSchedule.is_initialized
              clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
              if envelope_data_hash[:space_types][space_type][:clg_setpoint].key?(clg_sch)
                envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] += space_floor_area
              else
                envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] = space_floor_area
              end
            else
              OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
            end

          else
            OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.name} either isn't in a thermal zone or doesn't have a thermostat assigned")
          end

        else
          OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.name} is included in the building floor area but isn't assigned a space type.")
        end

      end

      # check for walls with adiabatic and ground boundary condition
      space.surfaces.each do |surface|
        next if surface.surfaceType != 'Wall'

        if surface.outsideBoundaryCondition == 'Ground'
          story_has_ground_walls << surface
        elsif surface.outsideBoundaryCondition == 'Adiabatic'
          story_has_adiabatic_walls << surface
        end
      end

      # populate overhang values
      space.surfaces.each do |surface|
        surface.subSurfaces.each do |sub_surface|
          sub_surface.shadingSurfaceGroups.each do |shading_surface_group|
            shading_surface_group.shadingSurfaces.each do |shading_surface|
              absolute_azimuth = OpenStudio.convert(sub_surface.azimuth, 'rad', 'deg').get + sub_surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
              absolute_azimuth -= 360.0 until absolute_azimuth < 360.0
              # add to hash based on orientation
              if (facade_options['north_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_east']) # East overhang
                building_overhang_area_e += shading_surface.grossArea * space.multiplier
              elsif (facade_options['south_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_west']) # South overhang
                building_overhang_area_s += shading_surface.grossArea * space.multiplier
              elsif (facade_options['south_west'] <= absolute_azimuth) && (absolute_azimuth < facade_options['north_west']) # West overhang
                building_overhang_area_w += shading_surface.grossArea * space.multiplier
              else # North overhang
                building_overhang_area_n += shading_surface.grossArea * space.multiplier
              end
            end
          end
        end
      end

      # get max z
      space_z_max = OpenstudioStandards::Geometry.surfaces_get_z_values(space.surfaces.to_a).max + space.zOrigin
      if story_max_z.nil? || (story_max_z > space_z_max)
        story_max_z = space_z_max
      end
    end

    # populate hash for story data
    envelope_data_hash[:stories][story] = {}
    envelope_data_hash[:stories][story][:story_min_height] = story_min_z
    envelope_data_hash[:stories][story][:story_max_height] = story_max_z
    envelope_data_hash[:stories][story][:story_min_multiplier] = story_min_multiplier
    envelope_data_hash[:stories][story][:story_has_ground_walls] = story_has_ground_walls
    envelope_data_hash[:stories][story][:story_has_adiabatic_walls] = story_has_adiabatic_walls
    envelope_data_hash[:stories][story][:story_included_in_building_area] = story_included_in_building_area
    envelope_data_hash[:stories][story][:story_footprint] = story_footprint
    envelope_data_hash[:stories][story][:story_multiplied_floor_area] = story_multiplied_floor_area
    envelope_data_hash[:stories][story][:story_exterior_surface_area] = story_multiplied_exterior_surface_area
    envelope_data_hash[:stories][story][:story_multiplied_exterior_wall_area] = story_multiplied_exterior_wall_area
    envelope_data_hash[:stories][story][:story_multiplied_exterior_roof_area] = story_multiplied_exterior_roof_area

    # get perimeter and adiabatic walls that appear to be party walls
    perimeter_and_party_walls = OpenstudioStandards::Geometry.building_story_get_exterior_wall_perimeter(story,
                                                                                                         multiplier_adjustment: story_min_multiplier,
                                                                                                         bounding_box: bounding_box)
    envelope_data_hash[:stories][story][:story_perimeter] = perimeter_and_party_walls[:perimeter]
    envelope_data_hash[:stories][story][:story_party_walls] = []
    east = false
    south = false
    west = false
    north = false
    perimeter_and_party_walls[:party_walls].each do |surface|
      absolute_azimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
      absolute_azimuth -= 360.0 until absolute_azimuth < 360.0

      # add to hash based on orientation (initially added array of sourfaces, but swtiched to just true/false flag)
      if (facade_options['north_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_east']) # East party walls
        east = true
      elsif (facade_options['south_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_west']) # South party walls
        south = true
      elsif (facade_options['south_west'] <= absolute_azimuth) && (absolute_azimuth < facade_options['north_west']) # West party walls
        west = true
      else # North party walls
        north = true
      end
    end

    if east then envelope_data_hash[:stories][story][:story_party_walls] << 'east' end
    if south then envelope_data_hash[:stories][story][:story_party_walls] << 'south' end
    if west then envelope_data_hash[:stories][story][:story_party_walls] << 'west' end
    if north then envelope_data_hash[:stories][story][:story_party_walls] << 'north' end

    # store perimeter from first story that doesn't have ground walls
    if story_has_ground_walls.empty? && envelope_data_hash[:building_perimeter].nil?
      envelope_data_hash[:building_perimeter] = envelope_data_hash[:stories][story][:story_perimeter]
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', " * #{story.name} is the first above grade story and will be used for the building perimeter.")
    end
  end

  envelope_data_hash[:building_overhang_proj_factor_n] = building_overhang_area_n / ext_surfaces_hash['north_window']
  envelope_data_hash[:building_overhang_proj_factor_s] = building_overhang_area_s / ext_surfaces_hash['south_window']
  envelope_data_hash[:building_overhang_proj_factor_e] = building_overhang_area_e / ext_surfaces_hash['east_window']
  envelope_data_hash[:building_overhang_proj_factor_w] = building_overhang_area_w / ext_surfaces_hash['west_window']

  # warn for spaces that are not on a story (in future could infer stories for these)
  model.getSpaces.sort.each do |space|
    if !space.buildingStory.is_initialized
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.name} is not on a building story, may have unexpected results.")
    end
  end

  return envelope_data_hash
end
model_get_building_stories_above_ground(model) click to toggle source

Returns an array of the above ground building stories in the model.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Array<OpenStudio::Model::BuildingStory>] Array of OpenStudio BuildingStory objects, empty array if none

# File lib/openstudio-standards/geometry/information.rb, line 927
def self.model_get_building_stories_above_ground(model)
  above_ground_stories = []
  model.getBuildingStorys.sort.each do |story|
    z = story.nominalZCoordinate
    if !z.empty? && z.to_f >= 0
      above_ground_stories << story
    end
  end
  return above_ground_stories
end
model_get_building_stories_below_ground(model) click to toggle source

Returns an array of the below ground building stories in the model.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Array<OpenStudio::Model::BuildingStory>] Array of OpenStudio BuildingStory objects, empty array if none

# File lib/openstudio-standards/geometry/information.rb, line 942
def self.model_get_building_stories_below_ground(model)
  below_ground_stories = []
  model.getBuildingStorys.sort.each do |story|
    z = story.nominalZCoordinate
    if !z.empty? && z.to_f < 0
      below_ground_stories << story
    end
  end
  return below_ground_stories
end
model_get_building_story_for_nominal_height(model, minimum_height, tolerance: 0.3) click to toggle source

Returns the building story associated with a given minimum height. This return the story that matches the minimum z value of any vertex of any surface of any space on the story, with the exception of plenum spaces.

@param model [OpenStudio::Model::Model] OpenStudio model object @param minimum_height [Double] The base height of the desired story, in meters. @param tolerance [Double] tolerance for comparison, in m. Default is 0.3 m ~1ft @return [OpenStudio::Model::BuildingStory] OpenStudio BuildingStory object, nil if none matching

# File lib/openstudio-standards/geometry/information.rb, line 910
def self.model_get_building_story_for_nominal_height(model, minimum_height, tolerance: 0.3)
  matched_story = nil
  model.getBuildingStorys.sort.each do |story|
    z = OpenstudioStandards::Geometry.building_story_get_minimum_height(story)
    if (minimum_height - z).abs < tolerance
      OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Model', "The story with a min z value of #{minimum_height.round(2)} is #{story.name}.")
      matched_story = story
    end
  end

  return matched_story
end
model_get_exterior_window_and_wall_area_by_orientation(model, spaces: []) click to toggle source

Returns the wall area and window area by orientation

@param model [OpenStudio::Model::Model] OpenStudio model object @param spaces [Array<OpenStudio::Model::Space>] optional array of Space objects.

If provided, the return will report for only those spaces.

@return [Hash] Hash of wall area in square meters and window to wall ratio for each orientation

# File lib/openstudio-standards/geometry/information.rb, line 1029
def self.model_get_exterior_window_and_wall_area_by_orientation(model, spaces: [])
  # counters
  total_gross_ext_wall_area_north = 0.0
  total_gross_ext_wall_area_south = 0.0
  total_gross_ext_wall_area_east = 0.0
  total_gross_ext_wall_area_west = 0.0
  total_ext_window_area_north = 0.0
  total_ext_window_area_south = 0.0
  total_ext_window_area_east = 0.0
  total_ext_window_area_west = 0.0

  if spaces.empty?
    spaces = model.getSpaces
  end

  spaces.each do |space|
    # get surface area adjusting for zone multiplier
    zone = space.thermalZone
    if zone.empty?
      zone_multiplier = 1 # space is not in a thermal zone
    else
      zone_multiplier = zone.get.multiplier
    end

    space.surfaces.each do |s|
      next if s.surfaceType != 'Wall'
      next if s.outsideBoundaryCondition != 'Outdoors'

      surface_gross_area = s.grossArea * zone_multiplier

      # loop through sub surfaces and add area including multiplier
      ext_window_area = 0
      s.subSurfaces.each do |sub_surface|
        ext_window_area += sub_surface.grossArea * sub_surface.multiplier * zone_multiplier
      end

      absolute_azimuth = OpenStudio.convert(s.azimuth, 'rad', 'deg').get + s.space.get.directionofRelativeNorth + model.getBuilding.northAxis
      absolute_azimuth -= 360.0 until absolute_azimuth < 360.0

      # add to exterior wall counter if north or south
      if (absolute_azimuth >= 45.0) && (absolute_azimuth < 125.0)
        # east exterior walls
        total_gross_ext_wall_area_east += surface_gross_area
        total_ext_window_area_east += ext_window_area
      elsif (absolute_azimuth >= 125.0) && (absolute_azimuth < 225.0)
        # south exterior walls
        total_gross_ext_wall_area_south += surface_gross_area
        total_ext_window_area_south += ext_window_area
      elsif (absolute_azimuth >= 225.0) && (absolute_azimuth < 315.0)
        # west exterior walls
        total_gross_ext_wall_area_west += surface_gross_area
        total_ext_window_area_west += ext_window_area
      else
        # north exterior walls
        total_gross_ext_wall_area_north += surface_gross_area
        total_ext_window_area_north += ext_window_area
      end
    end
  end

  result = { 'north_wall' => total_gross_ext_wall_area_north,
             'north_window' => total_ext_window_area_north,
             'south_wall' => total_gross_ext_wall_area_south,
             'south_window' => total_ext_window_area_south,
             'east_wall' => total_gross_ext_wall_area_east,
             'east_window' => total_ext_window_area_east,
             'west_wall' => total_gross_ext_wall_area_west,
             'west_window' => total_ext_window_area_west }
  return result
end
model_get_exterior_window_to_wall_ratio(model, spaces: [], cardinal_direction: nil) click to toggle source

Returns the window to wall ratio

@param model [OpenStudio::Model::Model] OpenStudio model object @param spaces [Array<OpenStudio::Model::Space>] optional array of Space objects.

If provided, the return will report for only those spaces.

@param cardinal_direction [String] Cardinal direction ‘N’, ‘E’, ‘S’, ‘W’

If provided, the return will report for only the provided cardinal direction

@return [Double] window to wall ratio

# File lib/openstudio-standards/geometry/information.rb, line 961
def self.model_get_exterior_window_to_wall_ratio(model,
                                                 spaces: [],
                                                 cardinal_direction: nil)
  # counters
  total_gross_ext_wall_area = 0.0
  total_ext_window_area = 0.0
  window_to_wall_ratio = 0.0

  # get spaces if none provided
  if spaces.empty?
    spaces = model.getSpaces
  end

  # loop through each space and log window and wall areas
  spaces.each do |space|
    # get surface area adjusting for zone multiplier
    zone = space.thermalZone
    if zone.empty?
      # space is not in a thermal zone
      zone_multiplier = 1
    else
      zone_multiplier = zone.get.multiplier
    end

    # loop through spaces and skip all that aren't exterior walls and don't match selected cardinal direction
    space.surfaces.each do |surface|
      next if surface.surfaceType != 'Wall'
      next if surface.outsideBoundaryCondition != 'Outdoors'

      # filter by cardinal direction if specified
      case cardinal_direction
      when 'N', 'n', 'North', 'north'
        next unless OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'N'
      when 'E', 'e', 'East', 'east'
        next unless OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'E'
      when 'S', 's', 'South', 'south'
        next unless OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'S'
      when 'W', 'w', 'West', 'west'
        next unless OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'W'
      end

      # Get wall and window area
      surface_gross_area = surface.grossArea * zone_multiplier

      # loop through sub surfaces and add area including multiplier
      ext_window_area = 0
      surface.subSurfaces.each do |sub_surface|
        ext_window_area += sub_surface.grossArea * sub_surface.multiplier * zone_multiplier
      end

      total_gross_ext_wall_area += surface_gross_area
      total_ext_window_area += ext_window_area
    end
  end

  if total_gross_ext_wall_area > 0.0
    window_to_wall_ratio = total_ext_window_area / total_gross_ext_wall_area
  end

  return window_to_wall_ratio
end
model_get_perimeter(model) click to toggle source

Calculates the exterior perimeter length, checking checks for edges shared by a ground exposed floor and exterior exposed wall.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Double] perimeter length in meters @todo this doesn’t catch walls that are split that sit above floor surfaces that are not (e.g. main corridoor in secondary school model) @todo also odd with multi-height spaces

# File lib/openstudio-standards/geometry/information.rb, line 1106
def self.model_get_perimeter(model)
  perimeter = 0.0
  model.getSpaces.sort.each do |space|
    # counter to use later
    edge_hash = {}
    edge_counter = 0
    space.surfaces.sort.each do |surface|
      # get vertices
      vertex_hash = {}
      vertex_counter = 0
      surface.vertices.each do |vertex|
        vertex_counter += 1
        vertex_hash[vertex_counter] = [vertex.x, vertex.y, vertex.z]
      end
      # make edges
      counter = 0
      vertex_hash.each do |k, v|
        edge_counter += 1
        counter += 1
        if vertex_hash.size == counter
          # different code for wrap around vertex
          edge_hash[edge_counter] = [v, vertex_hash[1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
        else
          edge_hash[edge_counter] = [v, vertex_hash[counter + 1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
        end
      end
    end

    # check edges for matches (need opposite vertices and proper boundary conditions)
    edge_hash.each do |k1, v1|
      next if v1[3] != 'Ground' # skip if not ground exposed floor
      next if v1[4] != 'Floor'

      edge_hash.each do |k2, v2|
        next if v2[3] != 'Outdoors' # skip if not exterior exposed wall (todo - update to handle basement)
        next if v2[4] != 'Wall'

        # see if edges have same geometry
        # found cases where the two lines below removed edges and resulted in lower than actual perimeter. Added new code with tolerance.
        # next if not v1[0] == v2[1] # next if not same geometry reversed
        # next if not v1[1] == v2[0]

        # these are three item array's add in tolerance for each array entry
        tolerance = 0.0001
        test_a = true
        test_b = true
        3.times.each do |i|
          if (v1[0][i] - v2[1][i]).abs > tolerance
            test_a = false
          end
          if (v1[1][i] - v2[0][i]).abs > tolerance
            test_b = false
          end
        end

        next if test_a != true
        next if test_b != true

        point_one = OpenStudio::Point3d.new(v1[0][0], v1[0][1], v1[0][2])
        point_two = OpenStudio::Point3d.new(v1[1][0], v1[1][1], v1[1][2])
        length = OpenStudio::Vector3d.new(point_one - point_two).length
        perimeter += length
      end
    end
  end

  return perimeter
end
model_group_thermal_zones_by_building_story(model, thermal_zones) click to toggle source

Group an array of zones into multiple arrays, one for each story in the building. Zones with spaces on multiple stories will be assigned to only one of the stories. Returns an empty array when the story doesn’t contain any of the zones.

@param model [OpenStudio::Model::Model] OpenStudio Model object @param thermal_zones [Array<OpenStudio::Model::ThermalZone>] An array of OpenStudio ThermalZone objects @return [Array<Array<OpenStudio::Model::ThermalZone>>] An array of arrays of OpenStudio ThermalZone objects

# File lib/openstudio-standards/geometry/group.rb, line 15
def self.model_group_thermal_zones_by_building_story(model, thermal_zones)
  story_zone_lists = []
  zones_already_assigned = []
  model.getBuildingStorys.sort.each do |story|
    # Get all the spaces on this story
    spaces = story.spaces

    # Get all the thermal zones that serve these spaces
    all_zones_on_story = []
    spaces.each do |space|
      if space.thermalZone.is_initialized
        all_zones_on_story << space.thermalZone.get
      else
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Model', "Space #{space.name} has no thermal zone, it is not included in the simulation.")
      end
    end

    # Find thermal zones in the list that are on this story
    zones_on_story = []
    thermal_zones.each do |zone|
      if all_zones_on_story.include?(zone)
        # Skip thermal zones that were already assigned to a story.
        # This can happen if a zone has multiple spaces on multiple stories.
        # Stairwells and atriums are typical scenarios.
        next if zones_already_assigned.include?(zone)

        zones_on_story << zone
        zones_already_assigned << zone
      end
    end

    unless zones_on_story.empty?
      story_zone_lists << zones_on_story
    end
  end

  return story_zone_lists
end
model_group_thermal_zones_by_building_type(model, min_area_m2: 1858.0608) click to toggle source

Split all zones in the model into groups that are big enough to justify their own HVAC system type. Similar to the logic from 90.1 Appendix G, but without regard to the fuel type of the existing HVAC system (because the model may not have one).

@param model [OpenStudio::Model::Model] OpenStudio Model object @param min_area_m2 [Double] the minimum area required to justify a different system type, default 20,000 ft^2 @return [Array<Hash>] an array of hashes of area information, with keys area_ft2, type, stories, and zones (an array of zones)

# File lib/openstudio-standards/geometry/group.rb, line 163
def self.model_group_thermal_zones_by_building_type(model, min_area_m2: 1858.0608)
  min_area_ft2 = OpenStudio.convert(min_area_m2, 'm^2', 'ft^2').get

  # Get occupancy type, building type, fuel type, and area information for all zones, excluding unconditioned zones
  std = Standard.build('90.1-2019') # delete once space methods refactored
  zones = std.model_zones_with_occ_and_fuel_type(model, nil)

  # Ensure that there is at least one conditioned zone
  if zones.empty?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', 'The building does not appear to have any conditioned zones. Make sure zones have thermostat with appropriate heating and cooling setpoint schedules.')
    return []
  end

  # Group the zones by building type
  type_to_area = Hash.new { 0.0 }
  zones_grouped_by_bldg_type = zones.group_by { |z| z['bldg_type'] }

  # Determine the dominant building type by area
  zones_grouped_by_bldg_type.each do |bldg_type, zns|
    zns.each do |zn|
      type_to_area[bldg_type] += zn['area']
    end
  end
  dom_bldg_type = type_to_area.sort_by { |k, v| v }.reverse[0][0]

  # Get the dominant building type group
  dom_bldg_type_group = zones_grouped_by_bldg_type[dom_bldg_type]

  # Check the non-dominant building type groups to see if they are big enough to trigger the building exception.
  # If they are, leave the group standing alone.
  # If they are not, add the zones in that group back to the dominant building type group.
  bldg_type_groups = []
  zones_grouped_by_bldg_type.each do |bldg_type, zns|
    # Skip the dominant building type
    next if bldg_type == dom_bldg_type

    # Add up the floor area of the group
    area_m2 = 0
    zns.each do |zn|
      area_m2 += zn['area']
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get

    # If the non-dominant group is big enough, preserve that group.
    if area_ft2 > min_area_ft2
      bldg_type_groups << [bldg_type, zns]
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The portion of the building with a building type of #{bldg_type} is bigger than the minimum area of #{min_area_ft2.round} ft2.  It will be assigned a separate HVAC system type.")
      # Otherwise, add the zones back to the dominant group.
    else
      dom_bldg_type_group += zns
    end
  end
  # Add the dominant building type group to the list
  bldg_type_groups << [dom_bldg_type, dom_bldg_type_group]

  # Calculate the area for each of the final groups
  # and replace the zone hashes with an array of zone objects
  final_groups = []
  bldg_type_groups.each do |bldg_type, zns|
    # Sum the area and put all zones into an array
    area_m2 = 0.0
    gp_zns = []
    zns.each do |zn|
      area_m2 += zn['area']
      gp_zns << zn['zone']
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get

    # Determine the number of stories this group spans
    num_stories = OpenstudioStandards::Geometry.thermal_zones_get_number_of_stories_spanned(gp_zns)

    # Create a hash representing this group
    group = {}
    group['area_ft2'] = area_ft2
    group['type'] = bldg_type
    group['stories'] = num_stories
    group['zones'] = gp_zns
    final_groups << group

    # Report out the final grouping
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Final system type group: bldg_type = #{group['type']}, area = #{group['area_ft2'].round} ft2, num stories = #{group['stories']}, zones:")
    group['zones'].sort.each_slice(5) do |zone_list|
      zone_names = []
      zone_list.each do |zone|
        zone_names << zone.name.get.to_s
      end
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "--- #{zone_names.join(', ')}")
    end
  end

  return final_groups
end
model_group_thermal_zones_by_occupancy_type(model, min_area_m2: 1858.0608) click to toggle source

Split all zones in the model into groups that are big enough to justify their own HVAC system type. Similar to the logic from 90.1 Appendix G, but without regard to the fuel type of the existing HVAC system (because the model may not have one).

@param model [OpenStudio::Model::Model] OpenStudio Model object @param min_area_m2 [Double] the minimum area required to justify a different system type, default 20,000 ft^2 @return [Array<Hash>] an array of hashes of area information, with keys area_ft2, type, stories, and zones (an array of zones)

# File lib/openstudio-standards/geometry/group.rb, line 60
def self.model_group_thermal_zones_by_occupancy_type(model, min_area_m2: 1858.0608)
  min_area_ft2 = OpenStudio.convert(min_area_m2, 'm^2', 'ft^2').get

  # Get occupancy type, fuel type, and area information for all zones, excluding unconditioned zones.
  # Occupancy types are:
  # Residential
  # NonResidential
  # Use 90.1-2010 so that retail and publicassembly are not split out
  std = Standard.build('90.1-2019') # delete once space methods refactored
  zones = std.model_zones_with_occ_and_fuel_type(model, nil)

  # Ensure that there is at least one conditioned zone
  if zones.empty?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', 'The building does not appear to have any conditioned zones. Make sure zones have thermostat with appropriate heating and cooling setpoint schedules.')
    return []
  end

  # Group the zones by occupancy type
  type_to_area = Hash.new { 0.0 }
  zones_grouped_by_occ = zones.group_by { |z| z['occ'] }

  # Determine the dominant occupancy type by area
  zones_grouped_by_occ.each do |occ_type, zns|
    zns.each do |zn|
      type_to_area[occ_type] += zn['area']
    end
  end
  dom_occ = type_to_area.sort_by { |k, v| v }.reverse[0][0]

  # Get the dominant occupancy type group
  dom_occ_group = zones_grouped_by_occ[dom_occ]

  # Check the non-dominant occupancy type groups to see if they are big enough to trigger the occupancy exception.
  # If they are, leave the group standing alone.
  # If they are not, add the zones in that group back to the dominant occupancy type group.
  occ_groups = []
  zones_grouped_by_occ.each do |occ_type, zns|
    # Skip the dominant occupancy type
    next if occ_type == dom_occ

    # Add up the floor area of the group
    area_m2 = 0
    zns.each do |zn|
      area_m2 += zn['area']
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get

    # If the non-dominant group is big enough, preserve that group.
    if area_ft2 > min_area_ft2
      occ_groups << [occ_type, zns]
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "The portion of the building with an occupancy type of #{occ_type} is bigger than the minimum area of #{min_area_ft2.round} ft2.  It will be assigned a separate HVAC system type.")
      # Otherwise, add the zones back to the dominant group.
    else
      dom_occ_group += zns
    end
  end
  # Add the dominant occupancy group to the list
  occ_groups << [dom_occ, dom_occ_group]

  # Calculate the area for each of the final groups
  # and replace the zone hashes with an array of zone objects
  final_groups = []
  occ_groups.each do |occ_type, zns|
    # Sum the area and put all zones into an array
    area_m2 = 0.0
    gp_zns = []
    zns.each do |zn|
      area_m2 += zn['area']
      gp_zns << zn['zone']
    end
    area_ft2 = OpenStudio.convert(area_m2, 'm^2', 'ft^2').get

    # Determine the number of stories this group spans
    num_stories = OpenstudioStandards::Geometry.thermal_zones_get_number_of_stories_spanned(gp_zns)

    # Create a hash representing this group
    group = {}
    group['area_ft2'] = area_ft2
    group['type'] = occ_type
    group['stories'] = num_stories
    group['zones'] = gp_zns
    final_groups << group

    # Report out the final grouping
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Final system type group: occ = #{group['type']}, area = #{group['area_ft2'].round} ft2, num stories = #{group['stories']}, zones:")
    group['zones'].sort.each_slice(5) do |zone_list|
      zone_names = []
      zone_list.each do |zone|
        zone_names << zone.name.get.to_s
      end
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "--- #{zone_names.join(', ')}")
    end
  end

  return final_groups
end
model_rename_surfaces_and_subsurfaces(model) click to toggle source

Rename all model surfaces using the convention ‘SpaceName SurfaceType #’. Rename all model sub surfaces using the convention ‘SurfaceName SubSurfaceType #’.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/modify.rb, line 186
def self.model_rename_surfaces_and_subsurfaces(model)
  model.getSpaces.each { |space| OpenstudioStandards::Geometry.space_rename_surfaces_and_subsurfaces(space) }

  return true
end
model_set_building_north_axis(model, north_axis) click to toggle source

Set the model’s north axis (degrees from true North)

@param model [OpenStudio::Model::Model] OpenStudio Model object @param north_axis [Float] Degrees from true North @return [Boolean] Returns true if successful, false otherwise

# File lib/openstudio-standards/geometry/modify.rb, line 197
def self.model_set_building_north_axis(model, north_axis)
  return false if north_axis.nil?

  building = model.getBuilding
  building.setNorthAxis(north_axis)

  return true
end
model_sort_building_stories_and_get_min_multiplier(model) click to toggle source

sort building stories

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Hash] An Hash with key OpenStudio BuildingStory objects and their minimum z value

# File lib/openstudio-standards/geometry/create_bar.rb, line 114
def self.model_sort_building_stories_and_get_min_multiplier(model)
  sorted_building_stories = {}
  # loop through stories
  model.getBuildingStorys.sort.each do |story|
    story_min_z = nil
    # loop through spaces in story.
    story.spaces.sort.each do |space|
      space_z_min = OpenstudioStandards::Geometry.surfaces_get_z_values(space.surfaces.to_a).min + space.zOrigin
      if story_min_z.nil? || (story_min_z > space_z_min)
        story_min_z = space_z_min
      end
    end
    sorted_building_stories[story] = story_min_z
  end

  return sorted_building_stories
end
space_create_point_at_center_of_floor(space, z_offset_m) click to toggle source

method to create a point object at the center of a floor

@param space [OpenStudio::Model::Space] OpenStudio Space object @param z_offset_m [Double] vertical offset in meters @return [OpenStudio::Point3d] point at the center of the space. return nil if point is not on floor in space.

# File lib/openstudio-standards/geometry/create.rb, line 12
def self.space_create_point_at_center_of_floor(space, z_offset_m)
  # find floors
  floor_surfaces = []
  space.surfaces.each { |surface| floor_surfaces << surface if surface.surfaceType == 'Floor' }

  # this method only works for flat (non-inclined) floors
  bounding_box = OpenStudio::BoundingBox.new
  floor_surfaces.each { |floor| bounding_box.addPoints(floor.vertices) }
  xmin = bounding_box.minX.get
  ymin = bounding_box.minY.get
  zmin = bounding_box.minZ.get
  xmax = bounding_box.maxX.get
  ymax = bounding_box.maxY.get

  x_pos = (xmin + xmax) / 2
  y_pos = (ymin + ymax) / 2
  z_pos = zmin + z_offset_m
  point_on_floor = OpenstudioStandards::Geometry.surfaces_contain_point?(floor_surfaces, OpenStudio::Point3d.new(x_pos, y_pos, zmin))

  if point_on_floor
    new_point = OpenStudio::Point3d.new(x_pos, y_pos, z_pos)
  else
    # don't make point, it doesn't appear to be inside of the space
    new_point = nil
  end

  return new_point
end
space_get_adjacent_space_with_most_shared_wall_area(space, same_floor: true) click to toggle source

Find the space that has the most wall area touching this space.

@param space [OpenStudio::Model::Space] OpenStudio Space object @param same_floor [Boolean] only consider spaces on the same floor @return [OpenStudio::Model::Space] OpenStudio Space object

# File lib/openstudio-standards/geometry/information.rb, line 480
def self.space_get_adjacent_space_with_most_shared_wall_area(space, same_floor: true)
  adjacent_space = OpenstudioStandards::Geometry.space_get_adjacent_spaces_with_shared_wall_areas(space, same_floor: same_floor)[0][0]
  return adjacent_space
end
space_get_adjacent_spaces_with_shared_wall_areas(space, same_floor: true) click to toggle source

Get a sorted array of tuples containing a list of spaces and connected area in descending order

@param space [OpenStudio::Model::Space] OpenStudio Space object @param same_floor [Boolean] only consider spaces on the same floor @return [Hash] sorted hash with array of spaces and area

# File lib/openstudio-standards/geometry/information.rb, line 409
def self.space_get_adjacent_spaces_with_shared_wall_areas(space, same_floor: true)
  same_floor_spaces = []
  spaces = []
  space.surfaces.each do |surface|
    adj_surface = surface.adjacentSurface
    unless adj_surface.empty?
      space.model.getSpaces.sort.each do |other_space|
        next if other_space == space

        other_space.surfaces.each do |surf|
          if surf == adj_surface.get
            spaces << other_space
          end
        end
      end
    end
  end
  # If looking for only spaces adjacent on the same floor.
  if same_floor == true
    if space.buildingStory.empty?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Information', "Cannot get adjacent spaces of space #{space.name} since space not set to BuildingStory.")
      return nil
    end

    spaces.each do |other_space|
      if space.buildingStory.empty?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Information', "One or more adjecent spaces to space #{space.name} is not assigned to a BuildingStory. Ensure all spaces are assigned.")
        return nil
      end

      if other_space.buildingStory.get == space.buildingStory.get
        same_floor_spaces << other_space
      end
    end
    spaces = same_floor_spaces
  end

  # now sort by areas.
  area_index = []
  array_hash = {}
  return array_hash if spaces.empty?

  # iterate through each surface in the space
  space.surfaces.each do |surface|
    # get the adjacent surface in another space.
    adj_surface = surface.adjacentSurface
    unless adj_surface.empty?
      # go through each of the adjacent spaces to find the matching surface/space.
      spaces.each_with_index do |other_space, index|
        next if other_space == space

        other_space.surfaces.each do |surf|
          if surf == adj_surface.get
            # initialize array index to zero for first time so += will work.
            area_index[index] = 0 if area_index[index].nil?
            area_index[index] += surf.grossArea
            array_hash[other_space] = area_index[index]
          end
        end
      end
    end
  end
  sorted_spaces = array_hash.sort_by { |_key, value| value }.reverse
  return sorted_spaces
end
space_get_below_grade_wall_height(space) click to toggle source

Finds heights of the first below grade walls and returns them as a numeric. Used when defining C Factor walls. Returns nil if the space is above grade.

@param space [OpenStudio::Model::Space] OpenStudio Space object @return [Double] height in meters, or nil if undefined

# File lib/openstudio-standards/geometry/information.rb, line 490
def self.space_get_below_grade_wall_height(space)
  # find height of first below-grade wall adjacent to the ground
  surface_height = nil
  space.surfaces.each do |surface|
    next unless surface.surfaceType == 'Wall'

    boundary_condition = surface.outsideBoundaryCondition
    next unless boundary_condition == 'OtherSideCoefficients' || boundary_condition.to_s.downcase.include?('ground')

    # calculate wall height as difference of maximum and minimum z values, assuming square, vertical walls
    z_values = []
    surface.vertices.each do |vertex|
      z_values << vertex.z
    end
    surface_height = z_values.max - z_values.min
  end

  return surface_height
end
space_get_envelope_area(space, multiplier: true) click to toggle source

Calculate the space envelope area. According to the 90.1 definition, building envelope include:

  1. “the elements of a building that separate conditioned spaces from the exterior”

  2. “the elements of a building that separate conditioned space from unconditioned space or that enclose semiheated spaces through which thermal energy may be transferred to or from the exterior, to or from unconditioned spaces or to or from conditioned spaces.”

Outside boundary conditions currently supported:

  • Adiabatic

  • Surface

  • Outdoors

  • Foundation

  • Ground

  • GroundFCfactorMethod

  • OtherSideCoefficients

  • OtherSideConditionsModel

  • GroundSlabPreprocessorAverage

  • GroundSlabPreprocessorCore

  • GroundSlabPreprocessorPerimeter

  • GroundBasementPreprocessorAverageWall

  • GroundBasementPreprocessorAverageFloor

  • GroundBasementPreprocessorUpperWall

  • GroundBasementPreprocessorLowerWall

Surface type currently supported:

  • Floor

  • Wall

  • RoofCeiling

@param space [OpenStudio::Model::Space] OpenStudio Space object @param multiplier [Boolean] account for space multiplier @return [Double] area in m^2

# File lib/openstudio-standards/geometry/information.rb, line 297
def self.space_get_envelope_area(space, multiplier: true)
  area_m2 = 0.0

  # Get the space conditioning type
  std = Standard.build('90.1-2019') # delete once space methods refactored
  space_cond_type = std.space_conditioning_category(space)

  # Loop through all surfaces in this space
  space.surfaces.sort.each do |surface|
    # Only account for spaces that are conditioned or semi-heated
    next unless space_cond_type != 'Unconditioned'

    surf_cnt = false

    # Conditioned space OR semi-heated space <-> exterior
    # Conditioned space OR semi-heated space <-> ground
    if surface.outsideBoundaryCondition == 'Outdoors' || surface.isGroundSurface
      surf_cnt = true
    end

    # Conditioned space OR semi-heated space <-> unconditioned spaces
    unless surf_cnt
      # @todo add a case for 'Zone' when supported
      if surface.outsideBoundaryCondition == 'Surface'
        adj_space = surface.adjacentSurface.get.space.get
        adj_space_cond_type = std.space_conditioning_category(adj_space)
        surf_cnt = true unless adj_space_cond_type != 'Unconditioned'
      end
    end

    if surf_cnt
      # This surface
      area_m2 += surface.netArea
      # Subsurfaces in this surface
      surface.subSurfaces.sort.each do |subsurface|
        area_m2 += subsurface.netArea
      end
    end
  end

  if multiplier
    area_m2 *= space.multiplier
  end

  return area_m2
end
space_get_exterior_wall_and_subsurface_and_roof_area(space, multiplier: false) click to toggle source

Calculate the area of the exterior walls, including the area of the windows and doors on these walls, and the area of roofs.

@param space [OpenStudio::Model::Space] OpenStudio Space object @param multiplier [Boolean] account for space multiplier, default false @return [Double] area in m^2

# File lib/openstudio-standards/geometry/information.rb, line 379
def self.space_get_exterior_wall_and_subsurface_and_roof_area(space, multiplier: false)
  area_m2 = 0.0

  # Loop through all surfaces in this space
  space.surfaces.sort.each do |surface|
    # Skip non-outdoor surfaces
    next unless surface.outsideBoundaryCondition == 'Outdoors'
    # Skip non-walls
    next unless surface.surfaceType == 'Wall' || surface.surfaceType == 'RoofCeiling'

    # This surface
    area_m2 += surface.netArea
    # Subsurfaces in this surface
    surface.subSurfaces.sort.each do |subsurface|
      area_m2 += subsurface.netArea
    end
  end

  if multiplier
    area_m2 *= space.multiplier
  end

  return area_m2
end
space_get_exterior_wall_and_subsurface_area(space, multiplier: false) click to toggle source

Calculate the area of the exterior walls, including the area of the windows and doors on these walls.

@param space [OpenStudio::Model::Space] OpenStudio Space object @param multiplier [Boolean] account for space multiplier, default false @return [Double] area in m^2

# File lib/openstudio-standards/geometry/information.rb, line 349
def self.space_get_exterior_wall_and_subsurface_area(space, multiplier: false)
  area_m2 = 0.0

  # Loop through all surfaces in this space
  space.surfaces.sort.each do |surface|
    # Skip non-outdoor surfaces
    next unless surface.outsideBoundaryCondition == 'Outdoors'
    # Skip non-walls
    next unless surface.surfaceType == 'Wall'

    # This surface
    area_m2 += surface.netArea
    # Subsurfaces in this surface
    surface.subSurfaces.sort.each do |subsurface|
      area_m2 += subsurface.netArea
    end
  end

  if multiplier
    area_m2 *= space.multiplier
  end

  return area_m2
end
space_get_f_floor_area(space) click to toggle source

This function returns the space’s ground area. Assumes only one floor per space!

@param space [OpenStudio::Model::Space] OpenStudio Space object @return [Double] area in m^2

# File lib/openstudio-standards/geometry/information.rb, line 550
def self.space_get_f_floor_area(space)
  # Find space's floors with ground contact
  floors = []
  space.surfaces.each do |surface|
    if surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition.to_s.downcase.include?('ground')
      floors << surface
    end
  end

  # If this space has no ground contact floors, return 0
  return 0.0 if floors.empty?

  # Raise a warning for any space with more than 1 ground contact floor surface.
  if floors.length > 1
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Information', "Space: #{space.name} has more than one ground contact floor. FFactorGroundFloorConstruction area in this space may be incorrect.")
  end

  # Get floor area
  floor = floors[0]
  area = floor.netArea

  return area
end
space_get_f_floor_perimeter(space) click to toggle source

This function returns the space’s ground perimeter length. Assumes only one floor per space!

@param space [OpenStudio::Model::Space] OpenStudio Space object @return [Double] length in meters

# File lib/openstudio-standards/geometry/information.rb, line 515
def self.space_get_f_floor_perimeter(space)
  # Find space's floors with ground contact
  floors = []
  space.surfaces.each do |surface|
    if surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition.to_s.downcase.include?('ground')
      floors << surface
    end
  end

  # If this space has no ground contact floors, return 0
  return 0.0 if floors.empty?

  # Raise a warning for any space with more than 1 ground contact floor surface.
  if floors.length > 1
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Information', "Space: #{space.name} has more than one ground contact floor. FFactorGroundFloorConstruction perimeter in this space may be incorrect.")
  end

  # cycle through surfaces in the space and get adjacency length to the floor
  floor = floors[0]
  perimeter = 0.0
  space.surfaces.each do |surface|
    # find perimeter of floor by finding intersecting outdoor walls and measuring the intersection
    if surface.surfaceType == 'Wall' && surface.outsideBoundaryCondition == 'Outdoors'
      perimeter += OpenstudioStandards::Geometry.wall_and_floor_intersection_length(surface, floor)
    end
  end

  return perimeter
end
space_rename_surfaces_and_subsurfaces(space) click to toggle source

Rename space surfaces using the convention ‘SpaceName SurfaceType #’. Rename sub surfaces using the convention ‘SurfaceName SubSurfaceType #’.

@param space [OpenStudio::Model::Space] OpenStudio space object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/modify.rb, line 97
def self.space_rename_surfaces_and_subsurfaces(space)
  # reset names
  surf_i = 1
  space.surfaces.each do |surface|
    surface.setName("temp surf #{surf_i}")
    sub_i = 1
    surface.subSurfaces.each do |sub_surface|
      sub_surface.setName("#{surface.name} sub #{sub_i}")
      sub_i += 1
    end
    surf_i += 1
  end

  # rename surfaces based on space name and surface type
  surface_type_counter = Hash.new(0)
  space.surfaces.sort.each do |surface|
    surface_type = surface.surfaceType
    surface_type_counter[surface_type] += 1
    surface.setName("#{space.name} #{surface_type} #{surface_type_counter[surface_type]}")

    # rename sub surfaces based on surface name and subsurface type
    sub_surface_type_counter = Hash.new(0)
    surface.subSurfaces.sort.each do |sub_surface|
      sub_surface_type = sub_surface.subSurfaceType
      sub_surface_type_counter[sub_surface_type] += 1
      sub_surface.setName("#{surface.name} #{sub_surface_type} #{sub_surface_type_counter[sub_surface_type]}")
    end
  end

  return true
end
spaces_get_exterior_area(spaces, multiplier: true) click to toggle source

Get the total exterior area of selected spaces

@param spaces [Array<OpenStudio::Model::Space>] array of OpenStudio Space objects @param multiplier [Boolean] account for space multiplier @return [Double] total exterior area of spaces in square meters

# File lib/openstudio-standards/geometry/information.rb, line 597
def self.spaces_get_exterior_area(spaces, multiplier: true)
  total_area = 0.0
  spaces.each do |space|
    space_multiplier = multiplier ? space.multiplier : 1.0
    total_area += space.exteriorArea * space_multiplier
  end
  return total_area
end
spaces_get_exterior_wall_area(spaces, multiplier: true) click to toggle source

Get the total exterior wall area of selected spaces

@param spaces [Array<OpenStudio::Model::Space>] array of OpenStudio Space objects @param multiplier [Boolean] account for space multiplier @return [Double] total exterior wall area of spaces in square meters

# File lib/openstudio-standards/geometry/information.rb, line 611
def self.spaces_get_exterior_wall_area(spaces, multiplier: true)
  total_area = 0.0
  spaces.each do |space|
    space_multiplier = multiplier ? space.multiplier : 1.0
    total_area += space.exteriorWallArea * space_multiplier
  end
  return total_area
end
spaces_get_floor_area(spaces, multiplier: true) click to toggle source

Get the total floor area of selected spaces

@param spaces [Array<OpenStudio::Model::Space>] array of OpenStudio Space objects @param multiplier [Boolean] account for space multiplier @return [Double] total floor area of spaces in square meters

# File lib/openstudio-standards/geometry/information.rb, line 583
def self.spaces_get_floor_area(spaces, multiplier: true)
  total_area = 0.0
  spaces.each do |space|
    space_multiplier = multiplier ? space.multiplier : 1.0
    total_area += space.floorArea * space_multiplier
  end
  return total_area
end
sub_surface_create_point_at_specific_height(sub_surface, reference_floor, distance_from_window_m, height_above_subsurface_bottom_m) click to toggle source

method to create a point object from a sub surface

@param sub_surface [OpenStudio::Model::SubSurface] OpenStudio SubSurface object @param reference_floor [OpenStudio::Model::SubSurface] OpenStudio SubSurface object @param distance_from_window_m [Double] distance in from the window, in meters @param height_above_subsurface_bottom_m [Double] height above the bottom of the subsurface, in meters @return [OpenStudio::Point3d] point at the center of the space. return nil if point is not on floor in space.

# File lib/openstudio-standards/geometry/create.rb, line 48
def self.sub_surface_create_point_at_specific_height(sub_surface, reference_floor, distance_from_window_m, height_above_subsurface_bottom_m)
  window_outward_normal = sub_surface.outwardNormal
  window_centroid = OpenStudio.getCentroid(sub_surface.vertices).get
  window_outward_normal.setLength(distance_from_window_m)
  vertex = window_centroid + window_outward_normal.reverseVector
  vertex_on_floorplane = reference_floor.plane.project(vertex)
  floor_outward_normal = reference_floor.outwardNormal
  floor_outward_normal.setLength(height_above_subsurface_bottom_m)

  floor_surfaces = []
  space.surfaces.each { |surface| floor_surfaces << surface if surface.surfaceType == 'Floor' }

  point_on_floor = OpenstudioStandards::Geometry.surfaces_contain_point?(floor_surfaces, vertex_on_floorplane)

  if point_on_floor
    new_point = vertex_on_floorplane + floor_outward_normal.reverseVector
  else
    # don't make point, it doesn't appear to be inside of the space
    # nil
    new_point = vertex_on_floorplane + floor_outward_normal.reverseVector
  end

  return new_point
end
sub_surface_reduce_area_by_percent_by_raising_sill(sub_surface, percent_reduction) click to toggle source

Reduce the area of the subsurface by raising the sill height

@param sub_surface [OpenStudio::Model::SubSurface] OpenStudio SubSurface object @param percent_reduction [Double] the fractional amount to reduce the area @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/modify.rb, line 51
def self.sub_surface_reduce_area_by_percent_by_raising_sill(sub_surface, percent_reduction)
  # Find the min and max z values
  min_z_val = 99_999
  max_z_val = -99_999
  sub_surface.vertices.each do |vertex|
    # Min z value
    if vertex.z < min_z_val
      min_z_val = vertex.z
    end
    # Max z value
    if vertex.z > max_z_val
      max_z_val = vertex.z
    end
  end

  # Calculate the window height
  height = max_z_val - min_z_val

  # Calculate the new sill height
  z_delta = height * percent_reduction

  # Reset the z value of the lowest points within a certain threshold
  new_vertices = []
  sub_surface.vertices.each do |vertex|
    if (vertex.z - min_z_val).abs < 0.025
      new_vertices << (vertex + OpenStudio::Vector3d.new(0.0, 0.0, z_delta))
    else
      new_vertices << vertex
    end
  end

  # Reset the vertices
  sub_surface.setVertices(new_vertices)

  return true
end
sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(sub_surface, percent_reduction) click to toggle source

Reduce the area of the subsurface by shrinking it toward the centroid @author Julien Marrec

@param sub_surface [OpenStudio::Model::SubSurface] OpenStudio SubSurface object @param percent_reduction [Double] the fractional amount to reduce the area @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/geometry/modify.rb, line 14
def self.sub_surface_reduce_area_by_percent_by_shrinking_toward_centroid(sub_surface, percent_reduction)
  # if percent_reduction > 1=> percent increase instead of reduction
  mult = percent_reduction <= 1 ? 1 - percent_reduction : percent_reduction
  scale_factor = mult**0.5

  # Get the centroid (Point3d)
  g = sub_surface.centroid

  # Create an array to collect the new vertices
  new_vertices = []

  # Loop on vertices (Point3ds)
  sub_surface.vertices.each do |vertex|
    # Point3d - Point3d = Vector3d
    # Vector from centroid to vertex (GA, GB, GC, etc)
    centroid_vector = vertex - g

    # Resize the vector (done in place) according to scale_factor
    centroid_vector.setLength(centroid_vector.length * scale_factor)

    # Move the vertex toward the centroid
    vertex = g + centroid_vector

    new_vertices << vertex
  end

  # Assign the new vertices to the self
  sub_surface.setVertices(new_vertices)

  return true
end
sub_surface_vertical_rectangle?(sub_surface) click to toggle source

Determine if the sub surface is a vertical rectangle, meaning a rectangle where the bottom is parallel to the ground.

@param sub_surface [OpenStudio::Model::SubSurface] OpenStudio SubSurface object @return [Boolean] returns true if the surface is a vertical rectangle, false if not

# File lib/openstudio-standards/geometry/information.rb, line 235
def self.sub_surface_vertical_rectangle?(sub_surface)
  # Get the vertices once
  verts = sub_surface.vertices

  # Check for 4 vertices
  return false unless verts.size == 4

  # Check if the 2 lowest z-values
  # are the same
  z_vals = []
  verts.each do |vertex|
    z_vals << vertex.z
  end
  z_vals = z_vals.sort
  return false unless z_vals[0] == z_vals[1]

  # Check if the diagonals are equal length
  diag_a = verts[0] - verts[2]
  diag_b = verts[1] - verts[3]
  return false unless diag_a.length == diag_b.length

  # If here, we have a rectangle
  return true
end
surface_get_absolute_azimuth(surface) click to toggle source

Calculate a surface’s absolute azimuth

@param surface [OpenStudio::Model::Surface] OpenStudio Surface object @return [Double] surface absolute azimuth in degrees

# File lib/openstudio-standards/geometry/information.rb, line 136
def self.surface_get_absolute_azimuth(surface)
  # Get associated space
  space = surface.space.get

  # Get model object
  model = surface.model

  # Calculate azimuth
  surface_azimuth_rel_space = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get
  space_dir_rel_north = space.directionofRelativeNorth
  building_dir_rel_north = model.getBuilding.northAxis
  surface_abs_azimuth = surface_azimuth_rel_space + space_dir_rel_north + building_dir_rel_north
  surface_abs_azimuth -= 360.0 until surface_abs_azimuth < 360.0

  return surface_abs_azimuth
end
surface_get_cardinal_direction(surface) click to toggle source

Determine a surface absolute cardinal direction

@param surface [OpenStudio::Model::Surface] OpenStudio Surface object @return [String] surface absolute cardinal direction, ‘N’, ‘E’, ‘S, ’W’

# File lib/openstudio-standards/geometry/information.rb, line 157
def self.surface_get_cardinal_direction(surface)
  # Get the surface's absolute azimuth
  surface_abs_azimuth = OpenstudioStandards::Geometry.surface_get_absolute_azimuth(surface)

  # Determine the surface's cardinal direction
  cardinal_direction = ''
  if (surface_abs_azimuth >= 0 && surface_abs_azimuth <= 45) || (surface_abs_azimuth > 315 && surface_abs_azimuth <= 360)
    cardinal_direction = 'N'
  elsif surface_abs_azimuth > 45 && surface_abs_azimuth <= 135
    cardinal_direction = 'E'
  elsif surface_abs_azimuth > 135 && surface_abs_azimuth <= 225
    cardinal_direction = 'S'
  elsif surface_abs_azimuth > 225 && surface_abs_azimuth <= 315
    cardinal_direction = 'W'
  end

  return cardinal_direction
end
surface_get_door_to_wall_ratio(surface) click to toggle source

Calculate the door to wall ratio of a surface

@param surface [OpenStudio::Model::Surface] OpenStudio Surface object @return [Double] door to wall ratio of a surface

# File lib/openstudio-standards/geometry/information.rb, line 121
def self.surface_get_door_to_wall_ratio(surface)
  surface_area = surface.grossArea
  surface_door_area = 0.0
  surface.subSurfaces.sort.each do |ss|
    next unless ss.subSurfaceType == 'Door'

    surface_door_area += ss.netArea
  end
  return surface_door_area / surface_area
end
surface_get_edges(surface) click to toggle source

Returns an array of OpenStudio::Point3D pairs of an OpenStudio::Model::Surface’s edges. Used to calculate surface intersections.

@param surface OpenStudio surface object @return [Array<Array(OpenStudio::Point3D, OpenStudio::Point3D)>] Array of pair of points describing the line segment of an edge

# File lib/openstudio-standards/geometry/information.rb, line 78
def self.surface_get_edges(surface)
  vertices = surface.vertices
  n_vertices = vertices.length

  # Create edge hash that keeps track of all edges in surface. An edge is defined here as an array of length 2
  # containing two OpenStudio::Point3Ds that define the line segment representing a surface edge.
  # format edge_array[i] = [OpenStudio::Point3D, OpenStudio::Point3D]
  edge_array = []

  # Iterate through each vertex in the surface and construct an edge for it
  for edge_counter in 0..n_vertices - 1

    # If not the last vertex in surface
    if edge_counter < n_vertices - 1
      edge_array << [vertices[edge_counter], vertices[edge_counter + 1]]
    else
      # Make index adjustments for final index in vertices array
      edge_array << [vertices[edge_counter], vertices[0]]
    end
  end

  return edge_array
end
surface_get_window_to_wall_ratio(surface) click to toggle source

Calculate the window to wall ratio of a surface

@param surface [OpenStudio::Model::Surface] OpenStudio Surface object @return [Double] window to wall ratio of a surface

# File lib/openstudio-standards/geometry/information.rb, line 106
def self.surface_get_window_to_wall_ratio(surface)
  surface_area = surface.grossArea
  surface_fene_area = 0.0
  surface.subSurfaces.sort.each do |ss|
    next unless ss.subSurfaceType == 'FixedWindow' || ss.subSurfaceType == 'OperableWindow' || ss.subSurfaceType == 'GlassDoor'

    surface_fene_area += ss.netArea
  end
  return surface_fene_area / surface_area
end
surfaces_contain_point?(surfaces, point) click to toggle source

Check if a point is contained on any surface in an array of surfaces

@param surfaces [Array<OpenStudio::Model::Surface>] Array of OpenStudio Surface objects @param point [OpenStudio::Point3d] Point3d object @return [Boolean] true if on a surface in surface array, false if not

# File lib/openstudio-standards/geometry/information.rb, line 205
def self.surfaces_contain_point?(surfaces, point)
  on_surface = false

  surfaces.each do |surface|
    # Check if sensor is on floor plane (I need to loop through all floors)
    plane = surface.plane
    point_on_plane = plane.project(point)

    face_transform = OpenStudio::Transformation.alignFace(surface.vertices)
    face_vertices = face_transform * surface.vertices
    face_point_on_plane = face_transform * point_on_plane

    if OpenStudio.pointInPolygon(face_point_on_plane, face_vertices.reverse, 0.01)
      # initial_sensor location lands in this surface's polygon
      on_surface = true
    end
  end

  return on_surface
end
surfaces_get_z_values(surfaces) click to toggle source

return an array of z values for surfaces passed in. The values will be relative to the parent origin.

@param surfaces [Array<OpenStudio::Model::Surface>] Array of OpenStudio Surface objects @return [Array<Double>] array of z values in meters

# File lib/openstudio-standards/geometry/information.rb, line 184
def self.surfaces_get_z_values(surfaces)
  z_values = []

  # loop over all surfaces
  surfaces.each do |surface|
    # get the existing vertices
    vertices = surface.vertices
    vertices.each do |vertex|
      # push z value to array
      z_values << vertex.z
    end
  end

  return z_values
end
thermal_zone_get_adjacent_zones_with_shared_walls(thermal_zone, same_floor: true) click to toggle source

Return an array of zones that share a wall with the zone

@param thermal_zone [OpenStudio::Model::ThermalZone] OpenStudio ThermalZone object @param same_floor [Boolean] only valid option for now is true @return [Array<OpenStudio::Model::ThermalZone>] Array of OpenStudio ThermalZone objects

# File lib/openstudio-standards/geometry/information.rb, line 629
def self.thermal_zone_get_adjacent_zones_with_shared_walls(thermal_zone, same_floor: true)
  adjacent_zones = []

  thermal_zone.spaces.each do |space|
    adj_spaces = OpenstudioStandards::Geometry.space_get_adjacent_spaces_with_shared_wall_areas(space, same_floor: same_floor)
    adj_spaces.each do |k, v|
      # skip if space is in current thermal zone.
      next unless space.thermalZone.is_initialized
      next if k.thermalZone.get == thermal_zone

      adjacent_zones << k.thermalZone.get
    end
  end

  adjacent_zones = adjacent_zones.uniq

  return adjacent_zones
end
thermal_zones_get_number_of_stories_spanned(thermal_zones) click to toggle source

Determine the number of stories spanned by the supplied thermal zones. If all zones on one of the stories have an identical multiplier, assume that the multiplier is a floor multiplier and increase the number of stories accordingly. Stories do not have to be contiguous.

@param thermal_zones [Array<OpenStudio::Model::ThermalZone>] An array of OpenStudio ThermalZone objects @return [Integer] The number of stories spanned by the thermal zones

# File lib/openstudio-standards/geometry/information.rb, line 659
def self.thermal_zones_get_number_of_stories_spanned(thermal_zones)
  # Get the story object for all zones
  stories = []
  thermal_zones.each do |zone|
    zone.spaces.each do |space|
      story = space.buildingStory
      next if story.empty?

      stories << story.get
    end
  end

  # Reduce down to the unique set of stories
  stories = stories.uniq

  # Tally up stories including multipliers
  num_stories = 0
  stories.each do |story|
    num_stories += OpenstudioStandards::Geometry.building_story_get_floor_multiplier(story)
  end

  return num_stories
end
wall_and_floor_intersection_length(wall, floor) click to toggle source

This function returns the length of intersection between a wall and floor sharing space. Primarily used for FFactorGroundFloorConstruction exposed perimeter calculations. @note this calculation has a few assumptions:

  • Floors are flat. This means they have a constant z-axis value.

  • If a wall shares an edge with a floor, it’s assumed that edge intersects with only this floor.

  • The wall and floor share a common space. This space is assumed to only have one floor!

@param wall wall surface being compared to the floor of interest @param floor floor occupying same space as wall. Edges checked for interesections with wall @return [Double] returns the intersection/overlap length of the wall and floor in meters

# File lib/openstudio-standards/geometry/information.rb, line 31
def self.wall_and_floor_intersection_length(wall, floor)
  # Used for determining if two points are 'equal' if within this length
  tolerance = 0.0001

  # Get floor and wall edges
  wall_edge_array = OpenstudioStandards::Geometry.surface_get_edges(wall)
  floor_edge_array = OpenstudioStandards::Geometry.surface_get_edges(floor)

  # Floor assumed flat and constant in x-y plane (i.e. a single z value)
  floor_z_value = floor_edge_array[0][0].z

  # Iterate through wall edges
  wall_edge_array.each do |wall_edge|
    wall_edge_p1 = wall_edge[0]
    wall_edge_p2 = wall_edge[1]

    # If points representing the wall surface edge have different z-coordinates, this edge is not parallel to the
    # floor and can be skipped

    if tolerance <= (wall_edge_p1.z - wall_edge_p2.z).abs
      next
    end

    # If wall edge is parallel to the floor, ensure it's on the same x-y plane as the floor.
    if tolerance <= (wall_edge_p1.z - floor_z_value).abs
      next
    end

    # If the edge is parallel with the floor and in the same x-y plane as the floor, assume an intersection the
    # length of the wall edge
    intersect_vector = wall_edge_p1 - wall_edge_p2
    edge_vector = OpenStudio::Vector3d.new(intersect_vector.x, intersect_vector.y, intersect_vector.z)
    return(edge_vector.length)
  end

  # If no edges intersected, return 0
  return 0.0
end