openstudio-extension 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.md +1 -1
- data/README.md +2 -0
- data/lib/openstudio/extension/runner.rb +12 -8
- data/lib/openstudio/extension/runner_config.rb +33 -6
- data/lib/openstudio/extension/version.rb +1 -1
- data/openstudio-extension.gemspec +6 -6
- metadata +15 -66
- data/lib/openstudio/extension/core/CreateResults.rb +0 -1033
- data/lib/openstudio/extension/core/check_air_sys_temps.rb +0 -160
- data/lib/openstudio/extension/core/check_calibration.rb +0 -125
- data/lib/openstudio/extension/core/check_cond_zns.rb +0 -54
- data/lib/openstudio/extension/core/check_domestic_hot_water.rb +0 -304
- data/lib/openstudio/extension/core/check_envelope_conductance.rb +0 -423
- data/lib/openstudio/extension/core/check_eui_by_end_use.rb +0 -132
- data/lib/openstudio/extension/core/check_eui_reasonableness.rb +0 -105
- data/lib/openstudio/extension/core/check_fan_pwr.rb +0 -68
- data/lib/openstudio/extension/core/check_internal_loads.rb +0 -363
- data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +0 -196
- data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +0 -296
- data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +0 -434
- data/lib/openstudio/extension/core/check_mech_sys_type.rb +0 -109
- data/lib/openstudio/extension/core/check_part_loads.rb +0 -421
- data/lib/openstudio/extension/core/check_placeholder.rb +0 -45
- data/lib/openstudio/extension/core/check_plant_cap.rb +0 -93
- data/lib/openstudio/extension/core/check_plant_temps.rb +0 -129
- data/lib/openstudio/extension/core/check_plenum_loads.rb +0 -57
- data/lib/openstudio/extension/core/check_pump_pwr.rb +0 -78
- data/lib/openstudio/extension/core/check_sch_coord.rb +0 -211
- data/lib/openstudio/extension/core/check_schedules.rb +0 -281
- data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +0 -128
- data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +0 -118
- data/lib/openstudio/extension/core/check_weather_files.rb +0 -102
- data/lib/openstudio/extension/core/deer_vintages.rb +0 -281
- data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +0 -461
- data/lib/openstudio/extension/core/os_lib_constructions.rb +0 -353
- data/lib/openstudio/extension/core/os_lib_geometry.rb +0 -1169
- data/lib/openstudio/extension/core/os_lib_helper_methods.rb +0 -383
- data/lib/openstudio/extension/core/os_lib_hvac.rb +0 -2163
- data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +0 -184
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +0 -3584
- data/lib/openstudio/extension/core/os_lib_model_simplification.rb +0 -1019
- data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +0 -135
- data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +0 -170
- 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
|