openstudio-extension 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +2 -0
  6. data/lib/openstudio/extension/runner.rb +12 -8
  7. data/lib/openstudio/extension/runner_config.rb +33 -6
  8. data/lib/openstudio/extension/version.rb +1 -1
  9. data/openstudio-extension.gemspec +6 -6
  10. metadata +15 -66
  11. data/lib/openstudio/extension/core/CreateResults.rb +0 -1033
  12. data/lib/openstudio/extension/core/check_air_sys_temps.rb +0 -160
  13. data/lib/openstudio/extension/core/check_calibration.rb +0 -125
  14. data/lib/openstudio/extension/core/check_cond_zns.rb +0 -54
  15. data/lib/openstudio/extension/core/check_domestic_hot_water.rb +0 -304
  16. data/lib/openstudio/extension/core/check_envelope_conductance.rb +0 -423
  17. data/lib/openstudio/extension/core/check_eui_by_end_use.rb +0 -132
  18. data/lib/openstudio/extension/core/check_eui_reasonableness.rb +0 -105
  19. data/lib/openstudio/extension/core/check_fan_pwr.rb +0 -68
  20. data/lib/openstudio/extension/core/check_internal_loads.rb +0 -363
  21. data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +0 -196
  22. data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +0 -296
  23. data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +0 -434
  24. data/lib/openstudio/extension/core/check_mech_sys_type.rb +0 -109
  25. data/lib/openstudio/extension/core/check_part_loads.rb +0 -421
  26. data/lib/openstudio/extension/core/check_placeholder.rb +0 -45
  27. data/lib/openstudio/extension/core/check_plant_cap.rb +0 -93
  28. data/lib/openstudio/extension/core/check_plant_temps.rb +0 -129
  29. data/lib/openstudio/extension/core/check_plenum_loads.rb +0 -57
  30. data/lib/openstudio/extension/core/check_pump_pwr.rb +0 -78
  31. data/lib/openstudio/extension/core/check_sch_coord.rb +0 -211
  32. data/lib/openstudio/extension/core/check_schedules.rb +0 -281
  33. data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +0 -128
  34. data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +0 -118
  35. data/lib/openstudio/extension/core/check_weather_files.rb +0 -102
  36. data/lib/openstudio/extension/core/deer_vintages.rb +0 -281
  37. data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +0 -461
  38. data/lib/openstudio/extension/core/os_lib_constructions.rb +0 -353
  39. data/lib/openstudio/extension/core/os_lib_geometry.rb +0 -1169
  40. data/lib/openstudio/extension/core/os_lib_helper_methods.rb +0 -383
  41. data/lib/openstudio/extension/core/os_lib_hvac.rb +0 -2163
  42. data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +0 -184
  43. data/lib/openstudio/extension/core/os_lib_model_generation.rb +0 -3584
  44. data/lib/openstudio/extension/core/os_lib_model_simplification.rb +0 -1019
  45. data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +0 -135
  46. data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +0 -170
  47. data/lib/openstudio/extension/core/os_lib_schedules.rb +0 -933
@@ -1,1169 +0,0 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
- # See also https://openstudio.net/license
4
- # *******************************************************************************
5
-
6
- module OsLib_Geometry
7
- # lower z value of vertices with starting value above x to new value of y
8
- def self.lowerSurfaceZvalue(surfaceArray, zValueTarget)
9
- counter = 0
10
-
11
- # loop over all surfaces
12
- surfaceArray.each do |surface|
13
- # create a new set of vertices
14
- newVertices = OpenStudio::Point3dVector.new
15
-
16
- # get the existing vertices for this interior partition
17
- vertices = surface.vertices
18
- flag = false
19
- vertices.each do |vertex|
20
- # initialize new vertex to old vertex
21
- x = vertex.x
22
- y = vertex.y
23
- z = vertex.z
24
-
25
- # if this z vertex is not on the z = 0 plane
26
- if z > zValueTarget
27
- z = zValueTarget
28
- flag = true
29
- end
30
-
31
- # add point to new vertices
32
- newVertices << OpenStudio::Point3d.new(x, y, z)
33
- end
34
-
35
- # set vertices to new vertices
36
- surface.setVertices(newVertices) # todo check if this was made, and issue warning if it was not. Could happen if resulting surface not planer.
37
-
38
- if flag then counter += 1 end
39
- end
40
-
41
- result = counter
42
- return result
43
- end
44
-
45
- # return an array of z values for surfaces passed in. The values will be relative to the parent origin. This was intended for spaces.
46
- def self.getSurfaceZValues(surfaceArray)
47
- zValueArray = []
48
-
49
- # loop over all surfaces
50
- surfaceArray.each do |surface|
51
- # get the existing vertices
52
- vertices = surface.vertices
53
- vertices.each do |vertex|
54
- # push z value to array
55
- zValueArray << vertex.z
56
- end
57
- end
58
-
59
- result = zValueArray
60
- return result
61
- end
62
-
63
- def self.createPointAtCenterOfFloor(model, space, zOffset)
64
- # find floors
65
- floors = []
66
- space.surfaces.each do |surface|
67
- next if surface.surfaceType != 'Floor'
68
- floors << surface
69
- end
70
-
71
- # this method only works for flat (non-inclined) floors
72
- boundingBox = OpenStudio::BoundingBox.new
73
- floors.each do |floor|
74
- boundingBox.addPoints(floor.vertices)
75
- end
76
- xmin = boundingBox.minX.get
77
- ymin = boundingBox.minY.get
78
- zmin = boundingBox.minZ.get
79
- xmax = boundingBox.maxX.get
80
- ymax = boundingBox.maxY.get
81
-
82
- x_pos = (xmin + xmax) / 2
83
- y_pos = (ymin + ymax) / 2
84
- z_pos = zmin + zOffset
85
-
86
- floorSurfacesInSpace = []
87
- space.surfaces.each do |surface|
88
- if surface.surfaceType == 'Floor'
89
- floorSurfacesInSpace << surface
90
- end
91
- end
92
-
93
- pointIsOnFloor = OsLib_Geometry.checkIfPointIsOnSurfaceInArray(OpenStudio::Point3d.new(x_pos, y_pos, zmin), floorSurfacesInSpace)
94
-
95
- if pointIsOnFloor
96
- new_point = OpenStudio::Point3d.new(x_pos, y_pos, z_pos)
97
- else
98
- # don't make point, it doesn't appear to be inside of the space
99
- new_point = nil
100
- end
101
-
102
- result = new_point
103
- return result
104
- end
105
-
106
- def self.createPointInFromSubSurfaceAtSpecifiedHeight(model, subSurface, referenceFloor, distanceInFromWindow, heightAboveBottomOfSubSurface)
107
- window_outward_normal = subSurface.outwardNormal
108
- window_centroid = OpenStudio.getCentroid(subSurface.vertices).get
109
- window_outward_normal.setLength(distanceInFromWindow)
110
- vertex = window_centroid + window_outward_normal.reverseVector
111
- vertex_on_floorplane = referenceFloor.plane.project(vertex)
112
- floor_outward_normal = referenceFloor.outwardNormal
113
- floor_outward_normal.setLength(heightAboveBottomOfSubSurface)
114
-
115
- floorSurfacesInSpace = []
116
- subSurface.space.get.surfaces.each do |surface|
117
- if surface.surfaceType == 'Floor'
118
- floorSurfacesInSpace << surface
119
- end
120
- end
121
-
122
- pointIsOnFloor = OsLib_Geometry.checkIfPointIsOnSurfaceInArray(vertex_on_floorplane, floorSurfacesInSpace)
123
-
124
- if pointIsOnFloor
125
- new_point = vertex_on_floorplane + floor_outward_normal.reverseVector
126
- else
127
- # don't make point, it doesn't appear to be inside of the space
128
- new_point = vertex_on_floorplane + floor_outward_normal.reverseVector # nil
129
- end
130
-
131
- result = new_point
132
- return result
133
- end
134
-
135
- def self.checkIfPointIsOnSurfaceInArray(point, surfaceArray)
136
- onSurfacesFlag = false
137
-
138
- surfaceArray.each do |surface|
139
- # Check if sensor is on floor plane (I need to loop through all floors)
140
- plane = surface.plane
141
- point_on_plane = plane.project(point)
142
-
143
- faceTransform = OpenStudio::Transformation.alignFace(surface.vertices)
144
- faceVertices = faceTransform * surface.vertices
145
- facePointOnPlane = faceTransform * point_on_plane
146
-
147
- if OpenStudio.pointInPolygon(facePointOnPlane, faceVertices.reverse, 0.01)
148
- # initial_sensor location lands in this surface's polygon
149
- onSurfacesFlag = true
150
- end
151
- end
152
-
153
- if onSurfacesFlag
154
- result = true
155
- else
156
- result = false
157
- end
158
-
159
- return result
160
- end
161
-
162
- def self.getExteriorWindowToWallRatio(spaceArray)
163
- # counters
164
- total_gross_ext_wall_area = 0
165
- total_ext_window_area = 0
166
-
167
- spaceArray.each do |space|
168
- # get surface area adjusting for zone multiplier
169
- zone = space.thermalZone
170
- if !zone.empty?
171
- zone_multiplier = zone.get.multiplier
172
- if zone_multiplier > 1
173
- end
174
- else
175
- zone_multiplier = 1 # space is not in a thermal zone
176
- end
177
-
178
- space.surfaces.each do |s|
179
- next if s.surfaceType != 'Wall'
180
- next if s.outsideBoundaryCondition != 'Outdoors'
181
-
182
- surface_gross_area = s.grossArea * zone_multiplier
183
-
184
- # loop through sub surfaces and add area including multiplier
185
- ext_window_area = 0
186
- s.subSurfaces.each do |subSurface|
187
- ext_window_area += subSurface.grossArea * subSurface.multiplier * zone_multiplier
188
- end
189
-
190
- total_gross_ext_wall_area += surface_gross_area
191
- total_ext_window_area += ext_window_area
192
- end
193
- end
194
-
195
- if total_gross_ext_wall_area > 0
196
- result = total_ext_window_area / total_gross_ext_wall_area
197
- else
198
- result = 0.0 # TODO: - this should not happen if the building has geometry
199
- end
200
-
201
- return result
202
- end
203
-
204
- # create core and perimeter polygons from length width and origin
205
- def self.make_core_and_perimeter_polygons(runner, length, width, footprint_origin = OpenStudio::Point3d.new(0, 0, 0), perimeter_zone_depth = OpenStudio.convert(15, 'ft', 'm').get)
206
- hash_of_point_vectors = {} # key is name, value is a hash, one item of which is polygon. Another could be space type
207
-
208
- # determine if core and perimeter zoning can be used
209
- if !(length > perimeter_zone_depth * 2.5 && width > perimeter_zone_depth * 2.5)
210
- perimeter_zone_depth = 0 # if any size is to small then just model floor as single zone, issue warning
211
- runner.registerWarning('Due to the size of the building modeling each floor as a single zone.')
212
- end
213
-
214
- x_delta = footprint_origin.x - length / 2.0
215
- y_delta = footprint_origin.y - width / 2.0
216
- z = 0
217
- nw_point = OpenStudio::Point3d.new(x_delta, y_delta + width, z)
218
- ne_point = OpenStudio::Point3d.new(x_delta + length, y_delta + width, z)
219
- se_point = OpenStudio::Point3d.new(x_delta + length, y_delta, z)
220
- sw_point = OpenStudio::Point3d.new(x_delta, y_delta, z)
221
-
222
- # Define polygons for a rectangular building
223
- if perimeter_zone_depth > 0
224
- perimeter_nw_point = nw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, -perimeter_zone_depth, 0)
225
- perimeter_ne_point = ne_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, -perimeter_zone_depth, 0)
226
- perimeter_se_point = se_point + OpenStudio::Vector3d.new(-perimeter_zone_depth, perimeter_zone_depth, 0)
227
- perimeter_sw_point = sw_point + OpenStudio::Vector3d.new(perimeter_zone_depth, perimeter_zone_depth, 0)
228
-
229
- west_polygon = OpenStudio::Point3dVector.new
230
- west_polygon << sw_point
231
- west_polygon << nw_point
232
- west_polygon << perimeter_nw_point
233
- west_polygon << perimeter_sw_point
234
- hash_of_point_vectors['West Perimeter Space'] = {}
235
- hash_of_point_vectors['West Perimeter Space'][:space_type] = nil # other methods being used by makeSpacesFromPolygons may have space types associated with each polygon but this doesn't.
236
- hash_of_point_vectors['West Perimeter Space'][:polygon] = west_polygon
237
-
238
- north_polygon = OpenStudio::Point3dVector.new
239
- north_polygon << nw_point
240
- north_polygon << ne_point
241
- north_polygon << perimeter_ne_point
242
- north_polygon << perimeter_nw_point
243
- hash_of_point_vectors['North Perimeter Space'] = {}
244
- hash_of_point_vectors['North Perimeter Space'][:space_type] = nil
245
- hash_of_point_vectors['North Perimeter Space'][:polygon] = north_polygon
246
-
247
- east_polygon = OpenStudio::Point3dVector.new
248
- east_polygon << ne_point
249
- east_polygon << se_point
250
- east_polygon << perimeter_se_point
251
- east_polygon << perimeter_ne_point
252
- hash_of_point_vectors['East Perimeter Space'] = {}
253
- hash_of_point_vectors['East Perimeter Space'][:space_type] = nil
254
- hash_of_point_vectors['East Perimeter Space'][:polygon] = east_polygon
255
-
256
- south_polygon = OpenStudio::Point3dVector.new
257
- south_polygon << se_point
258
- south_polygon << sw_point
259
- south_polygon << perimeter_sw_point
260
- south_polygon << perimeter_se_point
261
- hash_of_point_vectors['South Perimeter Space'] = {}
262
- hash_of_point_vectors['South Perimeter Space'][:space_type] = nil
263
- hash_of_point_vectors['South Perimeter Space'][:polygon] = south_polygon
264
-
265
- core_polygon = OpenStudio::Point3dVector.new
266
- core_polygon << perimeter_sw_point
267
- core_polygon << perimeter_nw_point
268
- core_polygon << perimeter_ne_point
269
- core_polygon << perimeter_se_point
270
- hash_of_point_vectors['Core Space'] = {}
271
- hash_of_point_vectors['Core Space'][:space_type] = nil
272
- hash_of_point_vectors['Core Space'][:polygon] = core_polygon
273
-
274
- # Minimal zones
275
- else
276
- whole_story_polygon = OpenStudio::Point3dVector.new
277
- whole_story_polygon << sw_point
278
- whole_story_polygon << nw_point
279
- whole_story_polygon << ne_point
280
- whole_story_polygon << se_point
281
- hash_of_point_vectors['Whole Story Space'] = {}
282
- hash_of_point_vectors['Whole Story Space'][:space_type] = nil
283
- hash_of_point_vectors['Whole Story Space'][:polygon] = whole_story_polygon
284
- end
285
-
286
- return hash_of_point_vectors
287
- end
288
-
289
- # sliced bar multi creates and array of multiple sliced bar simple hashes
290
- def self.make_sliced_bar_multi_polygons(runner, space_types, length, width, footprint_origin = OpenStudio::Point3d.new(0, 0, 0), story_hash)
291
- # total building floor area to calculate ratios from space type floor areas
292
- total_floor_area = 0.0
293
- target_per_space_type = {}
294
- space_types.each do |space_type, space_type_hash|
295
- total_floor_area += space_type_hash[:floor_area]
296
- target_per_space_type[space_type] = space_type_hash[:floor_area]
297
- end
298
-
299
- # sort array by floor area, this hash will be altered to reduce floor area for each space type to 0
300
- space_types_running_count = space_types.sort_by { |k, v| v[:floor_area] }
301
-
302
- # array entry for each story
303
- footprints = []
304
-
305
- # variables for sliver check
306
- valid_bar_width_min = OpenStudio.convert(3, 'ft', 'm').get # re-evaluate what this should be
307
- bar_length = width # building width
308
- valid_bar_area_min = valid_bar_width_min * bar_length
309
-
310
- # loop through stories to populate footprints
311
- story_hash.each_with_index do |(k, v), i|
312
- # update the length and width for partial floors
313
- if i + 1 == story_hash.size
314
- area_multiplier = v[:partial_story_multiplier]
315
- edge_multiplier = Math.sqrt(area_multiplier)
316
- length *= edge_multiplier
317
- width *= edge_multiplier
318
- end
319
-
320
- # this will be populated for each building story
321
- target_footprint_area = v[:multiplier] * length * width
322
- current_footprint_area = 0.0
323
- space_types_local_count = {}
324
-
325
- space_types_running_count.each do |space_type, space_type_hash|
326
- # next if floor area is full or space type is empty
327
-
328
- tol_value = 0.0001
329
- next if current_footprint_area + tol_value >= target_footprint_area
330
- next if space_type_hash[:floor_area] <= tol_value
331
-
332
- # special test for when total floor area is smaller than valid_bar_area_min, just make bar smaller that valid min and warn user
333
- if target_per_space_type[space_type] < valid_bar_area_min
334
- sliver_override = true
335
- runner.registerWarning("Floor area of #{space_type.name} results in a bar with smaller than target minimum width.")
336
- else
337
- sliver_override = false
338
- end
339
-
340
- # add entry for space type if it doesn't have one yet
341
- if !space_types_local_count.key?(space_type)
342
- if space_type_hash.has_key?(:children)
343
- space_type = space_type_hash[:children][:default][:space_type] # will re-using space type create issue
344
- space_types_local_count[space_type] = { floor_area: 0.0 }
345
- space_types_local_count[space_type][:children] = space_type_hash[:children]
346
- else
347
- space_types_local_count[space_type] = { floor_area: 0.0 }
348
- end
349
- end
350
-
351
- # if there is enough of this space type to fill rest of floor area
352
- remaining_in_footprint = target_footprint_area - current_footprint_area
353
- raw_footprint_area_used = [space_type_hash[:floor_area],remaining_in_footprint].min
354
-
355
- # add to local hash
356
- space_types_local_count[space_type][:floor_area] = raw_footprint_area_used / v[:multiplier].to_f
357
-
358
- # adjust balance ot running and local counts
359
- current_footprint_area += raw_footprint_area_used
360
- space_type_hash[:floor_area] -= raw_footprint_area_used
361
-
362
- # test if think sliver left on current floor.
363
- # fix by moving smallest space type to next floor and and the same amount more of the sliver space type to this story
364
- raw_footprint_area_used < valid_bar_area_min && sliver_override == false ? (test_a = true) : (test_a = false)
365
-
366
- # test if what would be left of the current space type would result in a sliver on the next story.
367
- # fix by removing some of this space type so their is enough left for the next story, and replace the removed amount with the largest space type in the model
368
- (space_type_hash[:floor_area] < valid_bar_area_min) && (space_type_hash[:floor_area] > tol_value) ? (test_b = true) : (test_b = false)
369
-
370
- # identify very small slices and re-arrange spaces to different stories to avoid this
371
- if test_a
372
-
373
- # get first/smallest space type to move to another story
374
- first_space = space_types_local_count.first
375
-
376
- # adjustments running counter for space type being removed from this story
377
- space_types_running_count.each do |k2, v2|
378
- next if k2 != first_space[0]
379
- v2[:floor_area] += first_space[1][:floor_area] * v[:multiplier]
380
- end
381
-
382
- # adjust running count for current space type
383
- space_type_hash[:floor_area] -= first_space[1][:floor_area] * v[:multiplier]
384
-
385
- # add to local count for current space type
386
- space_types_local_count[space_type][:floor_area] += first_space[1][:floor_area]
387
-
388
- # remove from local count for removed space type
389
- space_types_local_count.shift
390
-
391
- elsif test_b
392
-
393
- # swap size
394
- swap_size = valid_bar_area_min * 5 # currently equal to default perimeter zone depth of 15'
395
- # this prevents too much area from being swapped resulting in a negative number for floor area
396
- if swap_size > space_types_local_count[space_type][:floor_area] * v[:multiplier].to_f
397
- swap_size = space_types_local_count[space_type][:floor_area] * v[:multiplier].to_f
398
- end
399
-
400
- # adjust running count for current space type
401
- space_type_hash[:floor_area] += swap_size
402
-
403
- # remove from local count for current space type
404
- space_types_local_count[space_type][:floor_area] -= swap_size / v[:multiplier].to_f
405
-
406
- # adjust footprint used
407
- current_footprint_area -= swap_size
408
-
409
- # the next larger space type will be brought down to fill out the footprint without any additional code
410
-
411
- end
412
- end
413
-
414
- # creating footprint for story
415
- footprints << OsLib_Geometry.make_sliced_bar_simple_polygons(runner, space_types_local_count, length, width, footprint_origin)
416
- end
417
-
418
- return footprints
419
- end
420
-
421
- # sliced bar simple creates a single sliced bar for space types passed in
422
- # look at length and width to adjust slicing direction
423
- def self.make_sliced_bar_simple_polygons(runner, space_types, length, width, footprint_origin = OpenStudio::Point3d.new(0, 0, 0), perimeter_zone_depth = OpenStudio.convert(15, 'ft', 'm').get)
424
- hash_of_point_vectors = {} # key is name, value is a hash, one item of which is polygon. Another could be space type
425
-
426
- reverse_slice = false
427
- if length < width
428
- reverse_slice = true
429
- #runner.registerInfo("reverse typical slice direction for bar because of aspect ratio less than 1.0.")
430
- end
431
-
432
- # determine if core and perimeter zoning can be used
433
- if !([length,width].min > perimeter_zone_depth * 2.5 && [length,width].min > perimeter_zone_depth * 2.5)
434
- perimeter_zone_depth = 0 # if any size is to small then just model floor as single zone, issue warning
435
- runner.registerWarning('Not modeling core and perimeter zones for some portion of the model.')
436
- end
437
-
438
- x_delta = footprint_origin.x - length / 2.0
439
- y_delta = footprint_origin.y - width / 2.0
440
- z = 0
441
- # this represents the entire bar, not individual space type slices
442
- nw_point = OpenStudio::Point3d.new(x_delta, y_delta + width, z)
443
- sw_point = OpenStudio::Point3d.new(x_delta, y_delta, z)
444
- se_point = OpenStudio::Point3d.new(x_delta + length, y_delta, z) # used when length is less than width
445
-
446
- # total building floor area to calculate ratios from space type floor areas
447
- total_floor_area = 0.0
448
- space_types.each do |space_type, space_type_hash|
449
- total_floor_area += space_type_hash[:floor_area]
450
- end
451
-
452
- # sort array by floor area but shift largest object to front
453
- space_types = space_types.sort_by { |k, v| v[:floor_area] }
454
- space_types.insert(0, space_types.delete_at(space_types.size - 1)) #.to_h
455
-
456
- # min and max bar end values
457
- min_bar_end_multiplier = 0.75
458
- max_bar_end_multiplier = 1.5
459
-
460
- # sort_by results in arrays with two items , first is key, second is hash value
461
- re_apply_largest_space_type_at_end = false
462
- max_reduction = nil # used when looping through section_hash_for_space_type if first space type needs to also be at far end of bar
463
- space_types.each do |space_type, space_type_hash|
464
- # setup end perimeter zones if needed
465
- start_perimeter_width_deduction = 0.0
466
- end_perimeter_width_deduction = 0.0
467
- if space_type == space_types.first[0]
468
- if [length,width].max * space_type_hash[:floor_area] / total_floor_area > max_bar_end_multiplier * perimeter_zone_depth
469
- start_perimeter_width_deduction = perimeter_zone_depth
470
- end
471
- # see if last space type is too small for perimeter. If it is then save some of this space type
472
- if [length,width].max * space_types.last[1][:floor_area] / total_floor_area < perimeter_zone_depth * min_bar_end_multiplier
473
- re_apply_largest_space_type_at_end = true
474
- end
475
- end
476
- if space_type == space_types.last[0]
477
- if [length,width].max * space_type_hash[:floor_area] / total_floor_area > max_bar_end_multiplier * perimeter_zone_depth
478
- end_perimeter_width_deduction = perimeter_zone_depth
479
- end
480
- end
481
- non_end_adjusted_width = ([length,width].max * space_type_hash[:floor_area] / total_floor_area) - start_perimeter_width_deduction - end_perimeter_width_deduction
482
-
483
- # adjustment of end space type is too small and is replaced with largest space type
484
- if (space_type == space_types.first[0]) && re_apply_largest_space_type_at_end
485
- max_reduction = [perimeter_zone_depth, non_end_adjusted_width].min
486
- non_end_adjusted_width -= max_reduction
487
- end
488
- if (space_type == space_types.last[0]) && re_apply_largest_space_type_at_end
489
- end_perimeter_width_deduction = space_types.first[0]
490
- end_b_flag = true
491
- else
492
- end_b_flag = false
493
- end
494
-
495
- # populate data for core and perimeter of slice
496
- section_hash_for_space_type = {}
497
- section_hash_for_space_type['end_a'] = start_perimeter_width_deduction
498
- section_hash_for_space_type[''] = non_end_adjusted_width
499
- section_hash_for_space_type['end_b'] = end_perimeter_width_deduction
500
-
501
- # determine if this space+type is double loaded corridor, and if so what the perimeter zone depth should be based on building width
502
- # look at reverse_slice to see if length or width should be used to determine perimeter depth
503
- if space_type_hash.has_key?(:children)
504
- core_ratio = space_type_hash[:children][:circ][:orig_ratio]
505
- perim_ratio = space_type_hash[:children][:default][:orig_ratio]
506
- core_ratio_adj = core_ratio / (core_ratio + perim_ratio)
507
- perim_ratio_adj = perim_ratio / (core_ratio + perim_ratio)
508
- core_space_type = space_type_hash[:children][:circ][:space_type]
509
- perim_space_type = space_type_hash[:children][:default][:space_type]
510
- if !reverse_slice
511
- custom_cor_val = width * core_ratio_adj
512
- custom_perim_val = (width - custom_cor_val)/2.0
513
- else
514
- custom_cor_val = length * core_ratio_adj
515
- custom_perim_val = (length - custom_cor_val)/2.0
516
- end
517
- actual_perim = custom_perim_val
518
- double_loaded_corridor = true
519
- else
520
- actual_perim = perimeter_zone_depth
521
- double_loaded_corridor = false
522
- end
523
-
524
- # may overwrite
525
- first_space_type_hash = space_types.first[1]
526
- if end_b_flag && first_space_type_hash.has_key?(:children)
527
- end_b_core_ratio = first_space_type_hash[:children][:circ][:orig_ratio]
528
- end_b_perim_ratio = first_space_type_hash[:children][:default][:orig_ratio]
529
- end_b_core_ratio_adj = end_b_core_ratio / (end_b_core_ratio + end_b_perim_ratio)
530
- end_b_perim_ratio_adj = end_b_perim_ratio / (end_b_core_ratio + end_b_perim_ratio)
531
- end_b_core_space_type = first_space_type_hash[:children][:circ][:space_type]
532
- end_b_perim_space_type = first_space_type_hash[:children][:default][:space_type]
533
- if !reverse_slice
534
- end_b_custom_cor_val = width * end_b_core_ratio_adj
535
- end_b_custom_perim_val = (width - end_b_custom_cor_val)/2.0
536
- else
537
- end_b_custom_cor_val = length * end_b_core_ratio_adj
538
- end_b_custom_perim_val = (length - end_b_custom_cor_val)/2.0
539
- end
540
- end_b_actual_perim = end_b_custom_perim_val
541
- end_b_double_loaded_corridor = true
542
- else
543
- end_b_actual_perim = perimeter_zone_depth
544
- end_b_double_loaded_corridor = false
545
- end
546
-
547
- # loop through sections for space type (main and possibly one or two end perimeter sections)
548
- section_hash_for_space_type.each do |k, slice|
549
-
550
- # need to use different space type for end_b
551
- if end_b_flag && k == "end_b" && space_types.first[1].has_key?(:children)
552
- slice = space_types.first[0]
553
- actual_perim = end_b_actual_perim
554
- double_loaded_corridor = end_b_double_loaded_corridor
555
- core_ratio = end_b_core_ratio
556
- perim_ratio = end_b_perim_ratio
557
- core_ratio_adj = end_b_core_ratio_adj
558
- perim_ratio_adj = end_b_perim_ratio_adj
559
- core_space_type = end_b_core_space_type
560
- perim_space_type = end_b_perim_space_type
561
- end
562
-
563
- if slice.class.to_s == 'OpenStudio::Model::SpaceType' || slice.class.to_s == 'OpenStudio::Model::Building'
564
- space_type = slice
565
- max_reduction = [perimeter_zone_depth, max_reduction].min
566
- slice = max_reduction
567
- end
568
- if slice == 0
569
- next
570
- end
571
-
572
- if !reverse_slice
573
-
574
- ne_point = nw_point + OpenStudio::Vector3d.new(slice, 0, 0)
575
- se_point = sw_point + OpenStudio::Vector3d.new(slice, 0, 0)
576
-
577
- if actual_perim > 0 && (actual_perim * 2.0) < width
578
- polygon_a = OpenStudio::Point3dVector.new
579
- polygon_a << sw_point
580
- polygon_a << sw_point + OpenStudio::Vector3d.new(0, actual_perim, 0)
581
- polygon_a << se_point + OpenStudio::Vector3d.new(0, actual_perim, 0)
582
- polygon_a << se_point
583
- if double_loaded_corridor
584
- hash_of_point_vectors["#{perim_space_type.name} A #{k}"] = {}
585
- hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:space_type] = perim_space_type
586
- hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:polygon] = polygon_a
587
- else
588
- hash_of_point_vectors["#{space_type.name} A #{k}"] = {}
589
- hash_of_point_vectors["#{space_type.name} A #{k}"][:space_type] = space_type
590
- hash_of_point_vectors["#{space_type.name} A #{k}"][:polygon] = polygon_a
591
- end
592
-
593
- polygon_b = OpenStudio::Point3dVector.new
594
- polygon_b << sw_point + OpenStudio::Vector3d.new(0, actual_perim, 0)
595
- polygon_b << nw_point + OpenStudio::Vector3d.new(0, - actual_perim, 0)
596
- polygon_b << ne_point + OpenStudio::Vector3d.new(0, - actual_perim, 0)
597
- polygon_b << se_point + OpenStudio::Vector3d.new(0, actual_perim, 0)
598
- if double_loaded_corridor
599
- hash_of_point_vectors["#{core_space_type.name} B #{k}"] = {}
600
- hash_of_point_vectors["#{core_space_type.name} B #{k}"][:space_type] = core_space_type
601
- hash_of_point_vectors["#{core_space_type.name} B #{k}"][:polygon] = polygon_b
602
- else
603
- hash_of_point_vectors["#{space_type.name} B #{k}"] = {}
604
- hash_of_point_vectors["#{space_type.name} B #{k}"][:space_type] = space_type
605
- hash_of_point_vectors["#{space_type.name} B #{k}"][:polygon] = polygon_b
606
- end
607
-
608
- polygon_c = OpenStudio::Point3dVector.new
609
- polygon_c << nw_point + OpenStudio::Vector3d.new(0, - actual_perim, 0)
610
- polygon_c << nw_point
611
- polygon_c << ne_point
612
- polygon_c << ne_point + OpenStudio::Vector3d.new(0, - actual_perim, 0)
613
- if double_loaded_corridor
614
- hash_of_point_vectors["#{perim_space_type.name} C #{k}"] = {}
615
- hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:space_type] = perim_space_type
616
- hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:polygon] = polygon_c
617
- else
618
- hash_of_point_vectors["#{space_type.name} C #{k}"] = {}
619
- hash_of_point_vectors["#{space_type.name} C #{k}"][:space_type] = space_type
620
- hash_of_point_vectors["#{space_type.name} C #{k}"][:polygon] = polygon_c
621
- end
622
- else
623
- polygon_a = OpenStudio::Point3dVector.new
624
- polygon_a << sw_point
625
- polygon_a << nw_point
626
- polygon_a << ne_point
627
- polygon_a << se_point
628
- hash_of_point_vectors["#{space_type.name} #{k}"] = {}
629
- hash_of_point_vectors["#{space_type.name} #{k}"][:space_type] = space_type
630
- hash_of_point_vectors["#{space_type.name} #{k}"][:polygon] = polygon_a
631
- end
632
-
633
- # update west points
634
- nw_point = ne_point
635
- sw_point = se_point
636
-
637
- else
638
-
639
- # create_bar at 90 degrees if aspect ration is less than 1.0
640
- # typical order (sw,nw,ne,se)
641
- # order used here (se,sw,nw,ne)
642
-
643
- nw_point = sw_point + OpenStudio::Vector3d.new(0, slice, 0)
644
- ne_point = se_point + OpenStudio::Vector3d.new(0, slice, 0)
645
-
646
- if actual_perim > 0 && (actual_perim * 2.0) < length
647
- polygon_a = OpenStudio::Point3dVector.new
648
- polygon_a << se_point
649
- polygon_a << se_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0)
650
- polygon_a << ne_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0)
651
- polygon_a << ne_point
652
- if double_loaded_corridor
653
- hash_of_point_vectors["#{perim_space_type.name} A #{k}"] = {}
654
- hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:space_type] = perim_space_type
655
- hash_of_point_vectors["#{perim_space_type.name} A #{k}"][:polygon] = polygon_a
656
- else
657
- hash_of_point_vectors["#{space_type.name} A #{k}"] = {}
658
- hash_of_point_vectors["#{space_type.name} A #{k}"][:space_type] = space_type
659
- hash_of_point_vectors["#{space_type.name} A #{k}"][:polygon] = polygon_a
660
- end
661
-
662
- polygon_b = OpenStudio::Point3dVector.new
663
- polygon_b << se_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0)
664
- polygon_b << sw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0)
665
- polygon_b << nw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0)
666
- polygon_b << ne_point + OpenStudio::Vector3d.new(- actual_perim, 0, 0)
667
- if double_loaded_corridor
668
- hash_of_point_vectors["#{core_space_type.name} B #{k}"] = {}
669
- hash_of_point_vectors["#{core_space_type.name} B #{k}"][:space_type] = core_space_type
670
- hash_of_point_vectors["#{core_space_type.name} B #{k}"][:polygon] = polygon_b
671
- else
672
- hash_of_point_vectors["#{space_type.name} B #{k}"] = {}
673
- hash_of_point_vectors["#{space_type.name} B #{k}"][:space_type] = space_type
674
- hash_of_point_vectors["#{space_type.name} B #{k}"][:polygon] = polygon_b
675
- end
676
-
677
- polygon_c = OpenStudio::Point3dVector.new
678
- polygon_c << sw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0)
679
- polygon_c << sw_point
680
- polygon_c << nw_point
681
- polygon_c << nw_point + OpenStudio::Vector3d.new(actual_perim, 0, 0)
682
- if double_loaded_corridor
683
- hash_of_point_vectors["#{perim_space_type.name} C #{k}"] = {}
684
- hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:space_type] = perim_space_type
685
- hash_of_point_vectors["#{perim_space_type.name} C #{k}"][:polygon] = polygon_c
686
- else
687
- hash_of_point_vectors["#{space_type.name} C #{k}"] = {}
688
- hash_of_point_vectors["#{space_type.name} C #{k}"][:space_type] = space_type
689
- hash_of_point_vectors["#{space_type.name} C #{k}"][:polygon] = polygon_c
690
- end
691
- else
692
- polygon_a = OpenStudio::Point3dVector.new
693
- polygon_a << se_point
694
- polygon_a << sw_point
695
- polygon_a << nw_point
696
- polygon_a << ne_point
697
- hash_of_point_vectors["#{space_type.name} #{k}"] = {}
698
- hash_of_point_vectors["#{space_type.name} #{k}"][:space_type] = space_type
699
- hash_of_point_vectors["#{space_type.name} #{k}"][:polygon] = polygon_a
700
- end
701
-
702
- # update west points
703
- sw_point = nw_point
704
- se_point = ne_point
705
-
706
- end
707
- end
708
- end
709
-
710
- return hash_of_point_vectors
711
- end
712
-
713
- # take diagram made by make_core_and_perimeter_polygons and make multi-story building
714
- # todo - add option to create shading surfaces when using multiplier. Mainly important for non rectangular buildings where self shading would be an issue
715
- def self.makeSpacesFromPolygons(runner, model, footprints, typical_story_height, effective_num_stories, footprint_origin = OpenStudio::Point3d.new(0, 0, 0), story_hash = {})
716
- # default story hash is for three stories with mid-story multiplier, but user can pass in custom versions
717
- if story_hash.empty?
718
- if effective_num_stories > 2
719
- story_hash['Ground'] = { space_origin_z: footprint_origin.z, space_height: typical_story_height, multiplier: 1 }
720
- story_hash['Mid'] = { space_origin_z: footprint_origin.z + typical_story_height + typical_story_height * (effective_num_stories.ceil - 3) / 2.0, space_height: typical_story_height, multiplier: effective_num_stories - 2 }
721
- story_hash['Top'] = { space_origin_z: footprint_origin.z + typical_story_height * (effective_num_stories.ceil - 1), space_height: typical_story_height, multiplier: 1 }
722
- elsif effective_num_stories > 1
723
- story_hash['Ground'] = { space_origin_z: footprint_origin.z, space_height: typical_story_height, multiplier: 1 }
724
- story_hash['Top'] = { space_origin_z: footprint_origin.z + typical_story_height * (effective_num_stories.ceil - 1), space_height: typical_story_height, multiplier: 1 }
725
- else # one story only
726
- story_hash['Ground'] = { space_origin_z: footprint_origin.z, space_height: typical_story_height, multiplier: 1 }
727
- end
728
- end
729
-
730
- # hash of new spaces (only change boundary conditions for these)
731
- new_spaces = []
732
-
733
- # loop through story_hash and polygons to generate all of the spaces
734
- story_hash.each_with_index do |(story_name, story_data), index|
735
- # make new story unless story at requested height already exists.
736
- story = nil
737
- model.getBuildingStorys.sort.each do |ext_story|
738
- if (ext_story.nominalZCoordinate.to_f - story_data[:space_origin_z].to_f).abs < 0.01
739
- story = ext_story
740
- end
741
- end
742
- if story.nil?
743
- story = OpenStudio::Model::BuildingStory.new(model)
744
- story.setNominalFloortoFloorHeight(story_data[:space_height]) # not used for anything
745
- story.setNominalZCoordinate (story_data[:space_origin_z]) # not used for anything
746
- story.setName("Story #{story_name}")
747
- end
748
-
749
- # multiplier values for adjacent stories to be altered below as needed
750
- multiplier_story_above = 1
751
- multiplier_story_below = 1
752
-
753
- if index == 0 # bottom floor, only check above
754
- if story_hash.size > 1
755
- multiplier_story_above = story_hash.values[index + 1][:multiplier]
756
- end
757
- elsif index == story_hash.size - 1 # top floor, check only below
758
- multiplier_story_below = story_hash.values[index + -1][:multiplier]
759
- else # mid floor, check above and below
760
- multiplier_story_above = story_hash.values[index + 1][:multiplier]
761
- multiplier_story_below = story_hash.values[index + -1][:multiplier]
762
- end
763
-
764
- # if adjacent story has multiplier > 1 then make appropriate surfaces adiabatic
765
- adiabatic_ceilings = false
766
- adiabatic_floors = false
767
- if story_data[:multiplier] > 1
768
- adiabatic_ceilings = true
769
- adiabatic_floors = true
770
- elsif multiplier_story_above > 1
771
- adiabatic_ceilings = true
772
- elsif multiplier_story_below > 1
773
- adiabatic_floors = true
774
- end
775
-
776
- # get the right collection of polygons to make up footprint for each building story
777
- if index > footprints.size - 1
778
- # use last footprint
779
- target_footprint = footprints.last
780
- else
781
- target_footprint = footprints[index]
782
- end
783
- target_footprint.each do |name, space_data|
784
- # gather options
785
- options = {
786
- 'name' => "#{name} - #{story.name}",
787
- 'spaceType' => space_data[:space_type],
788
- 'story' => story,
789
- 'makeThermalZone' => true,
790
- 'thermalZoneMultiplier' => story_data[:multiplier],
791
- 'floor_to_floor_height' => story_data[:space_height]
792
- }
793
-
794
- # make space
795
- space = OsLib_Geometry.makeSpaceFromPolygon(model, space_data[:polygon].first, space_data[:polygon], options)
796
- new_spaces << space
797
-
798
- # set z origin to proper position
799
- space.setZOrigin(story_data[:space_origin_z])
800
-
801
- # loop through celings and floors to hard asssign constructions and set boundary condition
802
- if adiabatic_ceilings || adiabatic_floors
803
- space.surfaces.each do |surface|
804
- if adiabatic_floors && (surface.surfaceType == 'Floor')
805
- if surface.construction.is_initialized
806
- surface.setConstruction(surface.construction.get)
807
- end
808
- surface.setOutsideBoundaryCondition('Adiabatic')
809
- end
810
- if adiabatic_ceilings && (surface.surfaceType == 'RoofCeiling')
811
- if surface.construction.is_initialized
812
- surface.setConstruction(surface.construction.get)
813
- end
814
- surface.setOutsideBoundaryCondition('Adiabatic')
815
- end
816
- end
817
- end
818
- end
819
-
820
- # TODO: - in future add code to include plenums or raised floor to each/any story.
821
- end
822
-
823
- # any changes to wall boundary conditions will be handled by same code that calls this method.
824
- # this method doesn't need to know about basements and party walls.
825
-
826
- return new_spaces
827
- end
828
-
829
- # add def to create a space from input, optionally take a name, space type, story and thermal zone.
830
- def self.makeSpaceFromPolygon(model, space_origin, point3dVector, options = {})
831
- # set defaults to use if user inputs not passed in
832
- defaults = {
833
- 'name' => nil,
834
- 'spaceType' => nil,
835
- 'story' => nil,
836
- 'makeThermalZone' => nil,
837
- 'thermalZone' => nil,
838
- 'thermalZoneMultiplier' => 1,
839
- 'floor_to_floor_height' => OpenStudio.convert(10, 'ft', 'm').get
840
- }
841
-
842
- # merge user inputs with defaults
843
- options = defaults.merge(options)
844
-
845
- # Identity matrix for setting space origins
846
- m = OpenStudio::Matrix.new(4, 4, 0)
847
- m[0, 0] = 1
848
- m[1, 1] = 1
849
- m[2, 2] = 1
850
- m[3, 3] = 1
851
-
852
- # make space from floor print
853
- space = OpenStudio::Model::Space.fromFloorPrint(point3dVector, options['floor_to_floor_height'], model)
854
- space = space.get
855
- m[0, 3] = space_origin.x
856
- m[1, 3] = space_origin.y
857
- m[2, 3] = space_origin.z
858
- space.changeTransformation(OpenStudio::Transformation.new(m))
859
- space.setBuildingStory(options['story'])
860
- if !options['name'].nil?
861
- space.setName(options['name'])
862
- end
863
-
864
- if !options['spaceType'].nil? && options['spaceType'].class.to_s == 'OpenStudio::Model::SpaceType'
865
- space.setSpaceType(options['spaceType'])
866
- end
867
-
868
- # create thermal zone if requested and assign
869
- if options['makeThermalZone']
870
- new_zone = OpenStudio::Model::ThermalZone.new(model)
871
- new_zone.setMultiplier(options['thermalZoneMultiplier'])
872
- space.setThermalZone(new_zone)
873
- new_zone.setName("Zone #{space.name}")
874
- else
875
- if !options['thermalZone'].nil? then space.setThermalZone(options['thermalZone']) end
876
- end
877
-
878
- result = space
879
- return result
880
- end
881
-
882
- def self.getExteriorWindowAndWllAreaByOrientation(model, spaceArray, options = {})
883
- # set defaults to use if user inputs not passed in
884
- defaults = {
885
- 'northEast' => 45,
886
- 'southEast' => 125,
887
- 'southWest' => 225,
888
- 'northWest' => 315
889
- }
890
-
891
- # merge user inputs with defaults
892
- options = defaults.merge(options)
893
-
894
- # counters
895
- total_gross_ext_wall_area_North = 0
896
- total_gross_ext_wall_area_South = 0
897
- total_gross_ext_wall_area_East = 0
898
- total_gross_ext_wall_area_West = 0
899
- total_ext_window_area_North = 0
900
- total_ext_window_area_South = 0
901
- total_ext_window_area_East = 0
902
- total_ext_window_area_West = 0
903
-
904
- spaceArray.each do |space|
905
- # get surface area adjusting for zone multiplier
906
- zone = space.thermalZone
907
- if !zone.empty?
908
- zone_multiplier = zone.get.multiplier
909
- if zone_multiplier > 1
910
- end
911
- else
912
- zone_multiplier = 1 # space is not in a thermal zone
913
- end
914
-
915
- space.surfaces.each do |s|
916
- next if s.surfaceType != 'Wall'
917
- next if s.outsideBoundaryCondition != 'Outdoors'
918
-
919
- surface_gross_area = s.grossArea * zone_multiplier
920
-
921
- # loop through sub surfaces and add area including multiplier
922
- ext_window_area = 0
923
- s.subSurfaces.each do |subSurface|
924
- ext_window_area += subSurface.grossArea * subSurface.multiplier * zone_multiplier
925
- end
926
-
927
- absoluteAzimuth = OpenStudio.convert(s.azimuth, 'rad', 'deg').get + s.space.get.directionofRelativeNorth + model.getBuilding.northAxis
928
- absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
929
-
930
- # add to exterior wall counter if north or south
931
- if (options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < options['southEast']) # East exterior walls
932
- total_gross_ext_wall_area_East += surface_gross_area
933
- total_ext_window_area_East += ext_window_area
934
- elsif (options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < options['southWest']) # South exterior walls
935
- total_gross_ext_wall_area_South += surface_gross_area
936
- total_ext_window_area_South += ext_window_area
937
- elsif (options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < options['northWest']) # West exterior walls
938
- total_gross_ext_wall_area_West += surface_gross_area
939
- total_ext_window_area_West += ext_window_area
940
- else # North exterior walls
941
- total_gross_ext_wall_area_North += surface_gross_area
942
- total_ext_window_area_North += ext_window_area
943
- end
944
- end
945
- end
946
-
947
- result = { 'northWall' => total_gross_ext_wall_area_North,
948
- 'northWindow' => total_ext_window_area_North,
949
- 'southWall' => total_gross_ext_wall_area_South,
950
- 'southWindow' => total_ext_window_area_South,
951
- 'eastWall' => total_gross_ext_wall_area_East,
952
- 'eastWindow' => total_ext_window_area_East,
953
- 'westWall' => total_gross_ext_wall_area_West,
954
- 'westWindow' => total_ext_window_area_West }
955
- return result
956
- end
957
-
958
- def self.getAbsoluteAzimuthForSurface(surface, model)
959
- absolute_azimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
960
- absolute_azimuth -= 360.0 until absolute_azimuth < 360.0
961
- return absolute_azimuth
962
- end
963
-
964
- # dont use this, use calculate_story_exterior_wall_perimeter instead
965
- def self.estimate_perimeter(perim_story)
966
- perimeter = 0
967
- perim_story.spaces.each do |space|
968
- space.surfaces.each do |surface|
969
- next if (surface.outsideBoundaryCondition != 'Outdoors') || (surface.surfaceType != 'Wall')
970
- area = surface.grossArea
971
- z_value_array = OsLib_Geometry.getSurfaceZValues([surface])
972
- next if z_value_array.max == z_value_array.min # shouldn't see this unless wall is horizontal
973
- perimeter += area / (z_value_array.max - z_value_array.min)
974
- end
975
- end
976
-
977
- return perimeter
978
- end
979
-
980
- # calculate story perimeter. Selected story should have above grade walls. If not perimeter may return zero.
981
- # optional_multiplier_adjustment is used in special case when there are zone multipliers that represent additional zones within the same story
982
- # the value entered represents the story_multiplier which reduces the adjustment by that factor over the full zone multiplier
983
- # todo - this doesn't catch walls that are split that sit above floor surfaces that are not (e.g. main corridoor in secondary school model)
984
- # todo - also odd with multi-height spaces
985
- def self.calculate_story_exterior_wall_perimeter(runner, story, optional_multiplier_adjustment = nil, tested_wall_boundary_condition = ['Outdoors', 'Ground'], bounding_box = nil)
986
- perimeter = 0
987
- party_walls = []
988
- story.spaces.each do |space|
989
- # counter to use later
990
- edge_hash = {}
991
- edge_counter = 0
992
- space.surfaces.each do |surface|
993
- # get vertices
994
- vertex_hash = {}
995
- vertex_counter = 0
996
- surface.vertices.each do |vertex|
997
- vertex_counter += 1
998
- vertex_hash[vertex_counter] = [vertex.x, vertex.y, vertex.z]
999
- end
1000
- # make edges
1001
- counter = 0
1002
- vertex_hash.each do |k, v|
1003
- edge_counter += 1
1004
- counter += 1
1005
- if vertex_hash.size != counter
1006
- edge_hash[edge_counter] = [v, vertex_hash[counter + 1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
1007
- else # different code for wrap around vertex
1008
- edge_hash[edge_counter] = [v, vertex_hash[1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
1009
- end
1010
- end
1011
- end
1012
-
1013
- # check edges for matches (need opposite vertices and proper boundary conditions)
1014
- edge_hash.each do |k1, v1|
1015
- # apply to any floor boundary condition. This supports used in floors above basements
1016
- next if v1[4] != 'Floor'
1017
- edge_hash.each do |k2, v2|
1018
- test_boundary_cond = false
1019
- next if !tested_wall_boundary_condition.include?(v2[3]) # method arg takes multiple conditions
1020
- next if v2[4] != 'Wall'
1021
-
1022
- # see if edges have same geometry
1023
-
1024
- # found cases where the two lines below removed edges and resulted in lower than actual perimeter. Added new code with tolerance.
1025
- # next if not v1[0] == v2[1] # next if not same geometry reversed
1026
- # next if not v1[1] == v2[0]
1027
-
1028
- # these are three item array's add in tollerance for each array entry
1029
- tolerance = 0.0001
1030
- test_a = true
1031
- test_b = true
1032
- 3.times.each do |i|
1033
- if (v1[0][i] - v2[1][i]).abs > tolerance
1034
- test_a = false
1035
- end
1036
- if (v1[1][i] - v2[0][i]).abs > tolerance
1037
- test_b = false
1038
- end
1039
- end
1040
-
1041
- next if test_a != true
1042
- next if test_b != true
1043
-
1044
- # edge_bounding_box = OpenStudio::BoundingBox.new
1045
- # edge_bounding_box.addPoints(space.transformation() * v2[2].vertices)
1046
- # if not edge_bounding_box.intersects(bounding_box) doesn't seem to work reliably, writing custom code to check
1047
-
1048
- point_one = OpenStudio::Point3d.new(v2[0][0], v2[0][1], v2[0][2])
1049
- point_one = (space.transformation * point_one)
1050
- point_two = OpenStudio::Point3d.new(v2[1][0], v2[1][1], v2[1][2])
1051
- point_two = (space.transformation * point_two)
1052
-
1053
- if !bounding_box.nil? && (v2[3] == 'Adiabatic')
1054
-
1055
- on_bounding_box = false
1056
- if ((bounding_box.minX.to_f - point_one.x).abs < tolerance) && ((bounding_box.minX.to_f - point_two.x).abs < tolerance)
1057
- on_bounding_box = true
1058
- elsif ((bounding_box.maxX.to_f - point_one.x).abs < tolerance) && ((bounding_box.maxX.to_f - point_two.x).abs < tolerance)
1059
- on_bounding_box = true
1060
- elsif ((bounding_box.minY.to_f - point_one.y).abs < tolerance) && ((bounding_box.minY.to_f - point_two.y).abs < tolerance)
1061
- on_bounding_box = true
1062
- elsif ((bounding_box.maxY.to_f - point_one.y).abs < tolerance) && ((bounding_box.maxY.to_f - point_two.y).abs < tolerance)
1063
- on_bounding_box = true
1064
- end
1065
-
1066
- # if not edge_bounding_box.intersects(bounding_box) doesn't seem to work reliably, writing custom code to check
1067
- # todo - this is basic check for adiabatic party walls and won't catch all situations. Can be made more robust in the future
1068
- if on_bounding_box == true
1069
- length = OpenStudio::Vector3d.new(point_one - point_two).length
1070
- party_walls << v2[2]
1071
- length_ip_display = OpenStudio.convert(length, 'm', 'ft').get.round(2)
1072
- runner.registerInfo(" * #{v2[2].name} has an adiabatic boundary condition and sits in plane with the building bounding box. Adding #{length_ip_display} (ft) to perimeter length of #{story.name} for this surface, assuming it is a party wall.")
1073
- elsif space.multiplier == 1
1074
- length = OpenStudio::Vector3d.new(point_one - point_two).length
1075
- party_walls << v2[2]
1076
- length_ip_display = OpenStudio.convert(length, 'm', 'ft').get.round(2)
1077
- runner.registerInfo(" * #{v2[2].name} has an adiabatic boundary condition and is in a zone with a multiplier of 1. Adding #{length_ip_display} (ft) to perimeter length of #{story.name} for this surface, assuming it is a party wall.")
1078
- else
1079
- length = 0
1080
- end
1081
-
1082
- else
1083
- length = OpenStudio::Vector3d.new(point_one - point_two).length
1084
- end
1085
-
1086
- if optional_multiplier_adjustment.nil?
1087
- perimeter += length
1088
- else
1089
- # adjust for multiplier
1090
- non_story_multiplier = space.multiplier / optional_multiplier_adjustment.to_f
1091
- perimeter += length * non_story_multiplier
1092
- end
1093
- end
1094
- end
1095
- end
1096
-
1097
- return { perimeter: perimeter, party_walls: party_walls }
1098
- end
1099
-
1100
- # currently takes in model and checks for edges shared by a ground exposed floor and exterior exposed wall. Later could be updated for a specific story independent of floor boundary condition.
1101
- # todo - this doesn't catch walls that are split that sit above floor surfaces that are not (e.g. main corridoor in secondary school model)
1102
- # todo - also odd with multi-height spaces
1103
- def self.calculate_perimeter(model)
1104
- perimeter = 0
1105
- model.getSpaces.sort.each do |space|
1106
- # counter to use later
1107
- edge_hash = {}
1108
- edge_counter = 0
1109
- space.surfaces.sort.each do |surface|
1110
- # get vertices
1111
- vertex_hash = {}
1112
- vertex_counter = 0
1113
- surface.vertices.each do |vertex|
1114
- vertex_counter += 1
1115
- vertex_hash[vertex_counter] = [vertex.x, vertex.y, vertex.z]
1116
- end
1117
- # make edges
1118
- counter = 0
1119
- vertex_hash.each do |k, v|
1120
- edge_counter += 1
1121
- counter += 1
1122
- if vertex_hash.size != counter
1123
- edge_hash[edge_counter] = [v, vertex_hash[counter + 1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
1124
- else # different code for wrap around vertex
1125
- edge_hash[edge_counter] = [v, vertex_hash[1], surface, surface.outsideBoundaryCondition, surface.surfaceType]
1126
- end
1127
- end
1128
- end
1129
-
1130
- # check edges for matches (need opposite vertices and proper boundary conditions)
1131
- edge_hash.each do |k1, v1|
1132
- next if v1[3] != 'Ground' # skip if not ground exposed floor
1133
- next if v1[4] != 'Floor'
1134
- edge_hash.each do |k2, v2|
1135
- next if v2[3] != 'Outdoors' # skip if not exterior exposed wall (todo - update to handle basement)
1136
- next if v2[4] != 'Wall'
1137
-
1138
- # see if edges have same geometry
1139
- # found cases where the two lines below removed edges and resulted in lower than actual perimeter. Added new code with tolerance.
1140
- # next if not v1[0] == v2[1] # next if not same geometry reversed
1141
- # next if not v1[1] == v2[0]
1142
-
1143
- # these are three item array's add in tollerance for each array entry
1144
- tolerance = 0.0001
1145
- test_a = true
1146
- test_b = true
1147
- 3.times.each do |i|
1148
- if (v1[0][i] - v2[1][i]).abs > tolerance
1149
- test_a = false
1150
- end
1151
- if (v1[1][i] - v2[0][i]).abs > tolerance
1152
- test_b = false
1153
- end
1154
- end
1155
-
1156
- next if test_a != true
1157
- next if test_b != true
1158
-
1159
- point_one = OpenStudio::Point3d.new(v1[0][0], v1[0][1], v1[0][2])
1160
- point_two = OpenStudio::Point3d.new(v1[1][0], v1[1][1], v1[1][2])
1161
- length = OpenStudio::Vector3d.new(point_one - point_two).length
1162
- perimeter += length
1163
- end
1164
- end
1165
- end
1166
-
1167
- return perimeter
1168
- end
1169
- end