openstudio-model-articulation 0.8.0 → 0.10.0

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