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
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
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
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
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
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 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
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
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
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
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
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 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 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 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 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 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 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 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 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 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 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 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Calculate the space envelope area. According to the 90.1 definition, building envelope include:
-
“the elements of a building that separate conditioned spaces from the exterior”
-
“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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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