openstudio-extension 0.2.0 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +45 -1
- data/Jenkinsfile +2 -2
- data/README.md +22 -28
- data/Rakefile +1 -1
- data/lib/change_log.rb +138 -128
- data/lib/measures/openstudio_extension_test_measure/measure.xml +20 -19
- data/lib/openstudio/extension.rb +27 -10
- data/lib/openstudio/extension/core/CreateResults.rb +178 -1
- data/lib/openstudio/extension/core/os_lib_constructions.rb +6 -1
- data/lib/openstudio/extension/core/os_lib_geometry.rb +255 -77
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +2547 -86
- data/lib/openstudio/extension/core/os_lib_reporting.rb +133 -30
- data/lib/openstudio/extension/rake_task.rb +232 -18
- data/lib/openstudio/extension/runner.rb +32 -19
- data/lib/openstudio/extension/runner_config.rb +16 -1
- data/lib/openstudio/extension/version.rb +1 -1
- data/openstudio-extension.gemspec +12 -12
- metadata +30 -33
- data/lib/measures/openstudio_extension_test_measure/tests/openstudio_extension_test_measure_test.rb +0 -74
- data/lib/openstudio/extension/core/os_lib_cofee.rb +0 -259
@@ -35,7 +35,32 @@
|
|
35
35
|
|
36
36
|
module OsLib_ModelGeneration
|
37
37
|
# simple list of building types that are valid for get_space_types_from_building_type
|
38
|
-
|
38
|
+
# for general public use use extended = false
|
39
|
+
def get_building_types(extended = false)
|
40
|
+
|
41
|
+
# get building_types
|
42
|
+
if extended
|
43
|
+
doe = get_doe_building_types(true)
|
44
|
+
deer = get_deer_building_types(true)
|
45
|
+
else
|
46
|
+
doe = get_doe_building_types
|
47
|
+
deer = get_deer_building_types
|
48
|
+
end
|
49
|
+
|
50
|
+
# combine building_types
|
51
|
+
array = OpenStudio::StringVector.new
|
52
|
+
temp_array = doe.to_a + deer.to_a
|
53
|
+
temp_array.each do |i|
|
54
|
+
array << i
|
55
|
+
end
|
56
|
+
|
57
|
+
return array
|
58
|
+
end
|
59
|
+
|
60
|
+
# get_doe_building_types
|
61
|
+
# for general public use use extended = false
|
62
|
+
def get_doe_building_types(extended = false)
|
63
|
+
# DOE Prototypes
|
39
64
|
array = OpenStudio::StringVector.new
|
40
65
|
array << 'SecondarySchool'
|
41
66
|
array << 'PrimarySchool'
|
@@ -54,25 +79,215 @@ module OsLib_ModelGeneration
|
|
54
79
|
array << 'Hospital'
|
55
80
|
array << 'Outpatient'
|
56
81
|
array << 'SuperMarket'
|
82
|
+
array << 'Laboratory'
|
83
|
+
array << 'LargeDataCenterLowITE'
|
84
|
+
array << 'LargeDataCenterHighITE'
|
85
|
+
array << 'SmallDataCenterLowITE'
|
86
|
+
array << 'SmallDataCenterHighITE'
|
87
|
+
|
88
|
+
return array
|
89
|
+
end
|
90
|
+
|
91
|
+
# get_deer_building_types
|
92
|
+
# for general public use use extended = false
|
93
|
+
def get_deer_building_types(extended = false)
|
94
|
+
# DOE Prototypes
|
95
|
+
array = OpenStudio::StringVector.new
|
96
|
+
array << 'Asm'
|
97
|
+
array << 'DMo'
|
98
|
+
array << 'ECC'
|
99
|
+
array << 'EPr'
|
100
|
+
array << 'ERC'
|
101
|
+
array << 'ESe'
|
102
|
+
array << 'EUn'
|
103
|
+
array << 'GHs'
|
104
|
+
array << 'Gro'
|
105
|
+
array << 'Hsp'
|
106
|
+
array << 'Htl'
|
107
|
+
array << 'MBT'
|
108
|
+
array << 'MFm'
|
109
|
+
array << 'MLI'
|
110
|
+
array << 'Mtl'
|
111
|
+
array << 'Nrs'
|
112
|
+
array << 'OfL'
|
113
|
+
array << 'OfS'
|
114
|
+
array << 'RFF'
|
115
|
+
array << 'RSD'
|
116
|
+
array << 'Rt3'
|
117
|
+
array << 'RtL'
|
118
|
+
array << 'RtS'
|
119
|
+
array << 'SCn'
|
120
|
+
array << 'SFm'
|
121
|
+
array << 'SUn'
|
122
|
+
array << 'WRf'
|
57
123
|
|
58
124
|
return array
|
59
125
|
end
|
60
126
|
|
61
127
|
# simple list of templates that are valid for get_space_types_from_building_type
|
62
|
-
|
128
|
+
# for general public use use extended = false
|
129
|
+
def get_templates(extended = false)
|
130
|
+
|
131
|
+
# get templates
|
132
|
+
if extended
|
133
|
+
doe = get_doe_templates(true)
|
134
|
+
deer = get_deer_templates(true)
|
135
|
+
else
|
136
|
+
doe = get_doe_templates
|
137
|
+
deer = get_deer_templates
|
138
|
+
end
|
139
|
+
|
140
|
+
# combine templates
|
141
|
+
array = OpenStudio::StringVector.new
|
142
|
+
temp_array = doe.to_a + deer.to_a
|
143
|
+
temp_array.each do |i|
|
144
|
+
array << i
|
145
|
+
end
|
146
|
+
|
147
|
+
return array
|
148
|
+
end
|
149
|
+
|
150
|
+
# get_doe_templates
|
151
|
+
# for general public use use extended = false
|
152
|
+
def get_doe_templates(extended = false)
|
63
153
|
array = OpenStudio::StringVector.new
|
64
154
|
array << 'DOE Ref Pre-1980'
|
65
155
|
array << 'DOE Ref 1980-2004'
|
66
156
|
array << '90.1-2004'
|
67
157
|
array << '90.1-2007'
|
68
|
-
# array << '189.1-2009' # if turn this on need to update space_type_array for RetailStripmall
|
69
158
|
array << '90.1-2010'
|
70
159
|
array << '90.1-2013'
|
71
|
-
|
160
|
+
if extended
|
161
|
+
# array << '189.1-2009' # if turn this on need to update space_type_array for RetailStripmall
|
162
|
+
array << 'NREL ZNE Ready 2017'
|
163
|
+
end
|
164
|
+
|
165
|
+
return array
|
166
|
+
end
|
167
|
+
|
168
|
+
# get_deer_templates
|
169
|
+
# for general public use use extended = false
|
170
|
+
def get_deer_templates(extended = false)
|
171
|
+
array = OpenStudio::StringVector.new
|
172
|
+
array << 'DEER Pre-1975'
|
173
|
+
array << 'DEER 1985'
|
174
|
+
array << 'DEER 1996'
|
175
|
+
array << 'DEER 2003'
|
176
|
+
array << 'DEER 2007'
|
177
|
+
array << 'DEER 2011'
|
178
|
+
array << 'DEER 2014'
|
179
|
+
array << 'DEER 2015'
|
180
|
+
array << 'DEER 2017'
|
181
|
+
array << 'DEER 2020'
|
182
|
+
if extended
|
183
|
+
array << 'DEER 2025'
|
184
|
+
array << 'DEER 2030'
|
185
|
+
array << 'DEER 2035'
|
186
|
+
array << 'DEER 2040'
|
187
|
+
array << 'DEER 2045'
|
188
|
+
array << 'DEER 2050'
|
189
|
+
array << 'DEER 2055'
|
190
|
+
array << 'DEER 2060'
|
191
|
+
array << 'DEER 2065'
|
192
|
+
array << 'DEER 2070'
|
193
|
+
array << 'DEER 2075'
|
194
|
+
end
|
72
195
|
|
73
196
|
return array
|
74
197
|
end
|
75
198
|
|
199
|
+
# get_climate_zones
|
200
|
+
# for general public use use extended = false
|
201
|
+
def get_climate_zones(extended = false, extra = nil)
|
202
|
+
|
203
|
+
# get climate_zones
|
204
|
+
if extended && extra != nil
|
205
|
+
doe = get_doe_climate_zones(true, extra)
|
206
|
+
deer = get_deer_climate_zones(true, nil)
|
207
|
+
elsif extended
|
208
|
+
doe = get_doe_climate_zones(true, nil)
|
209
|
+
deer = get_deer_climate_zones(true, nil)
|
210
|
+
elsif extra != nil
|
211
|
+
doe = get_doe_climate_zones(false, extra)
|
212
|
+
deer = get_deer_climate_zones(false, nil)
|
213
|
+
else
|
214
|
+
doe = get_doe_climate_zones
|
215
|
+
deer = get_deer_climate_zones
|
216
|
+
end
|
217
|
+
|
218
|
+
# combine climate zones
|
219
|
+
array = OpenStudio::StringVector.new
|
220
|
+
temp_array = doe.to_a + deer.to_a
|
221
|
+
temp_array.each do |i|
|
222
|
+
array << i
|
223
|
+
end
|
224
|
+
|
225
|
+
return array
|
226
|
+
end
|
227
|
+
|
228
|
+
# get_doe_climate_zones
|
229
|
+
# for general public use use extended = false
|
230
|
+
def get_doe_climate_zones(extended = false, extra = nil)
|
231
|
+
|
232
|
+
# Lookup From Model should be added as an option where appropriate in the measure
|
233
|
+
cz_choices = OpenStudio::StringVector.new
|
234
|
+
if extra != nil
|
235
|
+
cz_choices << extra
|
236
|
+
end
|
237
|
+
cz_choices << 'ASHRAE 169-2013-1A'
|
238
|
+
cz_choices << 'ASHRAE 169-2013-1B'
|
239
|
+
cz_choices << 'ASHRAE 169-2013-2A'
|
240
|
+
cz_choices << 'ASHRAE 169-2013-2B'
|
241
|
+
cz_choices << 'ASHRAE 169-2013-3A'
|
242
|
+
cz_choices << 'ASHRAE 169-2013-3B'
|
243
|
+
cz_choices << 'ASHRAE 169-2013-3C'
|
244
|
+
cz_choices << 'ASHRAE 169-2013-4A'
|
245
|
+
cz_choices << 'ASHRAE 169-2013-4B'
|
246
|
+
cz_choices << 'ASHRAE 169-2013-4C'
|
247
|
+
cz_choices << 'ASHRAE 169-2013-5A'
|
248
|
+
cz_choices << 'ASHRAE 169-2013-5B'
|
249
|
+
cz_choices << 'ASHRAE 169-2013-5C'
|
250
|
+
cz_choices << 'ASHRAE 169-2013-6A'
|
251
|
+
cz_choices << 'ASHRAE 169-2013-6B'
|
252
|
+
cz_choices << 'ASHRAE 169-2013-7A'
|
253
|
+
cz_choices << 'ASHRAE 169-2013-8A'
|
254
|
+
if extended
|
255
|
+
cz_choices << 'ASHRAE 169-2013-0A'
|
256
|
+
cz_choices << 'ASHRAE 169-2013-0B'
|
257
|
+
end
|
258
|
+
|
259
|
+
return cz_choices
|
260
|
+
end
|
261
|
+
|
262
|
+
# get_deer_climate_zones
|
263
|
+
# for general public use use extended = false
|
264
|
+
def get_deer_climate_zones(extended = false, extra = nil)
|
265
|
+
|
266
|
+
# Lookup From Model should be added as an option where appropriate in the measure
|
267
|
+
cz_choices = OpenStudio::StringVector.new
|
268
|
+
if extra != nil
|
269
|
+
cz_choices << extra
|
270
|
+
end
|
271
|
+
cz_choices << 'CEC T24-CEC1'
|
272
|
+
cz_choices << 'CEC T24-CEC2'
|
273
|
+
cz_choices << 'CEC T24-CEC3'
|
274
|
+
cz_choices << 'CEC T24-CEC4'
|
275
|
+
cz_choices << 'CEC T24-CEC5'
|
276
|
+
cz_choices << 'CEC T24-CEC6'
|
277
|
+
cz_choices << 'CEC T24-CEC7'
|
278
|
+
cz_choices << 'CEC T24-CEC8'
|
279
|
+
cz_choices << 'CEC T24-CEC9'
|
280
|
+
cz_choices << 'CEC T24-CEC10'
|
281
|
+
cz_choices << 'CEC T24-CEC11'
|
282
|
+
cz_choices << 'CEC T24-CEC12'
|
283
|
+
cz_choices << 'CEC T24-CEC13'
|
284
|
+
cz_choices << 'CEC T24-CEC14'
|
285
|
+
cz_choices << 'CEC T24-CEC15'
|
286
|
+
cz_choices << 'CEC T24-CEC16'
|
287
|
+
|
288
|
+
return cz_choices
|
289
|
+
end
|
290
|
+
|
76
291
|
# calculate aspect ratio from area and perimeter
|
77
292
|
def calc_aspect_ratio(a, p)
|
78
293
|
l = 0.25 * (p + Math.sqrt(p**2 - 16 * a))
|
@@ -84,74 +299,163 @@ module OsLib_ModelGeneration
|
|
84
299
|
|
85
300
|
# Building Form Defaults from Table 4.2 in Achieving the 30% Goal: Energy and Cost Savings Analysis of ASHRAE Standard 90.1-2010
|
86
301
|
# aspect ratio for NA replaced with floor area to perimeter ratio from prototype model
|
302
|
+
# currently no reason to split apart doe and deer inputs here
|
87
303
|
def building_form_defaults(building_type)
|
88
304
|
hash = {}
|
89
305
|
|
306
|
+
# DOE Prototypes
|
307
|
+
|
90
308
|
# calculate aspect ratios not represented on Table 4.2
|
91
|
-
|
92
|
-
|
93
|
-
|
309
|
+
primary_footprint = 73958.0
|
310
|
+
primary_p = 619.0 # wrote measure using calculate_perimeter method in os_lib_geometry
|
311
|
+
primary_ns_ew_ratio = 2.829268293 # estimated from ratio of ns/ew total wall area
|
312
|
+
primary_width = Math.sqrt(primary_footprint/primary_ns_ew_ratio)
|
313
|
+
primary_p_min = 2 * (primary_width + primary_width / primary_footprint)
|
314
|
+
primary_p_mult = primary_p / primary_p_min
|
315
|
+
|
316
|
+
secondary_footprint = 210887.0 / 2.0 # floor area divided by area instead of true footprint 128112.0)
|
317
|
+
secondary_p = 708.0 # wrote measure using calculate_perimeter method in os_lib_geometry
|
318
|
+
secondary_ns_ew_ratio = 2.069230769 # estimated from ratio of ns/ew total wall area
|
319
|
+
secondary_width = Math.sqrt(secondary_footprint/secondary_ns_ew_ratio)
|
320
|
+
secondary_p_min = 2 * (secondary_width + secondary_width / secondary_footprint)
|
321
|
+
secondary_p_mult = secondary_p / secondary_p_min
|
322
|
+
|
323
|
+
outpatient_footprint = 40946.0 / 3.0 # floor area divided by area instead of true footprint 17872.0)
|
324
|
+
outpatient_p = 537.0 # wrote measure using calculate_perimeter method in os_lib_geometry
|
325
|
+
outpatient_ns_ew_ratio = 1.56448737 # estimated from ratio of ns/ew total wall area
|
326
|
+
outpatient_width = Math.sqrt(outpatient_footprint/outpatient_ns_ew_ratio)
|
327
|
+
outpatient_p_min = 2 * (outpatient_width + outpatient_footprint/outpatient_width)
|
328
|
+
outpatient_p_mult = outpatient_p / outpatient_p_min
|
329
|
+
|
330
|
+
#primary_aspet_ratio = calc_aspect_ratio(73958.0, 2060.0)
|
331
|
+
#secondary_aspet_ratio = calc_aspect_ratio(128112.0, 2447.0)
|
332
|
+
#outpatient_aspet_ratio = calc_aspect_ratio(14782.0, 588.0)
|
94
333
|
supermarket_a = 45001.0
|
95
334
|
supermarket_p = 866.0
|
96
335
|
supermarket_wwr = 1880.0 / (supermarket_p * 20.0)
|
97
|
-
|
98
|
-
|
99
|
-
hash['SmallOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 10.0 }
|
100
|
-
hash['MediumOffice'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 13.0 }
|
101
|
-
hash['LargeOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 13.0 }
|
102
|
-
hash['RetailStandalone'] = { aspect_ratio: 1.28, wwr: 0.07, typical_story: 20.0 }
|
103
|
-
hash['RetailStripmall'] = { aspect_ratio: 4.0, wwr: 0.11, typical_story: 17.0 }
|
104
|
-
hash['PrimarySchool'] = { aspect_ratio:
|
105
|
-
hash['SecondarySchool'] = { aspect_ratio:
|
106
|
-
hash['Outpatient'] = { aspect_ratio:
|
107
|
-
hash['Hospital'] = { aspect_ratio: 1.33, wwr: 0.16, typical_story: 14.0 }
|
108
|
-
hash['SmallHotel'] = { aspect_ratio: 3.0, wwr: 0.11, typical_story: 9.0, first_story: 11.0 }
|
109
|
-
hash['LargeHotel'] = { aspect_ratio: 5.1, wwr: 0.27, typical_story: 10.0, first_story: 13.0 }
|
336
|
+
supermarket_aspect_ratio = calc_aspect_ratio(supermarket_a, supermarket_p)
|
337
|
+
|
338
|
+
hash['SmallOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
|
339
|
+
hash['MediumOffice'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 13.0, perim_mult: 1.0 }
|
340
|
+
hash['LargeOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 13.0, perim_mult: 1.0 }
|
341
|
+
hash['RetailStandalone'] = { aspect_ratio: 1.28, wwr: 0.07, typical_story: 20.0, perim_mult: 1.0 }
|
342
|
+
hash['RetailStripmall'] = { aspect_ratio: 4.0, wwr: 0.11, typical_story: 17.0, perim_mult: 1.0 }
|
343
|
+
hash['PrimarySchool'] = { aspect_ratio: primary_ns_ew_ratio.round(1), wwr: 0.35, typical_story: 13.0, perim_mult: primary_p_mult.round(3) }
|
344
|
+
hash['SecondarySchool'] = { aspect_ratio: secondary_ns_ew_ratio.round(1), wwr: 0.33, typical_story: 13.0, perim_mult: secondary_p_mult.round(3) }
|
345
|
+
hash['Outpatient'] = { aspect_ratio: outpatient_ns_ew_ratio.round(1), wwr: 0.20, typical_story: 10.0, perim_mult: outpatient_p_mult.round(3) }
|
346
|
+
hash['Hospital'] = { aspect_ratio: 1.33, wwr: 0.16, typical_story: 14.0, perim_mult: 1.0 }
|
347
|
+
hash['SmallHotel'] = { aspect_ratio: 3.0, wwr: 0.11, typical_story: 9.0, first_story: 11.0, perim_mult: 1.0 }
|
348
|
+
hash['LargeHotel'] = { aspect_ratio: 5.1, wwr: 0.27, typical_story: 10.0, first_story: 13.0, perim_mult: 1.0 }
|
110
349
|
|
111
350
|
# code in get_space_types_from_building_type is used to override building wwr with space type specific wwr
|
112
|
-
hash['Warehouse'] = { aspect_ratio: 2.2, wwr: 0.0, typical_story: 28.0}
|
351
|
+
hash['Warehouse'] = { aspect_ratio: 2.2, wwr: 0.0, typical_story: 28.0, perim_mult: 1.0 }
|
113
352
|
|
114
|
-
hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.14, typical_story: 10.0 }
|
115
|
-
hash['FullServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0 }
|
116
|
-
hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0 }
|
117
|
-
hash['MidriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0 }
|
118
|
-
hash['HighriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0 }
|
353
|
+
hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.14, typical_story: 10.0, perim_mult: 1.0 }
|
354
|
+
hash['FullServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
|
355
|
+
hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
|
356
|
+
hash['MidriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
|
357
|
+
hash['HighriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
|
119
358
|
# SuperMarket inputs come from prototype model
|
120
|
-
hash['SuperMarket'] = { aspect_ratio:
|
359
|
+
hash['SuperMarket'] = { aspect_ratio: supermarket_aspect_ratio.round(1), wwr: supermarket_wwr.round(2), typical_story: 20.0, perim_mult: 1.0 }
|
360
|
+
|
361
|
+
# Add Laboratory and Data Centers
|
362
|
+
hash['Laboratory'] = { aspect_ratio: 1.33, wwr: 0.12, typical_story: 10.0, perim_mult: 1.0 }
|
363
|
+
hash['LargeDataCenterLowITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
|
364
|
+
hash['LargeDataCenterHighITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
|
365
|
+
hash['SmallDataCenterLowITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
|
366
|
+
hash['SmallDataCenterHighITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
|
367
|
+
|
368
|
+
# DEER Prototypes
|
369
|
+
hash['Asm'] = { aspect_ratio: 1.0, wwr: 0.19, typical_story: 15.0 }
|
370
|
+
hash['ECC'] = { aspect_ratio: 4.0, wwr: 0.25, typical_story: 13.0 }
|
371
|
+
hash['EPr'] = { aspect_ratio: 2.0, wwr: 0.16, typical_story: 12.0 }
|
372
|
+
hash['ERC'] = { aspect_ratio: 1.7, wwr: 0.03, typical_story: 12.0 }
|
373
|
+
hash['ESe'] = { aspect_ratio: 1.0, wwr: 0.15, typical_story: 13.0 }
|
374
|
+
hash['EUn'] = { aspect_ratio: 2.5, wwr: 0.3, typical_story: 14.0 }
|
375
|
+
hash['Gro'] = { aspect_ratio: 1.0, wwr: 0.07, typical_story: 25.0 }
|
376
|
+
hash['Hsp'] = { aspect_ratio: 1.5, wwr: 0.11, typical_story: 13.0 }
|
377
|
+
hash['Htl'] = { aspect_ratio: 3.0, wwr: 0.23, typical_story: 9.5, first_story: 12.0 }
|
378
|
+
hash['MBT'] = { aspect_ratio: 10.7, wwr: 0.12, typical_story: 15.0 }
|
379
|
+
hash['MFm'] = { aspect_ratio: 1.4, wwr: 0.24, typical_story: 9.5 }
|
380
|
+
hash['MLI'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 35.0 }
|
381
|
+
hash['Mtl'] = { aspect_ratio: 5.1, wwr: 0.41, typical_story: 9.0 }
|
382
|
+
hash['Nrs'] = { aspect_ratio: 10.3, wwr: 0.2, typical_story: 13.0 }
|
383
|
+
hash['OfL'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
|
384
|
+
hash['OfS'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
|
385
|
+
hash['RFF'] = { aspect_ratio: 1.0, wwr: 0.25, typical_story: 13.0 }
|
386
|
+
hash['RSD'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 13.0 }
|
387
|
+
hash['Rt3'] = { aspect_ratio: 1.0, wwr: 0.02, typical_story: 20.8 }
|
388
|
+
hash['RtL'] = { aspect_ratio: 1.0, wwr: 0.03, typical_story: 20.5 }
|
389
|
+
hash['RtS'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 12.0 }
|
390
|
+
hash['SCn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
|
391
|
+
hash['SUn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
|
392
|
+
hash['WRf'] = { aspect_ratio: 1.6, wwr: 0.0, typical_story: 32.0 }
|
121
393
|
|
122
394
|
return hash[building_type]
|
123
395
|
end
|
124
396
|
|
125
397
|
# create hash of space types and generic ratios of building floor area
|
398
|
+
# currently no reason to split apart doe and deer inputs here
|
126
399
|
def get_space_types_from_building_type(building_type, template, whole_building = true)
|
127
400
|
hash = {}
|
128
401
|
|
129
402
|
# TODO: - Confirm that these work for all standards
|
130
|
-
|
403
|
+
# DOE Prototypes
|
131
404
|
if building_type == 'SecondarySchool'
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
405
|
+
if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004'].include?(template)
|
406
|
+
hash['Auditorium'] = { ratio: 0.0504, space_type_gen: true, default: false, story_height: 26.0 }
|
407
|
+
hash['Cafeteria'] = { ratio: 0.0319, space_type_gen: true, default: false }
|
408
|
+
hash['Classroom'] = { ratio: 0.3528, space_type_gen: true, default: true }
|
409
|
+
hash['Corridor'] = { ratio: 0.2144, space_type_gen: true, default: false, circ: true }
|
410
|
+
hash['Gym'] = { ratio: 0.1009, space_type_gen: true, default: false , story_height: 26.0 }
|
411
|
+
hash['Gym - audience'] = { ratio: 0.0637, space_type_gen: true, default: false , story_height: 26.0 }
|
412
|
+
hash['Kitchen'] = { ratio: 0.0110, space_type_gen: true, default: false }
|
413
|
+
hash['Library'] = { ratio: 0.0429, space_type_gen: true, default: false }
|
414
|
+
hash['Lobby'] = { ratio: 0.0214, space_type_gen: true, default: false }
|
415
|
+
hash['Mechanical'] = { ratio: 0.0349, space_type_gen: true, default: false }
|
416
|
+
hash['Office'] = { ratio: 0.0543, space_type_gen: true, default: false }
|
417
|
+
hash['Restroom'] = { ratio: 0.0214, space_type_gen: true, default: false }
|
418
|
+
else
|
419
|
+
hash['Auditorium'] = { ratio: 0.0504, space_type_gen: true, default: false, story_height: 26.0 }
|
420
|
+
hash['Cafeteria'] = { ratio: 0.0319, space_type_gen: true, default: false }
|
421
|
+
hash['Classroom'] = { ratio: 0.3041, space_type_gen: true, default: true }
|
422
|
+
hash['ComputerRoom'] = { ratio: 0.0487, space_type_gen: true, default: true }
|
423
|
+
hash['Corridor'] = { ratio: 0.2144, space_type_gen: true, default: false, circ: true }
|
424
|
+
hash['Gym'] = { ratio: 0.1646, space_type_gen: true, default: false , story_height: 26.0 }
|
425
|
+
hash['Kitchen'] = { ratio: 0.0110, space_type_gen: true, default: false }
|
426
|
+
hash['Library'] = { ratio: 0.0429, space_type_gen: true, default: false }
|
427
|
+
hash['Lobby'] = { ratio: 0.0214, space_type_gen: true, default: false }
|
428
|
+
hash['Mechanical'] = { ratio: 0.0349, space_type_gen: true, default: false }
|
429
|
+
hash['Office'] = { ratio: 0.0543, space_type_gen: true, default: false }
|
430
|
+
hash['Restroom'] = { ratio: 0.0214, space_type_gen: true, default: false }
|
431
|
+
end
|
143
432
|
elsif building_type == 'PrimarySchool'
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
433
|
+
if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004'].include?(template)
|
434
|
+
# updated to 2004 which includes library vs. pre-1980
|
435
|
+
hash['Cafeteria'] = { ratio: 0.0458, space_type_gen: true, default: false }
|
436
|
+
hash['Classroom'] = { ratio: 0.5610, space_type_gen: true, default: true }
|
437
|
+
hash['Corridor'] = { ratio: 0.1633, space_type_gen: true, default: false, circ: true }
|
438
|
+
hash['Gym'] = { ratio: 0.0520, space_type_gen: true, default: false }
|
439
|
+
hash['Kitchen'] = { ratio: 0.0244, space_type_gen: true, default: false }
|
440
|
+
hash['Library'] = { ratio: 0.0, space_type_gen: true, default: false } # no library in model
|
441
|
+
hash['Lobby'] = { ratio: 0.0249, space_type_gen: true, default: false }
|
442
|
+
hash['Mechanical'] = { ratio: 0.0367, space_type_gen: true, default: false }
|
443
|
+
hash['Office'] = { ratio: 0.0642, space_type_gen: true, default: false }
|
444
|
+
hash['Restroom'] = { ratio: 0.0277, space_type_gen: true, default: false }
|
445
|
+
else
|
446
|
+
# updated to 2004 which includes library vs. pre-1980
|
447
|
+
hash['Cafeteria'] = { ratio: 0.0458, space_type_gen: true, default: false }
|
448
|
+
hash['Classroom'] = { ratio: 0.4793, space_type_gen: true, default: true }
|
449
|
+
hash['ComputerRoom'] = { ratio: 0.0236, space_type_gen: true, default: true }
|
450
|
+
hash['Corridor'] = { ratio: 0.1633, space_type_gen: true, default: false, circ: true }
|
451
|
+
hash['Gym'] = { ratio: 0.0520, space_type_gen: true, default: false}
|
452
|
+
hash['Kitchen'] = { ratio: 0.0244, space_type_gen: true, default: false }
|
453
|
+
hash['Library'] = { ratio: 0.0581, space_type_gen: true, default: false }
|
454
|
+
hash['Lobby'] = { ratio: 0.0249, space_type_gen: true, default: false }
|
455
|
+
hash['Mechanical'] = { ratio: 0.0367, space_type_gen: true, default: false }
|
456
|
+
hash['Office'] = { ratio: 0.0642, space_type_gen: true, default: false }
|
457
|
+
hash['Restroom'] = { ratio: 0.0277, space_type_gen: true, default: false }
|
458
|
+
end
|
155
459
|
elsif building_type == 'SmallOffice'
|
156
460
|
# TODO: - populate Small, Medium, and Large office for whole_building false
|
157
461
|
if whole_building
|
@@ -160,7 +464,7 @@ module OsLib_ModelGeneration
|
|
160
464
|
hash['SmallOffice - Breakroom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
161
465
|
hash['SmallOffice - ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
|
162
466
|
hash['SmallOffice - Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
|
163
|
-
hash['SmallOffice - Corridor'] = { ratio: 0.99, space_type_gen: true, default: false }
|
467
|
+
hash['SmallOffice - Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
|
164
468
|
hash['SmallOffice - Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
165
469
|
hash['SmallOffice - Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
|
166
470
|
hash['SmallOffice - OpenOffice'] = { ratio: 0.99, space_type_gen: true, default: true }
|
@@ -178,7 +482,7 @@ module OsLib_ModelGeneration
|
|
178
482
|
hash['MediumOffice - Breakroom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
179
483
|
hash['MediumOffice - ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
|
180
484
|
hash['MediumOffice - Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
|
181
|
-
hash['MediumOffice - Corridor'] = { ratio: 0.99, space_type_gen: true, default: false }
|
485
|
+
hash['MediumOffice - Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
|
182
486
|
hash['MediumOffice - Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
183
487
|
hash['MediumOffice - Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
|
184
488
|
hash['MediumOffice - OpenOffice'] = { ratio: 0.99, space_type_gen: true, default: true }
|
@@ -197,7 +501,7 @@ module OsLib_ModelGeneration
|
|
197
501
|
hash['BreakRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
198
502
|
hash['ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
|
199
503
|
hash['Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
|
200
|
-
hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: false }
|
504
|
+
hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
|
201
505
|
hash['Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
202
506
|
hash['IT_Room'] = { ratio: 0.99, space_type_gen: true, default: false }
|
203
507
|
hash['Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
|
@@ -218,7 +522,7 @@ module OsLib_ModelGeneration
|
|
218
522
|
hash['BreakRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
219
523
|
hash['ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
|
220
524
|
hash['Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
|
221
|
-
hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: false }
|
525
|
+
hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
|
222
526
|
hash['Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
|
223
527
|
hash['IT_Room'] = { ratio: 0.99, space_type_gen: true, default: false }
|
224
528
|
hash['Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
|
@@ -235,7 +539,7 @@ module OsLib_ModelGeneration
|
|
235
539
|
end
|
236
540
|
elsif building_type == 'SmallHotel'
|
237
541
|
if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004'].include?(template)
|
238
|
-
hash['Corridor'] = { ratio: 0.1313, space_type_gen: true, default: false }
|
542
|
+
hash['Corridor'] = { ratio: 0.1313, space_type_gen: true, default: false, circ: true }
|
239
543
|
hash['Elec/MechRoom'] = { ratio: 0.0038, space_type_gen: true, default: false }
|
240
544
|
hash['ElevatorCore'] = { ratio: 0.0113, space_type_gen: true, default: false }
|
241
545
|
hash['Exercise'] = { ratio: 0.0081, space_type_gen: true, default: false }
|
@@ -250,7 +554,7 @@ module OsLib_ModelGeneration
|
|
250
554
|
hash['Stair'] = { ratio: 0.0400, space_type_gen: true, default: false }
|
251
555
|
hash['Storage'] = { ratio: 0.0325, space_type_gen: true, default: false }
|
252
556
|
else
|
253
|
-
hash['Corridor'] = { ratio: 0.1313, space_type_gen: true, default: false }
|
557
|
+
hash['Corridor'] = { ratio: 0.1313, space_type_gen: true, default: false, circ: true }
|
254
558
|
hash['Elec/MechRoom'] = { ratio: 0.0038, space_type_gen: true, default: false }
|
255
559
|
hash['ElevatorCore'] = { ratio: 0.0113, space_type_gen: true, default: false }
|
256
560
|
hash['Exercise'] = { ratio: 0.0081, space_type_gen: true, default: false }
|
@@ -270,7 +574,7 @@ module OsLib_ModelGeneration
|
|
270
574
|
hash['Banquet'] = { ratio: 0.0585, space_type_gen: true, default: false }
|
271
575
|
hash['Basement'] = { ratio: 0.1744, space_type_gen: false, default: false }
|
272
576
|
hash['Cafe'] = { ratio: 0.0166, space_type_gen: true, default: false }
|
273
|
-
hash['Corridor'] = { ratio: 0.1736, space_type_gen: true, default: false }
|
577
|
+
hash['Corridor'] = { ratio: 0.1736, space_type_gen: true, default: false, circ: true }
|
274
578
|
hash['GuestRoom'] = { ratio: 0.4099, space_type_gen: true, default: true }
|
275
579
|
hash['Kitchen'] = { ratio: 0.0091, space_type_gen: true, default: false }
|
276
580
|
hash['Laundry'] = { ratio: 0.0069, space_type_gen: true, default: false }
|
@@ -281,7 +585,7 @@ module OsLib_ModelGeneration
|
|
281
585
|
elsif building_type == 'Warehouse'
|
282
586
|
hash['Bulk'] = { ratio: 0.6628, space_type_gen: true, default: true }
|
283
587
|
hash['Fine'] = { ratio: 0.2882, space_type_gen: true, default: false }
|
284
|
-
hash['Office'] = { ratio: 0.0490, space_type_gen: true, default: false, wwr: 0.71 }
|
588
|
+
hash['Office'] = { ratio: 0.0490, space_type_gen: true, default: false, wwr: 0.71, story_height: 14.0 }
|
285
589
|
elsif building_type == 'RetailStandalone'
|
286
590
|
hash['Back_Space'] = { ratio: 0.1656, space_type_gen: true, default: false }
|
287
591
|
hash['Entry'] = { ratio: 0.0052, space_type_gen: true, default: false }
|
@@ -299,22 +603,22 @@ module OsLib_ModelGeneration
|
|
299
603
|
hash['Kitchen'] = { ratio: 0.2728, space_type_gen: true, default: false }
|
300
604
|
elsif building_type == 'MidriseApartment'
|
301
605
|
hash['Apartment'] = { ratio: 0.8727, space_type_gen: true, default: true }
|
302
|
-
hash['Corridor'] = { ratio: 0.0991, space_type_gen: true, default: false }
|
606
|
+
hash['Corridor'] = { ratio: 0.0991, space_type_gen: true, default: false, circ: true }
|
303
607
|
hash['Office'] = { ratio: 0.0282, space_type_gen: true, default: false }
|
304
608
|
elsif building_type == 'HighriseApartment'
|
305
609
|
hash['Apartment'] = { ratio: 0.8896, space_type_gen: true, default: true }
|
306
|
-
hash['Corridor'] = { ratio: 0.0991, space_type_gen: true, default: false }
|
610
|
+
hash['Corridor'] = { ratio: 0.0991, space_type_gen: true, default: false, circ: true }
|
307
611
|
hash['Office'] = { ratio: 0.0113, space_type_gen: true, default: false }
|
308
612
|
elsif building_type == 'Hospital'
|
309
613
|
hash['Basement'] = { ratio: 0.1667, space_type_gen: false, default: false }
|
310
|
-
hash['Corridor'] = { ratio: 0.1741, space_type_gen: true, default: false }
|
614
|
+
hash['Corridor'] = { ratio: 0.1741, space_type_gen: true, default: false, circ: true }
|
311
615
|
hash['Dining'] = { ratio: 0.0311, space_type_gen: true, default: false }
|
312
616
|
hash['ER_Exam'] = { ratio: 0.0099, space_type_gen: true, default: false }
|
313
617
|
hash['ER_NurseStn'] = { ratio: 0.0551, space_type_gen: true, default: false }
|
314
618
|
hash['ER_Trauma'] = { ratio: 0.0025, space_type_gen: true, default: false }
|
315
619
|
hash['ER_Triage'] = { ratio: 0.0050, space_type_gen: true, default: false }
|
316
620
|
hash['ICU_NurseStn'] = { ratio: 0.0298, space_type_gen: true, default: false }
|
317
|
-
hash['
|
621
|
+
hash['ICU_Open'] = { ratio: 0.0275, space_type_gen: true, default: false }
|
318
622
|
hash['ICU_PatRm'] = { ratio: 0.0115, space_type_gen: true, default: false }
|
319
623
|
hash['Kitchen'] = { ratio: 0.0414, space_type_gen: true, default: false }
|
320
624
|
hash['Lab'] = { ratio: 0.0236, space_type_gen: true, default: false }
|
@@ -336,7 +640,7 @@ module OsLib_ModelGeneration
|
|
336
640
|
hash['Elec/MechRoom'] = { ratio: 0.0109, space_type_gen: true, default: false }
|
337
641
|
hash['ElevatorPumpRoom'] = { ratio: 0.0022, space_type_gen: true, default: false }
|
338
642
|
hash['Exam'] = { ratio: 0.1029, space_type_gen: true, default: true }
|
339
|
-
hash['Hall'] = { ratio: 0.1924, space_type_gen: true, default: false }
|
643
|
+
hash['Hall'] = { ratio: 0.1924, space_type_gen: true, default: false, circ: true }
|
340
644
|
hash['IT_Room'] = { ratio: 0.0027, space_type_gen: true, default: false }
|
341
645
|
hash['Janitor'] = { ratio: 0.0672, space_type_gen: true, default: false }
|
342
646
|
hash['Lobby'] = { ratio: 0.0152, space_type_gen: true, default: false }
|
@@ -372,6 +676,142 @@ module OsLib_ModelGeneration
|
|
372
676
|
hash['Meeting'] = { ratio: 0.99, space_type_gen: true, default: true }
|
373
677
|
hash['Restroom'] = { ratio: 0.99, space_type_gen: true, default: true }
|
374
678
|
hash['Vestibule'] = { ratio: 0.99, space_type_gen: true, default: true }
|
679
|
+
elsif building_type == 'Laboratory'
|
680
|
+
hash['Office'] = { ratio: 0.50, space_type_gen: true, default: true }
|
681
|
+
hash['Open lab'] = { ratio: 0.35, space_type_gen: true, default: true }
|
682
|
+
hash['Equipment corridor'] = { ratio: 0.05, space_type_gen: true, default: true }
|
683
|
+
hash['Lab with fume hood'] = { ratio: 0.10, space_type_gen: true, default: true }
|
684
|
+
elsif building_type == 'LargeDataCenterHighITE'
|
685
|
+
hash['StandaloneDataCenter'] = { ratio: 1.0, space_type_gen: true, default: true }
|
686
|
+
elsif building_type == 'LargeDataCenterLowITE'
|
687
|
+
hash['StandaloneDataCenter'] = { ratio: 1.0, space_type_gen: true, default: true }
|
688
|
+
elsif building_type == 'SmallDataCenterHighITE'
|
689
|
+
hash['ComputerRoom'] = { ratio: 1.0, space_type_gen: true, default: true }
|
690
|
+
elsif building_type == 'SmallDataCenterLowITE'
|
691
|
+
hash['ComputerRoom'] = { ratio: 1.0, space_type_gen: true, default: true }
|
692
|
+
# DEER Prototypes
|
693
|
+
elsif building_type == 'Asm'
|
694
|
+
hash['Auditorium'] = { ratio: 0.7658, space_type_gen: true, default: true }
|
695
|
+
hash['OfficeGeneral'] = { ratio: 0.2342, space_type_gen: true, default: false }
|
696
|
+
elsif building_type == 'ECC'
|
697
|
+
hash['Classroom'] = { ratio: 0.5558, space_type_gen: true, default: true }
|
698
|
+
hash['CompRoomClassRm'] = { ratio: 0.0319, space_type_gen: true, default: false }
|
699
|
+
hash['Shop'] = { ratio: 0.1249, space_type_gen: true, default: false }
|
700
|
+
hash['Dining'] = { ratio: 0.0876, space_type_gen: true, default: false }
|
701
|
+
hash['Kitchen'] = { ratio: 0.0188, space_type_gen: true, default: false }
|
702
|
+
hash['OfficeGeneral'] = { ratio: 0.181, space_type_gen: true, default: false }
|
703
|
+
elsif building_type == 'EPr'
|
704
|
+
hash['Classroom'] = { ratio: 0.53, space_type_gen: true, default: true }
|
705
|
+
hash['CorridorStairway'] = { ratio: 0.1, space_type_gen: true, default: false }
|
706
|
+
hash['Dining'] = { ratio: 0.15, space_type_gen: true, default: false }
|
707
|
+
hash['Gymnasium'] = { ratio: 0.15, space_type_gen: true, default: false }
|
708
|
+
hash['Kitchen'] = { ratio: 0.07, space_type_gen: true, default: false }
|
709
|
+
elsif building_type == 'ERC'
|
710
|
+
hash['Classroom'] = { ratio: 0.5, space_type_gen: true, default: true }
|
711
|
+
elsif building_type == 'ESe'
|
712
|
+
hash['Classroom'] = { ratio: 0.488, space_type_gen: true, default: true }
|
713
|
+
hash['CompRoomClassRm'] = { ratio: 0.021, space_type_gen: true, default: false }
|
714
|
+
hash['CorridorStairway'] = { ratio: 0.1, space_type_gen: true, default: false }
|
715
|
+
hash['Dining'] = { ratio: 0.15, space_type_gen: true, default: false }
|
716
|
+
hash['Gymnasium'] = { ratio: 0.15, space_type_gen: true, default: false }
|
717
|
+
hash['Kitchen'] = { ratio: 0.07, space_type_gen: true, default: false }
|
718
|
+
hash['OfficeGeneral'] = { ratio: 0.021, space_type_gen: true, default: true }
|
719
|
+
elsif building_type == 'EUn'
|
720
|
+
hash['Dining'] = { ratio: 0.0238, space_type_gen: true, default: false }
|
721
|
+
hash['Classroom'] = { ratio: 0.3056, space_type_gen: true, default: false }
|
722
|
+
hash['OfficeGeneral'] = { ratio: 0.3422, space_type_gen: true, default: true }
|
723
|
+
hash['CompRoomClassRm'] = { ratio: 0.038, space_type_gen: true, default: false }
|
724
|
+
hash['Kitchen'] = { ratio: 0.0105, space_type_gen: true, default: false }
|
725
|
+
hash['CorridorStairway'] = { ratio: 0.03, space_type_gen: true, default: false }
|
726
|
+
hash['FacMaint'] = { ratio: 0.08, space_type_gen: true, default: false }
|
727
|
+
hash['DormitoryRoom'] = { ratio: 0.1699, space_type_gen: true, default: false }
|
728
|
+
elsif building_type == 'Gro'
|
729
|
+
hash['GrocSales'] = { ratio: 0.8002, space_type_gen: true, default: true }
|
730
|
+
hash['RefWalkInCool'] = { ratio: 0.0312, space_type_gen: true, default: false }
|
731
|
+
hash['OfficeGeneral'] = { ratio: 0.07, space_type_gen: true, default: false }
|
732
|
+
hash['RefFoodPrep'] = { ratio: 0.0253, space_type_gen: true, default: false }
|
733
|
+
hash['RefWalkInFreeze'] = { ratio: 0.0162, space_type_gen: true, default: false }
|
734
|
+
hash['IndLoadDock'] = { ratio: 0.057, space_type_gen: true, default: false }
|
735
|
+
elsif building_type == 'Hsp'
|
736
|
+
hash['HspSurgOutptLab'] = { ratio: 0.2317, space_type_gen: true, default: false }
|
737
|
+
hash['Dining'] = { ratio: 0.0172, space_type_gen: true, default: false }
|
738
|
+
hash['Kitchen'] = { ratio: 0.0075, space_type_gen: true, default: false }
|
739
|
+
hash['OfficeGeneral'] = { ratio: 0.3636, space_type_gen: true, default: false }
|
740
|
+
hash['PatientRoom'] = { ratio: 0.38, space_type_gen: true, default: true }
|
741
|
+
elsif building_type == 'Htl'
|
742
|
+
hash['Dining'] = { ratio: 0.004, space_type_gen: true, default: false }
|
743
|
+
hash['BarCasino'] = { ratio: 0.005, space_type_gen: true, default: false }
|
744
|
+
hash['HotelLobby'] = { ratio: 0.0411, space_type_gen: true, default: false }
|
745
|
+
hash['OfficeGeneral'] = { ratio: 0.0205, space_type_gen: true, default: false }
|
746
|
+
hash['GuestRmCorrid'] = { ratio: 0.1011, space_type_gen: true, default: false }
|
747
|
+
hash['Laundry'] = { ratio: 0.0205, space_type_gen: true, default: false }
|
748
|
+
hash['GuestRmOcc'] = { ratio: 0.64224, space_type_gen: true, default: true }
|
749
|
+
hash['GuestRmUnOcc'] = { ratio: 0.16056, space_type_gen: true, default: true }
|
750
|
+
hash['Kitchen'] = { ratio: 0.005, space_type_gen: true, default: false }
|
751
|
+
elsif building_type == 'MBT'
|
752
|
+
hash['CompRoomData'] = { ratio: 0.02, space_type_gen: true, default: false }
|
753
|
+
hash['Laboratory'] = { ratio: 0.4534, space_type_gen: true, default: true }
|
754
|
+
hash['CorridorStairway'] = { ratio: 0.2, space_type_gen: true, default: false }
|
755
|
+
hash['Conference'] = { ratio: 0.02, space_type_gen: true, default: false }
|
756
|
+
hash['Dining'] = { ratio: 0.03, space_type_gen: true, default: false }
|
757
|
+
hash['OfficeOpen'] = { ratio: 0.2666, space_type_gen: true, default: false }
|
758
|
+
hash['Kitchen'] = { ratio: 0.01, space_type_gen: true, default: false }
|
759
|
+
elsif building_type == 'MFm'
|
760
|
+
hash['ResLiving'] = { ratio: 0.9297, space_type_gen: true, default: true }
|
761
|
+
hash['ResPublicArea'] = { ratio: 0.0725, space_type_gen: true, default: false }
|
762
|
+
elsif building_type == 'MLI'
|
763
|
+
hash['StockRoom'] = { ratio: 0.2, space_type_gen: true, default: false }
|
764
|
+
hash['Work'] = { ratio: 0.8, space_type_gen: true, default: true }
|
765
|
+
elsif building_type == 'Mtl'
|
766
|
+
hash['OfficeGeneral'] = { ratio: 0.02, space_type_gen: true, default: false }
|
767
|
+
hash['GuestRmCorrid'] = { ratio: 0.649, space_type_gen: true, default: true }
|
768
|
+
hash['Laundry'] = { ratio: 0.016, space_type_gen: true, default: false }
|
769
|
+
hash['GuestRmOcc'] = { ratio: 0.25208, space_type_gen: true, default: false }
|
770
|
+
hash['GuestRmUnOcc'] = { ratio: 0.06302, space_type_gen: true, default: false }
|
771
|
+
elsif building_type == 'Nrs'
|
772
|
+
hash['CorridorStairway'] = { ratio: 0.0555, space_type_gen: true, default: false }
|
773
|
+
hash['Dining'] = { ratio: 0.105, space_type_gen: true, default: false }
|
774
|
+
hash['Kitchen'] = { ratio: 0.045, space_type_gen: true, default: false }
|
775
|
+
hash['OfficeGeneral'] = { ratio: 0.35, space_type_gen: true, default: false }
|
776
|
+
hash['PatientRoom'] = { ratio: 0.4445, space_type_gen: true, default: true }
|
777
|
+
elsif building_type == 'OfL'
|
778
|
+
hash['LobbyWaiting'] = { ratio: 0.0412, space_type_gen: true, default: false }
|
779
|
+
hash['OfficeSmall'] = { ratio: 0.3704, space_type_gen: true, default: false }
|
780
|
+
hash['OfficeOpen'] = { ratio: 0.5296, space_type_gen: true, default: true }
|
781
|
+
hash['MechElecRoom'] = { ratio: 0.0588, space_type_gen: true, default: false }
|
782
|
+
elsif building_type == 'OfS'
|
783
|
+
hash['Hall'] = { ratio: 0.3141, space_type_gen: true, default: false }
|
784
|
+
hash['OfficeSmall'] = { ratio: 0.6859, space_type_gen: true, default: true }
|
785
|
+
elsif building_type == 'RFF'
|
786
|
+
hash['Dining'] = { ratio: 0.3997, space_type_gen: true, default: false }
|
787
|
+
hash['Kitchen'] = { ratio: 0.4, space_type_gen: true, default: true }
|
788
|
+
hash['LobbyWaiting'] = { ratio: 0.1501, space_type_gen: true, default: false }
|
789
|
+
hash['Restroom'] = { ratio: 0.0501, space_type_gen: true, default: false }
|
790
|
+
elsif building_type == 'RSD'
|
791
|
+
hash['Restroom'] = { ratio: 0.0357, space_type_gen: true, default: false }
|
792
|
+
hash['Dining'] = { ratio: 0.5353, space_type_gen: true, default: true }
|
793
|
+
hash['LobbyWaiting'] = { ratio: 0.1429, space_type_gen: true, default: false }
|
794
|
+
hash['Kitchen'] = { ratio: 0.2861, space_type_gen: true, default: false }
|
795
|
+
elsif building_type == 'Rt3'
|
796
|
+
hash['RetailSales'] = { ratio: 1.0, space_type_gen: true, default: true }
|
797
|
+
elsif building_type == 'RtL'
|
798
|
+
hash['OfficeGeneral'] = { ratio: 0.0359, space_type_gen: true, default: false }
|
799
|
+
hash['Work'] = { ratio: 0.04, space_type_gen: true, default: false }
|
800
|
+
hash['StockRoom'] = { ratio: 0.091, space_type_gen: true, default: false }
|
801
|
+
hash['RetailSales'] = { ratio: 0.8219, space_type_gen: true, default: true }
|
802
|
+
hash['Kitchen'] = { ratio: 0.0113, space_type_gen: true, default: false }
|
803
|
+
elsif building_type == 'RtS'
|
804
|
+
hash['RetailSales'] = { ratio: 0.8, space_type_gen: true, default: true }
|
805
|
+
hash['StockRoom'] = { ratio: 0.2, space_type_gen: true, default: false }
|
806
|
+
elsif building_type == 'SCn'
|
807
|
+
hash['WarehouseCond'] = { ratio: 1.0, space_type_gen: true, default: true }
|
808
|
+
elsif building_type == 'SUn'
|
809
|
+
hash['WarehouseUnCond'] = { ratio: 1.0, space_type_gen: true, default: true }
|
810
|
+
elsif building_type == 'WRf'
|
811
|
+
hash['IndLoadDock'] = { ratio: 0.08, space_type_gen: true, default: false }
|
812
|
+
hash['OfficeGeneral'] = { ratio: 0.02, space_type_gen: true, default: false }
|
813
|
+
hash['RefStorFreezer'] = { ratio: 0.4005, space_type_gen: true, default: false }
|
814
|
+
hash['RefStorCooler'] = { ratio: 0.4995, space_type_gen: true, default: true }
|
375
815
|
else
|
376
816
|
return false
|
377
817
|
end
|
@@ -588,12 +1028,14 @@ module OsLib_ModelGeneration
|
|
588
1028
|
|
589
1029
|
# set primary space type to building default space type
|
590
1030
|
space_types = bar_hash[:space_types].sort_by { |k, v| v[:floor_area] }
|
591
|
-
|
1031
|
+
if space_types.last.first.class.to_s == 'OpenStudio::Model::SpaceType'
|
1032
|
+
model.getBuilding.setSpaceType(space_types.last.first)
|
1033
|
+
end
|
592
1034
|
|
593
1035
|
end
|
594
1036
|
|
595
1037
|
# makeSpacesFromPolygons
|
596
|
-
OsLib_Geometry.makeSpacesFromPolygons(runner, model, footprints, bar_hash[:floor_height], bar_hash[:num_stories], bar_hash[:center_of_footprint], story_hash)
|
1038
|
+
new_spaces = OsLib_Geometry.makeSpacesFromPolygons(runner, model, footprints, bar_hash[:floor_height], bar_hash[:num_stories], bar_hash[:center_of_footprint], story_hash)
|
597
1039
|
|
598
1040
|
# put all of the spaces in the model into a vector for intersection and surface matching
|
599
1041
|
spaces = OpenStudio::Model::SpaceVector.new
|
@@ -601,19 +1043,136 @@ module OsLib_ModelGeneration
|
|
601
1043
|
spaces << space
|
602
1044
|
end
|
603
1045
|
|
1046
|
+
# flag for intersection and matching type
|
1047
|
+
diagnostic_intersect = true
|
1048
|
+
|
604
1049
|
# only intersect if make_mid_story_surfaces_adiabatic false
|
605
|
-
if
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
1050
|
+
if diagnostic_intersect
|
1051
|
+
|
1052
|
+
model.getPlanarSurfaces.each do |surface|
|
1053
|
+
array = []
|
1054
|
+
vertices = surface.vertices
|
1055
|
+
fixed = false
|
1056
|
+
vertices.each do |vertex|
|
1057
|
+
next if fixed
|
1058
|
+
if array.include?(vertex)
|
1059
|
+
# create a new set of vertices
|
1060
|
+
new_vertices = OpenStudio::Point3dVector.new
|
1061
|
+
array_b = []
|
1062
|
+
surface.vertices.each do |vertex_b|
|
1063
|
+
next if array_b.include?(vertex_b)
|
1064
|
+
new_vertices << vertex_b
|
1065
|
+
array_b << vertex_b
|
1066
|
+
end
|
1067
|
+
surface.setVertices(new_vertices)
|
1068
|
+
num_removed = vertices.size - surface.vertices.size
|
1069
|
+
runner.registerWarning("#{surface.name} has duplicate vertices. Started with #{vertices.size} vertices, removed #{num_removed}.")
|
1070
|
+
fixed = true
|
1071
|
+
else
|
1072
|
+
array << vertex
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
# remove collinear points in a surface
|
1078
|
+
model.getPlanarSurfaces.each do |surface|
|
1079
|
+
new_vertices = OpenStudio.removeCollinear(surface.vertices)
|
1080
|
+
starting_count = surface.vertices.size
|
1081
|
+
final_count = new_vertices.size
|
1082
|
+
if final_count < starting_count
|
1083
|
+
runner.registerWarning("Removing #{starting_count - final_count} collinear vertices from #{surface.name}.")
|
1084
|
+
surface.setVertices(new_vertices)
|
1085
|
+
end
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
# remove duplicate surfaces in a space (should be done after remove duplicate and collinear points)
|
1089
|
+
model.getSpaces.each do |space|
|
1090
|
+
|
1091
|
+
# secondary array to compare against
|
1092
|
+
surfaces_b = space.surfaces.sort
|
1093
|
+
|
1094
|
+
space.surfaces.sort.each do |surface_a|
|
1095
|
+
|
1096
|
+
# delete from secondary array
|
1097
|
+
surfaces_b.delete(surface_a)
|
1098
|
+
|
1099
|
+
surfaces_b.each do |surface_b|
|
1100
|
+
next if surface_a == surface_b # dont' test against same surface
|
1101
|
+
if surface_a.equalVertices(surface_b)
|
1102
|
+
runner.registerWarning("#{surface_a.name} and #{surface_b.name} in #{space.name} have duplicate geometry, removing #{surface_b.name}.")
|
1103
|
+
surface_b.remove
|
1104
|
+
elsif surface_a.reverseEqualVertices(surface_b)
|
1105
|
+
# todo - add logic to determine which face naormal is reversed and which is correct
|
1106
|
+
runner.registerWarning("#{surface_a.name} and #{surface_b.name} in #{space.name} have reversed geometry, removing #{surface_b.name}.")
|
1107
|
+
surface_b.remove
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
end
|
612
1113
|
end
|
613
|
-
end
|
614
1114
|
|
615
|
-
|
616
|
-
|
1115
|
+
if !(bar_hash[:make_mid_story_surfaces_adiabatic])
|
1116
|
+
# intersect and surface match two pair by pair
|
1117
|
+
spaces_b = model.getSpaces.sort
|
1118
|
+
# looping through vector of each space
|
1119
|
+
model.getSpaces.sort.each do |space_a|
|
1120
|
+
spaces_b.delete(space_a)
|
1121
|
+
spaces_b.each do |space_b|
|
1122
|
+
#runner.registerInfo("Intersecting and matching surfaces between #{space_a.name} and #{space.name}")
|
1123
|
+
spaces_temp = OpenStudio::Model::SpaceVector.new
|
1124
|
+
spaces_temp << space_a
|
1125
|
+
spaces_temp << space_b
|
1126
|
+
# intersect and sort
|
1127
|
+
OpenStudio::Model.intersectSurfaces(spaces_temp)
|
1128
|
+
OpenStudio::Model.matchSurfaces(spaces_temp)
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
runner.registerInfo('Intersecting and matching surfaces in model, this will create additional geometry.')
|
1132
|
+
else #elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
|
1133
|
+
model.getBuilding.buildingStories.each do |story|
|
1134
|
+
# intersect and surface match two pair by pair
|
1135
|
+
spaces_b = story.spaces.sort
|
1136
|
+
# looping through vector of each space
|
1137
|
+
story.spaces.sort.each do |space_a|
|
1138
|
+
spaces_b.delete(space_a)
|
1139
|
+
spaces_b.each do |space_b|
|
1140
|
+
spaces_temp = OpenStudio::Model::SpaceVector.new
|
1141
|
+
spaces_temp << space_a
|
1142
|
+
spaces_temp << space_b
|
1143
|
+
# intersect and sort
|
1144
|
+
OpenStudio::Model.intersectSurfaces(spaces_temp)
|
1145
|
+
OpenStudio::Model.matchSurfaces(spaces_temp)
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
runner.registerInfo("Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
|
1149
|
+
end
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
else
|
1153
|
+
|
1154
|
+
if !(bar_hash[:make_mid_story_surfaces_adiabatic])
|
1155
|
+
# intersect surfaces
|
1156
|
+
# (when bottom floor has many space types and one above doesn't will end up with heavily subdivided floor. Maybe use adiabatic and don't intersect floor/ceilings)
|
1157
|
+
intersect_surfaces = true
|
1158
|
+
if intersect_surfaces
|
1159
|
+
OpenStudio::Model.intersectSurfaces(spaces)
|
1160
|
+
OpenStudio::Model.matchSurfaces(spaces)
|
1161
|
+
runner.registerInfo('Intersecting and matching surfaces in model, this will create additional geometry.')
|
1162
|
+
end
|
1163
|
+
else #elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
|
1164
|
+
model.getBuilding.buildingStories.each do |story|
|
1165
|
+
story_spaces = OpenStudio::Model::SpaceVector.new
|
1166
|
+
story.spaces.sort.each do |space|
|
1167
|
+
story_spaces << space
|
1168
|
+
end
|
1169
|
+
OpenStudio::Model.intersectSurfaces(story_spaces)
|
1170
|
+
OpenStudio::Model.matchSurfaces(story_spaces)
|
1171
|
+
runner.registerInfo("Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
end
|
617
1176
|
|
618
1177
|
# set boundary conditions if not already set when geometry was created
|
619
1178
|
# todo - update this to use space original z value vs. story name
|
@@ -621,6 +1180,7 @@ module OsLib_ModelGeneration
|
|
621
1180
|
model.getBuildingStorys.each do |story|
|
622
1181
|
next if !story.name.to_s.include?('Story B')
|
623
1182
|
story.spaces.each do |space|
|
1183
|
+
next if not new_spaces.include?(space)
|
624
1184
|
space.surfaces.each do |surface|
|
625
1185
|
next if surface.surfaceType != 'Wall'
|
626
1186
|
next if surface.outsideBoundaryCondition != 'Outdoors'
|
@@ -631,10 +1191,13 @@ module OsLib_ModelGeneration
|
|
631
1191
|
end
|
632
1192
|
|
633
1193
|
# sort stories (by name for now but need better way)
|
634
|
-
# todo - need to change this so doesn't create issues when models have existing stories and spaces. Should be able to run it multiple times
|
635
1194
|
sorted_stories = {}
|
636
|
-
|
637
|
-
|
1195
|
+
new_spaces.each do |space|
|
1196
|
+
next if ! space.buildingStory.is_initialized
|
1197
|
+
story = space.buildingStory.get
|
1198
|
+
if ! sorted_stories.has_key?(name.to_s)
|
1199
|
+
sorted_stories[story.name.to_s] = story
|
1200
|
+
end
|
638
1201
|
end
|
639
1202
|
|
640
1203
|
# flag space types that have wwr overrides
|
@@ -651,7 +1214,7 @@ module OsLib_ModelGeneration
|
|
651
1214
|
adiabatic_ceiling = true
|
652
1215
|
end
|
653
1216
|
|
654
|
-
# make all mid story floor and
|
1217
|
+
# make all mid story floor and ceilings adiabatic if requested
|
655
1218
|
if bar_hash[:make_mid_story_surfaces_adiabatic]
|
656
1219
|
if i > 0
|
657
1220
|
adiabatic_floor = true
|
@@ -665,6 +1228,7 @@ module OsLib_ModelGeneration
|
|
665
1228
|
party_wall_facades = stories_flat[i][:story_party_walls]
|
666
1229
|
|
667
1230
|
story.spaces.each do |space|
|
1231
|
+
next if not new_spaces.include?(space)
|
668
1232
|
space.surfaces. each do |surface|
|
669
1233
|
# set floor to adiabatic if requited
|
670
1234
|
if adiabatic_floor && surface.surfaceType == 'Floor'
|
@@ -693,11 +1257,11 @@ module OsLib_ModelGeneration
|
|
693
1257
|
space_type = surface.space.get.spaceType.get
|
694
1258
|
|
695
1259
|
# see if space type has wwr value
|
696
|
-
bar_hash[:space_types].each do |k,v|
|
697
|
-
if v.
|
1260
|
+
bar_hash[:space_types].each do |k, v|
|
1261
|
+
if v.key?(:space_type) && space_type == v[:space_type]
|
698
1262
|
|
699
|
-
# if matching space type specifies a wwr then override the
|
700
|
-
if v.
|
1263
|
+
# if matching space type specifies a wwr then override the orientation specific recommendations for this surface.
|
1264
|
+
if v.key?(:wwr)
|
701
1265
|
wwr_n = v[:wwr]
|
702
1266
|
wwr_e = v[:wwr]
|
703
1267
|
wwr_s = v[:wwr]
|
@@ -742,12 +1306,25 @@ module OsLib_ModelGeneration
|
|
742
1306
|
end
|
743
1307
|
|
744
1308
|
# report space types with custom wwr values
|
745
|
-
space_type_wwr_overrides.each do |space_type,wwr|
|
746
|
-
runner.registerInfo("For #{space_type.name} the default building wwr was replaced with a space type
|
1309
|
+
space_type_wwr_overrides.each do |space_type, wwr|
|
1310
|
+
runner.registerInfo("For #{space_type.name} the default building wwr was replaced with a space type specfic value of #{wwr}")
|
747
1311
|
end
|
748
1312
|
|
1313
|
+
new_floor_area_si = 0.0
|
1314
|
+
new_spaces.each do |space|
|
1315
|
+
new_floor_area_si += space.floorArea * space.multiplier
|
1316
|
+
end
|
1317
|
+
new_floor_area_ip = OpenStudio.convert(new_floor_area_si, 'm^2', 'ft^2').get
|
1318
|
+
|
749
1319
|
final_floor_area_ip = OpenStudio.convert(model.getBuilding.floorArea, 'm^2', 'ft^2').get
|
750
|
-
|
1320
|
+
if new_floor_area_ip == final_floor_area_ip
|
1321
|
+
runner.registerInfo("Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2.")
|
1322
|
+
else
|
1323
|
+
runner.registerInfo("Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2. Total building area is #{OpenStudio.toNeatString(final_floor_area_ip, 0, true)} ft^2.")
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
return new_spaces
|
1327
|
+
|
751
1328
|
end
|
752
1329
|
|
753
1330
|
# make selected surfaces adiabatic
|
@@ -814,4 +1391,1888 @@ module OsLib_ModelGeneration
|
|
814
1391
|
|
815
1392
|
return bar
|
816
1393
|
end
|
1394
|
+
|
1395
|
+
def bar_hash_setup_run(runner,model,args,length,width,floor_height_si,center_of_footprint,space_types_hash,num_stories)
|
1396
|
+
# create envelope
|
1397
|
+
# populate bar_hash and create envelope with data from envelope_data_hash and user arguments
|
1398
|
+
bar_hash = {}
|
1399
|
+
bar_hash[:length] = length
|
1400
|
+
bar_hash[:width] = width
|
1401
|
+
bar_hash[:num_stories_below_grade] = args['num_stories_below_grade']
|
1402
|
+
bar_hash[:num_stories_above_grade] = args['num_stories_above_grade']
|
1403
|
+
bar_hash[:floor_height] = floor_height_si
|
1404
|
+
bar_hash[:center_of_footprint] = center_of_footprint
|
1405
|
+
bar_hash[:bar_division_method] = args['bar_division_method']
|
1406
|
+
bar_hash[:make_mid_story_surfaces_adiabatic] = args['make_mid_story_surfaces_adiabatic']
|
1407
|
+
bar_hash[:space_types] = space_types_hash
|
1408
|
+
bar_hash[:building_wwr_n] = args['wwr']
|
1409
|
+
bar_hash[:building_wwr_s] = args['wwr']
|
1410
|
+
bar_hash[:building_wwr_e] = args['wwr']
|
1411
|
+
bar_hash[:building_wwr_w] = args['wwr']
|
1412
|
+
|
1413
|
+
# round up non integer stoires to next integer
|
1414
|
+
num_stories_round_up = num_stories.ceil
|
1415
|
+
runner.registerInfo("Making bar with length of #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft and width of #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft")
|
1416
|
+
|
1417
|
+
# party_walls_array to be used by orientation specific or fractional party wall values
|
1418
|
+
party_walls_array = [] # this is an array of arrays, where each entry is effective building story with array of directions
|
1419
|
+
|
1420
|
+
if args['party_wall_stories_north'] + args['party_wall_stories_south'] + args['party_wall_stories_east'] + args['party_wall_stories_west'] > 0
|
1421
|
+
|
1422
|
+
# loop through effective number of stories add orientation specific party walls per user arguments
|
1423
|
+
num_stories_round_up.times do |i|
|
1424
|
+
test_value = i + 1 - bar_hash[:num_stories_below_grade]
|
1425
|
+
|
1426
|
+
array = []
|
1427
|
+
if args['party_wall_stories_north'] >= test_value
|
1428
|
+
array << 'north'
|
1429
|
+
end
|
1430
|
+
if args['party_wall_stories_south'] >= test_value
|
1431
|
+
array << 'south'
|
1432
|
+
end
|
1433
|
+
if args['party_wall_stories_east'] >= test_value
|
1434
|
+
array << 'east'
|
1435
|
+
end
|
1436
|
+
if args['party_wall_stories_west'] >= test_value
|
1437
|
+
array << 'west'
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
# populate party_wall_array for this story
|
1441
|
+
party_walls_array << array
|
1442
|
+
end
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
# calculate party walls if using party_wall_fraction method
|
1446
|
+
if args['party_wall_fraction'] > 0 && !party_walls_array.empty?
|
1447
|
+
runner.registerWarning('Both orientation and fractional party wall values arguments were populated, will ignore fractional party wall input')
|
1448
|
+
elsif args['party_wall_fraction'] > 0
|
1449
|
+
|
1450
|
+
# orientation of long and short side of building will vary based on building rotation
|
1451
|
+
|
1452
|
+
# full story ext wall area
|
1453
|
+
typical_length_facade_area = length * floor_height_si
|
1454
|
+
typical_width_facade_area = width * floor_height_si
|
1455
|
+
|
1456
|
+
# top story ext wall area, may be partial story
|
1457
|
+
partial_story_multiplier = (1.0 - args['num_stories_above_grade'].ceil + args['num_stories_above_grade'])
|
1458
|
+
area_multiplier = partial_story_multiplier
|
1459
|
+
edge_multiplier = Math.sqrt(area_multiplier)
|
1460
|
+
top_story_length = length * edge_multiplier
|
1461
|
+
top_story_width = width * edge_multiplier
|
1462
|
+
top_story_length_facade_area = top_story_length * floor_height_si
|
1463
|
+
top_story_width_facade_area = top_story_width * floor_height_si
|
1464
|
+
|
1465
|
+
total_exterior_wall_area = 2 * (length + width) * (args['num_stories_above_grade'].ceil - 1.0) * floor_height_si + 2 * (top_story_length + top_story_width) * floor_height_si
|
1466
|
+
target_party_wall_area = total_exterior_wall_area * args['party_wall_fraction']
|
1467
|
+
|
1468
|
+
width_counter = 0
|
1469
|
+
width_area = 0.0
|
1470
|
+
facade_area = typical_width_facade_area
|
1471
|
+
until (width_area + facade_area >= target_party_wall_area) || (width_counter == args['num_stories_above_grade'].ceil * 2)
|
1472
|
+
# update facade area for top story
|
1473
|
+
if width_counter == args['num_stories_above_grade'].ceil - 1 || width_counter == args['num_stories_above_grade'].ceil * 2 - 1
|
1474
|
+
facade_area = top_story_width_facade_area
|
1475
|
+
else
|
1476
|
+
facade_area = typical_width_facade_area
|
1477
|
+
end
|
1478
|
+
|
1479
|
+
width_counter += 1
|
1480
|
+
width_area += facade_area
|
1481
|
+
|
1482
|
+
end
|
1483
|
+
width_area_remainder = target_party_wall_area - width_area
|
1484
|
+
|
1485
|
+
length_counter = 0
|
1486
|
+
length_area = 0.0
|
1487
|
+
facade_area = typical_length_facade_area
|
1488
|
+
until (length_area + facade_area >= target_party_wall_area) || (length_counter == args['num_stories_above_grade'].ceil * 2)
|
1489
|
+
# update facade area for top story
|
1490
|
+
if length_counter == args['num_stories_above_grade'].ceil - 1 || length_counter == args['num_stories_above_grade'].ceil * 2 - 1
|
1491
|
+
facade_area = top_story_length_facade_area
|
1492
|
+
else
|
1493
|
+
facade_area = typical_length_facade_area
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
length_counter += 1
|
1497
|
+
length_area += facade_area
|
1498
|
+
end
|
1499
|
+
length_area_remainder = target_party_wall_area - length_area
|
1500
|
+
|
1501
|
+
# get rotation and best fit to adjust orientation for fraction party wall
|
1502
|
+
rotation = args['building_rotation'] % 360.0 # should result in value between 0 and 360
|
1503
|
+
card_dir_array = [0.0, 90.0, 180.0, 270.0, 360.0]
|
1504
|
+
# reverse array to properly handle 45, 135, 225, and 315
|
1505
|
+
best_fit = card_dir_array.reverse.min_by { |x| (x.to_f - rotation).abs }
|
1506
|
+
|
1507
|
+
if ![90.0, 270.0].include? best_fit
|
1508
|
+
width_card_dir = ['east', 'west']
|
1509
|
+
length_card_dir = ['north', 'south']
|
1510
|
+
else # if rotation is closest to 90 or 270 then reverse which orientation is used for length and width
|
1511
|
+
width_card_dir = ['north', 'south']
|
1512
|
+
length_card_dir = ['east', 'west']
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
# if dont' find enough on short sides
|
1516
|
+
if width_area_remainder <= typical_length_facade_area
|
1517
|
+
|
1518
|
+
num_stories_round_up.times do |i|
|
1519
|
+
if i + 1 <= args['num_stories_below_grade']
|
1520
|
+
party_walls_array << []
|
1521
|
+
next
|
1522
|
+
end
|
1523
|
+
if i + 1 - args['num_stories_below_grade'] <= width_counter
|
1524
|
+
if i + 1 - args['num_stories_below_grade'] <= width_counter - args['num_stories_above_grade']
|
1525
|
+
party_walls_array << width_card_dir
|
1526
|
+
else
|
1527
|
+
party_walls_array << [width_card_dir.first]
|
1528
|
+
end
|
1529
|
+
else
|
1530
|
+
party_walls_array << []
|
1531
|
+
end
|
1532
|
+
end
|
1533
|
+
|
1534
|
+
else # use long sides instead
|
1535
|
+
|
1536
|
+
num_stories_round_up.times do |i|
|
1537
|
+
if i + 1 <= args['num_stories_below_grade']
|
1538
|
+
party_walls_array << []
|
1539
|
+
next
|
1540
|
+
end
|
1541
|
+
if i + 1 - args['num_stories_below_grade'] <= length_counter
|
1542
|
+
if i + 1 - args['num_stories_below_grade'] <= length_counter - args['num_stories_above_grade']
|
1543
|
+
party_walls_array << length_card_dir
|
1544
|
+
else
|
1545
|
+
party_walls_array << [length_card_dir.first]
|
1546
|
+
end
|
1547
|
+
else
|
1548
|
+
party_walls_array << []
|
1549
|
+
end
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
end
|
1553
|
+
|
1554
|
+
# TODO: - currently won't go past making two opposing sets of walls party walls. Info and registerValue are after create_bar in measure.rb
|
1555
|
+
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
# populate bar hash with story information
|
1559
|
+
bar_hash[:stories] = {}
|
1560
|
+
num_stories_round_up.times do |i|
|
1561
|
+
if party_walls_array.empty?
|
1562
|
+
party_walls = []
|
1563
|
+
else
|
1564
|
+
party_walls = party_walls_array[i]
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
# add below_partial_story
|
1568
|
+
if num_stories.ceil > num_stories && i == num_stories_round_up - 2
|
1569
|
+
below_partial_story = true
|
1570
|
+
else
|
1571
|
+
below_partial_story = false
|
1572
|
+
end
|
1573
|
+
|
1574
|
+
# bottom_story_ground_exposed_floor and top_story_exterior_exposed_roof already setup as bool
|
1575
|
+
bar_hash[:stories]["key #{i}"] = { story_party_walls: party_walls, story_min_multiplier: 1, story_included_in_building_area: true, below_partial_story: below_partial_story, bottom_story_ground_exposed_floor: args['bottom_story_ground_exposed_floor'], top_story_exterior_exposed_roof: args['top_story_exterior_exposed_roof'] }
|
1576
|
+
end
|
1577
|
+
|
1578
|
+
# create bar
|
1579
|
+
new_spaces = create_bar(runner, model, bar_hash, args['story_multiplier'])
|
1580
|
+
|
1581
|
+
# check expect roof and wall area
|
1582
|
+
target_footprint = bar_hash[:length] * bar_hash[:width]
|
1583
|
+
ground_floor_area = 0.0
|
1584
|
+
roof_area = 0.0
|
1585
|
+
new_spaces.each do |space|
|
1586
|
+
space.surfaces.each do |surface|
|
1587
|
+
if surface.surfaceType == "Floor" && surface.outsideBoundaryCondition == "Ground"
|
1588
|
+
ground_floor_area += surface.netArea
|
1589
|
+
elsif surface.surfaceType == "RoofCeiling" && surface.outsideBoundaryCondition == "Outdoors"
|
1590
|
+
roof_area += surface.netArea
|
1591
|
+
end
|
1592
|
+
end
|
1593
|
+
end
|
1594
|
+
# todo - extend to address when top and or bottom story are not exposed via argument
|
1595
|
+
if ground_floor_area > target_footprint + 0.001 || roof_area > target_footprint + 0.001
|
1596
|
+
#runner.registerError("Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.")
|
1597
|
+
#return false
|
1598
|
+
|
1599
|
+
# not providing adiabatic work around when top story is partial story.
|
1600
|
+
if args['num_stories_above_grade'].to_f != args['num_stories_above_grade'].ceil
|
1601
|
+
runner.registerError("Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.")
|
1602
|
+
return false
|
1603
|
+
else
|
1604
|
+
runner.registerInfo("Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error, altering impacted surfaces boundary condition to be adiabatic.")
|
1605
|
+
match_error = true
|
1606
|
+
end
|
1607
|
+
else
|
1608
|
+
match_error = false
|
1609
|
+
end
|
1610
|
+
|
1611
|
+
# todo - should be able to remove this fix after OpenStudio intersection issue is fixed. At that time turn the above message into an error with return false after it
|
1612
|
+
if match_error
|
1613
|
+
|
1614
|
+
# identify z value of top and bottom story
|
1615
|
+
bottom_story = nil
|
1616
|
+
top_story = nil
|
1617
|
+
new_spaces.each do |space|
|
1618
|
+
story = space.buildingStory.get
|
1619
|
+
nom_z = story.nominalZCoordinate.get
|
1620
|
+
if bottom_story.nil?
|
1621
|
+
bottom_story = nom_z
|
1622
|
+
elsif bottom_story > nom_z
|
1623
|
+
bottom_story = nom_z
|
1624
|
+
end
|
1625
|
+
if top_story.nil?
|
1626
|
+
top_story = nom_z
|
1627
|
+
elsif top_story < nom_z
|
1628
|
+
top_story = nom_z
|
1629
|
+
end
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
# change boundary condition and intersection as needed.
|
1633
|
+
new_spaces.each do |space|
|
1634
|
+
if space.buildingStory.get.nominalZCoordinate.get > bottom_story
|
1635
|
+
# change floors
|
1636
|
+
space.surfaces.each do |surface|
|
1637
|
+
next if not surface.surfaceType == "Floor" && surface.outsideBoundaryCondition == "Ground"
|
1638
|
+
surface.setOutsideBoundaryCondition("Adiabatic")
|
1639
|
+
end
|
1640
|
+
end
|
1641
|
+
if space.buildingStory.get.nominalZCoordinate.get < top_story
|
1642
|
+
# change ceilings
|
1643
|
+
space.surfaces.each do |surface|
|
1644
|
+
next if not surface.surfaceType == "RoofCeiling" && surface.outsideBoundaryCondition == "Outdoors"
|
1645
|
+
surface.setOutsideBoundaryCondition("Adiabatic")
|
1646
|
+
end
|
1647
|
+
end
|
1648
|
+
end
|
1649
|
+
|
1650
|
+
end
|
1651
|
+
|
1652
|
+
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
# bar_from_building_type_ratios
|
1656
|
+
# used for varieties of measures that create bar from building type ratios
|
1657
|
+
def bar_from_building_type_ratios(model, runner, user_arguments)
|
1658
|
+
|
1659
|
+
# assign the user inputs to variables
|
1660
|
+
args = OsLib_HelperMethods.createRunVariables(runner, model, user_arguments, arguments(model))
|
1661
|
+
if !args then return false end
|
1662
|
+
|
1663
|
+
# add in arguments that may not be passed in
|
1664
|
+
if !args.has_key?("double_loaded_corridor")
|
1665
|
+
args["double_loaded_corridor"] = "None" # use None when not in measure building type data may not contain this
|
1666
|
+
end
|
1667
|
+
if ! args.has_key?("perim_mult")
|
1668
|
+
args["perim_mult"] = 1.0 # will not make two bars for extended perimeter
|
1669
|
+
end
|
1670
|
+
|
1671
|
+
# lookup and replace argument values from upstream measures
|
1672
|
+
if args['use_upstream_args'] == true
|
1673
|
+
args.each do |arg, value|
|
1674
|
+
next if arg == 'use_upstream_args' # this argument should not be changed
|
1675
|
+
value_from_osw = OsLib_HelperMethods.check_upstream_measure_for_arg(runner, arg)
|
1676
|
+
if !value_from_osw.empty?
|
1677
|
+
runner.registerInfo("Replacing argument named #{arg} from current measure with a value of #{value_from_osw[:value]} from #{value_from_osw[:measure_name]}.")
|
1678
|
+
new_val = value_from_osw[:value]
|
1679
|
+
# TODO: - make code to handle non strings more robust. check_upstream_measure_for_arg could pass back the argument type
|
1680
|
+
if arg == 'total_bldg_floor_area'
|
1681
|
+
args[arg] = new_val.to_f
|
1682
|
+
elsif arg == 'num_stories_above_grade'
|
1683
|
+
args[arg] = new_val.to_f
|
1684
|
+
elsif arg == 'zipcode'
|
1685
|
+
args[arg] = new_val.to_i
|
1686
|
+
else
|
1687
|
+
args[arg] = new_val
|
1688
|
+
end
|
1689
|
+
end
|
1690
|
+
end
|
1691
|
+
end
|
1692
|
+
|
1693
|
+
# check expected values of double arguments
|
1694
|
+
fraction_args = ['bldg_type_b_fract_bldg_area',
|
1695
|
+
'bldg_type_c_fract_bldg_area',
|
1696
|
+
'bldg_type_d_fract_bldg_area',
|
1697
|
+
'wwr', 'party_wall_fraction']
|
1698
|
+
fraction = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => 1.0, 'min_eq_bool' => true, 'max_eq_bool' => true, 'arg_array' => fraction_args)
|
1699
|
+
|
1700
|
+
positive_args = ['total_bldg_floor_area']
|
1701
|
+
positive = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => nil, 'min_eq_bool' => false, 'max_eq_bool' => false, 'arg_array' => positive_args)
|
1702
|
+
|
1703
|
+
one_or_greater_args = ['num_stories_above_grade']
|
1704
|
+
one_or_greater = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 1.0, 'max' => nil, 'min_eq_bool' => true, 'max_eq_bool' => false, 'arg_array' => one_or_greater_args)
|
1705
|
+
|
1706
|
+
non_neg_args = ['num_stories_below_grade',
|
1707
|
+
'floor_height',
|
1708
|
+
'ns_to_ew_ratio',
|
1709
|
+
'party_wall_stories_north',
|
1710
|
+
'party_wall_stories_south',
|
1711
|
+
'party_wall_stories_east',
|
1712
|
+
'party_wall_stories_west',
|
1713
|
+
'single_floor_area',
|
1714
|
+
'bar_width',]
|
1715
|
+
non_neg = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => nil, 'min_eq_bool' => true, 'max_eq_bool' => false, 'arg_array' => non_neg_args)
|
1716
|
+
|
1717
|
+
# return false if any errors fail
|
1718
|
+
if !fraction then return false end
|
1719
|
+
if !positive then return false end
|
1720
|
+
if !one_or_greater then return false end
|
1721
|
+
if !non_neg then return false end
|
1722
|
+
|
1723
|
+
# if aspect ratio, story height or wwr have argument value of 0 then use smart building type defaults
|
1724
|
+
building_form_defaults = building_form_defaults(args['bldg_type_a'])
|
1725
|
+
|
1726
|
+
# store list of defaulted items
|
1727
|
+
defaulted_args = []
|
1728
|
+
|
1729
|
+
if args['ns_to_ew_ratio'] == 0.0
|
1730
|
+
args['ns_to_ew_ratio'] = building_form_defaults[:aspect_ratio]
|
1731
|
+
runner.registerInfo("0.0 value for aspect ratio will be replaced with smart default for #{args['bldg_type_a']} of #{building_form_defaults[:aspect_ratio]}.")
|
1732
|
+
end
|
1733
|
+
|
1734
|
+
if args['perim_mult'] == 0.0
|
1735
|
+
# if this is not defined then use default of 1.0
|
1736
|
+
if !building_form_defaults.has_key?(:perim_mult)
|
1737
|
+
args['perim_mult'] = 1.0
|
1738
|
+
else
|
1739
|
+
args['perim_mult'] = building_form_defaults[:perim_mult]
|
1740
|
+
end
|
1741
|
+
runner.registerInfo("0.0 value for minimum perimeter multiplier will be replaced with smart default for #{args['bldg_type_a']} of #{building_form_defaults[:perim_mult]}.")
|
1742
|
+
elsif args['perim_mult'] < 1.0
|
1743
|
+
runner.registerError("Other than the smart default value of 0, the minimum perimeter multiplier should be equal to 1.0 or greater.")
|
1744
|
+
return false
|
1745
|
+
end
|
1746
|
+
|
1747
|
+
if args['floor_height'] == 0.0
|
1748
|
+
args['floor_height'] = building_form_defaults[:typical_story]
|
1749
|
+
runner.registerInfo("0.0 value for floor height will be replaced with smart default for #{args['bldg_type_a']} of #{building_form_defaults[:typical_story]}.")
|
1750
|
+
defaulted_args << 'floor_height'
|
1751
|
+
end
|
1752
|
+
# because of this can't set wwr to 0.0. If that is desired then we can change this to check for 1.0 instead of 0.0
|
1753
|
+
if args['wwr'] == 0.0
|
1754
|
+
args['wwr'] = building_form_defaults[:wwr]
|
1755
|
+
runner.registerInfo("0.0 value for window to wall ratio will be replaced with smart default for #{args['bldg_type_a']} of #{building_form_defaults[:wwr]}.")
|
1756
|
+
end
|
1757
|
+
|
1758
|
+
# check that sum of fractions for b,c, and d is less than 1.0 (so something is left for primary building type)
|
1759
|
+
bldg_type_a_fract_bldg_area = 1.0 - args['bldg_type_b_fract_bldg_area'] - args['bldg_type_c_fract_bldg_area'] - args['bldg_type_d_fract_bldg_area']
|
1760
|
+
if bldg_type_a_fract_bldg_area <= 0.0
|
1761
|
+
runner.registerError('Primary Building Type fraction of floor area must be greater than 0. Please lower one or more of the fractions for Building Type B-D.')
|
1762
|
+
return false
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
# Make the standard applier
|
1766
|
+
standard = Standard.build("#{args['template']}")
|
1767
|
+
|
1768
|
+
# report initial condition of model
|
1769
|
+
runner.registerInitialCondition("The building started with #{model.getSpaces.size} spaces.")
|
1770
|
+
|
1771
|
+
# determine of ns_ew needs to be mirrored
|
1772
|
+
mirror_ns_ew = false
|
1773
|
+
rotation = model.getBuilding.northAxis
|
1774
|
+
if rotation > 45.0 && rotation < 135.0
|
1775
|
+
mirror_ns_ew = true
|
1776
|
+
elsif rotation > 45.0 && rotation < 135.0
|
1777
|
+
mirror_ns_ew = true
|
1778
|
+
end
|
1779
|
+
|
1780
|
+
# remove non-resource objects not removed by removing the building
|
1781
|
+
remove_non_resource_objects(runner, model)
|
1782
|
+
|
1783
|
+
# rename building to infer template in downstream measure
|
1784
|
+
name_array = [args['template'], args['bldg_type_a']]
|
1785
|
+
if args['bldg_type_b_fract_bldg_area'] > 0 then name_array << args['bldg_type_b'] end
|
1786
|
+
if args['bldg_type_c_fract_bldg_area'] > 0 then name_array << args['bldg_type_c'] end
|
1787
|
+
if args['bldg_type_d_fract_bldg_area'] > 0 then name_array << args['bldg_type_d'] end
|
1788
|
+
model.getBuilding.setName(name_array.join('|').to_s)
|
1789
|
+
|
1790
|
+
# hash to whole building type data
|
1791
|
+
building_type_hash = {}
|
1792
|
+
|
1793
|
+
# gather data for bldg_type_a
|
1794
|
+
building_type_hash[args['bldg_type_a']] = {}
|
1795
|
+
building_type_hash[args['bldg_type_a']][:frac_bldg_area] = bldg_type_a_fract_bldg_area
|
1796
|
+
#building_type_hash[args['bldg_type_a']][:num_units] = args['bldg_type_a_num_units']
|
1797
|
+
building_type_hash[args['bldg_type_a']][:space_types] = get_space_types_from_building_type(args['bldg_type_a'], args['template'], true)
|
1798
|
+
|
1799
|
+
# gather data for bldg_type_b
|
1800
|
+
if args['bldg_type_b_fract_bldg_area'] > 0
|
1801
|
+
building_type_hash[args['bldg_type_b']] = {}
|
1802
|
+
building_type_hash[args['bldg_type_b']][:frac_bldg_area] = args['bldg_type_b_fract_bldg_area']
|
1803
|
+
#building_type_hash[args['bldg_type_b']][:num_units] = args['bldg_type_b_num_units']
|
1804
|
+
building_type_hash[args['bldg_type_b']][:space_types] = get_space_types_from_building_type(args['bldg_type_b'], args['template'], true)
|
1805
|
+
end
|
1806
|
+
|
1807
|
+
# gather data for bldg_type_c
|
1808
|
+
if args['bldg_type_c_fract_bldg_area'] > 0
|
1809
|
+
building_type_hash[args['bldg_type_c']] = {}
|
1810
|
+
building_type_hash[args['bldg_type_c']][:frac_bldg_area] = args['bldg_type_c_fract_bldg_area']
|
1811
|
+
#building_type_hash[args['bldg_type_c']][:num_units] = args['bldg_type_c_num_units']
|
1812
|
+
building_type_hash[args['bldg_type_c']][:space_types] = get_space_types_from_building_type(args['bldg_type_c'], args['template'], true)
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
# gather data for bldg_type_d
|
1816
|
+
if args['bldg_type_d_fract_bldg_area'] > 0
|
1817
|
+
building_type_hash[args['bldg_type_d']] = {}
|
1818
|
+
building_type_hash[args['bldg_type_d']][:frac_bldg_area] = args['bldg_type_d_fract_bldg_area']
|
1819
|
+
#building_type_hash[args['bldg_type_d']][:num_units] = args['bldg_type_d_num_units']
|
1820
|
+
building_type_hash[args['bldg_type_d']][:space_types] = get_space_types_from_building_type(args['bldg_type_d'], args['template'], true)
|
1821
|
+
end
|
1822
|
+
|
1823
|
+
# creating space types for requested building types
|
1824
|
+
building_type_hash.each do |building_type, building_type_hash|
|
1825
|
+
runner.registerInfo("Creating Space Types for #{building_type}.")
|
1826
|
+
|
1827
|
+
# mapping building_type name is needed for a few methods
|
1828
|
+
building_type = standard.model_get_lookup_name(building_type)
|
1829
|
+
|
1830
|
+
# create space_type_map from array
|
1831
|
+
sum_of_ratios = 0.0
|
1832
|
+
building_type_hash[:space_types] = building_type_hash[:space_types].sort_by { |k, v| v[:ratio] }.to_h
|
1833
|
+
building_type_hash[:space_types].each do |space_type_name, hash|
|
1834
|
+
next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
|
1835
|
+
|
1836
|
+
# create space type
|
1837
|
+
space_type = OpenStudio::Model::SpaceType.new(model)
|
1838
|
+
space_type.setStandardsBuildingType(building_type)
|
1839
|
+
space_type.setStandardsSpaceType(space_type_name)
|
1840
|
+
space_type.setName("#{building_type} #{space_type_name}")
|
1841
|
+
|
1842
|
+
# set color
|
1843
|
+
test = standard.space_type_apply_rendering_color(space_type) # this uses openstudio-standards
|
1844
|
+
if !test
|
1845
|
+
# todo - once fixed in standards un-comment this
|
1846
|
+
#runner.registerWarning("Could not find color for #{args['template']} #{space_type.name}")
|
1847
|
+
end
|
1848
|
+
|
1849
|
+
# extend hash to hold new space type object
|
1850
|
+
hash[:space_type] = space_type
|
1851
|
+
|
1852
|
+
# add to sum_of_ratios counter for adjustment multiplier
|
1853
|
+
sum_of_ratios += hash[:ratio]
|
1854
|
+
end
|
1855
|
+
|
1856
|
+
# store multiplier needed to adjust sum of ratios to equal 1.0
|
1857
|
+
building_type_hash[:ratio_adjustment_multiplier] = 1.0 / sum_of_ratios
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
# calculate length and with of bar
|
1861
|
+
total_bldg_floor_area_si = OpenStudio.convert(args['total_bldg_floor_area'], 'ft^2', 'm^2').get
|
1862
|
+
single_floor_area_si = OpenStudio.convert(args['single_floor_area'], 'ft^2', 'm^2').get
|
1863
|
+
|
1864
|
+
# store number of stories
|
1865
|
+
num_stories = args['num_stories_below_grade'] + args['num_stories_above_grade']
|
1866
|
+
|
1867
|
+
# handle user-assigned single floor plate size condition
|
1868
|
+
if args['single_floor_area'] > 0.0
|
1869
|
+
footprint_si = single_floor_area_si
|
1870
|
+
total_bldg_floor_area_si = footprint_si * num_stories.to_f
|
1871
|
+
runner.registerWarning('User-defined single floor area was used for calculation of total building floor area')
|
1872
|
+
# add warning if custom_height_bar is true and applicable building type is selected
|
1873
|
+
if args['custom_height_bar']
|
1874
|
+
runner.registerWarning("Cannot use custom height bar with single floor area method, will not create custom height bar.")
|
1875
|
+
args['custom_height_bar'] = false
|
1876
|
+
end
|
1877
|
+
else
|
1878
|
+
footprint_si = nil
|
1879
|
+
end
|
1880
|
+
|
1881
|
+
# populate space_types_hash
|
1882
|
+
space_types_hash = {}
|
1883
|
+
multi_height_space_types_hash = {}
|
1884
|
+
custom_story_heights = []
|
1885
|
+
if args['space_type_sort_logic'] == 'Building Type > Size'
|
1886
|
+
building_type_hash = building_type_hash.sort_by { |k, v| v[:frac_bldg_area] }
|
1887
|
+
end
|
1888
|
+
building_type_hash.each do |building_type, building_type_hash|
|
1889
|
+
|
1890
|
+
if args["double_loaded_corridor"] == "Primary Space Type"
|
1891
|
+
|
1892
|
+
# see if building type has circulation space type, if so then merge that along with default space type into hash key in place of space type
|
1893
|
+
default_st = nil
|
1894
|
+
circ_st = nil
|
1895
|
+
building_type_hash[:space_types].each do |space_type_name, hash|
|
1896
|
+
if hash[:default] then default_st = space_type_name end
|
1897
|
+
if hash[:circ] then circ_st = space_type_name end
|
1898
|
+
end
|
1899
|
+
|
1900
|
+
# update building hash
|
1901
|
+
if !default_st.nil? && !circ_st.nil?
|
1902
|
+
runner.registerInfo("Combining #{default_st} and #{circ_st} into a group representing a double loaded corridor")
|
1903
|
+
|
1904
|
+
# add new item
|
1905
|
+
building_type_hash[:space_types]["Double Loaded Corridor"] = {}
|
1906
|
+
double_loaded_st = building_type_hash[:space_types]["Double Loaded Corridor"]
|
1907
|
+
double_loaded_st[:ratio] = building_type_hash[:space_types][default_st][:ratio] + building_type_hash[:space_types][circ_st][:ratio]
|
1908
|
+
double_loaded_st[:double_loaded_corridor] = true
|
1909
|
+
double_loaded_st[:space_type] = model.getBuilding
|
1910
|
+
double_loaded_st[:children] = {}
|
1911
|
+
building_type_hash[:space_types][default_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][default_st][:ratio]
|
1912
|
+
building_type_hash[:space_types][circ_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][circ_st][:ratio]
|
1913
|
+
building_type_hash[:space_types][default_st][:name] = default_st
|
1914
|
+
building_type_hash[:space_types][circ_st][:name] = circ_st
|
1915
|
+
double_loaded_st[:children][:default] = building_type_hash[:space_types][default_st]
|
1916
|
+
double_loaded_st[:children][:circ] = building_type_hash[:space_types][circ_st]
|
1917
|
+
double_loaded_st[:orig_ratio] = 0.0
|
1918
|
+
|
1919
|
+
# zero out ratios from old item (don't delete because I still want the space types made)
|
1920
|
+
building_type_hash[:space_types][default_st][:ratio] = 0.0
|
1921
|
+
building_type_hash[:space_types][circ_st][:ratio] = 0.0
|
1922
|
+
end
|
1923
|
+
end
|
1924
|
+
|
1925
|
+
building_type_hash[:space_types].each do |space_type_name, hash|
|
1926
|
+
next if hash[:space_type_gen] == false
|
1927
|
+
|
1928
|
+
space_type = hash[:space_type]
|
1929
|
+
ratio_of_bldg_total = hash[:ratio] * building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area]
|
1930
|
+
final_floor_area = ratio_of_bldg_total * total_bldg_floor_area_si # I think I can just pass ratio but passing in area is cleaner
|
1931
|
+
|
1932
|
+
# only add custom height space if 0 is used for floor_height
|
1933
|
+
if defaulted_args.include?('floor_height') && hash.key?(:story_height) && args['custom_height_bar']
|
1934
|
+
multi_height_space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type, story_height: hash[:story_height] }
|
1935
|
+
if hash.key?(:orig_ratio) then multi_height_space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
|
1936
|
+
custom_story_heights << hash[:story_height]
|
1937
|
+
if args['wwr'] == 0 && hash.key?(:wwr)
|
1938
|
+
multi_height_space_types_hash[space_type][:wwr] = hash[:wwr]
|
1939
|
+
end
|
1940
|
+
else
|
1941
|
+
# only add wwr if 0 used for wwr arg and if space type has wwr as key
|
1942
|
+
space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type }
|
1943
|
+
if hash.key?(:orig_ratio) then space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
|
1944
|
+
if args['wwr'] == 0 && hash.key?(:wwr)
|
1945
|
+
space_types_hash[space_type][:wwr] = hash[:wwr]
|
1946
|
+
end
|
1947
|
+
if hash[:double_loaded_corridor]
|
1948
|
+
space_types_hash[space_type][:children] = hash[:children]
|
1949
|
+
end
|
1950
|
+
end
|
1951
|
+
end
|
1952
|
+
end
|
1953
|
+
|
1954
|
+
# resort if not sorted by building type
|
1955
|
+
if args['space_type_sort_logic'] == "Size"
|
1956
|
+
# added code to convert to hash. I use sort_by 3 other times, but those seem to be working fine as is now.
|
1957
|
+
space_types_hash = Hash[space_types_hash.sort_by { |k, v| v[:floor_area] }]
|
1958
|
+
end
|
1959
|
+
|
1960
|
+
# calculate targets for testing
|
1961
|
+
target_areas = {} # used for checks
|
1962
|
+
target_areas_cust_height = 0.0
|
1963
|
+
space_types_hash.each do |k,v|
|
1964
|
+
if v.key?(:orig_ratio)
|
1965
|
+
target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
|
1966
|
+
else
|
1967
|
+
target_areas[k] = v[:floor_area]
|
1968
|
+
end
|
1969
|
+
end
|
1970
|
+
multi_height_space_types_hash.each do |k,v|
|
1971
|
+
if v.key?(:orig_ratio)
|
1972
|
+
target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
|
1973
|
+
target_areas_cust_height += v[:orig_ratio] * total_bldg_floor_area_si
|
1974
|
+
else
|
1975
|
+
target_areas[k] = v[:floor_area]
|
1976
|
+
target_areas_cust_height += v[:floor_area]
|
1977
|
+
end
|
1978
|
+
end
|
1979
|
+
|
1980
|
+
# gather inputs
|
1981
|
+
if footprint_si.nil?
|
1982
|
+
footprint_si = (total_bldg_floor_area_si - target_areas_cust_height) / num_stories.to_f
|
1983
|
+
end
|
1984
|
+
floor_height_si = OpenStudio.convert(args['floor_height'], 'ft', 'm').get
|
1985
|
+
min_allow_size = OpenStudio.convert(15.0,'ft','m').get
|
1986
|
+
specified_bar_width_si = OpenStudio.convert(args['bar_width'],'ft','m').get
|
1987
|
+
|
1988
|
+
# set custom width
|
1989
|
+
if specified_bar_width_si > 0
|
1990
|
+
runner.registerInfo("Ignoring perimeter multiplier argument when non zero width argument is used")
|
1991
|
+
if footprint_si / specified_bar_width_si >= min_allow_size
|
1992
|
+
width = specified_bar_width_si
|
1993
|
+
length = footprint_si / width
|
1994
|
+
else
|
1995
|
+
length = min_allow_size
|
1996
|
+
width = footprint_si / length
|
1997
|
+
runner.registerWarning("User specified width results in a length that is too short, adjusting width to be narrower than specified.")
|
1998
|
+
end
|
1999
|
+
width_cust_height = specified_bar_width_si
|
2000
|
+
else
|
2001
|
+
width = Math.sqrt(footprint_si / args['ns_to_ew_ratio'])
|
2002
|
+
length = footprint_si / width
|
2003
|
+
width_cust_height = Math.sqrt(target_areas_cust_height / args['ns_to_ew_ratio'])
|
2004
|
+
end
|
2005
|
+
length_cust_height = target_areas_cust_height / width_cust_height
|
2006
|
+
if args['perim_mult'] > 1.0 && target_areas_cust_height > 0.0
|
2007
|
+
# todo - update tests that hit this warning
|
2008
|
+
runner.registerWarning("Ignoring perimeter multiplier for bar that represents custom height spaces.")
|
2009
|
+
end
|
2010
|
+
|
2011
|
+
# check if dual bar is needed
|
2012
|
+
dual_bar = false
|
2013
|
+
if specified_bar_width_si > 0.0 && args['bar_division_method'] == 'Multiple Space Types - Individual Stories Sliced'
|
2014
|
+
if length/width != args['ns_to_ew_ratio']
|
2015
|
+
|
2016
|
+
if args['ns_to_ew_ratio'] >= 1.0 && args['ns_to_ew_ratio'] > length/width
|
2017
|
+
runner.registerWarning("Can't meet target aspect ratio of #{args['ns_to_ew_ratio']}, Lowering it to #{length/width} ")
|
2018
|
+
args['ns_to_ew_ratio'] = length/width
|
2019
|
+
elsif args['ns_to_ew_ratio'] < 1.0 && args['ns_to_ew_ratio'] > length/width
|
2020
|
+
runner.registerWarning("Can't meet target aspect ratio of #{args['ns_to_ew_ratio']}, Increasing it to #{length/width} ")
|
2021
|
+
args['ns_to_ew_ratio'] = length/width
|
2022
|
+
else
|
2023
|
+
# check if each bar would be longer then 15 feet, then set as dual bar and override perimeter multiplier
|
2024
|
+
length_alt1 = ((args['ns_to_ew_ratio'] * footprint_si) / width + 2 * args['ns_to_ew_ratio'] * width - 2 * width)/(1 + args['ns_to_ew_ratio'])
|
2025
|
+
length_alt2 = length - length_alt1
|
2026
|
+
if [length_alt1,length_alt2].min >= min_allow_size
|
2027
|
+
dual_bar = true
|
2028
|
+
else
|
2029
|
+
runner.registerInfo("Second bar would be below minimum length, will model as single bar")
|
2030
|
+
# swap length and width if single bar and aspect ratio less than 1
|
2031
|
+
if args['ns_to_ew_ratio'] < 1.0
|
2032
|
+
width = length
|
2033
|
+
length = specified_bar_width_si
|
2034
|
+
end
|
2035
|
+
end
|
2036
|
+
end
|
2037
|
+
end
|
2038
|
+
elsif args['perim_mult'] > 1.0 && args['bar_division_method'] == 'Multiple Space Types - Individual Stories Sliced'
|
2039
|
+
runner.registerInfo("You selected a perimeter multiplier greater than 1.0 for a supported bar division method. This will result in two detached rectangular buildings if secondary bar meets minimum size requirements.")
|
2040
|
+
dual_bar = true
|
2041
|
+
elsif args['perim_mult'] > 1.0
|
2042
|
+
runner.registerWarning("You selected a perimeter multiplier greater than 1.0 but didn't select a bar division method that supports this. The value for this argument will be ignored by the measure")
|
2043
|
+
end
|
2044
|
+
|
2045
|
+
# calculations for dual bar, which later will be setup to run create_bar twice
|
2046
|
+
if dual_bar
|
2047
|
+
min_perim = 2 * width + 2 * length
|
2048
|
+
target_area = footprint_si
|
2049
|
+
target_perim = min_perim * args['perim_mult']
|
2050
|
+
tol_testing = 0.00001
|
2051
|
+
dual_bar_calc_approach = nil # stretched, adiabatic_ends_bar_b, dual_bar
|
2052
|
+
runner.registerInfo("Minimum rectangle is #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft with an area of #{OpenStudio.toNeatString(OpenStudio.convert(length * width, 'm^2', 'ft^2').get, 0, true)} ft^2. Perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(min_perim, 'm', 'ft').get, 0, true)} ft.")
|
2053
|
+
runner.registerInfo("Target dual bar perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(target_perim, 'm', 'ft').get, 0, true)} ft.")
|
2054
|
+
|
2055
|
+
# determine which of the three paths to hit target perimeter multiplier are possible
|
2056
|
+
# A use dual bar non adiabatic
|
2057
|
+
# B use dual bar adiabatic
|
2058
|
+
# C use stretched bar (requires model to miss ns/ew ratio)
|
2059
|
+
|
2060
|
+
# custom quadratic equation to solve two bars with common width 2l^2 - p*l + 4a = 0
|
2061
|
+
if target_perim**2 - 32 * footprint_si > 0
|
2062
|
+
if specified_bar_width_si > 0
|
2063
|
+
runner.registerInfo("Ignoring perimeter multiplier argument and using use specified bar width.")
|
2064
|
+
dual_double_end_width = specified_bar_width_si
|
2065
|
+
dual_double_end_length = footprint_si / dual_double_end_width
|
2066
|
+
else
|
2067
|
+
dual_double_end_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 32 * footprint_si))
|
2068
|
+
dual_double_end_width = footprint_si / dual_double_end_length
|
2069
|
+
end
|
2070
|
+
|
2071
|
+
# now that stretched bar is made, determine where to split it and rotate
|
2072
|
+
bar_a_length = (args['ns_to_ew_ratio'] * (dual_double_end_length + dual_double_end_width) - dual_double_end_width)/(1 + args['ns_to_ew_ratio'])
|
2073
|
+
bar_b_length = dual_double_end_length - bar_a_length
|
2074
|
+
area_a = bar_a_length * dual_double_end_width
|
2075
|
+
area_b = bar_b_length * dual_double_end_width
|
2076
|
+
else
|
2077
|
+
# this will throw it to adiabatic ends test
|
2078
|
+
bar_a_length = 0
|
2079
|
+
bar_b_length = 0
|
2080
|
+
end
|
2081
|
+
|
2082
|
+
if bar_a_length >= min_allow_size && bar_b_length >= min_allow_size
|
2083
|
+
dual_bar_calc_approach = 'dual_bar'
|
2084
|
+
else
|
2085
|
+
# adiabatic bar input calcs
|
2086
|
+
if target_perim**2 - 16 * footprint_si > 0
|
2087
|
+
adiabatic_dual_double_end_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 16 * footprint_si))
|
2088
|
+
adiabatic_dual_double_end_width = footprint_si / adiabatic_dual_double_end_length
|
2089
|
+
# test for unexpected
|
2090
|
+
unexpected = false
|
2091
|
+
if (target_area - adiabatic_dual_double_end_length*adiabatic_dual_double_end_width).abs > tol_testing then unexpected = true end
|
2092
|
+
if specified_bar_width_si == 0
|
2093
|
+
if (target_perim - (adiabatic_dual_double_end_length * 2 + adiabatic_dual_double_end_width * 2)).abs > tol_testing then unexpected = true end
|
2094
|
+
end
|
2095
|
+
if unexpected
|
2096
|
+
runner.registerWarning("Unexpected values for dual rectangle adiabatic ends bar b.")
|
2097
|
+
end
|
2098
|
+
# now that stretched bar is made, determine where to split it and rotate
|
2099
|
+
adiabatic_bar_a_length = (args['ns_to_ew_ratio'] * (adiabatic_dual_double_end_length + adiabatic_dual_double_end_width))/(1 + args['ns_to_ew_ratio'])
|
2100
|
+
adiabatic_bar_b_length = adiabatic_dual_double_end_length - adiabatic_bar_a_length
|
2101
|
+
adiabatic_area_a = adiabatic_bar_a_length * adiabatic_dual_double_end_width
|
2102
|
+
adiabatic_area_b = adiabatic_bar_b_length * adiabatic_dual_double_end_width
|
2103
|
+
else
|
2104
|
+
# this will throw it stretched single bar
|
2105
|
+
adiabatic_bar_a_length = 0
|
2106
|
+
adiabatic_bar_b_length = 0
|
2107
|
+
end
|
2108
|
+
if adiabatic_bar_a_length >= min_allow_size && adiabatic_bar_b_length >= min_allow_size
|
2109
|
+
dual_bar_calc_approach = 'adiabatic_ends_bar_b'
|
2110
|
+
else
|
2111
|
+
dual_bar_calc_approach = 'stretched'
|
2112
|
+
end
|
2113
|
+
end
|
2114
|
+
|
2115
|
+
# apply prescribed approach for stretched or dual bar
|
2116
|
+
if dual_bar_calc_approach == 'dual_bar'
|
2117
|
+
runner.registerInfo("Stretched #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_width, 'm', 'ft').get, 0, true)} ft rectangle has an area of #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * dual_double_end_width, 'm^2', 'ft^2').get, 0, true)} ft^2. When split in two the perimeter will be #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * 2 + dual_double_end_width * 4, 'm', 'ft').get, 0, true)} ft")
|
2118
|
+
if (target_area - dual_double_end_length*dual_double_end_width).abs > tol_testing || (target_perim - (dual_double_end_length * 2 + dual_double_end_width * 4)).abs > tol_testing
|
2119
|
+
runner.registerWarning("Unexpected values for dual rectangle.")
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
runner.registerInfo("For stretched split bar, to match target ns/ew aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be horizontal, with #{OpenStudio.toNeatString(OpenStudio.convert(bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(area_a + area_b, 'm^2', 'ft^2').get, 0, true)} ft^2. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length*2 + bar_b_length*2 + dual_double_end_width*4, 'm', 'ft').get, 0, true)} ft")
|
2123
|
+
if (target_area - (area_a + area_b)).abs > tol_testing || (target_perim - (bar_a_length*2 + bar_b_length*2 + dual_double_end_width*4)).abs > tol_testing
|
2124
|
+
runner.registerWarning("Unexpected values for rotated dual rectangle")
|
2125
|
+
end
|
2126
|
+
elsif dual_bar_calc_approach == 'adiabatic_ends_bar_b'
|
2127
|
+
runner.registerInfo("Can't hit target perimeter with two rectangles, need to make two ends adiabatic")
|
2128
|
+
|
2129
|
+
runner.registerInfo("For dual bar with adiabatic ends on bar b, to reach target aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be north/south, with #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_area_a + adiabatic_area_b, 'm^2', 'ft^2').get, 0, true)} ft^2}. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length*2 + adiabatic_bar_b_length*2 + adiabatic_dual_double_end_width*2, 'm', 'ft').get, 0, true)} ft")
|
2130
|
+
if (target_area - (adiabatic_area_a + adiabatic_area_b)).abs > tol_testing || (target_perim - (adiabatic_bar_a_length*2 + adiabatic_bar_b_length*2 + adiabatic_dual_double_end_width*2)).abs > tol_testing
|
2131
|
+
runner.registerWarning("Unexpected values for rotated dual rectangle adiabatic ends bar b")
|
2132
|
+
end
|
2133
|
+
else # stretched bar
|
2134
|
+
dual_bar = false
|
2135
|
+
|
2136
|
+
stretched_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 16 * footprint_si))
|
2137
|
+
stretched_width = footprint_si / stretched_length
|
2138
|
+
if (target_area - stretched_length*stretched_width).abs > tol_testing || (target_perim - (stretched_length + stretched_width)*2) > tol_testing
|
2139
|
+
runner.registerWarning("Unexpected values for single stretched")
|
2140
|
+
end
|
2141
|
+
|
2142
|
+
width = stretched_width
|
2143
|
+
length = stretched_length
|
2144
|
+
runner.registerInfo("Creating a dual bar to match the target minimum perimeter multiplier at the given aspect ratio would result in a bar with edge shorter than #{OpenStudio.toNeatString(OpenStudio.convert(min_allow_size, 'm', 'ft').get, 0, true)} ft. Will create a single stretched bar instead that hits the target perimeter with a slightly different ns/ew aspect ratio.")
|
2145
|
+
end
|
2146
|
+
end
|
2147
|
+
|
2148
|
+
bars = {}
|
2149
|
+
bars['primary'] = {}
|
2150
|
+
if dual_bar
|
2151
|
+
if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
|
2152
|
+
bars['primary'][:length] = dual_double_end_width
|
2153
|
+
bars['primary'][:width] = bar_a_length
|
2154
|
+
elsif dual_bar_calc_approach == 'dual_bar'
|
2155
|
+
bars['primary'][:length] = bar_a_length
|
2156
|
+
bars['primary'][:width] = dual_double_end_width
|
2157
|
+
elsif mirror_ns_ew
|
2158
|
+
bars['primary'][:length] = adiabatic_dual_double_end_width
|
2159
|
+
bars['primary'][:width] = adiabatic_bar_a_length
|
2160
|
+
else
|
2161
|
+
bars['primary'][:length] = adiabatic_bar_a_length
|
2162
|
+
bars['primary'][:width] = adiabatic_dual_double_end_width
|
2163
|
+
end
|
2164
|
+
else
|
2165
|
+
if mirror_ns_ew
|
2166
|
+
bars['primary'][:length] = width
|
2167
|
+
bars['primary'][:width] = length
|
2168
|
+
else
|
2169
|
+
bars['primary'][:length] = length
|
2170
|
+
bars['primary'][:width] = width
|
2171
|
+
end
|
2172
|
+
end
|
2173
|
+
bars['primary'][:floor_height_si] = floor_height_si # can make use of this when breaking out multi-height spaces
|
2174
|
+
bars['primary'][:num_stories] = num_stories
|
2175
|
+
bars['primary'][:center_of_footprint] = OpenStudio::Point3d.new(0.0,0.0,0.0)
|
2176
|
+
space_types_hash_secondary = {}
|
2177
|
+
if dual_bar
|
2178
|
+
# loop through each story and move portion for other bar to its own hash
|
2179
|
+
primary_footprint = bars['primary'][:length] * bars['primary'][:width]
|
2180
|
+
secondary_footprint = target_area - primary_footprint
|
2181
|
+
footprint_counter = primary_footprint
|
2182
|
+
secondary_footprint_counter = secondary_footprint
|
2183
|
+
story_counter = 0
|
2184
|
+
pri_sec_tol = 0.0001 #m^2
|
2185
|
+
pri_sec_min_area = 0.0001 #m^2
|
2186
|
+
space_types_hash.each do |k,v|
|
2187
|
+
space_type_left = v[:floor_area]
|
2188
|
+
|
2189
|
+
# do not go to next space type until this one is evaulate, which may span stories
|
2190
|
+
until space_type_left == 0.0 || story_counter >= num_stories
|
2191
|
+
|
2192
|
+
# use secondary footprint if any left
|
2193
|
+
if secondary_footprint_counter > 0.0
|
2194
|
+
hash_area = [space_type_left,secondary_footprint_counter].min
|
2195
|
+
|
2196
|
+
# confirm that the part of space type use or what is left is greater than min allowed value
|
2197
|
+
projected_space_type_left = space_type_left - hash_area
|
2198
|
+
test_a = if hash_area >= pri_sec_min_area then true else false end
|
2199
|
+
test_b = if projected_space_type_left >= pri_sec_min_area || projected_space_type_left == 0.0 then true else false end
|
2200
|
+
test_c = if k == space_types_hash.keys.last then true else false end # if last space type accept sliver, no other space to infil
|
2201
|
+
if (test_a && test_b) || test_c
|
2202
|
+
if space_types_hash_secondary.has_key?(k)
|
2203
|
+
# add to what was added for previous story
|
2204
|
+
space_types_hash_secondary[k][:floor_area] += hash_area
|
2205
|
+
else
|
2206
|
+
# add new space type to hash
|
2207
|
+
if v.has_key?(:children)
|
2208
|
+
space_types_hash_secondary[k] = {:floor_area => hash_area, :space_type => v[:space_type], :children => v[:children],}
|
2209
|
+
else
|
2210
|
+
space_types_hash_secondary[k] = {:floor_area => hash_area, :space_type => v[:space_type]}
|
2211
|
+
end
|
2212
|
+
end
|
2213
|
+
space_types_hash[k][:floor_area] -= hash_area
|
2214
|
+
secondary_footprint_counter -= hash_area
|
2215
|
+
space_type_left -= hash_area
|
2216
|
+
else
|
2217
|
+
runner.registerInfo("Shifting space types between bars to avoid sliver of #{k.name}.")
|
2218
|
+
end
|
2219
|
+
end
|
2220
|
+
|
2221
|
+
# remove space if entirely used up by secondary bar
|
2222
|
+
if space_types_hash[k][:floor_area] <= pri_sec_tol
|
2223
|
+
space_types_hash.delete(k)
|
2224
|
+
space_type_left = 0.0
|
2225
|
+
else
|
2226
|
+
# then look at primary bar
|
2227
|
+
hash_area_pri = [space_type_left,footprint_counter].min
|
2228
|
+
footprint_counter -= hash_area_pri
|
2229
|
+
space_type_left -= hash_area_pri
|
2230
|
+
end
|
2231
|
+
|
2232
|
+
# reset counter when full
|
2233
|
+
if footprint_counter <= pri_sec_tol && secondary_footprint_counter <= pri_sec_tol
|
2234
|
+
# check if this is partial top floor
|
2235
|
+
story_counter += 1
|
2236
|
+
if num_stories < story_counter + 1
|
2237
|
+
footprint_counter = primary_footprint * (num_stories - story_counter)
|
2238
|
+
secondary_footprint_counter = secondary_footprint * (num_stories - story_counter)
|
2239
|
+
else
|
2240
|
+
footprint_counter = primary_footprint
|
2241
|
+
secondary_footprint_counter = secondary_footprint
|
2242
|
+
end
|
2243
|
+
end
|
2244
|
+
end
|
2245
|
+
end
|
2246
|
+
end
|
2247
|
+
|
2248
|
+
# setup bar_hash and run create_bar
|
2249
|
+
bars['primary'][:space_types_hash] = space_types_hash
|
2250
|
+
bars['primary'][:args] = args
|
2251
|
+
v = bars['primary']
|
2252
|
+
bar_hash_setup_run(runner,model,v[:args],v[:length],v[:width],v[:floor_height_si],v[:center_of_footprint],v[:space_types_hash],v[:num_stories])
|
2253
|
+
|
2254
|
+
# store offset value for multiple bars
|
2255
|
+
if args.has_key?('bar_sep_dist_mult') && args['bar_sep_dist_mult'] > 0.0
|
2256
|
+
offset_val = num_stories.ceil * floor_height_si * args['bar_sep_dist_mult']
|
2257
|
+
elsif args.has_key?('bar_sep_dist_mult')
|
2258
|
+
runner.registerWarning("Positive valu eis required for bar_sep_dist_mult, ignoring input and using value of 0.1")
|
2259
|
+
offset_val = num_stories.ceil * floor_height_si * 0.1
|
2260
|
+
else
|
2261
|
+
offset_val = num_stories.ceil * floor_height_si * 10.0
|
2262
|
+
end
|
2263
|
+
|
2264
|
+
if dual_bar
|
2265
|
+
args2 = args.clone
|
2266
|
+
bars['secondary'] = {}
|
2267
|
+
if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
|
2268
|
+
bars['secondary'][:length] = bar_b_length
|
2269
|
+
bars['secondary'][:width] = dual_double_end_width
|
2270
|
+
elsif dual_bar_calc_approach == 'dual_bar'
|
2271
|
+
bars['secondary'][:length] = dual_double_end_width
|
2272
|
+
bars['secondary'][:width] = bar_b_length
|
2273
|
+
elsif mirror_ns_ew
|
2274
|
+
bars['secondary'][:length] = adiabatic_bar_b_length
|
2275
|
+
bars['secondary'][:width] = adiabatic_dual_double_end_width
|
2276
|
+
args2['party_wall_stories_east'] = num_stories.ceil
|
2277
|
+
args2['party_wall_stories_west'] = num_stories.ceil
|
2278
|
+
else
|
2279
|
+
bars['secondary'][:length] = adiabatic_dual_double_end_width
|
2280
|
+
bars['secondary'][:width] = adiabatic_bar_b_length
|
2281
|
+
args2['party_wall_stories_south'] = num_stories.ceil
|
2282
|
+
args2['party_wall_stories_north'] = num_stories.ceil
|
2283
|
+
end
|
2284
|
+
bars['secondary'][:floor_height_si] = floor_height_si # can make use of this when breaking out multi-height spaces
|
2285
|
+
bars['secondary'][:num_stories] = num_stories
|
2286
|
+
bars['secondary'][:space_types_hash] = space_types_hash_secondary
|
2287
|
+
if dual_bar_calc_approach == 'adiabatic_ends_bar_b'
|
2288
|
+
# warn that combination of dual bar with low perimeter multiplier and use of party wall may result in discrepency between target and actual adiabatic walls
|
2289
|
+
if args['party_wall_fraction'] > 0 || args['party_wall_stories_north'] > 0 || args['party_wall_stories_south'] > 0 || args['party_wall_stories_east'] > 0 || args['party_wall_stories_west'] > 0
|
2290
|
+
runner.registerWarning('The combination of low perimeter multiplier and use of non zero party wall inputs may result in discrepency between target and actual adiabatic walls. This is due to the need to create adiabatic walls on secondary bar to maintian target building perimeter.')
|
2291
|
+
else
|
2292
|
+
runner.registerInfo('Adiabatic ends added to secondary bar because target perimeter multiplier could not be met with two full rectangular footprints.')
|
2293
|
+
end
|
2294
|
+
bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new(adiabatic_bar_a_length * 0.5 + adiabatic_dual_double_end_width * 0.5 + offset_val,adiabatic_bar_b_length * 0.5 + adiabatic_dual_double_end_width * 0.5 + offset_val,0.0)
|
2295
|
+
else
|
2296
|
+
bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new(bar_a_length * 0.5 + dual_double_end_width * 0.5 + offset_val,bar_b_length * 0.5 + dual_double_end_width * 0.5 + offset_val,0.0)
|
2297
|
+
end
|
2298
|
+
bars['secondary'][:args] = args2
|
2299
|
+
|
2300
|
+
# setup bar_hash and run create_bar
|
2301
|
+
v = bars['secondary']
|
2302
|
+
bar_hash_setup_run(runner,model,v[:args],v[:length],v[:width],v[:floor_height_si],v[:center_of_footprint],v[:space_types_hash],v[:num_stories])
|
2303
|
+
|
2304
|
+
end
|
2305
|
+
|
2306
|
+
# future development (up against primary bar run intersection and surface matching after add all bars, avoid interior windows)
|
2307
|
+
# I could loop through each space type and give them unique height but for now will just take largest height and make bar of that height, which is fine for prototypes
|
2308
|
+
if multi_height_space_types_hash.size > 0
|
2309
|
+
args3 = args.clone
|
2310
|
+
bars['custom_height'] = {}
|
2311
|
+
if mirror_ns_ew
|
2312
|
+
bars['custom_height'][:length] = width_cust_height
|
2313
|
+
bars['custom_height'][:width] = length_cust_height
|
2314
|
+
else
|
2315
|
+
bars['custom_height'][:length] = length_cust_height
|
2316
|
+
bars['custom_height'][:width] = width_cust_height
|
2317
|
+
end
|
2318
|
+
if args['party_wall_stories_east'] + args['party_wall_stories_west'] + args['party_wall_stories_south'] + args['party_wall_stories_north'] > 0.0
|
2319
|
+
runner.registerWarning("Ignorning party wall inputs for custom height bar")
|
2320
|
+
end
|
2321
|
+
|
2322
|
+
# disable party walls
|
2323
|
+
args3['party_wall_stories_east'] = 0
|
2324
|
+
args3['party_wall_stories_west'] = 0
|
2325
|
+
args3['party_wall_stories_south'] = 0
|
2326
|
+
args3['party_wall_stories_north'] = 0
|
2327
|
+
|
2328
|
+
# setup stories
|
2329
|
+
args3['num_stories_below_grade'] = 0
|
2330
|
+
args3['num_stories_above_grade'] = 1
|
2331
|
+
|
2332
|
+
bars['custom_height'][:floor_height_si] = floor_height_si # can make use of this when breaking out multi-height spaces
|
2333
|
+
bars['custom_height'][:num_stories] = num_stories
|
2334
|
+
bars['custom_height'][:center_of_footprint] = OpenStudio::Point3d.new(bars['primary'][:length] * -0.5 - length_cust_height * 0.5 - offset_val,0.0,0.0)
|
2335
|
+
bars['custom_height'][:floor_height_si] = OpenStudio.convert(custom_story_heights.max,'ft','m').get
|
2336
|
+
bars['custom_height'][:num_stories] = 1
|
2337
|
+
bars['custom_height'][:space_types_hash] = multi_height_space_types_hash
|
2338
|
+
bars['custom_height'][:args] = args3
|
2339
|
+
|
2340
|
+
v = bars['custom_height']
|
2341
|
+
bar_hash_setup_run(runner,model,v[:args],v[:length],v[:width],v[:floor_height_si],v[:center_of_footprint],v[:space_types_hash],v[:num_stories])
|
2342
|
+
end
|
2343
|
+
|
2344
|
+
# diagnostic log
|
2345
|
+
sum_actual = 0.0
|
2346
|
+
sum_target = 0.0
|
2347
|
+
throw_error = false
|
2348
|
+
|
2349
|
+
# check expected floor areas against actual
|
2350
|
+
model.getSpaceTypes.sort.each do |space_type|
|
2351
|
+
next if !target_areas.key? space_type # space type in model not part of building type(s), maybe issue warning
|
2352
|
+
|
2353
|
+
# convert to IP
|
2354
|
+
actual_ip = OpenStudio.convert(space_type.floorArea, 'm^2', 'ft^2').get
|
2355
|
+
target_ip = OpenStudio.convert(target_areas[space_type], 'm^2', 'ft^2').get
|
2356
|
+
sum_actual += actual_ip
|
2357
|
+
sum_target += target_ip
|
2358
|
+
|
2359
|
+
if (space_type.floorArea - target_areas[space_type]).abs >= 1.0
|
2360
|
+
|
2361
|
+
if !args['bar_division_method'].include? 'Single Space Type'
|
2362
|
+
runner.registerError("#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
|
2363
|
+
throw_error = true
|
2364
|
+
else
|
2365
|
+
# will see this if use Single Space type division method on multi-use building or single building type without whole building space type
|
2366
|
+
runner.registerWarning("#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
|
2367
|
+
end
|
2368
|
+
|
2369
|
+
end
|
2370
|
+
end
|
2371
|
+
|
2372
|
+
# report summary then throw error
|
2373
|
+
if throw_error
|
2374
|
+
runner.registerError("Sum of actual floor area is #{sum_actual} ft^2, sum of target floor area is #{sum_target}.")
|
2375
|
+
return false
|
2376
|
+
end
|
2377
|
+
|
2378
|
+
# check party wall fraction by looping through surfaces
|
2379
|
+
if args['party_wall_fraction'] > 0
|
2380
|
+
actual_ext_wall_area = model.getBuilding.exteriorWallArea
|
2381
|
+
actual_party_wall_area = 0.0
|
2382
|
+
model.getSurfaces.each do |surface|
|
2383
|
+
next if surface.outsideBoundaryCondition != 'Adiabatic'
|
2384
|
+
next if surface.surfaceType != 'Wall'
|
2385
|
+
actual_party_wall_area += surface.grossArea * surface.space.get.multiplier
|
2386
|
+
end
|
2387
|
+
actual_party_wall_fraction = actual_party_wall_area / (actual_party_wall_area + actual_ext_wall_area)
|
2388
|
+
runner.registerInfo("Target party wall fraction is #{args['party_wall_fraction']}. Realized fraction is #{actual_party_wall_fraction.round(2)}")
|
2389
|
+
runner.registerValue('party_wall_fraction_actual', actual_party_wall_fraction)
|
2390
|
+
end
|
2391
|
+
|
2392
|
+
# check ns/ew aspect ratio (harder to check when party walls are added)
|
2393
|
+
wall_and_window_by_orientation = OsLib_Geometry.getExteriorWindowAndWllAreaByOrientation(model,model.getSpaces)
|
2394
|
+
wall_ns = (wall_and_window_by_orientation['northWall'] + wall_and_window_by_orientation['southWall'])
|
2395
|
+
wall_ew = wall_and_window_by_orientation['eastWall'] + wall_and_window_by_orientation['westWall']
|
2396
|
+
wall_ns_ip = OpenStudio.convert(wall_ns,'m^2','ft^2').get
|
2397
|
+
wall_ew_ip = OpenStudio.convert(wall_ew,'m^2','ft^2').get
|
2398
|
+
runner.registerValue('wall_area_ip',wall_ns_ip + wall_ew_ip,'ft^2')
|
2399
|
+
runner.registerValue('ns_wall_area_ip',wall_ns_ip,'ft^2')
|
2400
|
+
runner.registerValue('ew_wall_area_ip',wall_ew_ip,'ft^2')
|
2401
|
+
# for now using perimeter of ground floor and average story area (building area / num_stories)
|
2402
|
+
runner.registerValue('floor_area_to_perim_ratio',model.getBuilding.floorArea / (OsLib_Geometry.calculate_perimeter(model) * num_stories))
|
2403
|
+
runner.registerValue('bar_width',OpenStudio.convert(bars['primary'][:width],'m','ft').get,'ft')
|
2404
|
+
|
2405
|
+
if args['party_wall_fraction'] > 0 || args['party_wall_stories_north'] > 0 || args['party_wall_stories_south'] > 0 || args['party_wall_stories_east'] > 0 || args['party_wall_stories_west'] > 0
|
2406
|
+
runner.registerInfo("Target facade area by orientation not validated when party walls are applied")
|
2407
|
+
elsif args['num_stories_above_grade'] != args['num_stories_above_grade'].ceil
|
2408
|
+
runner.registerInfo("Target facade area by orientation not validated when partial top story is used")
|
2409
|
+
elsif dual_bar_calc_approach == 'stretched'
|
2410
|
+
runner.registerInfo("Target facade area by orientation not validated when single stretched bar has to be used to meet target minimum perimeter multiplier")
|
2411
|
+
elsif defaulted_args.include?('floor_height') && args['custom_height_bar'] && multi_height_space_types_hash.size > 0
|
2412
|
+
runner.registerInfo("Target facade area by orientation not validated when a dedicated bar is added for space types with custom heights")
|
2413
|
+
elsif args['bar_width'] > 0
|
2414
|
+
runner.registerInfo("Target facade area by orientation not validated when a dedicated custom bar width is defined")
|
2415
|
+
else
|
2416
|
+
|
2417
|
+
# adjust length versus width based on building rotation
|
2418
|
+
if mirror_ns_ew
|
2419
|
+
wall_target_ns_ip = 2 * OpenStudio.convert(width,'m','ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
|
2420
|
+
wall_target_ew_ip = 2 * OpenStudio.convert(length,'m','ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
|
2421
|
+
else
|
2422
|
+
wall_target_ns_ip = 2 * OpenStudio.convert(length,'m','ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
|
2423
|
+
wall_target_ew_ip = 2 * OpenStudio.convert(width,'m','ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
|
2424
|
+
end
|
2425
|
+
flag_error = false
|
2426
|
+
if (wall_target_ns_ip - wall_ns_ip).abs > 0.1
|
2427
|
+
runner.registerError("North/South walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ns_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ns_ip, 4, true)} ft^2)")
|
2428
|
+
flag_error = true
|
2429
|
+
end
|
2430
|
+
if (wall_target_ew_ip - wall_ew_ip).abs > 0.1
|
2431
|
+
runner.registerError("East/West walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ew_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ew_ip, 4, true)} ft^2)")
|
2432
|
+
flag_error = true
|
2433
|
+
end
|
2434
|
+
if flag_error
|
2435
|
+
return false
|
2436
|
+
end
|
2437
|
+
end
|
2438
|
+
|
2439
|
+
# test for excessive exterior roof area (indication of problem with intersection and or surface matching)
|
2440
|
+
ext_roof_area = model.getBuilding.exteriorSurfaceArea - model.getBuilding.exteriorWallArea
|
2441
|
+
expected_roof_area = args['total_bldg_floor_area'] / (args['num_stories_above_grade'] + args['num_stories_below_grade']).to_f
|
2442
|
+
if ext_roof_area > expected_roof_area && single_floor_area_si == 0.0 # only test if using whole-building area input
|
2443
|
+
runner.registerError('Roof area larger than expected, may indicate problem with inter-floor surface intersection or matching.')
|
2444
|
+
return false
|
2445
|
+
end
|
2446
|
+
|
2447
|
+
# set building rotation
|
2448
|
+
initial_rotation = model.getBuilding.northAxis
|
2449
|
+
if args['building_rotation'] != initial_rotation
|
2450
|
+
model.getBuilding.setNorthAxis(args['building_rotation'])
|
2451
|
+
runner.registerInfo("Set Building Rotation to #{model.getBuilding.northAxis}. Rotation altered after geometry generation is completed, as a result party wall orientation and aspect ratio may not reflect input values.")
|
2452
|
+
end
|
2453
|
+
|
2454
|
+
# report final condition of model
|
2455
|
+
runner.registerFinalCondition("The building finished with #{model.getSpaces.size} spaces.")
|
2456
|
+
|
2457
|
+
return true
|
2458
|
+
end
|
2459
|
+
|
2460
|
+
# typical
|
2461
|
+
# used for varieties of measures that create typical building from model
|
2462
|
+
def typical_building_from_model(model, runner, user_arguments)
|
2463
|
+
|
2464
|
+
# assign the user inputs to variables
|
2465
|
+
args = OsLib_HelperMethods.createRunVariables(runner, model, user_arguments, arguments(model))
|
2466
|
+
if !args then return false end
|
2467
|
+
|
2468
|
+
# lookup and replace argument values from upstream measures
|
2469
|
+
if args['use_upstream_args'] == true
|
2470
|
+
args.each do |arg, value|
|
2471
|
+
next if arg == 'use_upstream_args' # this argument should not be changed
|
2472
|
+
value_from_osw = OsLib_HelperMethods.check_upstream_measure_for_arg(runner, arg)
|
2473
|
+
if !value_from_osw.empty?
|
2474
|
+
runner.registerInfo("Replacing argument named #{arg} from current measure with a value of #{value_from_osw[:value]} from #{value_from_osw[:measure_name]}.")
|
2475
|
+
new_val = value_from_osw[:value]
|
2476
|
+
# TODO: - make code to handle non strings more robust. check_upstream_measure_for_arg coudl pass bakc the argument type
|
2477
|
+
if arg == 'total_bldg_floor_area'
|
2478
|
+
args[arg] = new_val.to_f
|
2479
|
+
elsif arg == 'num_stories_above_grade'
|
2480
|
+
args[arg] = new_val.to_f
|
2481
|
+
elsif arg == 'zipcode'
|
2482
|
+
args[arg] = new_val.to_i
|
2483
|
+
else
|
2484
|
+
args[arg] = new_val
|
2485
|
+
end
|
2486
|
+
end
|
2487
|
+
end
|
2488
|
+
end
|
2489
|
+
|
2490
|
+
# validate fraction parking
|
2491
|
+
fraction = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => 1.0, 'min_eq_bool' => true, 'max_eq_bool' => true, 'arg_array' => ['onsite_parking_fraction'])
|
2492
|
+
if !fraction then return false end
|
2493
|
+
|
2494
|
+
# validate unmet hours tolerance
|
2495
|
+
unmet_hours_tolerance_valid = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => 5.0, 'min_eq_bool' => true, 'max_eq_bool' => true, 'arg_array' => ['unmet_hours_tolerance'])
|
2496
|
+
if !unmet_hours_tolerance_valid then return false end
|
2497
|
+
|
2498
|
+
# validate weekday hours of operation
|
2499
|
+
wkdy_op_hrs_start_time_hr = nil
|
2500
|
+
wkdy_op_hrs_start_time_min = nil
|
2501
|
+
wkdy_op_hrs_duration_hr = nil
|
2502
|
+
wkdy_op_hrs_duration_min = nil
|
2503
|
+
if args['modify_wkdy_op_hrs']
|
2504
|
+
# weekday start time hr
|
2505
|
+
wkdy_op_hrs_start_time_hr = args['wkdy_op_hrs_start_time'].floor
|
2506
|
+
if wkdy_op_hrs_start_time_hr < 0 || wkdy_op_hrs_start_time_hr > 24
|
2507
|
+
runner.registerError("Weekday operating hours start time hrs must be between 0 and 24. #{args['wkdy_op_hrs_start_time']} was entered.")
|
2508
|
+
return false
|
2509
|
+
end
|
2510
|
+
|
2511
|
+
# weekday start time min
|
2512
|
+
wkdy_op_hrs_start_time_min = (60.0 * (args['wkdy_op_hrs_start_time'] - args['wkdy_op_hrs_start_time'].floor)).floor
|
2513
|
+
if wkdy_op_hrs_start_time_min < 0 || wkdy_op_hrs_start_time_min > 59
|
2514
|
+
runner.registerError("Weekday operating hours start time mins must be between 0 and 59. #{args['wkdy_op_hrs_start_time']} was entered.")
|
2515
|
+
return false
|
2516
|
+
end
|
2517
|
+
|
2518
|
+
# weekday duration hr
|
2519
|
+
wkdy_op_hrs_duration_hr = args['wkdy_op_hrs_duration'].floor
|
2520
|
+
if wkdy_op_hrs_duration_hr < 0 || wkdy_op_hrs_duration_hr > 24
|
2521
|
+
runner.registerError("Weekday operating hours duration hrs must be between 0 and 24. #{args['wkdy_op_hrs_duration']} was entered.")
|
2522
|
+
return false
|
2523
|
+
end
|
2524
|
+
|
2525
|
+
# weekday duration min
|
2526
|
+
wkdy_op_hrs_duration_min = (60.0 * (args['wkdy_op_hrs_duration'] - args['wkdy_op_hrs_duration'].floor)).floor
|
2527
|
+
if wkdy_op_hrs_duration_min < 0 || wkdy_op_hrs_duration_min > 59
|
2528
|
+
runner.registerError("Weekday operating hours duration mins must be between 0 and 59. #{args['wkdy_op_hrs_duration']} was entered.")
|
2529
|
+
return false
|
2530
|
+
end
|
2531
|
+
|
2532
|
+
# check that weekday start time plus duration does not exceed 24 hrs
|
2533
|
+
if (wkdy_op_hrs_start_time_hr + wkdy_op_hrs_duration_hr + (wkdy_op_hrs_start_time_min + wkdy_op_hrs_duration_min)/60.0) > 24.0
|
2534
|
+
runner.registerInfo("Weekday start time of #{args['wkdy_op_hrs_start']} plus duration of #{args['wkdy_op_hrs_duration']} is more than 24 hrs, hours of operation overlap midnight.")
|
2535
|
+
end
|
2536
|
+
end
|
2537
|
+
|
2538
|
+
# validate weekend hours of operation
|
2539
|
+
wknd_op_hrs_start_time_hr = nil
|
2540
|
+
wknd_op_hrs_start_time_min = nil
|
2541
|
+
wknd_op_hrs_duration_hr = nil
|
2542
|
+
wknd_op_hrs_duration_min = nil
|
2543
|
+
if args['modify_wknd_op_hrs']
|
2544
|
+
# weekend start time hr
|
2545
|
+
wknd_op_hrs_start_time_hr = args['wknd_op_hrs_start_time'].floor
|
2546
|
+
if wknd_op_hrs_start_time_hr < 0 || wknd_op_hrs_start_time_hr > 24
|
2547
|
+
runner.registerError("Weekend operating hours start time hrs must be between 0 and 24. #{args['wknd_op_hrs_start_time_change']} was entered.")
|
2548
|
+
return false
|
2549
|
+
end
|
2550
|
+
|
2551
|
+
# weekend start time min
|
2552
|
+
wknd_op_hrs_start_time_min = (60.0 * (args['wknd_op_hrs_start_time'] - args['wknd_op_hrs_start_time'].floor)).floor
|
2553
|
+
if wknd_op_hrs_start_time_min < 0 || wknd_op_hrs_start_time_min > 59
|
2554
|
+
runner.registerError("Weekend operating hours start time mins must be between 0 and 59. #{args['wknd_op_hrs_start_time_change']} was entered.")
|
2555
|
+
return false
|
2556
|
+
end
|
2557
|
+
|
2558
|
+
# weekend duration hr
|
2559
|
+
wknd_op_hrs_duration_hr = args['wknd_op_hrs_duration'].floor
|
2560
|
+
if wknd_op_hrs_duration_hr < 0 || wknd_op_hrs_duration_hr > 24
|
2561
|
+
runner.registerError("Weekend operating hours duration hrs must be between 0 and 24. #{args['wknd_op_hrs_duration']} was entered.")
|
2562
|
+
return false
|
2563
|
+
end
|
2564
|
+
|
2565
|
+
# weekend duration min
|
2566
|
+
wknd_op_hrs_duration_min = (60.0 * (args['wknd_op_hrs_duration'] - args['wknd_op_hrs_duration'].floor)).floor
|
2567
|
+
if wknd_op_hrs_duration_min < 0 || wknd_op_hrs_duration_min > 59
|
2568
|
+
runner.registerError("Weekend operating hours duration min smust be between 0 and 59. #{args['wknd_op_hrs_duration']} was entered.")
|
2569
|
+
return false
|
2570
|
+
end
|
2571
|
+
|
2572
|
+
# check that weekend start time plus duration does not exceed 24 hrs
|
2573
|
+
if (wknd_op_hrs_start_time_hr + wknd_op_hrs_duration_hr + (wknd_op_hrs_start_time_min + wknd_op_hrs_duration_min)/60.0) > 24.0
|
2574
|
+
runner.registerInfo("Weekend start time of #{args['wknd_op_hrs_start']} plus duration of #{args['wknd_op_hrs_duration']} is more than 24 hrs, hours of operation overlap midnight.")
|
2575
|
+
end
|
2576
|
+
end
|
2577
|
+
|
2578
|
+
# report initial condition of model
|
2579
|
+
initial_objects = model.getModelObjects.size
|
2580
|
+
runner.registerInitialCondition("The building started with #{initial_objects} objects.")
|
2581
|
+
|
2582
|
+
# open channel to log messages
|
2583
|
+
reset_log
|
2584
|
+
|
2585
|
+
# Make the standard applier
|
2586
|
+
standard = Standard.build((args['template']).to_s)
|
2587
|
+
|
2588
|
+
# validate climate zone
|
2589
|
+
if !args.has_key?('climate_zone') || args['climate_zone'] == 'Lookup From Model'
|
2590
|
+
climate_zone = standard.model_get_building_climate_zone_and_building_type(model)['climate_zone']
|
2591
|
+
runner.registerInfo("Using climate zone #{climate_zone} from model")
|
2592
|
+
else
|
2593
|
+
climate_zone = args['climate_zone']
|
2594
|
+
runner.registerInfo("Using climate zone #{climate_zone} from user arguments")
|
2595
|
+
end
|
2596
|
+
if climate_zone == ''
|
2597
|
+
runner.registerError("Could not determine climate zone from measure arguments or model.")
|
2598
|
+
return false
|
2599
|
+
end
|
2600
|
+
|
2601
|
+
# make sure daylight savings is turned on up prior to any sizing runs being done.
|
2602
|
+
if args['enable_dst']
|
2603
|
+
start_date = '2nd Sunday in March'
|
2604
|
+
end_date = '1st Sunday in November'
|
2605
|
+
|
2606
|
+
runperiodctrl_daylgtsaving = model.getRunPeriodControlDaylightSavingTime
|
2607
|
+
runperiodctrl_daylgtsaving.setStartDate(start_date)
|
2608
|
+
runperiodctrl_daylgtsaving.setEndDate(end_date)
|
2609
|
+
end
|
2610
|
+
|
2611
|
+
# add internal loads to space types
|
2612
|
+
if args['add_space_type_loads']
|
2613
|
+
|
2614
|
+
# remove internal loads
|
2615
|
+
if args['remove_objects']
|
2616
|
+
model.getSpaceLoads.each do |instance|
|
2617
|
+
next if instance.name.to_s.include?('Elevator') # most prototype building types model exterior elevators with name Elevator
|
2618
|
+
next if instance.to_InternalMass.is_initialized
|
2619
|
+
next if instance.to_WaterUseEquipment.is_initialized
|
2620
|
+
instance.remove
|
2621
|
+
end
|
2622
|
+
model.getDesignSpecificationOutdoorAirs.each(&:remove)
|
2623
|
+
model.getDefaultScheduleSets.each(&:remove)
|
2624
|
+
end
|
2625
|
+
|
2626
|
+
model.getSpaceTypes.each do |space_type|
|
2627
|
+
# Don't add infiltration here; will be added later in the script
|
2628
|
+
test = standard.space_type_apply_internal_loads(space_type, true, true, true, true, true, false)
|
2629
|
+
if test == false
|
2630
|
+
runner.registerWarning("Could not add loads for #{space_type.name}. Not expected for #{args['template']}")
|
2631
|
+
next
|
2632
|
+
end
|
2633
|
+
|
2634
|
+
# apply internal load schedules
|
2635
|
+
# the last bool test it to make thermostat schedules. They are now added in HVAC section instead of here
|
2636
|
+
standard.space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, false)
|
2637
|
+
|
2638
|
+
# extend space type name to include the args['template']. Consider this as well for load defs
|
2639
|
+
space_type.setName("#{space_type.name} - #{args['template']}")
|
2640
|
+
runner.registerInfo("Adding loads to space type named #{space_type.name}")
|
2641
|
+
end
|
2642
|
+
|
2643
|
+
# warn if spaces in model without space type
|
2644
|
+
spaces_without_space_types = []
|
2645
|
+
model.getSpaces.each do |space|
|
2646
|
+
next if space.spaceType.is_initialized
|
2647
|
+
spaces_without_space_types << space
|
2648
|
+
end
|
2649
|
+
if !spaces_without_space_types.empty?
|
2650
|
+
runner.registerWarning("#{spaces_without_space_types.size} spaces do not have space types assigned, and wont' receive internal loads from standards space type lookups.")
|
2651
|
+
end
|
2652
|
+
end
|
2653
|
+
|
2654
|
+
# identify primary building type (used for construction, and ideally HVAC as well)
|
2655
|
+
building_types = {}
|
2656
|
+
model.getSpaceTypes.each do |space_type|
|
2657
|
+
# populate hash of building types
|
2658
|
+
if space_type.standardsBuildingType.is_initialized
|
2659
|
+
bldg_type = space_type.standardsBuildingType.get
|
2660
|
+
if !building_types.key?(bldg_type)
|
2661
|
+
building_types[bldg_type] = space_type.floorArea
|
2662
|
+
else
|
2663
|
+
building_types[bldg_type] += space_type.floorArea
|
2664
|
+
end
|
2665
|
+
else
|
2666
|
+
runner.registerWarning("Can't identify building type for #{space_type.name}")
|
2667
|
+
end
|
2668
|
+
end
|
2669
|
+
primary_bldg_type = building_types.key(building_types.values.max) # TODO: - this fails if no space types, or maybe just no space types with standards
|
2670
|
+
lookup_building_type = standard.model_get_lookup_name(primary_bldg_type) # Used for some lookups in the standards gem
|
2671
|
+
model.getBuilding.setStandardsBuildingType(primary_bldg_type)
|
2672
|
+
|
2673
|
+
# make construction set and apply to building
|
2674
|
+
if args['add_constructions']
|
2675
|
+
|
2676
|
+
# remove default construction sets
|
2677
|
+
if args['remove_objects']
|
2678
|
+
model.getDefaultConstructionSets.each(&:remove)
|
2679
|
+
end
|
2680
|
+
|
2681
|
+
# TODO: - allow building type and space type specific constructions set selection.
|
2682
|
+
if ['SmallHotel', 'LargeHotel', 'MidriseApartment', 'HighriseApartment'].include?(primary_bldg_type)
|
2683
|
+
is_residential = 'Yes'
|
2684
|
+
else
|
2685
|
+
is_residential = 'No'
|
2686
|
+
end
|
2687
|
+
bldg_def_const_set = standard.model_add_construction_set(model, climate_zone, lookup_building_type, nil, is_residential)
|
2688
|
+
if bldg_def_const_set.is_initialized
|
2689
|
+
bldg_def_const_set = bldg_def_const_set.get
|
2690
|
+
if is_residential then bldg_def_const_set.setName("Res #{bldg_def_const_set.name}") end
|
2691
|
+
model.getBuilding.setDefaultConstructionSet(bldg_def_const_set)
|
2692
|
+
runner.registerInfo("Adding default construction set named #{bldg_def_const_set.name}")
|
2693
|
+
else
|
2694
|
+
runner.registerError("Could not create default construction set for the building type #{lookup_building_type} in climate zone #{climate_zone}.")
|
2695
|
+
log_messages_to_runner(runner, debug = true)
|
2696
|
+
return false
|
2697
|
+
end
|
2698
|
+
|
2699
|
+
# address any adiabatic surfaces that don't have hard assigned constructions
|
2700
|
+
model.getSurfaces.each do |surface|
|
2701
|
+
next if surface.outsideBoundaryCondition != 'Adiabatic'
|
2702
|
+
next if surface.construction.is_initialized
|
2703
|
+
surface.setAdjacentSurface(surface)
|
2704
|
+
surface.setConstruction(surface.construction.get)
|
2705
|
+
surface.setOutsideBoundaryCondition('Adiabatic')
|
2706
|
+
end
|
2707
|
+
|
2708
|
+
# modify the infiltration rates
|
2709
|
+
if args['remove_objects']
|
2710
|
+
model.getSpaceInfiltrationDesignFlowRates.each(&:remove)
|
2711
|
+
end
|
2712
|
+
standard.model_apply_infiltration_standard(model)
|
2713
|
+
standard.model_modify_infiltration_coefficients(model, primary_bldg_type, climate_zone)
|
2714
|
+
|
2715
|
+
# set ground temperatures from DOE prototype buildings
|
2716
|
+
standard.model_add_ground_temperatures(model, primary_bldg_type, climate_zone)
|
2717
|
+
|
2718
|
+
end
|
2719
|
+
|
2720
|
+
# add elevators (returns ElectricEquipment object)
|
2721
|
+
if args['add_elevators']
|
2722
|
+
|
2723
|
+
# remove elevators as spaceLoads or exteriorLights
|
2724
|
+
model.getSpaceLoads.each do |instance|
|
2725
|
+
next if !instance.name.to_s.include?('Elevator') # most prototype building types model exterior elevators with name Elevator
|
2726
|
+
instance.remove
|
2727
|
+
end
|
2728
|
+
model.getExteriorLightss.each do |ext_light|
|
2729
|
+
next if !ext_light.name.to_s.include?('Fuel equipment') # some prototype building types model exterior elevators by this name
|
2730
|
+
ext_light.remove
|
2731
|
+
end
|
2732
|
+
|
2733
|
+
elevators = standard.model_add_elevators(model)
|
2734
|
+
if elevators.nil?
|
2735
|
+
runner.registerInfo('No elevators added to the building.')
|
2736
|
+
else
|
2737
|
+
elevator_def = elevators.electricEquipmentDefinition
|
2738
|
+
design_level = elevator_def.designLevel.get
|
2739
|
+
runner.registerInfo("Adding #{elevators.multiplier.round(1)} elevators each with power of #{OpenStudio.toNeatString(design_level, 0, true)} (W), plus lights and fans.")
|
2740
|
+
elevator_def.setFractionLost(1.0)
|
2741
|
+
elevator_def.setFractionRadiant(0.0)
|
2742
|
+
end
|
2743
|
+
end
|
2744
|
+
|
2745
|
+
# add exterior lights (returns a hash where key is lighting type and value is exteriorLights object)
|
2746
|
+
if args['add_exterior_lights']
|
2747
|
+
|
2748
|
+
if args['remove_objects']
|
2749
|
+
model.getExteriorLightss.each do |ext_light|
|
2750
|
+
next if ext_light.name.to_s.include?('Fuel equipment') # some prototype building types model exterior elevators by this name
|
2751
|
+
ext_light.remove
|
2752
|
+
end
|
2753
|
+
end
|
2754
|
+
|
2755
|
+
exterior_lights = standard.model_add_typical_exterior_lights(model, args['exterior_lighting_zone'].chars[0].to_i, args['onsite_parking_fraction'])
|
2756
|
+
exterior_lights.each do |k, v|
|
2757
|
+
runner.registerInfo("Adding Exterior Lights named #{v.exteriorLightsDefinition.name} with design level of #{v.exteriorLightsDefinition.designLevel} * #{OpenStudio.toNeatString(v.multiplier, 0, true)}.")
|
2758
|
+
end
|
2759
|
+
end
|
2760
|
+
|
2761
|
+
# add_exhaust
|
2762
|
+
if args['add_exhaust']
|
2763
|
+
|
2764
|
+
# remove exhaust objects
|
2765
|
+
if args['remove_objects']
|
2766
|
+
model.getFanZoneExhausts.each(&:remove)
|
2767
|
+
end
|
2768
|
+
|
2769
|
+
zone_exhaust_fans = standard.model_add_exhaust(model, args['kitchen_makeup']) # second argument is strategy for finding makeup zones for exhaust zones
|
2770
|
+
zone_exhaust_fans.each do |k, v|
|
2771
|
+
max_flow_rate_ip = OpenStudio.convert(k.maximumFlowRate.get, 'm^3/s', 'cfm').get
|
2772
|
+
if v.key?(:zone_mixing)
|
2773
|
+
zone_mixing = v[:zone_mixing]
|
2774
|
+
mixing_source_zone_name = zone_mixing.sourceZone.get.name
|
2775
|
+
mixing_design_flow_rate_ip = OpenStudio.convert(zone_mixing.designFlowRate.get, 'm^3/s', 'cfm').get
|
2776
|
+
runner.registerInfo("Adding #{OpenStudio.toNeatString(max_flow_rate_ip, 0, true)} (cfm) of exhaust to #{k.thermalZone.get.name}, with #{OpenStudio.toNeatString(mixing_design_flow_rate_ip, 0, true)} (cfm) of makeup air from #{mixing_source_zone_name}")
|
2777
|
+
else
|
2778
|
+
runner.registerInfo("Adding #{OpenStudio.toNeatString(max_flow_rate_ip, 0, true)} (cfm) of exhaust to #{k.thermalZone.get.name}")
|
2779
|
+
end
|
2780
|
+
end
|
2781
|
+
end
|
2782
|
+
|
2783
|
+
# add service water heating demand and supply
|
2784
|
+
if args['add_swh']
|
2785
|
+
|
2786
|
+
# remove water use equipment and water use connections
|
2787
|
+
if args['remove_objects']
|
2788
|
+
# TODO: - remove plant loops used for service water heating
|
2789
|
+
model.getWaterUseEquipments.each(&:remove)
|
2790
|
+
model.getWaterUseConnectionss.each(&:remove)
|
2791
|
+
end
|
2792
|
+
|
2793
|
+
# Infer the SWH type
|
2794
|
+
if args['swh_src'] == 'Inferred'
|
2795
|
+
if args['htg_src'] == 'NaturalGas' || args['htg_src'] == 'DistrictHeating'
|
2796
|
+
args['swh_src'] = 'NaturalGas' # If building has gas service, probably uses natural gas for SWH
|
2797
|
+
elsif args['htg_src'] == 'Electricity'
|
2798
|
+
args['swh_src'] == 'Electricity' # If building is doing space heating with electricity, probably used for SWH
|
2799
|
+
elsif args['htg_src'] == 'DistrictAmbient'
|
2800
|
+
args['swh_src'] == 'HeatPump' # If building has district ambient loop, it is fancy and probably uses HPs for SWH
|
2801
|
+
else
|
2802
|
+
args['swh_src'] = nil # Use inferences built into OpenStudio Standards for each building and space type
|
2803
|
+
end
|
2804
|
+
end
|
2805
|
+
|
2806
|
+
typical_swh = standard.model_add_typical_swh(model, water_heater_fuel: args['swh_src'])
|
2807
|
+
midrise_swh_loops = []
|
2808
|
+
stripmall_swh_loops = []
|
2809
|
+
typical_swh.each do |loop|
|
2810
|
+
if loop.name.get.include?('MidriseApartment')
|
2811
|
+
midrise_swh_loops << loop
|
2812
|
+
elsif loop.name.get.include?('RetailStripmall')
|
2813
|
+
stripmall_swh_loops << loop
|
2814
|
+
else
|
2815
|
+
water_use_connections = []
|
2816
|
+
loop.demandComponents.each do |component|
|
2817
|
+
next if !component.to_WaterUseConnections.is_initialized
|
2818
|
+
water_use_connections << component
|
2819
|
+
end
|
2820
|
+
runner.registerInfo("Adding #{loop.name} to the building. It has #{water_use_connections.size} water use connections.")
|
2821
|
+
end
|
2822
|
+
end
|
2823
|
+
if !midrise_swh_loops.empty?
|
2824
|
+
runner.registerInfo("Adding #{midrise_swh_loops.size} MidriseApartment service water heating loops.")
|
2825
|
+
end
|
2826
|
+
if !stripmall_swh_loops.empty?
|
2827
|
+
runner.registerInfo("Adding #{stripmall_swh_loops.size} RetailStripmall service water heating loops.")
|
2828
|
+
end
|
2829
|
+
end
|
2830
|
+
|
2831
|
+
# add_daylighting_controls (since outdated measure don't have this default to true if arg not found)
|
2832
|
+
if !args.has_key?('add_daylighting_controls')
|
2833
|
+
args['add_daylighting_controls'] = true
|
2834
|
+
end
|
2835
|
+
if args['add_daylighting_controls']
|
2836
|
+
# remove add_daylighting_controls objects
|
2837
|
+
if args['remove_objects']
|
2838
|
+
model.getDaylightingControls.each(&:remove)
|
2839
|
+
end
|
2840
|
+
|
2841
|
+
# add daylight controls, need to perform a sizing run for 2010
|
2842
|
+
if args['template'] == '90.1-2010'
|
2843
|
+
if standard.model_run_sizing_run(model, "#{Dir.pwd}/SRvt") == false
|
2844
|
+
log_messages_to_runner(runner, debug = true)
|
2845
|
+
return false
|
2846
|
+
end
|
2847
|
+
end
|
2848
|
+
standard.model_add_daylighting_controls(model)
|
2849
|
+
end
|
2850
|
+
|
2851
|
+
# add refrigeration
|
2852
|
+
if args['add_refrigeration']
|
2853
|
+
|
2854
|
+
# remove refrigeration equipment
|
2855
|
+
if args['remove_objects']
|
2856
|
+
model.getRefrigerationSystems.each(&:remove)
|
2857
|
+
end
|
2858
|
+
|
2859
|
+
# Add refrigerated cases and walkins
|
2860
|
+
standard.model_add_typical_refrigeration(model, primary_bldg_type)
|
2861
|
+
end
|
2862
|
+
|
2863
|
+
# add internal mass
|
2864
|
+
if args['add_internal_mass']
|
2865
|
+
|
2866
|
+
if args['remove_objects']
|
2867
|
+
model.getSpaceLoads.each do |instance|
|
2868
|
+
next unless instance.to_InternalMass.is_initialized
|
2869
|
+
instance.remove
|
2870
|
+
end
|
2871
|
+
end
|
2872
|
+
|
2873
|
+
# add internal mass to conditioned spaces; needs to happen after thermostats are applied
|
2874
|
+
standard.model_add_internal_mass(model, primary_bldg_type)
|
2875
|
+
end
|
2876
|
+
|
2877
|
+
# TODO: - add slab modeling and slab insulation
|
2878
|
+
|
2879
|
+
# TODO: - fuel customization for cooking and laundry
|
2880
|
+
# works by switching some fraction of electric loads to gas if requested (assuming base load is electric)
|
2881
|
+
|
2882
|
+
# add thermostats
|
2883
|
+
if args['add_thermostat']
|
2884
|
+
|
2885
|
+
# remove thermostats
|
2886
|
+
if args['remove_objects']
|
2887
|
+
model.getThermostatSetpointDualSetpoints.each(&:remove)
|
2888
|
+
end
|
2889
|
+
|
2890
|
+
model.getSpaceTypes.each do |space_type|
|
2891
|
+
# create thermostat schedules
|
2892
|
+
# skip un-recognized space types
|
2893
|
+
next if standard.space_type_get_standards_data(space_type).empty?
|
2894
|
+
# the last bool test it to make thermostat schedules. They are added to the model but not assigned
|
2895
|
+
standard.space_type_apply_internal_load_schedules(space_type, false, false, false, false, false, false, true)
|
2896
|
+
|
2897
|
+
# identify thermal thermostat and apply to zones (apply_internal_load_schedules names )
|
2898
|
+
model.getThermostatSetpointDualSetpoints.each do |thermostat|
|
2899
|
+
next if thermostat.name.to_s != "#{space_type.name} Thermostat"
|
2900
|
+
next if !thermostat.coolingSetpointTemperatureSchedule.is_initialized
|
2901
|
+
next if !thermostat.heatingSetpointTemperatureSchedule.is_initialized
|
2902
|
+
runner.registerInfo("Assigning #{thermostat.name} to thermal zones with #{space_type.name} assigned.")
|
2903
|
+
space_type.spaces.each do |space|
|
2904
|
+
next if !space.thermalZone.is_initialized
|
2905
|
+
space.thermalZone.get.setThermostatSetpointDualSetpoint(thermostat)
|
2906
|
+
end
|
2907
|
+
end
|
2908
|
+
end
|
2909
|
+
end
|
2910
|
+
|
2911
|
+
# add hvac system
|
2912
|
+
if args['add_hvac']
|
2913
|
+
|
2914
|
+
# remove HVAC objects
|
2915
|
+
if args['remove_objects']
|
2916
|
+
standard.model_remove_prm_hvac(model)
|
2917
|
+
end
|
2918
|
+
|
2919
|
+
case args['system_type']
|
2920
|
+
when 'Inferred'
|
2921
|
+
|
2922
|
+
# Get the hvac delivery type enum
|
2923
|
+
hvac_delivery = case args['hvac_delivery_type']
|
2924
|
+
when 'Forced Air'
|
2925
|
+
'air'
|
2926
|
+
when 'Hydronic'
|
2927
|
+
'hydronic'
|
2928
|
+
end
|
2929
|
+
|
2930
|
+
# Group the zones by occupancy type. Only split out non-dominant groups if their total area exceeds the limit.
|
2931
|
+
sys_groups = standard.model_group_zones_by_type(model, OpenStudio.convert(20_000, 'ft^2', 'm^2').get)
|
2932
|
+
|
2933
|
+
# For each group, infer the HVAC system type.
|
2934
|
+
sys_groups.each do |sys_group|
|
2935
|
+
# Infer the primary system type
|
2936
|
+
# runner.registerInfo("template = #{args['template']}, climate_zone = #{climate_zone}, occ_type = #{sys_group['type']}, hvac_delivery = #{hvac_delivery}, htg_src = #{args['htg_src']}, clg_src = #{args['clg_src']}, area_ft2 = #{sys_group['area_ft2']}, num_stories = #{sys_group['stories']}")
|
2937
|
+
sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel = standard.model_typical_hvac_system_type(model,
|
2938
|
+
climate_zone,
|
2939
|
+
sys_group['type'],
|
2940
|
+
hvac_delivery,
|
2941
|
+
args['htg_src'],
|
2942
|
+
args['clg_src'],
|
2943
|
+
OpenStudio.convert(sys_group['area_ft2'], 'ft^2', 'm^2').get,
|
2944
|
+
sys_group['stories'])
|
2945
|
+
|
2946
|
+
# Infer the secondary system type for multizone systems
|
2947
|
+
sec_sys_type = case sys_type
|
2948
|
+
when 'PVAV Reheat', 'VAV Reheat'
|
2949
|
+
'PSZ-AC'
|
2950
|
+
when 'PVAV PFP Boxes', 'VAV PFP Boxes'
|
2951
|
+
'PSZ-HP'
|
2952
|
+
else
|
2953
|
+
sys_type # same as primary system type
|
2954
|
+
end
|
2955
|
+
|
2956
|
+
# Group zones by story
|
2957
|
+
story_zone_lists = standard.model_group_zones_by_story(model, sys_group['zones'])
|
2958
|
+
|
2959
|
+
# On each story, add the primary system to the primary zones
|
2960
|
+
# and add the secondary system to any zones that are different.
|
2961
|
+
story_zone_lists.each do |story_group|
|
2962
|
+
# Differentiate primary and secondary zones, based on
|
2963
|
+
# operating hours and internal loads (same as 90.1 PRM)
|
2964
|
+
pri_sec_zone_lists = standard.model_differentiate_primary_secondary_thermal_zones(model, story_group)
|
2965
|
+
system_zones = pri_sec_zone_lists['primary']
|
2966
|
+
|
2967
|
+
# if the primary system type is PTAC, filter to cooled zones to prevent sizing error if no cooling
|
2968
|
+
if sys_type == 'PTAC'
|
2969
|
+
heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
|
2970
|
+
cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
|
2971
|
+
system_zones = heated_and_cooled_zones + cooled_only_zones
|
2972
|
+
end
|
2973
|
+
|
2974
|
+
# Add the primary system to the primary zones
|
2975
|
+
standard.model_add_hvac_system(model, sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
|
2976
|
+
|
2977
|
+
# Add the secondary system to the secondary zones (if any)
|
2978
|
+
if !pri_sec_zone_lists['secondary'].empty?
|
2979
|
+
system_zones = pri_sec_zone_lists['secondary']
|
2980
|
+
if (sec_sys_type == 'PTAC') || (sec_sys_type == 'PSZ-AC')
|
2981
|
+
heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
|
2982
|
+
cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
|
2983
|
+
system_zones = heated_and_cooled_zones + cooled_only_zones
|
2984
|
+
end
|
2985
|
+
standard.model_add_hvac_system(model, sec_sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
|
2986
|
+
end
|
2987
|
+
end
|
2988
|
+
end
|
2989
|
+
|
2990
|
+
else # HVAC system_type specified
|
2991
|
+
|
2992
|
+
# Group the zones by occupancy type. Only split out non-dominant groups if their total area exceeds the limit.
|
2993
|
+
sys_groups = standard.model_group_zones_by_type(model, OpenStudio.convert(20_000, 'ft^2', 'm^2').get)
|
2994
|
+
sys_groups.each do |sys_group|
|
2995
|
+
# Group the zones by story
|
2996
|
+
story_groups = standard.model_group_zones_by_story(model, sys_group['zones'])
|
2997
|
+
|
2998
|
+
# Add the user specified HVAC system for each story.
|
2999
|
+
# Single-zone systems will get one per zone.
|
3000
|
+
story_groups.each do |zones|
|
3001
|
+
model.add_cbecs_hvac_system(standard, args['system_type'], zones)
|
3002
|
+
end
|
3003
|
+
end
|
3004
|
+
end
|
3005
|
+
end
|
3006
|
+
|
3007
|
+
# hours of operation
|
3008
|
+
if args['modify_wkdy_op_hrs'] || args['modify_wknd_op_hrs']
|
3009
|
+
# Infer the current hours of operation schedule for the building
|
3010
|
+
op_sch = standard.model_infer_hours_of_operation_building(model)
|
3011
|
+
|
3012
|
+
# Convert existing schedules in the model to parametric schedules based on current hours of operation
|
3013
|
+
standard.model_setup_parametric_schedules(model)
|
3014
|
+
|
3015
|
+
# Create start and end times from start time and duration supplied
|
3016
|
+
wkdy_start_time = nil
|
3017
|
+
wkdy_end_time = nil
|
3018
|
+
wknd_start_time = nil
|
3019
|
+
wknd_end_time = nil
|
3020
|
+
# weekdays
|
3021
|
+
if args['modify_wkdy_op_hrs']
|
3022
|
+
wkdy_start_time = OpenStudio::Time.new(0, wkdy_op_hrs_start_time_hr, wkdy_op_hrs_start_time_min, 0)
|
3023
|
+
wkdy_end_time = wkdy_start_time + OpenStudio::Time.new(0, wkdy_op_hrs_duration_hr, wkdy_op_hrs_duration_min, 0)
|
3024
|
+
end
|
3025
|
+
# weekends
|
3026
|
+
if args['modify_wknd_op_hrs']
|
3027
|
+
wknd_start_time = OpenStudio::Time.new(0, wknd_op_hrs_start_time_hr, wknd_op_hrs_start_time_min, 0)
|
3028
|
+
wknd_end_time = wknd_start_time + OpenStudio::Time.new(0, wknd_op_hrs_duration_hr, wknd_op_hrs_duration_min, 0)
|
3029
|
+
end
|
3030
|
+
|
3031
|
+
# Modify hours of operation, using weekdays values for all weekdays and weekend values for Saturday and Sunday
|
3032
|
+
standard.schedule_ruleset_set_hours_of_operation(op_sch,
|
3033
|
+
wkdy_start_time: wkdy_start_time,
|
3034
|
+
wkdy_end_time: wkdy_end_time,
|
3035
|
+
sat_start_time: wknd_start_time,
|
3036
|
+
sat_end_time: wknd_end_time,
|
3037
|
+
sun_start_time: wknd_start_time,
|
3038
|
+
sun_end_time: wknd_end_time)
|
3039
|
+
|
3040
|
+
# Apply new operating hours to parametric schedules to make schedules in model reflect modified hours of operation
|
3041
|
+
parametric_schedules = standard.model_apply_parametric_schedules(model, error_on_out_of_order: false)
|
3042
|
+
runner.registerInfo("Updated #{parametric_schedules.size} schedules with new hours of operation.")
|
3043
|
+
end
|
3044
|
+
|
3045
|
+
# set hvac controls and efficiencies (this should be last model articulation element)
|
3046
|
+
if args['add_hvac']
|
3047
|
+
# set additional properties for building
|
3048
|
+
props = model.getBuilding.additionalProperties
|
3049
|
+
props.setFeature('hvac_system_type',"#{args['system_type']}")
|
3050
|
+
|
3051
|
+
case args['system_type']
|
3052
|
+
when 'Ideal Air Loads'
|
3053
|
+
|
3054
|
+
else
|
3055
|
+
# Set the heating and cooling sizing parameters
|
3056
|
+
standard.model_apply_prm_sizing_parameters(model)
|
3057
|
+
|
3058
|
+
# Perform a sizing run
|
3059
|
+
if standard.model_run_sizing_run(model, "#{Dir.pwd}/SR1") == false
|
3060
|
+
log_messages_to_runner(runner, debug = true)
|
3061
|
+
return false
|
3062
|
+
end
|
3063
|
+
|
3064
|
+
# If there are any multizone systems, reset damper positions
|
3065
|
+
# to achieve a 60% ventilation effectiveness minimum for the system
|
3066
|
+
# following the ventilation rate procedure from 62.1
|
3067
|
+
standard.model_apply_multizone_vav_outdoor_air_sizing(model)
|
3068
|
+
|
3069
|
+
# Apply the prototype HVAC assumptions
|
3070
|
+
standard.model_apply_prototype_hvac_assumptions(model, primary_bldg_type, climate_zone)
|
3071
|
+
|
3072
|
+
# Apply the HVAC efficiency standard
|
3073
|
+
standard.model_apply_hvac_efficiency_standard(model, climate_zone)
|
3074
|
+
end
|
3075
|
+
end
|
3076
|
+
|
3077
|
+
# set unmet hours tolerance
|
3078
|
+
unmet_hrs_tol_r = args['unmet_hours_tolerance']
|
3079
|
+
unmet_hrs_tol_k = OpenStudio.convert(unmet_hrs_tol_r, 'R', 'K').get
|
3080
|
+
tolerances = model.getOutputControlReportingTolerances
|
3081
|
+
tolerances.setToleranceforTimeHeatingSetpointNotMet(unmet_hrs_tol_k)
|
3082
|
+
tolerances.setToleranceforTimeCoolingSetpointNotMet(unmet_hrs_tol_k)
|
3083
|
+
|
3084
|
+
# remove everything but spaces, zones, and stub space types (extend as needed for additional objects, may make bool arg for this)
|
3085
|
+
if args['remove_objects']
|
3086
|
+
model.purgeUnusedResourceObjects
|
3087
|
+
objects_after_cleanup = initial_objects - model.getModelObjects.size
|
3088
|
+
if objects_after_cleanup > 0
|
3089
|
+
runner.registerInfo("Removing #{objects_after_cleanup} objects from model")
|
3090
|
+
end
|
3091
|
+
end
|
3092
|
+
|
3093
|
+
# report final condition of model
|
3094
|
+
runner.registerFinalCondition("The building finished with #{model.getModelObjects.size} objects.")
|
3095
|
+
|
3096
|
+
# log messages to info messages
|
3097
|
+
log_messages_to_runner(runner, debug = false)
|
3098
|
+
|
3099
|
+
return true
|
3100
|
+
end
|
3101
|
+
|
3102
|
+
# wizard
|
3103
|
+
# used for varieties of measures that create space type and construction set wizard
|
3104
|
+
def wizard(model, runner, user_arguments)
|
3105
|
+
|
3106
|
+
# use the built-in error checking
|
3107
|
+
if !runner.validateUserArguments(arguments(model), user_arguments)
|
3108
|
+
return false
|
3109
|
+
end
|
3110
|
+
|
3111
|
+
# assign the user inputs to variables
|
3112
|
+
building_type = runner.getStringArgumentValue('building_type', user_arguments)
|
3113
|
+
template = runner.getStringArgumentValue('template', user_arguments)
|
3114
|
+
climate_zone = runner.getStringArgumentValue('climate_zone', user_arguments)
|
3115
|
+
create_space_types = runner.getBoolArgumentValue('create_space_types', user_arguments)
|
3116
|
+
create_construction_set = runner.getBoolArgumentValue('create_construction_set', user_arguments)
|
3117
|
+
set_building_defaults = runner.getBoolArgumentValue('set_building_defaults', user_arguments)
|
3118
|
+
|
3119
|
+
# reporting initial condition of model
|
3120
|
+
starting_spaceTypes = model.getSpaceTypes
|
3121
|
+
starting_constructionSets = model.getDefaultConstructionSets
|
3122
|
+
runner.registerInitialCondition("The building started with #{starting_spaceTypes.size} space types and #{starting_constructionSets.size} construction sets.")
|
3123
|
+
|
3124
|
+
# lookup space types for specified building type (false indicates not to use whole building type only)
|
3125
|
+
space_type_hash = get_space_types_from_building_type(building_type, template, false)
|
3126
|
+
if space_type_hash == false
|
3127
|
+
runner.registerError("#{building_type} is an unexpected building type.")
|
3128
|
+
return false
|
3129
|
+
end
|
3130
|
+
|
3131
|
+
# create space_type_map from array
|
3132
|
+
space_type_map = {}
|
3133
|
+
default_space_type_name = nil
|
3134
|
+
space_type_hash.each do |space_type_name, hash|
|
3135
|
+
next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
|
3136
|
+
space_type_map[space_type_name] = [] # no spaces to pass in
|
3137
|
+
if hash[:default]
|
3138
|
+
default_space_type_name = space_type_name
|
3139
|
+
end
|
3140
|
+
end
|
3141
|
+
|
3142
|
+
# Make the standard applier
|
3143
|
+
standard = Standard.build(template)
|
3144
|
+
|
3145
|
+
# mapping building_type name is needed for a few methods
|
3146
|
+
lookup_building_type = standard.model_get_lookup_name(building_type)
|
3147
|
+
|
3148
|
+
# remap small medium and large office to office
|
3149
|
+
if building_type.include?("Office") then building_type = "Office" end
|
3150
|
+
|
3151
|
+
# get array of new space types
|
3152
|
+
space_types_new = []
|
3153
|
+
|
3154
|
+
# create_space_types
|
3155
|
+
if create_space_types
|
3156
|
+
|
3157
|
+
# array of starting space types
|
3158
|
+
space_types_starting = model.getSpaceTypes
|
3159
|
+
|
3160
|
+
# create stub space types
|
3161
|
+
space_type_hash.each do |space_type_name, hash|
|
3162
|
+
next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
|
3163
|
+
|
3164
|
+
# create space type
|
3165
|
+
space_type = OpenStudio::Model::SpaceType.new(model)
|
3166
|
+
space_type.setStandardsBuildingType(building_type)
|
3167
|
+
space_type.setStandardsSpaceType(space_type_name)
|
3168
|
+
space_type.setName("#{building_type} #{space_type_name}")
|
3169
|
+
|
3170
|
+
# add to array of new space types
|
3171
|
+
space_types_new << space_type
|
3172
|
+
|
3173
|
+
# add internal loads (the nil check isn't ncessary, but I will keep it in as a warning instad of an error)
|
3174
|
+
test = standard.space_type_apply_internal_loads(space_type, true, true, true, true, true, true)
|
3175
|
+
if test.nil?
|
3176
|
+
runner.registerWarning("Could not add loads for #{space_type.name}. Not expected for #{template} #{lookup_building_type}")
|
3177
|
+
end
|
3178
|
+
|
3179
|
+
# the last bool test it to make thermostat schedules. They are added to the model but not assigned
|
3180
|
+
standard.space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true)
|
3181
|
+
|
3182
|
+
# assign colors
|
3183
|
+
standard.space_type_apply_rendering_color(space_type)
|
3184
|
+
|
3185
|
+
# exend space type name to include the template. Consider this as well for load defs
|
3186
|
+
space_type.setName("#{space_type.name} - #{template}")
|
3187
|
+
runner.registerInfo("Added space type named #{space_type.name}")
|
3188
|
+
end
|
3189
|
+
|
3190
|
+
end
|
3191
|
+
|
3192
|
+
# add construction sets
|
3193
|
+
bldg_def_const_set = nil
|
3194
|
+
if create_construction_set
|
3195
|
+
|
3196
|
+
# Make the default construction set for the building
|
3197
|
+
is_residential = 'No' # default is nonresidential for building level
|
3198
|
+
bldg_def_const_set = standard.model_add_construction_set(model, climate_zone, lookup_building_type, nil, is_residential)
|
3199
|
+
if bldg_def_const_set.is_initialized
|
3200
|
+
bldg_def_const_set = bldg_def_const_set.get
|
3201
|
+
runner.registerInfo("Added default construction set named #{bldg_def_const_set.name}")
|
3202
|
+
else
|
3203
|
+
runner.registerError('Could not create default construction set for the building.')
|
3204
|
+
return false
|
3205
|
+
end
|
3206
|
+
|
3207
|
+
# make residential construction set as unused resource
|
3208
|
+
if ['SmallHotel', 'LargeHotel', 'MidriseApartment', 'HighriseApartment'].include?(building_type)
|
3209
|
+
res_const_set = standard.model_add_construction_set(model, climate_zone, lookup_building_type, nil, 'Yes')
|
3210
|
+
if res_const_set.is_initialized
|
3211
|
+
res_const_set = res_const_set.get
|
3212
|
+
res_const_set.setName("#{bldg_def_const_set.name} - Residential ")
|
3213
|
+
runner.registerInfo("Added residential construction set named #{res_const_set.name}")
|
3214
|
+
else
|
3215
|
+
runner.registerError('Could not create residential construction set for the building.')
|
3216
|
+
return false
|
3217
|
+
end
|
3218
|
+
end
|
3219
|
+
|
3220
|
+
end
|
3221
|
+
|
3222
|
+
# set_building_defaults
|
3223
|
+
if set_building_defaults
|
3224
|
+
|
3225
|
+
# identify default space type
|
3226
|
+
space_type_standards_info_hash = OsLib_HelperMethods.getSpaceTypeStandardsInformation(space_types_new)
|
3227
|
+
default_space_type = nil
|
3228
|
+
space_type_standards_info_hash.each do |space_type, standards_array|
|
3229
|
+
standards_space_type = standards_array[1]
|
3230
|
+
if default_space_type_name == standards_space_type
|
3231
|
+
default_space_type = space_type
|
3232
|
+
end
|
3233
|
+
end
|
3234
|
+
|
3235
|
+
# set default space type
|
3236
|
+
building = model.getBuilding
|
3237
|
+
if !default_space_type.nil?
|
3238
|
+
building.setSpaceType(default_space_type)
|
3239
|
+
runner.registerInfo("Setting default Space Type for building to #{building.spaceType.get.name}")
|
3240
|
+
end
|
3241
|
+
|
3242
|
+
# default construction
|
3243
|
+
if !bldg_def_const_set.nil?
|
3244
|
+
building.setDefaultConstructionSet(bldg_def_const_set)
|
3245
|
+
runner.registerInfo("Setting default Construction Set for building to #{building.defaultConstructionSet.get.name}")
|
3246
|
+
end
|
3247
|
+
|
3248
|
+
# set climate zone
|
3249
|
+
os_climate_zone = climate_zone.gsub('ASHRAE 169-2013-', '')
|
3250
|
+
# trim off letter from climate zone 7 or 8
|
3251
|
+
if (os_climate_zone[0] == '7') || (os_climate_zone[0] == '8')
|
3252
|
+
os_climate_zone = os_climate_zone[0]
|
3253
|
+
end
|
3254
|
+
climate_zone = model.getClimateZones.setClimateZone('ASHRAE', os_climate_zone)
|
3255
|
+
runner.registerInfo("Setting #{climate_zone.institution} Climate Zone to #{climate_zone.value}")
|
3256
|
+
|
3257
|
+
# set building type
|
3258
|
+
# use lookup_building_type so spaces like MediumOffice will map to Office (Supports baseline automation)
|
3259
|
+
building.setStandardsBuildingType(lookup_building_type)
|
3260
|
+
runner.registerInfo("Setting Standards Building Type to #{building.standardsBuildingType}")
|
3261
|
+
|
3262
|
+
# rename building if it is named "Building 1"
|
3263
|
+
if model.getBuilding.name.to_s == 'Building 1'
|
3264
|
+
model.getBuilding.setName("#{building_type} #{template} #{os_climate_zone}")
|
3265
|
+
runner.registerInfo("Renaming building to #{model.getBuilding.name}")
|
3266
|
+
end
|
3267
|
+
|
3268
|
+
end
|
3269
|
+
|
3270
|
+
# reporting final condition of model
|
3271
|
+
finishing_spaceTypes = model.getSpaceTypes
|
3272
|
+
finishing_constructionSets = model.getDefaultConstructionSets
|
3273
|
+
runner.registerFinalCondition("The building finished with #{finishing_spaceTypes.size} space types and #{finishing_constructionSets.size} construction sets.")
|
3274
|
+
|
3275
|
+
return true
|
3276
|
+
end
|
3277
|
+
|
817
3278
|
end
|