openstudio-extension 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.rubocop.yml +9 -0
- data/Gemfile +3 -1
- data/Jenkinsfile +10 -0
- data/README.md +230 -12
- data/Rakefile +88 -3
- data/bin/console +3 -3
- data/doc_templates/LICENSE.md +27 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +36 -0
- data/doc_templates/copyright_js.txt +4 -0
- data/doc_templates/copyright_ruby.txt +34 -0
- data/init_templates/README.md +37 -0
- data/init_templates/gemspec.txt +32 -0
- data/init_templates/openstudio_module.rb +50 -0
- data/init_templates/spec.rb +47 -0
- data/init_templates/spec_helper.rb +49 -0
- data/init_templates/template_gemfile.txt +17 -0
- data/init_templates/template_rakefile.txt +15 -0
- data/init_templates/version.rb +40 -0
- data/lib/files/openstudio-extension-gem-test.ddy +536 -0
- data/lib/files/openstudio-extension-gem-test.epw +8768 -0
- data/lib/files/openstudio-extension-gem-test.stat +554 -0
- data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
- data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
- data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
- data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
- data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
- data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
- data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
- data/lib/openstudio/extension.rb +220 -0
- data/lib/openstudio/extension/core/CreateResults.rb +879 -0
- data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
- data/lib/openstudio/extension/core/check_calibration.rb +155 -0
- data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
- data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
- data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
- data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
- data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
- data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
- data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
- data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
- data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
- data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
- data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
- data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
- data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
- data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
- data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
- data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
- data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
- data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
- data/lib/openstudio/extension/core/check_schedules.rb +311 -0
- data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
- data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
- data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
- data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
- data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
- data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
- data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
- data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
- data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
- data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
- data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
- data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
- data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
- data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
- data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
- data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
- data/lib/openstudio/extension/rake_task.rb +149 -0
- data/lib/openstudio/extension/runner.rb +644 -0
- data/lib/openstudio/extension/version.rb +40 -0
- data/openstudio-extension.gemspec +20 -15
- metadata +150 -14
- data/.travis.yml +0 -7
- data/lib/OpenStudio/Extension/rake_task.rb +0 -84
- data/lib/OpenStudio/Extension/version.rb +0 -33
- data/lib/OpenStudio/extension.rb +0 -65
@@ -0,0 +1,1049 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, 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
|