openstudio-model-articulation 0.4.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -1
  3. data/CHANGELOG.md +24 -0
  4. data/Gemfile +2 -2
  5. data/README.md +22 -3
  6. data/lib/measures/AssignConstructionSetToBuilding/measure.xml +10 -10
  7. data/lib/measures/AssignSpaceTypeBySpaceName/measure.xml +27 -9
  8. data/lib/measures/AssignSpaceTypeToBuilding/measure.xml +10 -10
  9. data/lib/measures/AssignSpacesToStories/measure.rb +2 -4
  10. data/lib/measures/AssignSpacesToStories/measure.xml +10 -10
  11. data/lib/measures/BarAspectRatioStudy/measure.rb +2 -1
  12. data/lib/measures/BarAspectRatioStudy/measure.xml +4 -4
  13. data/lib/measures/CleanupSpaceOrigins/measure.rb +3 -0
  14. data/lib/measures/CleanupSpaceOrigins/measure.xml +10 -10
  15. data/lib/measures/FindAndReplaceObjectNames/measure.xml +8 -8
  16. data/lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.rb +2 -2
  17. data/lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.xml +22 -4
  18. data/lib/measures/RemoveHardAssignedConstructions/measure.xml +8 -8
  19. data/lib/measures/RenameSpaceSurfacesBasedonParentSpaceandOrientation/measure.xml +37 -7
  20. data/lib/measures/RotateBuilding/measure.xml +8 -8
  21. data/lib/measures/SetExtWallToGroundBoundaryConditionByStory/measure.xml +13 -7
  22. data/lib/measures/SetInteriorWallsAndFloorsToAdiabatic/measure.rb +1 -1
  23. data/lib/measures/SetInteriorWallsAndFloorsToAdiabatic/measure.xml +10 -10
  24. data/lib/measures/SetInteriorWallsToSelectedConstruction/measure.rb +1 -1
  25. data/lib/measures/SetInteriorWallsToSelectedConstruction/measure.xml +10 -10
  26. data/lib/measures/SetWindowToWallRatioByFacade/README.md +11 -0
  27. data/lib/measures/SetWindowToWallRatioByFacade/measure.rb +90 -142
  28. data/lib/measures/SetWindowToWallRatioByFacade/measure.xml +32 -11
  29. data/lib/measures/SetWindowToWallRatioByFacade/resources/functions.rb +176 -0
  30. data/lib/measures/SimplifyGeometryToSlicedBar/measure.rb +8 -4
  31. data/lib/measures/SimplifyGeometryToSlicedBar/measure.xml +31 -13
  32. data/lib/measures/SimplifyGeometryToSlicedBar/resources/os_lib_cofee.rb +6 -4
  33. data/lib/measures/SimplifyGeometryToSlicedBar/resources/os_lib_geometry.rb +13 -8
  34. data/lib/measures/SimplifyGeometryToSlicedBar/resources/os_lib_helper_methods.rb +2 -4
  35. data/lib/measures/SpaceTypeAndConstructionSetWizard/measure.rb +1 -1
  36. data/lib/measures/SpaceTypeAndConstructionSetWizard/measure.xml +4 -4
  37. data/lib/measures/SurfaceMatching/measure.rb +1 -0
  38. data/lib/measures/SurfaceMatching/measure.xml +10 -10
  39. data/lib/measures/add_empd_material_properties/LICENSE.md +27 -0
  40. data/lib/measures/add_empd_material_properties/README.md +116 -0
  41. data/lib/measures/add_empd_material_properties/README.md.erb +42 -0
  42. data/lib/measures/add_empd_material_properties/docs/.gitkeep +0 -0
  43. data/lib/measures/add_empd_material_properties/measure.rb +247 -0
  44. data/lib/measures/add_empd_material_properties/measure.xml +201 -0
  45. data/lib/measures/blended_space_type_from_floor_area_ratios/measure.rb +4 -3
  46. data/lib/measures/blended_space_type_from_floor_area_ratios/measure.xml +4 -4
  47. data/lib/measures/blended_space_type_from_model/{resources → docs}/replace_occupied_spaces_with_blended_space_type_design_doc.txt +0 -0
  48. data/lib/measures/blended_space_type_from_model/measure.rb +11 -3
  49. data/lib/measures/blended_space_type_from_model/measure.xml +15 -21
  50. data/lib/measures/clone_building_from_external_model/measure.xml +3 -3
  51. data/lib/measures/create_DOE_prototype_building/measure.rb +2 -2
  52. data/lib/measures/create_DOE_prototype_building/measure.xml +442 -438
  53. data/lib/measures/create_and_assign_thermal_zones_for_unassigned_spaces/measure.xml +3 -3
  54. data/lib/measures/create_bar_from_building_type_ratios/README.md +109 -35
  55. data/lib/measures/create_bar_from_building_type_ratios/README.md.erb +54 -35
  56. data/lib/measures/create_bar_from_building_type_ratios/measure.rb +8 -3
  57. data/lib/measures/create_bar_from_building_type_ratios/measure.xml +12 -12
  58. data/lib/measures/create_bar_from_deer_building_type_ratios/README.md +301 -79
  59. data/lib/measures/create_bar_from_deer_building_type_ratios/README.md.erb +62 -79
  60. data/lib/measures/create_bar_from_deer_building_type_ratios/measure.rb +9 -4
  61. data/lib/measures/create_bar_from_deer_building_type_ratios/measure.xml +15 -59
  62. data/lib/measures/create_bar_from_doe_building_type_ratios/README.md +298 -57
  63. data/lib/measures/create_bar_from_doe_building_type_ratios/README.md.erb +54 -57
  64. data/lib/measures/create_bar_from_doe_building_type_ratios/measure.rb +9 -4
  65. data/lib/measures/create_bar_from_doe_building_type_ratios/measure.xml +12 -16
  66. data/lib/measures/create_bar_from_model/measure.rb +10 -6
  67. data/lib/measures/create_bar_from_model/measure.xml +4 -4
  68. data/lib/measures/create_bar_from_space_type_ratios/README.md +94 -42
  69. data/lib/measures/create_bar_from_space_type_ratios/README.md.erb +60 -42
  70. data/lib/measures/create_bar_from_space_type_ratios/measure.rb +8 -3
  71. data/lib/measures/create_bar_from_space_type_ratios/measure.xml +15 -15
  72. data/lib/measures/create_baseline_building/measure.rb +3 -2
  73. data/lib/measures/create_baseline_building/measure.xml +4 -4
  74. data/lib/measures/create_deer_prototype_building/measure.xml +3 -3
  75. data/lib/measures/create_parametric_schedules/measure.rb +15 -10
  76. data/lib/measures/create_parametric_schedules/measure.xml +4 -4
  77. data/lib/measures/create_typical_building_from_model/README.md +81 -16
  78. data/lib/measures/create_typical_building_from_model/README.md.erb +36 -16
  79. data/lib/measures/create_typical_building_from_model/measure.rb +6 -1
  80. data/lib/measures/create_typical_building_from_model/measure.xml +19 -19
  81. data/lib/measures/create_typical_deer_building_from_model/README.md +173 -53
  82. data/lib/measures/create_typical_deer_building_from_model/README.md.erb +57 -53
  83. data/lib/measures/create_typical_deer_building_from_model/measure.rb +7 -2
  84. data/lib/measures/create_typical_deer_building_from_model/measure.xml +26 -26
  85. data/lib/measures/create_typical_doe_building_from_model/README.md +182 -44
  86. data/lib/measures/create_typical_doe_building_from_model/README.md.erb +58 -44
  87. data/lib/measures/create_typical_doe_building_from_model/measure.rb +7 -2
  88. data/lib/measures/create_typical_doe_building_from_model/measure.xml +26 -26
  89. data/lib/measures/deer_space_type_and_construction_set_wizard/measure.rb +1 -1
  90. data/lib/measures/deer_space_type_and_construction_set_wizard/measure.xml +4 -4
  91. data/lib/measures/find_and_replace_in_all_thermal_zone_names/measure.xml +3 -3
  92. data/lib/measures/make_shading_surfaces_based_on_zone_multipliers/measure.rb +2 -0
  93. data/lib/measures/make_shading_surfaces_based_on_zone_multipliers/measure.xml +4 -4
  94. data/lib/measures/merge_floorspace_js_with_model/measure.rb +4 -1
  95. data/lib/measures/merge_floorspace_js_with_model/measure.xml +4 -4
  96. data/lib/measures/merge_spaces_from_external_file/measure.xml +3 -3
  97. data/lib/measures/radiance_measure/measure.rb +62 -56
  98. data/lib/measures/radiance_measure/measure.xml +4 -4
  99. data/lib/measures/radiant_slab_with_doas/measure.rb +8 -1
  100. data/lib/measures/radiant_slab_with_doas/measure.xml +9 -9
  101. data/lib/measures/replace_geometry_by_story/measure.rb +4 -0
  102. data/lib/measures/replace_geometry_by_story/measure.xml +4 -4
  103. data/lib/measures/scale_geometry/measure.xml +3 -3
  104. data/lib/openstudio/model_articulation/version.rb +1 -1
  105. data/openstudio-model-articulation.gemspec +2 -2
  106. metadata +15 -9
  107. data/lib/measures/blended_space_type_from_model/resources/os_lib_model_simplification.rb +0 -1049
@@ -1,1049 +0,0 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2021, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
- # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
- # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
- # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
- # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
- # *******************************************************************************
35
-
36
- module OsLib_ModelSimplification
37
- # get all loads for a space_or_space_type and place in hash by type
38
- def gather_internal_loads(space_or_space_type)
39
- internal_load_hash = {}
40
-
41
- # gather different load types (all vectors except dsoa which will be turned into an array)
42
- internal_load_hash[:internal_mass] = space_or_space_type.internalMass
43
- internal_load_hash[:people] = space_or_space_type.people
44
- internal_load_hash[:lights] = space_or_space_type.lights
45
- internal_load_hash[:luminaires] = space_or_space_type.luminaires
46
- internal_load_hash[:electric_equipment] = space_or_space_type.electricEquipment
47
- internal_load_hash[:gas_equipment] = space_or_space_type.gasEquipment
48
- internal_load_hash[:hot_water_equipment] = space_or_space_type.hotWaterEquipment
49
- internal_load_hash[:steam_equipment] = space_or_space_type.steamEquipment
50
- internal_load_hash[:other_equipment] = space_or_space_type.otherEquipment
51
- internal_load_hash[:space_infiltration_design_flow_rates] = space_or_space_type.spaceInfiltrationDesignFlowRates
52
- internal_load_hash[:space_infiltration_effective_leakage_areas] = space_or_space_type.spaceInfiltrationEffectiveLeakageAreas
53
- if space_or_space_type.designSpecificationOutdoorAir.nil?
54
- internal_load_hash[:design_specification_outdoor_air] = []
55
- else
56
- internal_load_hash[:design_specification_outdoor_air] = [space_or_space_type.designSpecificationOutdoorAir]
57
- end
58
- if space_or_space_type.class.to_s == 'OpenStudio::Model::Space'
59
- internal_load_hash[:water_use_equipment] = space_or_space_type.waterUseEquipment # don't think this reports
60
- internal_load_hash[:daylighting_controls] = space_or_space_type.daylightingControls
61
- end
62
-
63
- # TODO: - warn if daylighting controls in spaces (should I alter fraction controled based on lighting per area ratio)
64
-
65
- return internal_load_hash
66
- end
67
-
68
- # blend_space_types_from_floor_area_ratio used when working from space type ratio and un-assigned space types
69
- def blend_space_types_from_floor_area_ratio(runner, model, space_type_ratio_hash)
70
- # create stub blended space type
71
- blended_space_type = OpenStudio::Model::SpaceType.new(model)
72
- blended_space_type.setName('Blended Space Type')
73
-
74
- # TODO: - inspect people instances and see if any defs are not normalized per area. If find any issue warning
75
-
76
- # gather inputs
77
- sum_of_num_people_per_m_2 = 0.0
78
- space_type_ratio_hash.each do |space_type, ratios|
79
- # get number of peple per m 2 for space type. Can do this without looking at instances
80
- sum_of_num_people_per_m_2 += space_type.getPeoplePerFloorArea(1.0)
81
- end
82
-
83
- # raw num_people_ratios
84
- sum_area_adj_num_people_ratio = 0.0
85
- space_type_ratio_hash.each do |space_type, ratios|
86
- # calculate num_people_ratios
87
- area_adj_num_people_ratio = (space_type.getPeoplePerFloorArea(1.0) / sum_of_num_people_per_m_2) * ratios[:floor_area_ratio]
88
- sum_area_adj_num_people_ratio += area_adj_num_people_ratio
89
- end
90
-
91
- # set ratios
92
- largest_space_type = nil
93
- largest_space_type_ratio = 0.00
94
- space_type_ratio_hash.each do |space_type, ratios|
95
- # calculate num_people_ratios
96
- area_adj_num_people_ratio = (space_type.getPeoplePerFloorArea(1.0) / sum_of_num_people_per_m_2) * ratios[:floor_area_ratio]
97
- normalized_area_adj_num_people_ratio = area_adj_num_people_ratio / sum_area_adj_num_people_ratio
98
-
99
- # ratios[:floor_area_ratio] is already defined
100
- ratios[:num_people_ratio] = normalized_area_adj_num_people_ratio.round(4)
101
- ratios[:ext_surface_area_ratio] = ratios[:floor_area_ratio]
102
- ratios[:ext_wall_area_ratio] = ratios[:floor_area_ratio]
103
- ratios[:volume_ratio] = ratios[:floor_area_ratio]
104
-
105
- # update largest space type values
106
- if largest_space_type.nil?
107
- largest_space_type = space_type
108
- largest_space_type_ratio = ratios[:floor_area_ratio]
109
- elsif ratios[:floor_area_ratio] > largest_space_type_ratio
110
- largest_space_type = space_type
111
- largest_space_type_ratio = ratios[:floor_area_ratio]
112
- end
113
- end
114
-
115
- if largest_space_type.nil?
116
- runner.registerError("Didn't find any space types in model matching user argument string.")
117
- return nil
118
- end
119
-
120
- # set standards info for space type based on largest ratio (for use to apply HVAC system)
121
- standards_building_type = largest_space_type.standardsBuildingType
122
- standards_space_type = largest_space_type.standardsSpaceType
123
- if standards_building_type.is_initialized
124
- blended_space_type.setStandardsBuildingType(standards_building_type.get)
125
- end
126
- if standards_space_type.is_initialized
127
- blended_space_type.setStandardsSpaceType(standards_space_type.get)
128
- end
129
-
130
- # loop therough space types to get instances from and then remove
131
- space_type_ratio_hash.each do |space_type, ratios|
132
- # blend internal loads (nil is space_hash)
133
- space_type_load_instances = blend_internal_loads(runner, model, space_type, blended_space_type, ratios, model.getBuilding.floorArea, nil)
134
- runner.registerInfo("Blending #{space_type.name.get} with floor area ratio of #{ratios[:floor_area_ratio]} and number of people ratio of #{ratios[:num_people_ratio]}.")
135
-
136
- # delete space type. Don't want to leave in model since internal loads have been removed from it
137
- space_type.remove
138
- end
139
-
140
- return blended_space_type
141
- end
142
-
143
- # takes in space type hash where each hash value is a colleciton of space types. Each collection is blended into it's own space type
144
- # If key for any collection is "Building" it will also opererate on spaces that don't have space type assigned
145
- # where a space assigned to a space type from a collection has space loads, those space loads are normalized and added to the blended space type
146
- # load instances are maintained so that they can haave unique schedules, and can have EE measures selectivly applied.
147
- def blend_space_type_collections(runner, model, space_type_hash)
148
- # loop through building type hash to create multiple blends
149
- space_type_hash.each do |collection_name, space_types|
150
- if collection_name == 'Building'
151
- space_array = model.getSpaces # use all space types, not just space types passed in
152
- else
153
- space_array = []
154
- space_types.each do |space_type|
155
- space_array.concat(space_type.spaces)
156
- end
157
- end
158
-
159
- # calculate metrics for all spaces included in building area to pass into space_type and space hash
160
- # note: in the future this may be a subset of spaces if blending into multiple space types vs. just one.
161
- collection_totals = {}
162
- collection_totals[:floor_area] = 0.0
163
- collection_totals[:num_people] = 0.0
164
- collection_totals[:ext_surface_area] = 0.0
165
- collection_totals[:ext_wall_area] = 0.0
166
- collection_totals[:volume] = 0.0
167
- space_array.each do |space|
168
- next if !space.partofTotalFloorArea
169
- collection_totals[:floor_area] += space.floorArea * space.multiplier
170
- collection_totals[:num_people] += space.numberOfPeople * space.multiplier
171
- collection_totals[:ext_surface_area] += space.exteriorArea * space.multiplier
172
- collection_totals[:ext_wall_area] += space.exteriorWallArea * space.multiplier
173
- collection_totals[:volume] += space.volume * space.multiplier
174
- end
175
- area_ip = OpenStudio.convert(collection_totals[:floor_area], 'm^2', 'ft^2').get
176
- area_ip_neat = OpenStudio.toNeatString(area_ip, 2, true)
177
- runner.registerInfo("#{collection_name} area is #{area_ip_neat} ft^2, number of people is #{collection_totals[:num_people].round(0)}.")
178
-
179
- # create hash of space types and floor area for all space types with area > 0 when spaces included in floor area
180
- # code to gather space type areas came from openstudio_results measure.
181
- space_type_hash = {}
182
- largest_space_type = nil
183
- largest_space_type_ratio = 0.00
184
- space_types.each do |space_type|
185
- next if space_type.floorArea == 0
186
- space_type_totals = {}
187
- space_type_totals[:floor_area] = 0.0
188
- space_type_totals[:num_people] = 0.0
189
- space_type_totals[:ext_surface_area] = 0.0
190
- space_type_totals[:ext_wall_area] = 0.0
191
- space_type_totals[:volume] = 0.0
192
- # loop through spaces so I can skip if not included in floor area
193
- space_type.spaces.each do |space|
194
- next if !space.partofTotalFloorArea
195
- space_type_totals[:floor_area] += space.floorArea * space.multiplier
196
- space_type_totals[:num_people] += space.numberOfPeople * space.multiplier
197
- space_type_totals[:ext_surface_area] += space.exteriorArea * space.multiplier
198
- space_type_totals[:ext_wall_area] += space.exteriorWallArea * space.multiplier
199
- space_type_totals[:volume] += space.volume * space.multiplier
200
- end
201
-
202
- # update largest space type values
203
- if largest_space_type.nil?
204
- largest_space_type = space_type
205
- largest_space_type_ratio = space_type_totals[:floor_area]
206
- elsif space_type_totals[:floor_area] > largest_space_type_ratio
207
- largest_space_type = space_type
208
- largest_space_type_ratio = space_type_totals[:floor_area]
209
- end
210
-
211
- # gather internal loads
212
- space_type_loads_hash = gather_internal_loads(space_type)
213
-
214
- # don't add to hash if no spaces used for space type are included in building area (e.g. plenum and attic)
215
- # todo - log these and decide what to do for them. Leave loads alone or remove, do they add to blend at all?
216
- next if space_type_totals[:floor_area] == 0
217
-
218
- if !space_type_totals[:floor_area] = space_type.floorArea # TODO: - not sure if these would ever show as different
219
- runner.registerWarning("Some but not all spaces of #{space_type.name} space type are not included in the building floor area. May have unexpected results")
220
- end
221
-
222
- # populate space type hash
223
- space_type_hash[space_type] = { int_loads: space_type_loads_hash, totals: space_type_totals }
224
- end
225
-
226
- # report initial condition of model
227
- runner.registerInfo("#{collection_name} accounts for #{space_type_hash.size} space types.")
228
-
229
- if collection_name == 'Building'
230
- # count area of spaces that have no space type
231
- no_space_type_area_counter = 0
232
- model.getSpaces.each do |space|
233
- if space.spaceType.empty?
234
- next if !space.partofTotalFloorArea
235
- no_space_type_area_counter += space.floorArea * space.multiplier
236
- end
237
- end
238
- floor_area_ratio = no_space_type_area_counter / collection_totals[:floor_area]
239
- if floor_area_ratio > 0
240
- runner.registerInfo("#{floor_area_ratio} fraction of building area is composed of spaces without space type assignments.")
241
- end
242
- end
243
-
244
- # report the space ratio for hard spaces
245
- space_hash = {}
246
- space_array.each do |space|
247
- next if !space.partofTotalFloorArea
248
- space_loads_hash = gather_internal_loads(space)
249
- space_totals = {}
250
- space_totals[:floor_area] = space.floorArea * space.multiplier
251
- space_totals[:num_people] = space.numberOfPeople * space.multiplier
252
- space_totals[:ext_surface_area] = space.exteriorArea * space.multiplier
253
- space_totals[:ext_wall_area] = space.exteriorWallArea * space.multiplier
254
- space_totals[:volume] = space.volume * space.multiplier
255
- if !space_loads_hash[:daylighting_controls].empty?
256
- runner.registerWarning("#{space.name} has one or more daylighting controls. Lighting loads from blended space type may affect lighting reduction from daylighting controls.")
257
- end
258
- if !space_loads_hash[:water_use_equipment].empty?
259
- runner.registerInfo("One ore more water use equipment objects are associated with space #{space.name}. This can't be moved to a space type.")
260
- end
261
- # note: If generating ratios without geometry can calculate people_ratio given space_types floor_area_ratio
262
- space_hash[space] = { int_loads: space_loads_hash, totals: space_totals }
263
- end
264
-
265
- # create stub blended space type
266
- blended_space_type = OpenStudio::Model::SpaceType.new(model)
267
- blended_space_type.setName("#{collection_name} Blended Space Type")
268
-
269
- # set standards info for space type based on largest ratio (for use to apply HVAC system)
270
- standards_building_type = largest_space_type.standardsBuildingType
271
- standards_space_type = largest_space_type.standardsSpaceType
272
- if standards_building_type.is_initialized
273
- blended_space_type.setStandardsBuildingType(standards_building_type.get)
274
- end
275
- if standards_space_type.is_initialized
276
- blended_space_type.setStandardsSpaceType(standards_space_type.get)
277
- end
278
-
279
- # values from collection hash
280
- collection_floor_area = collection_totals[:floor_area]
281
- collection_num_people = collection_totals[:num_people]
282
- collection_ext_surface_area = collection_totals[:ext_surface_area]
283
- collection_ext_wall_area = collection_totals[:ext_wall_area]
284
- collection_volume = collection_totals[:volume]
285
-
286
- # loop through space that have one or more spaces included in the building area
287
- space_type_hash.each do |space_type, hash|
288
- # hard assign space load schedules before re-assign instances to blended space type
289
- space_type.hardApplySpaceLoadSchedules
290
-
291
- # vaules from space or space_type
292
- floor_area = hash[:totals][:floor_area]
293
- num_people = hash[:totals][:num_people]
294
- ext_surface_area = hash[:totals][:ext_surface_area]
295
- ext_wall_area = hash[:totals][:ext_wall_area]
296
- volume = hash[:totals][:volume]
297
-
298
- # ratios
299
- ratios = {}
300
- if collection_floor_area > 0
301
- ratios[:floor_area_ratio] = floor_area / collection_floor_area
302
- else
303
- ratios[:floor_area_ratio] = 0.0
304
- end
305
- if collection_num_people > 0
306
- ratios[:num_people_ratio] = num_people / collection_num_people
307
- else
308
- ratios[:num_people_ratio] = 0.0
309
- end
310
- if collection_ext_surface_area > 0
311
- ratios[:ext_surface_area_ratio] = ext_surface_area / collection_ext_surface_area
312
- else
313
- ratios[:ext_surface_area_ratio] = 0.0
314
- end
315
- if collection_ext_wall_area > 0
316
- ratios[:ext_wall_area_ratio] = ext_wall_area / collection_ext_wall_area
317
- else
318
- ratios[:ext_wall_area_ratio] = 0.0
319
- end
320
- if collection_volume > 0
321
- ratios[:volume_ratio] = volume / collection_volume
322
- else
323
- ratios[:volume_ratio] = 0.0
324
- end
325
-
326
- # populate blended space type with space type loads
327
- space_type_load_instances = blend_internal_loads(runner, model, space_type, blended_space_type, ratios, collection_floor_area, space_hash)
328
- runner.registerInfo("Blending space type #{space_type.name}. Floor area ratio is #{(hash[:totals][:floor_area] / collection_totals[:floor_area]).round(3)}. People ratio is #{(hash[:totals][:num_people] / collection_totals[:num_people]).round(3)}")
329
-
330
- # hard assign any constructions assigned by space types, except for space not included in the building area
331
- if space_type.defaultConstructionSet.is_initialized
332
- runner.registerInfo("Hard assigning constructions for #{space_type.name}.")
333
- space_type.spaces.each(&:hardApplyConstructions)
334
- end
335
-
336
- # remove all space type assignments, except for spaces not included in building area.
337
- space_type.spaces.each do |space|
338
- next if !space.partofTotalFloorArea
339
- space.resetSpaceType
340
- end
341
-
342
- # delete space type. Don't want to leave in model since internal loads have been removed from it
343
- space_type.remove
344
- end
345
-
346
- # loop through spaces that are included in building area
347
- space_hash.each do |space, hash|
348
- # hard assign space load schedules before re-assign instances to blended space type
349
- space.hardApplySpaceLoadSchedules
350
-
351
- # vaules from space or space_type
352
- floor_area = hash[:totals][:floor_area]
353
- num_people = hash[:totals][:num_people]
354
- ext_surface_area = hash[:totals][:ext_surface_area]
355
- ext_wall_area = hash[:totals][:ext_wall_area]
356
- volume = hash[:totals][:volume]
357
-
358
- # ratios
359
- ratios = {}
360
- if collection_floor_area > 0
361
- ratios[:floor_area_ratio] = floor_area / collection_floor_area
362
- else
363
- ratios[:floor_area_ratio] = 0.0
364
- end
365
- if collection_num_people > 0
366
- ratios[:num_people_ratio] = num_people / collection_num_people
367
- else
368
- ratios[:num_people_ratio] = 0.0
369
- end
370
- if collection_ext_surface_area > 0
371
- ratios[:ext_surface_area_ratio] = ext_surface_area / collection_ext_surface_area
372
- else
373
- ratios[:ext_surface_area_ratio] = 0.0
374
- end
375
- if collection_ext_wall_area > 0
376
- ratios[:ext_wall_area_ratio] = ext_wall_area / collection_ext_wall_area
377
- else
378
- ratios[:ext_wall_area_ratio] = 0.0
379
- end
380
- if collection_volume > 0
381
- ratios[:volume_ratio] = volume / collection_volume
382
- else
383
- ratios[:volume_ratio] = 0.0
384
- end
385
-
386
- # populate blended space type with space loads
387
- space_load_instances = blend_internal_loads(runner, model, space, blended_space_type, ratios, collection_floor_area, space_hash)
388
- next if space_load_instances.empty?
389
- runner.registerInfo("Blending space #{space.name}. Floor area ratio is #{(hash[:totals][:floor_area] / collection_totals[:floor_area]).round(3)}. People ratio is #{(hash[:totals][:num_people] / collection_totals[:num_people]).round(3)}")
390
- end
391
-
392
- if collection_name == 'Building'
393
- # assign blended space type to building
394
- model.getBuilding.setSpaceType(blended_space_type)
395
- building_space_type = model.getBuilding.spaceType
396
- else
397
- space_array.each do |space|
398
- space.setSpaceType(blended_space_type)
399
- end
400
- end
401
- end
402
-
403
- return model.getSpaceTypes
404
- end
405
-
406
- # blend internal loads used when working from existing model
407
- def blend_internal_loads(runner, model, source_space_or_space_type, target_space_type, ratios, collection_floor_area, space_hash)
408
- # ratios
409
- floor_area_ratio = ratios[:floor_area_ratio]
410
- num_people_ratio = ratios[:num_people_ratio]
411
- ext_surface_area_ratio = ratios[:ext_surface_area_ratio]
412
- ext_wall_area_ratio = ratios[:ext_wall_area_ratio]
413
- volume_ratio = ratios[:volume_ratio]
414
-
415
- # for normalizing design level loads I need to know effective number of spaces instance is applied to
416
- if source_space_or_space_type.to_Space.is_initialized
417
- eff_num_spaces = source_space_or_space_type.multiplier
418
- else
419
- eff_num_spaces = 0
420
- source_space_or_space_type.spaces.each do |space|
421
- eff_num_spaces += space.multiplier
422
- end
423
- end
424
-
425
- # array of load instacnes re-assigned to blended space
426
- instances_array = []
427
-
428
- # internal_mass
429
- source_space_or_space_type.internalMass.each do |load_inst|
430
- load_def = load_inst.definition.to_InternalMassDefinition.get
431
- if load_def.surfaceArea.is_initialized
432
- # edit and assign a clone of definition and normalize per area based on floor area ratio
433
- if collection_floor_area == 0
434
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
435
- else
436
- cloned_load_def = load_def.clone(model).to_InternalMass.get
437
- orig_design_level = cloned_load_def.surfaceArea.get
438
- cloned_load_def.setSurfaceAreaperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
439
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} m^2.")
440
- load_inst.setInternalMassDefinition(cloned_load_def)
441
- end
442
- elsif load_def.surfaceAreaperSpaceFloorArea.is_initialized
443
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
444
- elsif load_def.surfaceAreaperPerson.is_initialized
445
- if num_people_ratio.nil?
446
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
447
- return false
448
- else
449
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
450
- end
451
- else
452
- runner.registerError("Unexpected value type for #{load_def.name}")
453
- return false
454
- end
455
- load_inst.setSpaceType(target_space_type)
456
- instances_array << load_inst
457
- end
458
-
459
- # people
460
- source_space_or_space_type.people.each do |load_inst|
461
- load_def = load_inst.definition.to_PeopleDefinition.get
462
- if load_def.numberofPeople.is_initialized
463
- # edit and assign a clone of definition and normalize per area based on floor area ratio
464
- if collection_floor_area == 0
465
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
466
- else
467
- cloned_load_def = load_def.clone(model).to_PeopleDefinition.get
468
- orig_design_level = cloned_load_def.numberofPeople.get
469
- cloned_load_def.setPeopleperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
470
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} people.")
471
- load_inst.setPeopleDefinition(cloned_load_def)
472
- end
473
- elsif load_def.peopleperSpaceFloorArea.is_initialized
474
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
475
- elsif load_def.spaceFloorAreaperPerson.is_initialized
476
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
477
- else
478
- runner.registerError("Unexpected value type for #{load_def.name}")
479
- return false
480
- end
481
- load_inst.setSpaceType(target_space_type)
482
- instances_array << load_inst
483
- end
484
-
485
- # lights
486
- source_space_or_space_type.lights.each do |load_inst|
487
- load_def = load_inst.definition.to_LightsDefinition.get
488
- if load_def.lightingLevel.is_initialized
489
- # edit and assign a clone of definition and normalize per area based on floor area ratio
490
- if collection_floor_area == 0
491
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
492
- else
493
- cloned_load_def = load_def.clone(model).to_LightsDefinition.get
494
- orig_design_level = cloned_load_def.lightingLevel.get
495
- cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
496
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
497
- load_inst.setLightsDefinition(cloned_load_def)
498
- end
499
- elsif load_def.wattsperSpaceFloorArea.is_initialized
500
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
501
- elsif load_def.wattsperPerson.is_initialized
502
- if num_people_ratio.nil?
503
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
504
- return false
505
- else
506
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
507
- end
508
- else
509
- runner.registerError("Unexpected value type for #{load_def.name}")
510
- return false
511
- end
512
- load_inst.setSpaceType(target_space_type)
513
- instances_array << load_inst
514
- end
515
-
516
- # luminaires
517
- source_space_or_space_type.luminaires.each do |load_inst|
518
- # TODO: - can't normalize luminaire. Replace it with similar normalized lights def and instance
519
- runner.registerWarning("Can't area normalize luminaire. Instance will be applied to every space using the blended space type")
520
- instances_array << load_inst
521
- end
522
-
523
- # electric_equipment
524
- source_space_or_space_type.electricEquipment.each do |load_inst|
525
- load_def = load_inst.definition.to_ElectricEquipmentDefinition.get
526
- if load_def.designLevel.is_initialized
527
- # edit and assign a clone of definition and normalize per area based on floor area ratio
528
- if collection_floor_area == 0
529
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
530
- else
531
- cloned_load_def = load_def.clone(model).to_ElectricEquipmentDefinition.get
532
- orig_design_level = cloned_load_def.designLevel.get
533
- cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
534
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
535
- load_inst.setElectricEquipmentDefinition(cloned_load_def)
536
- end
537
- elsif load_def.wattsperSpaceFloorArea.is_initialized
538
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
539
- elsif load_def.wattsperPerson.is_initialized
540
- if num_people_ratio.nil?
541
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
542
- return false
543
- else
544
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
545
- end
546
- else
547
- runner.registerError("Unexpected value type for #{load_def.name}")
548
- return false
549
- end
550
- load_inst.setSpaceType(target_space_type)
551
- instances_array << load_inst
552
- end
553
-
554
- # gas_equipment
555
- source_space_or_space_type.gasEquipment.each do |load_inst|
556
- load_def = load_inst.definition.to_GasEquipmentDefinition.get
557
- if load_def.designLevel.is_initialized
558
- # edit and assign a clone of definition and normalize per area based on floor area ratio
559
- if collection_floor_area == 0
560
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
561
- else
562
- cloned_load_def = load_def.clone(model).to_GasEquipmentDefinition.get
563
- orig_design_level = cloned_load_def.designLevel.get
564
- cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
565
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
566
- load_inst.setGasEquipmentDefinition(cloned_load_def)
567
- end
568
- elsif load_def.wattsperSpaceFloorArea.is_initialized
569
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
570
- elsif load_def.wattsperPerson.is_initialized
571
- if num_people_ratio.nil?
572
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
573
- return false
574
- else
575
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
576
- end
577
- else
578
- runner.registerError("Unexpected value type for #{load_def.name}")
579
- return false
580
- end
581
- load_inst.setSpaceType(target_space_type)
582
- instances_array << load_inst
583
- end
584
-
585
- # hot_water_equipment
586
- source_space_or_space_type.hotWaterEquipment.each do |load_inst|
587
- load_def = load_inst.definition.to_HotWaterDefinition.get
588
- if load_def.designLevel.is_initialized
589
- # edit and assign a clone of definition and normalize per area based on floor area ratio
590
- if collection_floor_area == 0
591
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
592
- else
593
- cloned_load_def = load_def.clone(model).to_HotWaterEquipmentDefinition.get
594
- orig_design_level = cloned_load_def.designLevel.get
595
- cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
596
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
597
- load_inst.setHotWaterEquipmentDefinition(cloned_load_def)
598
- end
599
- elsif load_def.wattsperSpaceFloorArea.is_initialized
600
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
601
- elsif load_def.wattsperPerson.is_initialized
602
- if num_people_ratio.nil?
603
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
604
- return false
605
- else
606
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
607
- end
608
- else
609
- runner.registerError("Unexpected value type for #{load_def.name}")
610
- return false
611
- end
612
- load_inst.setSpaceType(target_space_type)
613
- instances_array << load_inst
614
- end
615
-
616
- # steam_equipment
617
- source_space_or_space_type.steamEquipment.each do |load_inst|
618
- load_def = load_inst.definition.to_SteamDefinition.get
619
- if load_def.designLevel.is_initialized
620
- # edit and assign a clone of definition and normalize per area based on floor area ratio
621
- if collection_floor_area == 0
622
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
623
- else
624
- cloned_load_def = load_def.clone(model).to_SteamEquipmentDefinition.get
625
- orig_design_level = cloned_load_def.designLevel.get
626
- cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
627
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
628
- load_inst.setSteamEquipmentDefinition(cloned_load_def)
629
- end
630
- elsif load_def.wattsperSpaceFloorArea.is_initialized
631
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
632
- elsif load_def.wattsperPerson.is_initialized
633
- if num_people_ratio.nil?
634
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
635
- return false
636
- else
637
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
638
- end
639
- else
640
- runner.registerError("Unexpected value type for #{load_def.name}")
641
- return false
642
- end
643
- load_inst.setSpaceType(target_space_type)
644
- instances_array << load_inst
645
- end
646
-
647
- # other_equipment
648
- source_space_or_space_type.otherEquipment.each do |load_inst|
649
- load_def = load_inst.definition.to_OtherDefinition.get
650
- if load_def.designLevel.is_initialized
651
- # edit and assign a clone of definition and normalize per area based on floor area ratio
652
- if collection_floor_area == 0
653
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
654
- else
655
- cloned_load_def = load_def.clone(model).to_OtherEquipmentDefinition.get
656
- orig_design_level = cloned_load_def.designLevel.get
657
- cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
658
- cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
659
- load_inst.setOtherEquipmentDefinition(cloned_load_def)
660
- end
661
- elsif load_def.wattsperSpaceFloorArea.is_initialized
662
- load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
663
- elsif load_def.wattsperPerson.is_initialized
664
- if num_people_ratio.nil?
665
- runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
666
- return false
667
- else
668
- load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
669
- end
670
- else
671
- runner.registerError("Unexpected value type for #{load_def.name}")
672
- return false
673
- end
674
- load_inst.setSpaceType(target_space_type)
675
- instances_array << load_inst
676
- end
677
-
678
- # space_infiltration_design_flow_rates
679
- source_space_or_space_type.spaceInfiltrationDesignFlowRates.each do |load_inst|
680
- if load_inst.designFlowRateCalculationMethod == 'Flow/Space'
681
- # edit load so normalized for building area
682
- if collection_floor_area == 0
683
- runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
684
- else
685
- orig_design_level = load_inst.designFlowRate.get
686
- load_inst.setFlowperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
687
- load_inst.setName("#{load_inst.name} - pre-normalized value was #{orig_design_level} m^3/sec")
688
- end
689
- elsif load_inst.designFlowRateCalculationMethod == 'Flow/Area'
690
- load_inst.setFlowperSpaceFloorArea(load_inst.flowperSpaceFloorArea.get * floor_area_ratio)
691
- elsif load_inst.designFlowRateCalculationMethod == 'Flow/ExteriorArea'
692
- load_inst.setFlowperExteriorSurfaceArea(load_inst.flowperExteriorSurfaceArea.get * ext_surface_area_ratio)
693
- elsif load_inst.designFlowRateCalculationMethod == 'Flow/ExteriorWallArea'
694
- load_inst.setFlowperExteriorWallArea(load_inst.flowperExteriorWallArea.get * ext_wall_area_ratio)
695
- elsif load_inst.designFlowRateCalculationMethod == 'AirChanges/Hour'
696
- load_inst.setAirChangesperHour (load_inst.airChangesperHour.get * volume_ratio)
697
- else
698
- runner.registerError("Unexpected value type for #{load_inst.name}")
699
- return false
700
- end
701
- load_inst.setSpaceType(target_space_type)
702
- instances_array << load_inst
703
- end
704
-
705
- # space_infiltration_effective_leakage_areas
706
- source_space_or_space_type.spaceInfiltrationEffectiveLeakageAreas.each do |load|
707
- # TODO: - can't normalize space_infiltration_effective_leakage_areas. Come up with logic to address this
708
- runner.registerWarning("Can't area normalize space_infiltration_effective_leakage_areas. It will be applied to every space using the blended space type")
709
- load.setSpaceType(target_space_type)
710
- instances_array << load
711
- end
712
-
713
- # add OA object if it doesn't already exist
714
- if target_space_type.designSpecificationOutdoorAir.is_initialized
715
- blended_oa = target_space_type.designSpecificationOutdoorAir.get
716
- else
717
- blended_oa = OpenStudio::Model::DesignSpecificationOutdoorAir.new(model)
718
- blended_oa.setName('Blended OA')
719
- blended_oa.setOutdoorAirMethod('Sum')
720
- target_space_type.setDesignSpecificationOutdoorAir(blended_oa)
721
- instances_array << blended_oa
722
- end
723
-
724
- # update OA object
725
- if source_space_or_space_type.designSpecificationOutdoorAir.is_initialized
726
- oa = source_space_or_space_type.designSpecificationOutdoorAir.get
727
- oa_sch = nil
728
- if oa.outdoorAirFlowRateFractionSchedule.is_initialized
729
- # TODO: - improve logic to address multiple schedules
730
- runner.registerWarning("Schedule #{oa.outdoorAirFlowRateFractionSchedule.get.name} assigned to #{oa.name} will be ignored. New OA object will not have a schedule assigned")
731
- end
732
- if oa.outdoorAirMethod == 'Maximum'
733
- # TODO: - see if way to address this by pre-calculating the max and only entering that value for space type
734
- runner.registerWarning("Outdoor air method of Maximum will be ignored for #{oa.name}. New OA object will have outdoor air method of Sum.")
735
- end
736
- # adjusted ratios for oa (lowered for space type if there is hard assigned oa load for one or more spaces)
737
- oa_floor_area_ratio = floor_area_ratio
738
- oa_num_people_ratio = num_people_ratio
739
- if source_space_or_space_type.class.to_s == 'OpenStudio::Model::SpaceType'
740
- source_space_or_space_type.spaces.each do |space|
741
- if !space.isDesignSpecificationOutdoorAirDefaulted
742
- if space_hash.nil?
743
- runner.registerWarning('No space_hash passed in and model has OA designed at space level.')
744
- else
745
- oa_floor_area_ratio -= space_hash[space][:floor_area_ratio]
746
- oa_num_people_ratio -= space_hash[space][:num_people_ratio]
747
- end
748
- end
749
- end
750
- end
751
- # add to values of blended OA load
752
- if oa.outdoorAirFlowperPerson > 0
753
- blended_oa.setOutdoorAirFlowperPerson(blended_oa.outdoorAirFlowperPerson + oa.outdoorAirFlowperPerson * oa_num_people_ratio)
754
- end
755
- if oa.outdoorAirFlowperFloorArea > 0
756
- blended_oa.setOutdoorAirFlowperFloorArea(blended_oa.outdoorAirFlowperFloorArea + oa.outdoorAirFlowperFloorArea * oa_floor_area_ratio)
757
- end
758
- if oa.outdoorAirFlowRate > 0
759
-
760
- # calculate quantity for instance (doesn't exist as a method in api)
761
- if source_space_or_space_type.class.to_s == 'OpenStudio::Model::SpaceType'
762
- quantity = 0
763
- source_space_or_space_type.spaces.each do |space|
764
- if !space.isDesignSpecificationOutdoorAirDefaulted
765
- quantity += space.multiplier
766
- end
767
- end
768
- else
769
- quantity = source_space_or_space_type.multiplier
770
- end
771
-
772
- # can't normalize air flow rate, convert to air flow rate per floor area
773
- blended_oa.setOutdoorAirFlowperFloorArea(blended_oa.outdoorAirFlowperFloorArea + quantity * oa.outdoorAirFlowRate / collection_floor_area)
774
- end
775
- if oa.outdoorAirFlowAirChangesperHour > 0
776
- # floor area should be good approximation of area for multiplier
777
- blended_oa.setOutdoorAirFlowAirChangesperHour(blended_oa.outdoorAirFlowAirChangesperHour + oa.outdoorAirFlowAirChangesperHour * oa_floor_area_ratio)
778
- end
779
- end
780
-
781
- # note: water_use_equipment can't be assigned to a space type. Leave it as is, if assigned to space type
782
- # todo - if we use this measure with new geometry need to find a way to pull water use equipment loads into new model
783
-
784
- return instances_array
785
- end
786
-
787
- # sort building stories
788
- def sort_building_stories_and_get_min_multiplier(model)
789
- sorted_building_stories = {}
790
- # loop through stories
791
- model.getBuildingStorys.each do |story|
792
- story_min_z = nil
793
- # loop through spaces in story.
794
- story.spaces.each do |space|
795
- space_z_min = OsLib_Geometry.getSurfaceZValues(space.surfaces.to_a).min + space.zOrigin
796
- if story_min_z.nil? || (story_min_z > space_z_min)
797
- story_min_z = space_z_min
798
- end
799
- end
800
- sorted_building_stories[story] = story_min_z
801
- end
802
-
803
- return sorted_building_stories
804
- end
805
-
806
- # gather_envelope_data for envelope simplification
807
- def gather_envelope_data(runner, model)
808
- runner.registerInfo('Gathering envelope data.')
809
-
810
- # hash to contain envelope data
811
- envelope_data_hash = {}
812
-
813
- # used for overhang and party wall orientation catigorization
814
- facade_options = {
815
- 'northEast' => 45,
816
- 'southEast' => 125,
817
- 'southWest' => 225,
818
- 'northWest' => 315
819
- }
820
-
821
- # get building level inputs
822
- envelope_data_hash[:north_axis] = model.getBuilding.northAxis
823
- envelope_data_hash[:building_floor_area] = model.getBuilding.floorArea
824
- envelope_data_hash[:building_exterior_surface_area] = model.getBuilding.exteriorSurfaceArea
825
- envelope_data_hash[:building_exterior_wall_area] = model.getBuilding.exteriorWallArea
826
- envelope_data_hash[:building_exterior_roof_area] = envelope_data_hash[:building_exterior_surface_area] - envelope_data_hash[:building_exterior_wall_area]
827
- envelope_data_hash[:building_air_volume] = model.getBuilding.airVolume
828
- envelope_data_hash[:building_perimeter] = nil # will be applied for first story without ground walls
829
-
830
- # get bounding_box
831
- bounding_box = OpenStudio::BoundingBox.new
832
- model.getSpaces.each do |space|
833
- space.surfaces.each do |spaceSurface|
834
- bounding_box.addPoints(space.transformation * spaceSurface.vertices)
835
- end
836
- end
837
- min_x = bounding_box.minX.get
838
- min_y = bounding_box.minY.get
839
- min_z = bounding_box.minZ.get
840
- max_x = bounding_box.maxX.get
841
- max_y = bounding_box.maxY.get
842
- max_z = bounding_box.maxZ.get
843
- envelope_data_hash[:building_min_xyz] = [min_x, min_y, min_z]
844
- envelope_data_hash[:building_max_xyz] = [max_x, max_y, max_z]
845
-
846
- # add orientation specific wwr
847
- ext_surfaces_hash = OsLib_Geometry.getExteriorWindowAndWllAreaByOrientation(model, model.getSpaces.to_a)
848
- envelope_data_hash[:building_wwr_n] = ext_surfaces_hash['northWindow'] / ext_surfaces_hash['northWall']
849
- envelope_data_hash[:building_wwr_s] = ext_surfaces_hash['southWindow'] / ext_surfaces_hash['southWall']
850
- envelope_data_hash[:building_wwr_e] = ext_surfaces_hash['eastWindow'] / ext_surfaces_hash['eastWall']
851
- envelope_data_hash[:building_wwr_w] = ext_surfaces_hash['westWindow'] / ext_surfaces_hash['westWall']
852
- envelope_data_hash[:stories] = {} # each entry will be hash with buildingStory as key and attributes has values
853
- envelope_data_hash[:space_types] = {} # each entry will be hash with spaceType as key and attributes has values
854
-
855
- # as rough estimate overhang area / glazing area should be close to projection factor assuming overhang is same width as windows
856
- # will only add building shading surfaces assoicated with a sub-surface.
857
- building_overhang_area_n = 0.0
858
- building_overhang_area_s = 0.0
859
- building_overhang_area_e = 0.0
860
- building_overhang_area_w = 0.0
861
-
862
- # loop through stories based on mine z height of surfaces.
863
- sorted_stories = sort_building_stories_and_get_min_multiplier(model).sort_by { |k, v| v }
864
- sorted_stories.each do |story, story_min_z|
865
- story_min_multiplier = nil
866
- story_footprint = nil
867
- story_multiplied_floor_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'floorArea')['totalArea']
868
- # goal of footprint calc is to count multiplier for hotel room on facade,but not to count what is intended as a story multiplier
869
- story_multiplied_exterior_surface_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'exteriorArea')['totalArea']
870
- story_multiplied_exterior_wall_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'exteriorWallArea')['totalArea']
871
- story_multiplied_exterior_roof_area = story_multiplied_exterior_surface_area - story_multiplied_exterior_wall_area
872
- story_has_ground_walls = []
873
- story_has_adiabatic_walls = []
874
- story_included_in_building_area = false # will be true if any spaces on story are inclued in building area
875
- story_max_z = nil
876
-
877
- # loop through spaces for story gathering information
878
- story.spaces.each do |space|
879
- # get min multiplier value
880
- multiplier = space.multiplier
881
- if story_min_multiplier.nil? || (story_min_multiplier > multiplier)
882
- story_min_multiplier = multiplier
883
- end
884
-
885
- # calculate footprint
886
- story_footprint = story_multiplied_floor_area / story_min_multiplier
887
-
888
- # see if part of floor area
889
- if space.partofTotalFloorArea
890
- story_included_in_building_area = true
891
-
892
- # add to space type ratio hash when space is included in building floor area
893
- if space.spaceType.is_initialized
894
- space_type = space.spaceType.get
895
- space_floor_area = space.floorArea * space.multiplier
896
- if envelope_data_hash[:space_types].key?(space_type)
897
- envelope_data_hash[:space_types][space_type][:floor_area] += space_floor_area
898
- else
899
- envelope_data_hash[:space_types][space_type] = {}
900
- envelope_data_hash[:space_types][space_type][:floor_area] = space_floor_area
901
-
902
- # make hash for heating and cooling setpoints
903
- envelope_data_hash[:space_types][space_type][:htg_setpoint] = {}
904
- envelope_data_hash[:space_types][space_type][:clg_setpoint] = {}
905
-
906
- end
907
-
908
- # add heating and cooling setpoints
909
- if space.thermalZone.is_initialized && space.thermalZone.get.thermostatSetpointDualSetpoint.is_initialized
910
- thermostat = space.thermalZone.get.thermostatSetpointDualSetpoint.get
911
-
912
- # log heating schedule
913
- if thermostat.heatingSetpointTemperatureSchedule.is_initialized
914
- htg_sch = thermostat.heatingSetpointTemperatureSchedule.get
915
- if envelope_data_hash[:space_types][space_type][:htg_setpoint].key?(htg_sch)
916
- envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] += space_floor_area
917
- else
918
- envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] = space_floor_area
919
- end
920
- else
921
- runner.registerWarning("#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
922
- end
923
-
924
- # log cooling schedule
925
- if thermostat.coolingSetpointTemperatureSchedule.is_initialized
926
- clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
927
- if envelope_data_hash[:space_types][space_type][:clg_setpoint].key?(clg_sch)
928
- envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] += space_floor_area
929
- else
930
- envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] = space_floor_area
931
- end
932
- else
933
- runner.registerWarning("#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
934
- end
935
-
936
- else
937
- runner.registerWarning("#{space.name} either isn't in a thermal zone or doesn't have a thermostat assigned")
938
- end
939
-
940
- else
941
- runner.regsiterWarning("#{space.name} is included in the building floor area but isn't assigned a space type.")
942
- end
943
-
944
- end
945
-
946
- # check for walls with adiabatic and ground boundary condition
947
- space.surfaces.each do |surface|
948
- next if surface.surfaceType != 'Wall'
949
- if surface.outsideBoundaryCondition == 'Ground'
950
- story_has_ground_walls << surface
951
- elsif surface.outsideBoundaryCondition == 'Adiabatic'
952
- story_has_adiabatic_walls << surface
953
- end
954
- end
955
-
956
- # populate overhang values
957
- space.surfaces.each do |surface|
958
- surface.subSurfaces.each do |sub_surface|
959
- sub_surface.shadingSurfaceGroups.each do |shading_surface_group|
960
- shading_surface_group.shadingSurfaces.each do |shading_surface|
961
- absoluteAzimuth = OpenStudio.convert(sub_surface.azimuth, 'rad', 'deg').get + sub_surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
962
- absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
963
- # add to hash based on orientation
964
- if (facade_options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southEast']) # East overhang
965
- building_overhang_area_e += shading_surface.grossArea * space.multiplier
966
- elsif (facade_options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southWest']) # South overhang
967
- building_overhang_area_s += shading_surface.grossArea * space.multiplier
968
- elsif (facade_options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['northWest']) # West overhang
969
- building_overhang_area_w += shading_surface.grossArea * space.multiplier
970
- else # North overhang
971
- building_overhang_area_n += shading_surface.grossArea * space.multiplier
972
- end
973
- end
974
- end
975
- end
976
- end
977
-
978
- # get max z
979
- space_z_max = OsLib_Geometry.getSurfaceZValues(space.surfaces.to_a).max + space.zOrigin
980
- if story_max_z.nil? || (story_max_z > space_z_max)
981
- story_max_z = space_z_max
982
- end
983
- end
984
-
985
- # populate hash for story data
986
- envelope_data_hash[:stories][story] = {}
987
- envelope_data_hash[:stories][story][:story_min_height] = story_min_z
988
- envelope_data_hash[:stories][story][:story_max_height] = story_max_z
989
- envelope_data_hash[:stories][story][:story_min_multiplier] = story_min_multiplier
990
- envelope_data_hash[:stories][story][:story_has_ground_walls] = story_has_ground_walls
991
- envelope_data_hash[:stories][story][:story_has_adiabatic_walls] = story_has_adiabatic_walls
992
- envelope_data_hash[:stories][story][:story_included_in_building_area] = story_included_in_building_area
993
- envelope_data_hash[:stories][story][:story_footprint] = story_footprint
994
- envelope_data_hash[:stories][story][:story_multiplied_floor_area] = story_multiplied_floor_area
995
- envelope_data_hash[:stories][story][:story_exterior_surface_area] = story_multiplied_exterior_surface_area
996
- envelope_data_hash[:stories][story][:story_multiplied_exterior_wall_area] = story_multiplied_exterior_wall_area
997
- envelope_data_hash[:stories][story][:story_multiplied_exterior_roof_area] = story_multiplied_exterior_roof_area
998
-
999
- # get perimeter and adiabatic walls that appear to be party walls
1000
- perimeter_and_party_walls = OsLib_Geometry.calculate_story_exterior_wall_perimeter(runner, story, story_min_multiplier, ['Outdoors', 'Ground', 'Adiabatic'], bounding_box)
1001
- envelope_data_hash[:stories][story][:story_perimeter] = perimeter_and_party_walls[:perimeter]
1002
- envelope_data_hash[:stories][story][:story_party_walls] = []
1003
- east = false
1004
- south = false
1005
- west = false
1006
- north = false
1007
- perimeter_and_party_walls[:party_walls].each do |surface|
1008
- absoluteAzimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
1009
- absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
1010
-
1011
- # add to hash based on orientation (initially added array of sourfaces, but swtiched to just true/false flag)
1012
- if (facade_options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southEast']) # East party walls
1013
- east = true
1014
- elsif (facade_options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southWest']) # South party walls
1015
- south = true
1016
- elsif (facade_options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['northWest']) # West party walls
1017
- west = true
1018
- else # North party walls
1019
- north = true
1020
- end
1021
- end
1022
-
1023
- if east then envelope_data_hash[:stories][story][:story_party_walls] << 'east' end
1024
- if south then envelope_data_hash[:stories][story][:story_party_walls] << 'south' end
1025
- if west then envelope_data_hash[:stories][story][:story_party_walls] << 'west' end
1026
- if north then envelope_data_hash[:stories][story][:story_party_walls] << 'north' end
1027
-
1028
- # store perimeter from first story that doesn't have ground walls
1029
- if story_has_ground_walls.empty? && envelope_data_hash[:building_perimeter].nil?
1030
- envelope_data_hash[:building_perimeter] = envelope_data_hash[:stories][story][:story_perimeter]
1031
- runner.registerInfo(" * #{story.name} is the first above grade story and will be used for the building perimeter.")
1032
- end
1033
- end
1034
-
1035
- envelope_data_hash[:building_overhang_proj_factor_n] = building_overhang_area_n / ext_surfaces_hash['northWindow']
1036
- envelope_data_hash[:building_overhang_proj_factor_s] = building_overhang_area_s / ext_surfaces_hash['southWindow']
1037
- envelope_data_hash[:building_overhang_proj_factor_e] = building_overhang_area_e / ext_surfaces_hash['eastWindow']
1038
- envelope_data_hash[:building_overhang_proj_factor_w] = building_overhang_area_w / ext_surfaces_hash['westWindow']
1039
-
1040
- # warn for spaces that are not on a story (in future could infer stories for these)
1041
- model.getSpaces.each do |space|
1042
- if !space.buildingStory.is_initialized
1043
- runner.registerWarning("#{space.name} is not on a building story, may have unexpected results.")
1044
- end
1045
- end
1046
-
1047
- return envelope_data_hash
1048
- end
1049
- end