tbd 3.2.3 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/pull_request.yml +32 -0
- data/.yardopts +14 -0
- data/README.md +23 -25
- data/json/tbd_warehouse17.json +8 -0
- data/json/tbd_warehouse18.json +12 -0
- data/json/tbd_warehouse4.json +17 -0
- data/lib/measures/tbd/README.md +27 -11
- data/lib/measures/tbd/measure.rb +155 -72
- data/lib/measures/tbd/measure.xml +168 -66
- data/lib/measures/tbd/resources/geo.rb +435 -221
- data/lib/measures/tbd/resources/oslog.rb +213 -161
- data/lib/measures/tbd/resources/psi.rb +1849 -900
- data/lib/measures/tbd/resources/ua.rb +380 -309
- data/lib/measures/tbd/resources/utils.rb +2491 -764
- data/lib/measures/tbd/resources/version.rb +1 -1
- data/lib/measures/tbd/tests/tbd_tests.rb +1 -1
- data/lib/tbd/geo.rb +435 -221
- data/lib/tbd/psi.rb +1849 -900
- data/lib/tbd/ua.rb +380 -309
- data/lib/tbd/version.rb +1 -1
- data/lib/tbd.rb +14 -34
- data/tbd.gemspec +2 -2
- data/tbd.schema.json +189 -20
- data/v291_MacOS.md +2 -4
- metadata +10 -6
@@ -21,9 +21,9 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
module TBD
|
24
|
-
# Sources for thermal bridge types and default KHI & PSI
|
24
|
+
# Sources for thermal bridge types and default KHI- & PSI-factor sets:
|
25
25
|
#
|
26
|
-
# a) BETBG = Building Envelope Thermal Bridging Guide v1.4 (or
|
26
|
+
# a) BETBG = Building Envelope Thermal Bridging Guide v1.4 (or newer):
|
27
27
|
#
|
28
28
|
# www.bchydro.com/content/dam/BCHydro/customer-portal/documents/power-smart/
|
29
29
|
# business/programs/BETB-Building-Envelope-Thermal-Bridging-Guide-v1-4.pdf
|
@@ -42,47 +42,63 @@ module TBD
|
|
42
42
|
# Library of point thermal bridges (e.g. columns). Each key:value entry
|
43
43
|
# requires a unique identifier e.g. "poor (BETBG)" and a KHI-value in W/K.
|
44
44
|
class KHI
|
45
|
+
extend OSut
|
46
|
+
|
45
47
|
# @return [Hash] KHI library
|
46
48
|
attr_reader :point
|
47
49
|
|
48
50
|
##
|
49
|
-
#
|
51
|
+
# Constructs a new KHI library (with defaults).
|
50
52
|
def initialize
|
51
53
|
@point = {}
|
52
54
|
|
53
|
-
# The following are
|
54
|
-
#
|
55
|
-
|
56
|
-
@point["
|
57
|
-
@point["
|
58
|
-
@point["
|
59
|
-
@point["
|
60
|
-
@point["
|
61
|
-
@point["
|
55
|
+
# The following are built-in KHI-factors. Users may append new key:value
|
56
|
+
# pairs, preferably through a TBD JSON input file. Units are in W/K.
|
57
|
+
@point["poor (BETBG)" ] = 0.900 # detail 5.7.2 BETBG
|
58
|
+
@point["regular (BETBG)" ] = 0.500 # detail 5.7.4 BETBG
|
59
|
+
@point["efficient (BETBG)" ] = 0.150 # detail 5.7.3 BETBG
|
60
|
+
@point["code (Quebec)" ] = 0.500 # art. 3.3.1.3. NECB-QC
|
61
|
+
@point["uncompliant (Quebec)" ] = 1.000 # NECB-QC Guide
|
62
|
+
@point["90.1.22|steel.m|default" ] = 0.480 # steel/metal, compliant
|
63
|
+
@point["90.1.22|steel.m|unmitigated"] = 0.920 # steel/metal, non-compliant
|
64
|
+
@point["90.1.22|mass.ex|default" ] = 0.330 # ext/integral, compliant
|
65
|
+
@point["90.1.22|mass.ex|unmitigated"] = 0.460 # ext/integral, non-compliant
|
66
|
+
@point["90.1.22|mass.in|default" ] = 0.330 # interior mass, compliant
|
67
|
+
@point["90.1.22|mass.in|unmitigated"] = 0.460 # interior, non-compliant
|
68
|
+
@point["90.1.22|wood.fr|default" ] = 0.040 # compliant
|
69
|
+
@point["90.1.22|wood.fr|unmitigated"] = 0.330 # non-compliant
|
70
|
+
@point["(non thermal bridging)" ] = 0.000 # defaults to 0
|
62
71
|
end
|
63
72
|
|
64
73
|
##
|
65
|
-
#
|
66
|
-
# a valid, unique :id key and valid :point value).
|
74
|
+
# Appends a new KHI entry.
|
67
75
|
#
|
68
|
-
# @param
|
76
|
+
# @param [Hash] k a new KHI entry
|
77
|
+
# @option k [#to_s] :id name
|
78
|
+
# @option k [#to_f] :point conductance, in W/K
|
69
79
|
#
|
70
|
-
# @return [Bool]
|
71
|
-
# @return [
|
80
|
+
# @return [Bool] whether KHI entry is successfully appended
|
81
|
+
# @return [false] if invalid input (see logs)
|
72
82
|
def append(k = {})
|
73
83
|
mth = "TBD::#{__callee__}"
|
74
84
|
a = false
|
75
|
-
|
76
|
-
return
|
77
|
-
return
|
78
|
-
return
|
79
|
-
|
80
|
-
|
81
|
-
|
85
|
+
ck1 = k.respond_to?(:key?)
|
86
|
+
return mismatch("KHI" , k, Hash , mth, DBG, a) unless ck1
|
87
|
+
return hashkey("KHI id" , k, :id , mth, DBG, a) unless k.key?(:id)
|
88
|
+
return hashkey("KHI point", k, :point, mth, DBG, a) unless k.key?(:point)
|
89
|
+
|
90
|
+
id = trim(k[:id])
|
91
|
+
ck1 = id.empty?
|
92
|
+
ck2 = k[:point].respond_to?(:to_f)
|
93
|
+
return mismatch("KHI id" , k[:id ], String, mth, ERR, a) if ck1
|
94
|
+
return mismatch("KHI point", k[:point], Float , mth, ERR, a) unless ck2
|
95
|
+
|
96
|
+
if @point.key?(id)
|
97
|
+
log(ERR, "Skipping '#{id}': existing KHI entry (#{mth})")
|
82
98
|
return false
|
83
99
|
end
|
84
100
|
|
85
|
-
@point[
|
101
|
+
@point[id] = k[:point].to_f
|
86
102
|
|
87
103
|
true
|
88
104
|
end
|
@@ -91,302 +107,753 @@ module TBD
|
|
91
107
|
##
|
92
108
|
# Library of linear thermal bridges (e.g. corners, balconies). Each key:value
|
93
109
|
# entry requires a unique identifier e.g. "poor (BETBG)" and a (partial or
|
94
|
-
# complete) set of PSI-
|
110
|
+
# complete) set of PSI-factors in W/K per linear meter.
|
95
111
|
class PSI
|
112
|
+
extend OSut
|
113
|
+
|
96
114
|
# @return [Hash] PSI set
|
97
115
|
attr_reader :set
|
98
116
|
|
99
117
|
# @return [Hash] shorthand listing of PSI types in a set
|
100
118
|
attr_reader :has
|
101
119
|
|
102
|
-
# @return [Hash] shorthand listing of PSI
|
120
|
+
# @return [Hash] shorthand listing of PSI-factors in a set
|
103
121
|
attr_reader :val
|
104
122
|
|
105
123
|
##
|
106
|
-
#
|
124
|
+
# Constructs a new PSI library (with defaults)
|
107
125
|
def initialize
|
108
126
|
@set = {}
|
109
127
|
@has = {}
|
110
128
|
@val = {}
|
111
129
|
|
112
|
-
# The following are
|
113
|
-
#
|
114
|
-
# TBD JSON input file.
|
115
|
-
#
|
116
|
-
|
130
|
+
# The following are built-in PSI-factor sets, more often predefined sets
|
131
|
+
# published in guides or energy codes. Users may append new sets,
|
132
|
+
# preferably through a TBD JSON input file. Units are in W/K per meter.
|
133
|
+
#
|
134
|
+
# The provided "spandrel" sets are suitable for early design.
|
135
|
+
#
|
117
136
|
# Convex vs concave PSI adjustments may be warranted if there is a
|
118
137
|
# mismatch between dimensioning conventions (interior vs exterior) used
|
119
|
-
# for the OpenStudio model
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
# using outside dimensions for an
|
138
|
+
# for the OpenStudio model vs published PSI data. For instance, the BETBG
|
139
|
+
# data reflects an interior dimensioning convention, while ISO 14683
|
140
|
+
# reports PSI-factors for both conventions. The following may be used
|
141
|
+
# (with caution) to adjust BETBG PSI-factors for convex corners when
|
142
|
+
# using outside dimensions for an OpenStudio model.
|
124
143
|
#
|
125
144
|
# PSIe = PSIi + U * 2(Li-Le), where:
|
126
|
-
# PSIe = adjusted PSI
|
127
|
-
# PSIi = initial published PSI
|
128
|
-
# U = average clear field U-factor of adjacent walls
|
129
|
-
# Li =
|
130
|
-
# Le =
|
145
|
+
# PSIe = adjusted PSI W/K per m
|
146
|
+
# PSIi = initial published PSI, in W/K per m
|
147
|
+
# U = average clear field U-factor of adjacent walls, in W/m2•K
|
148
|
+
# Li = 'interior corner to edge' length of "zone of influence", in m
|
149
|
+
# Le = 'exterior corner to edge' length of "zone of influence", in m
|
131
150
|
#
|
132
151
|
# Li-Le = wall thickness e.g., -0.25m (negative here as Li < Le)
|
152
|
+
|
153
|
+
# Based on INTERIOR dimensioning (p.15 BETBG).
|
133
154
|
@set["poor (BETBG)"] =
|
134
155
|
{
|
135
|
-
rimjoist:
|
136
|
-
parapet:
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
156
|
+
rimjoist: 1.000000, # re: BETBG
|
157
|
+
parapet: 0.800000, # re: BETBG
|
158
|
+
roof: 0.800000, # same as parapet
|
159
|
+
fenestration: 0.500000, # re: BETBG
|
160
|
+
door: 0.500000, # inferred, same as (vertical) fenestration
|
161
|
+
skylight: 0.500000, # inferred, same as (vertical) fenestration
|
162
|
+
spandrel: 0.155000, # Detail 5.4.4
|
163
|
+
corner: 0.850000, # re: BETBG
|
164
|
+
balcony: 1.000000, # re: BETBG
|
165
|
+
balconysill: 1.000000, # same as balcony
|
166
|
+
balconydoorsill: 1.000000, # same as balconysill
|
167
|
+
party: 0.850000, # re: BETBG
|
168
|
+
grade: 0.850000, # re: BETBG
|
169
|
+
joint: 0.300000, # re: BETBG
|
170
|
+
transition: 0.000000 # defaults to 0
|
171
|
+
}.freeze
|
146
172
|
|
173
|
+
# Based on INTERIOR dimensioning (p.15 BETBG).
|
147
174
|
@set["regular (BETBG)"] =
|
148
175
|
{
|
149
|
-
rimjoist:
|
150
|
-
parapet:
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
176
|
+
rimjoist: 0.500000, # re: BETBG
|
177
|
+
parapet: 0.450000, # re: BETBG
|
178
|
+
roof: 0.450000, # same as parapet
|
179
|
+
fenestration: 0.350000, # re: BETBG
|
180
|
+
door: 0.350000, # inferred, same as (vertical) fenestration
|
181
|
+
skylight: 0.350000, # inferred, same as (vertical) fenestration
|
182
|
+
spandrel: 0.155000, # Detail 5.4.4
|
183
|
+
corner: 0.450000, # re: BETBG
|
184
|
+
balcony: 0.500000, # re: BETBG
|
185
|
+
balconysill: 0.500000, # same as balcony
|
186
|
+
balconydoorsill: 0.500000, # same as balconysill
|
187
|
+
party: 0.450000, # re: BETBG
|
188
|
+
grade: 0.450000, # re: BETBG
|
189
|
+
joint: 0.200000, # re: BETBG
|
190
|
+
transition: 0.000000 # defaults to 0
|
191
|
+
}.freeze
|
160
192
|
|
193
|
+
# Based on INTERIOR dimensioning (p.15 BETBG).
|
161
194
|
@set["efficient (BETBG)"] =
|
162
195
|
{
|
163
|
-
rimjoist:
|
164
|
-
parapet:
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
196
|
+
rimjoist: 0.200000, # re: BETBG
|
197
|
+
parapet: 0.200000, # re: BETBG
|
198
|
+
roof: 0.200000, # same as parapet
|
199
|
+
fenestration: 0.199999, # re: BETBG
|
200
|
+
door: 0.199999, # inferred, same as (vertical) fenestration
|
201
|
+
skylight: 0.199999, # inferred, same as (vertical) fenestration
|
202
|
+
spandrel: 0.155000, # Detail 5.4.4
|
203
|
+
corner: 0.200000, # re: BETBG
|
204
|
+
balcony: 0.200000, # re: BETBG
|
205
|
+
balconysill: 0.200000, # same as balcony
|
206
|
+
balconydoorsill: 0.200000, # same as balconysill
|
207
|
+
party: 0.200000, # re: BETBG
|
208
|
+
grade: 0.200000, # re: BETBG
|
209
|
+
joint: 0.100000, # re: BETBG
|
210
|
+
transition: 0.000000 # defaults to 0
|
211
|
+
}.freeze
|
174
212
|
|
213
|
+
# "Conventional", closer to window wall spandrels.
|
175
214
|
@set["spandrel (BETBG)"] =
|
176
215
|
{
|
177
|
-
rimjoist:
|
178
|
-
parapet:
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
216
|
+
rimjoist: 0.615000, # Detail 1.2.1
|
217
|
+
parapet: 1.000000, # Detail 1.3.2
|
218
|
+
roof: 1.000000, # same as parapet
|
219
|
+
fenestration: 0.000000, # inferred, generally part of clear-field RSi
|
220
|
+
door: 0.000000, # inferred, generally part of clear-field RSi
|
221
|
+
skylight: 0.350000, # same as "regular (BETBG)"
|
222
|
+
spandrel: 0.155000, # Detail 5.4.4
|
223
|
+
corner: 0.425000, # Detail 1.4.1
|
224
|
+
balcony: 1.110000, # Detail 8.1.9/9.1.6
|
225
|
+
balconysill: 1.110000, # same as balcony
|
226
|
+
balconydoorsill: 1.110000, # same as balconysill
|
227
|
+
party: 0.990000, # inferred, similar to parapet/balcony
|
228
|
+
grade: 0.880000, # Detail 2.5.1
|
229
|
+
joint: 0.500000, # Detail 3.3.2
|
230
|
+
transition: 0.000000 # defaults to 0
|
231
|
+
}.freeze
|
188
232
|
|
233
|
+
# "GoodHigh performance" curtainwall spandrels.
|
189
234
|
@set["spandrel HP (BETBG)"] =
|
190
235
|
{
|
191
|
-
rimjoist:
|
192
|
-
parapet:
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
236
|
+
rimjoist: 0.170000, # Detail 1.2.7
|
237
|
+
parapet: 0.660000, # Detail 1.3.2
|
238
|
+
roof: 0.660000, # same as parapet
|
239
|
+
fenestration: 0.000000, # inferred, generally part of clear-field RSi
|
240
|
+
door: 0.000000, # inferred, generally part of clear-field RSi
|
241
|
+
skylight: 0.350000, # same as "regular (BETBG)"
|
242
|
+
spandrel: 0.155000, # Detail 5.4.4
|
243
|
+
corner: 0.200000, # Detail 1.4.2
|
244
|
+
balcony: 0.400000, # Detail 9.1.15
|
245
|
+
balconysill: 0.400000, # same as balcony
|
246
|
+
balconydoorsill: 0.400000, # same as balconysill
|
247
|
+
party: 0.500000, # inferred, similar to parapet/balcony
|
248
|
+
grade: 0.880000, # Detail 2.5.1
|
249
|
+
joint: 0.140000, # Detail 7.4.2
|
250
|
+
transition: 0.000000 # defaults to 0
|
251
|
+
}.freeze
|
252
|
+
|
253
|
+
# CCQ, Chapitre I1, code-compliant defaults.
|
254
|
+
@set["code (Quebec)"] =
|
255
|
+
{
|
256
|
+
rimjoist: 0.300000, # re I1
|
257
|
+
parapet: 0.325000, # re I1
|
258
|
+
roof: 0.325000, # same as parapet
|
259
|
+
fenestration: 0.200000, # re I1
|
260
|
+
door: 0.200000, # re I1
|
261
|
+
skylight: 0.200000, # re I1
|
262
|
+
spandrel: 0.155000, # BETBG Detail 5.4.4 (same as uncompliant)
|
263
|
+
corner: 0.300000, # inferred from description, not explicitely set
|
264
|
+
balcony: 0.500000, # re I1
|
265
|
+
balconysill: 0.500000, # same as balcony
|
266
|
+
balconydoorsill: 0.500000, # same as balconysill
|
267
|
+
party: 0.450000, # re I1
|
268
|
+
grade: 0.450000, # re I1
|
269
|
+
joint: 0.200000, # re I1
|
270
|
+
transition: 0.000000 # defaults to 0
|
271
|
+
}.freeze
|
272
|
+
|
273
|
+
# CCQ, Chapitre I1, non-code-compliant defaults.
|
274
|
+
@set["uncompliant (Quebec)"] =
|
275
|
+
{
|
276
|
+
rimjoist: 0.850000, # re I1
|
277
|
+
parapet: 0.800000, # re I1
|
278
|
+
roof: 0.800000, # same as parapet
|
279
|
+
fenestration: 0.500000, # re I1
|
280
|
+
door: 0.500000, # re I1
|
281
|
+
skylight: 0.500000, # re I1
|
282
|
+
spandrel: 0.155000, # BETBG Detail 5.4.4 (same as compliant)
|
283
|
+
corner: 0.850000, # inferred from description, not explicitely set
|
284
|
+
balcony: 1.000000, # re I1
|
285
|
+
balconysill: 1.000000, # same as balcony
|
286
|
+
balconydoorsill: 1.000000, # same as balconysill
|
287
|
+
party: 0.850000, # re I1
|
288
|
+
grade: 0.850000, # re I1
|
289
|
+
joint: 0.500000, # re I1
|
290
|
+
transition: 0.000000 # defaults to 0
|
291
|
+
}.freeze
|
292
|
+
|
293
|
+
# ASHRAE 90.1 2022 (A10) "default" steel-framed and metal buildings.
|
294
|
+
@set["90.1.22|steel.m|default"] =
|
204
295
|
{
|
205
|
-
rimjoist:
|
206
|
-
parapet:
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
296
|
+
rimjoist: 0.307000, # "intermediate floor to wall intersection"
|
297
|
+
parapet: 0.260000, # "parapet" edge
|
298
|
+
roof: 0.020000, # (non-parapet) "roof" edge
|
299
|
+
fenestration: 0.194000, # "wall to vertical fenestration intersection"
|
300
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
301
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
302
|
+
spandrel: 0.000001, # (unspecified, defaults to 0)
|
303
|
+
corner: 0.000002, # (unspecified, defaults to 0)
|
304
|
+
balcony: 0.307000, # "intermediate floor balcony/overhang" edge
|
305
|
+
balconysill: 0.307000, # "intermediate floor balcony" edge (when sill)
|
306
|
+
balconydoorsill: 0.307000, # same as balcony
|
307
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
308
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
309
|
+
joint: 0.376000, # placeholder for "cladding support"
|
310
|
+
transition: 0.000000 # defaults to 0
|
214
311
|
}.freeze
|
215
|
-
self.gen("code (Quebec)")
|
216
312
|
|
217
|
-
|
313
|
+
# ASHRAE 90.1 2022 (A10) "unmitigated" steel-framed and metal buildings.
|
314
|
+
@set["90.1.22|steel.m|unmitigated"] =
|
218
315
|
{
|
219
|
-
rimjoist:
|
220
|
-
parapet:
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
316
|
+
rimjoist: 0.842000, # "intermediate floor to wall intersection"
|
317
|
+
parapet: 0.500000, # "parapet" edge
|
318
|
+
roof: 0.650000, # (non-parapet) "roof" edge
|
319
|
+
fenestration: 0.505000, # "wall to vertical fenestration intersection"
|
320
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
321
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
322
|
+
spandrel: 0.000001, # (unspecified, defaults to 0)
|
323
|
+
corner: 0.000002, # (unspecified, defaults to 0)
|
324
|
+
balcony: 0.842000, # "intermediate floor balcony/overhang" edge
|
325
|
+
balconysill: 1.686000, # "intermediate floor balcony" edge (when sill)
|
326
|
+
balconydoorsill: 0.842000, # same as balcony
|
327
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
328
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
329
|
+
joint: 0.554000, # placeholder for "cladding support"
|
330
|
+
transition: 0.000000 # defaults to 0
|
228
331
|
}.freeze
|
229
|
-
self.gen("uncompliant (Quebec)")
|
230
332
|
|
231
|
-
|
333
|
+
# ASHRAE 90.1 2022 (A10) "default" exterior/integral mass walls.
|
334
|
+
@set["90.1.22|mass.ex|default"] =
|
232
335
|
{
|
233
|
-
rimjoist:
|
234
|
-
parapet:
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
336
|
+
rimjoist: 0.205000, # "intermediate floor to wall intersection"
|
337
|
+
parapet: 0.217000, # "parapet" edge
|
338
|
+
roof: 0.150000, # (non-parapet) "roof" edge
|
339
|
+
fenestration: 0.226000, # "wall to vertical fenestration intersection"
|
340
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
341
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
342
|
+
spandrel: 0.000001, # (unspecified, defaults to 0)
|
343
|
+
corner: 0.000002, # (unspecified, defaults to 0)
|
344
|
+
balcony: 0.205000, # "intermediate floor balcony/overhang" edge
|
345
|
+
balconysill: 0.307000, # "intermediate floor balcony" edge (when sill)
|
346
|
+
balconydoorsill: 0.205000, # same as balcony
|
347
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
348
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
349
|
+
joint: 0.322000, # placeholder for "cladding support"
|
350
|
+
transition: 0.000000 # defaults to 0
|
242
351
|
}.freeze
|
243
|
-
|
352
|
+
|
353
|
+
# ASHRAE 90.1 2022 (A10) "unmitigated" exterior/integral mass walls.
|
354
|
+
@set["90.1.22|mass.ex|unmitigated"] =
|
355
|
+
{
|
356
|
+
rimjoist: 0.824000, # "intermediate floor to wall intersection"
|
357
|
+
parapet: 0.412000, # "parapet" edge
|
358
|
+
roof: 0.750000, # (non-parapet) "roof" edge
|
359
|
+
fenestration: 0.325000, # "wall to vertical fenestration intersection"
|
360
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
361
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
362
|
+
spandrel: 0.000001, # (unspecified, defaults to 0)
|
363
|
+
corner: 0.000002, # (unspecified, defaults to 0)
|
364
|
+
balcony: 0.824000, # "intermediate floor balcony/overhang" edge
|
365
|
+
balconysill: 1.686000, # "intermediate floor balcony" edge (when sill)
|
366
|
+
balconydoorsill: 0.824000, # same as balcony
|
367
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
368
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
369
|
+
joint: 0.476000, # placeholder for "cladding support"
|
370
|
+
transition: 0.000000 # defaults to 0
|
371
|
+
}.freeze
|
372
|
+
|
373
|
+
# ASHRAE 90.1 2022 (A10) "default" interior mass walls.
|
374
|
+
@set["90.1.22|mass.in|default"] =
|
375
|
+
{
|
376
|
+
rimjoist: 0.495000, # "intermediate floor to wall intersection"
|
377
|
+
parapet: 0.393000, # "parapet" edge
|
378
|
+
roof: 0.150000, # (non-parapet) "roof" edge
|
379
|
+
fenestration: 0.143000, # "wall to vertical fenestration intersection"
|
380
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
381
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
382
|
+
spandrel: 0.000000, # (unspecified, defaults to 0)
|
383
|
+
corner: 0.000001, # (unspecified, defaults to 0)
|
384
|
+
balcony: 0.495000, # "intermediate floor balcony/overhang" edge
|
385
|
+
balconysill: 0.307000, # "intermediate floor balcony" edge (when sill)
|
386
|
+
balconydoorsill: 0.495000, # same as balcony
|
387
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
388
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
389
|
+
joint: 0.322000, # placeholder for "cladding support"
|
390
|
+
transition: 0.000000 # defaults to 0
|
391
|
+
}.freeze
|
392
|
+
|
393
|
+
# ASHRAE 90.1 2022 (A10) "unmitigated" interior mass walls.
|
394
|
+
@set["90.1.22|mass.in|unmitigated"] =
|
395
|
+
{
|
396
|
+
rimjoist: 0.824000, # "intermediate floor to wall intersection"
|
397
|
+
parapet: 0.884000, # "parapet" edge
|
398
|
+
roof: 0.750000, # (non-parapet) "roof" edge
|
399
|
+
fenestration: 0.543000, # "wall to vertical fenestration intersection"
|
400
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
401
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
402
|
+
spandrel: 0.000000, # (unspecified, defaults to 0)
|
403
|
+
corner: 0.000001, # (unspecified, defaults to 0)
|
404
|
+
balcony: 0.824000, # "intermediate floor balcony/overhang" edge
|
405
|
+
balconysill: 1.686000, # "intermediate floor balcony" edge (when sill)
|
406
|
+
balconydoorsill: 0.824000, # same as balcony
|
407
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
408
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
409
|
+
joint: 0.476000, # placeholder for "cladding support"
|
410
|
+
transition: 0.000000 # defaults to 0
|
411
|
+
}.freeze
|
412
|
+
|
413
|
+
# ASHRAE 90.1 2022 (A10) "default" wood-framed (and other) walls.
|
414
|
+
@set["90.1.22|wood.fr|default"] =
|
415
|
+
{
|
416
|
+
rimjoist: 0.084000, # "intermediate floor to wall intersection"
|
417
|
+
parapet: 0.056000, # "parapet" edge
|
418
|
+
roof: 0.020000, # (non-parapet) "roof" edge
|
419
|
+
fenestration: 0.171000, # "wall to vertical fenestration intersection"
|
420
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
421
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
422
|
+
spandrel: 0.000000, # (unspecified, defaults to 0)
|
423
|
+
corner: 0.000001, # (unspecified, defaults to 0)
|
424
|
+
balcony: 0.084000, # "intermediate floor balcony/overhang" edge
|
425
|
+
balconysill: 0.171001, # same as :fenestration
|
426
|
+
balconydoorsill: 0.084000, # same as balcony
|
427
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
428
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
429
|
+
joint: 0.074000, # placeholder for "cladding support"
|
430
|
+
transition: 0.000000 # defaults to 0
|
431
|
+
}.freeze
|
432
|
+
|
433
|
+
# ASHRAE 90.1 2022 (A10) "unmitigated" wood-framed (and other) walls.
|
434
|
+
@set["90.1.22|wood.fr|unmitigated"] =
|
435
|
+
{
|
436
|
+
rimjoist: 0.582000, # "intermediate floor to wall intersection"
|
437
|
+
parapet: 0.056000, # "parapet" edge
|
438
|
+
roof: 0.150000, # (non-parapet) "roof" edge
|
439
|
+
fenestration: 0.260000, # "wall to vertical fenestration intersection"
|
440
|
+
door: 0.000000, # (unspecified, defaults to 0)
|
441
|
+
skylight: 0.000000, # (unspecified, defaults to 0)
|
442
|
+
spandrel: 0.000000, # (unspecified, defaults to 0)
|
443
|
+
corner: 0.000001, # (unspecified, defaults to 0)
|
444
|
+
balcony: 0.582000, # same as :rimjoist
|
445
|
+
balconysill: 0.582000, # same as :rimjoist
|
446
|
+
balconydoorsill: 0.582000, # same as balcony
|
447
|
+
party: 0.000001, # (unspecified, defaults to 0)
|
448
|
+
grade: 0.000001, # (unspecified, defaults to 0)
|
449
|
+
joint: 0.322000, # placeholder for "cladding support"
|
450
|
+
transition: 0.000000 # defaults to 0
|
451
|
+
}.freeze
|
452
|
+
|
453
|
+
@set["(non thermal bridging)"] =
|
454
|
+
{
|
455
|
+
rimjoist: 0.000000, # defaults to 0
|
456
|
+
parapet: 0.000000, # defaults to 0
|
457
|
+
roof: 0.000000, # defaults to 0
|
458
|
+
fenestration: 0.000000, # defaults to 0
|
459
|
+
door: 0.000000, # defaults to 0
|
460
|
+
skylight: 0.000000, # defaults to 0
|
461
|
+
spandrel: 0.000000, # defaults to 0
|
462
|
+
corner: 0.000000, # defaults to 0
|
463
|
+
balcony: 0.000000, # defaults to 0
|
464
|
+
balconysill: 0.000000, # defaults to 0
|
465
|
+
party: 0.000000, # defaults to 0
|
466
|
+
grade: 0.000000, # defaults to 0
|
467
|
+
joint: 0.000000, # defaults to 0
|
468
|
+
transition: 0.000000 # defaults to 0
|
469
|
+
}.freeze
|
470
|
+
|
471
|
+
@set.keys.each { |k| self.gen(k) }
|
244
472
|
end
|
245
473
|
|
246
474
|
##
|
247
|
-
#
|
475
|
+
# Generates PSI set shorthand listings.
|
248
476
|
#
|
249
|
-
# @param id
|
477
|
+
# @param id PSI set identifier
|
250
478
|
#
|
251
|
-
# @return [Bool]
|
252
|
-
# @return [
|
479
|
+
# @return [Bool] whether successful in generating PSI set shorthands
|
480
|
+
# @return [false] if invalid input (see logs)
|
253
481
|
def gen(id = "")
|
254
482
|
mth = "TBD::#{__callee__}"
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
h
|
261
|
-
h[:
|
262
|
-
h[:
|
263
|
-
h[:
|
264
|
-
h[:
|
265
|
-
h[:
|
266
|
-
h[:
|
267
|
-
h[:
|
268
|
-
h[:
|
269
|
-
h[:
|
270
|
-
h[:
|
271
|
-
h[:
|
272
|
-
h[:
|
273
|
-
h[:
|
274
|
-
h[:
|
275
|
-
h[:
|
276
|
-
h[:
|
277
|
-
h[:
|
278
|
-
h[:
|
279
|
-
h[:
|
280
|
-
h[:
|
281
|
-
h[:
|
282
|
-
h[:
|
283
|
-
h[:
|
284
|
-
h[:
|
285
|
-
h[:
|
286
|
-
h[:
|
287
|
-
h[:
|
288
|
-
h[:
|
289
|
-
h[:
|
290
|
-
h[:
|
291
|
-
@
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
v
|
323
|
-
v[:
|
324
|
-
v[:
|
325
|
-
v[:
|
326
|
-
v[:
|
327
|
-
v[:
|
328
|
-
v[:
|
329
|
-
v[:
|
330
|
-
v[:
|
331
|
-
v[:
|
332
|
-
v[:
|
333
|
-
v[:
|
334
|
-
v[:
|
335
|
-
v[:
|
336
|
-
v[:
|
337
|
-
v[:
|
338
|
-
v[:
|
339
|
-
v[:
|
340
|
-
v[:
|
341
|
-
v[:
|
342
|
-
v[:
|
343
|
-
v[:
|
344
|
-
|
345
|
-
v[:
|
346
|
-
v[:
|
347
|
-
v[:
|
348
|
-
v[:
|
349
|
-
v[:
|
350
|
-
v[:
|
351
|
-
v[:
|
352
|
-
v[:
|
353
|
-
v[:
|
354
|
-
v[:
|
355
|
-
v[:
|
356
|
-
v[:
|
357
|
-
v[:
|
358
|
-
v[:
|
359
|
-
v[:
|
360
|
-
v[:
|
361
|
-
v[:
|
483
|
+
return hashkey(id, @set, id, mth, ERR, false) unless @set.key?(id)
|
484
|
+
|
485
|
+
h = {} # true/false if PSI set has PSI type
|
486
|
+
h[:joint ] = @set[id].key?(:joint)
|
487
|
+
h[:transition ] = @set[id].key?(:transition)
|
488
|
+
h[:fenestration ] = @set[id].key?(:fenestration)
|
489
|
+
h[:head ] = @set[id].key?(:head)
|
490
|
+
h[:headconcave ] = @set[id].key?(:headconcave)
|
491
|
+
h[:headconvex ] = @set[id].key?(:headconvex)
|
492
|
+
h[:sill ] = @set[id].key?(:sill)
|
493
|
+
h[:sillconcave ] = @set[id].key?(:sillconcave)
|
494
|
+
h[:sillconvex ] = @set[id].key?(:sillconvex)
|
495
|
+
h[:jamb ] = @set[id].key?(:jamb)
|
496
|
+
h[:jambconcave ] = @set[id].key?(:jambconcave)
|
497
|
+
h[:jambconvex ] = @set[id].key?(:jambconvex)
|
498
|
+
h[:door ] = @set[id].key?(:door)
|
499
|
+
h[:doorhead ] = @set[id].key?(:doorhead)
|
500
|
+
h[:doorheadconcave ] = @set[id].key?(:doorheadconcave)
|
501
|
+
h[:doorheadconvex ] = @set[id].key?(:doorheadconvex)
|
502
|
+
h[:doorsill ] = @set[id].key?(:doorsill)
|
503
|
+
h[:doorsillconcave ] = @set[id].key?(:doorsillconcave)
|
504
|
+
h[:doorsillconvex ] = @set[id].key?(:doorsillconvex)
|
505
|
+
h[:doorjamb ] = @set[id].key?(:doorjamb)
|
506
|
+
h[:doorjambconcave ] = @set[id].key?(:doorjambconcave)
|
507
|
+
h[:doorjambconvex ] = @set[id].key?(:doorjambconvex)
|
508
|
+
h[:skylight ] = @set[id].key?(:skylight)
|
509
|
+
h[:skylighthead ] = @set[id].key?(:skylighthead)
|
510
|
+
h[:skylightheadconcave ] = @set[id].key?(:skylightheadconcave)
|
511
|
+
h[:skylightheadconvex ] = @set[id].key?(:skylightheadconvex)
|
512
|
+
h[:skylightsill ] = @set[id].key?(:skylightsill)
|
513
|
+
h[:skylightsillconcave ] = @set[id].key?(:skylightsillconcave)
|
514
|
+
h[:skylightsillconvex ] = @set[id].key?(:skylightsillconvex)
|
515
|
+
h[:skylightjamb ] = @set[id].key?(:skylightjamb)
|
516
|
+
h[:skylightjambconcave ] = @set[id].key?(:skylightjambconcave)
|
517
|
+
h[:skylightjambconvex ] = @set[id].key?(:skylightjambconvex)
|
518
|
+
h[:spandrel ] = @set[id].key?(:spandrel)
|
519
|
+
h[:spandrelconcave ] = @set[id].key?(:spandrelconcave)
|
520
|
+
h[:spandrelconvex ] = @set[id].key?(:spandrelconvex)
|
521
|
+
h[:corner ] = @set[id].key?(:corner)
|
522
|
+
h[:cornerconcave ] = @set[id].key?(:cornerconcave)
|
523
|
+
h[:cornerconvex ] = @set[id].key?(:cornerconvex)
|
524
|
+
h[:party ] = @set[id].key?(:party)
|
525
|
+
h[:partyconcave ] = @set[id].key?(:partyconcave)
|
526
|
+
h[:partyconvex ] = @set[id].key?(:partyconvex)
|
527
|
+
h[:parapet ] = @set[id].key?(:parapet)
|
528
|
+
h[:partyconcave ] = @set[id].key?(:parapetconcave)
|
529
|
+
h[:parapetconvex ] = @set[id].key?(:parapetconvex)
|
530
|
+
h[:roof ] = @set[id].key?(:roof)
|
531
|
+
h[:roofconcave ] = @set[id].key?(:roofconcave)
|
532
|
+
h[:roofconvex ] = @set[id].key?(:roofconvex)
|
533
|
+
h[:grade ] = @set[id].key?(:grade)
|
534
|
+
h[:gradeconcave ] = @set[id].key?(:gradeconcave)
|
535
|
+
h[:gradeconvex ] = @set[id].key?(:gradeconvex)
|
536
|
+
h[:balcony ] = @set[id].key?(:balcony)
|
537
|
+
h[:balconyconcave ] = @set[id].key?(:balconyconcave)
|
538
|
+
h[:balconyconvex ] = @set[id].key?(:balconyconvex)
|
539
|
+
h[:balconysill ] = @set[id].key?(:balconysill)
|
540
|
+
h[:balconysillconcave ] = @set[id].key?(:balconysillconvex)
|
541
|
+
h[:balconysillconvex ] = @set[id].key?(:balconysillconvex)
|
542
|
+
h[:balconydoorsill ] = @set[id].key?(:balconydoorsill)
|
543
|
+
h[:balconydoorsillconcave] = @set[id].key?(:balconydoorsillconvex)
|
544
|
+
h[:balconydoorsillconvex ] = @set[id].key?(:balconydoorsillconvex)
|
545
|
+
h[:rimjoist ] = @set[id].key?(:rimjoist)
|
546
|
+
h[:rimjoistconcave ] = @set[id].key?(:rimjoistconcave)
|
547
|
+
h[:rimjoistconvex ] = @set[id].key?(:rimjoistconvex)
|
548
|
+
@has[id] = h
|
549
|
+
|
550
|
+
v = {} # PSI-value (W/K per linear meter)
|
551
|
+
v[:door ] = 0; v[:fenestration ] = 0; v[:skylight ] = 0
|
552
|
+
v[:head ] = 0; v[:headconcave ] = 0; v[:headconvex ] = 0
|
553
|
+
v[:sill ] = 0; v[:sillconcave ] = 0; v[:sillconvex ] = 0
|
554
|
+
v[:jamb ] = 0; v[:jambconcave ] = 0; v[:jambconvex ] = 0
|
555
|
+
v[:doorhead ] = 0; v[:doorheadconcave ] = 0; v[:doorconvex ] = 0
|
556
|
+
v[:doorsill ] = 0; v[:doorsillconcave ] = 0; v[:doorsillconvex ] = 0
|
557
|
+
v[:doorjamb ] = 0; v[:doorjambconcave ] = 0; v[:doorjambconvex ] = 0
|
558
|
+
v[:skylighthead ] = 0; v[:skylightheadconcave ] = 0; v[:skylightconvex ] = 0
|
559
|
+
v[:skylightsill ] = 0; v[:skylightsillconcave ] = 0; v[:skylightsillconvex ] = 0
|
560
|
+
v[:skylightjamb ] = 0; v[:skylightjambconcave ] = 0; v[:skylightjambconvex ] = 0
|
561
|
+
v[:spandrel ] = 0; v[:spandrelconcave ] = 0; v[:spandrelconvex ] = 0
|
562
|
+
v[:corner ] = 0; v[:cornerconcave ] = 0; v[:cornerconvex ] = 0
|
563
|
+
v[:parapet ] = 0; v[:parapetconcave ] = 0; v[:parapetconvex ] = 0
|
564
|
+
v[:roof ] = 0; v[:roofconcave ] = 0; v[:roofconvex ] = 0
|
565
|
+
v[:party ] = 0; v[:partyconcave ] = 0; v[:partyconvex ] = 0
|
566
|
+
v[:grade ] = 0; v[:gradeconcave ] = 0; v[:gradeconvex ] = 0
|
567
|
+
v[:balcony ] = 0; v[:balconyconcave ] = 0; v[:balconyconvex ] = 0
|
568
|
+
v[:balconysill ] = 0; v[:balconysillconcave ] = 0; v[:balconysillconvex ] = 0
|
569
|
+
v[:balconydoorsill] = 0; v[:balconydoorsillconcave] = 0; v[:balconydoorsillconvex] = 0
|
570
|
+
v[:rimjoist ] = 0; v[:rimjoistconcave ] = 0; v[:rimjoistconvex ] = 0
|
571
|
+
v[:joint ] = 0; v[:transition ] = 0
|
572
|
+
|
573
|
+
v[:joint ] = @set[id][:joint ] if h[:joint ]
|
574
|
+
v[:transition ] = @set[id][:transition ] if h[:transition ]
|
575
|
+
v[:fenestration ] = @set[id][:fenestration ] if h[:fenestration ]
|
576
|
+
v[:head ] = @set[id][:fenestration ] if h[:fenestration ]
|
577
|
+
v[:headconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
578
|
+
v[:headconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
579
|
+
v[:sill ] = @set[id][:fenestration ] if h[:fenestration ]
|
580
|
+
v[:sillconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
581
|
+
v[:sillconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
582
|
+
v[:jamb ] = @set[id][:fenestration ] if h[:fenestration ]
|
583
|
+
v[:jambconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
584
|
+
v[:jambconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
585
|
+
v[:door ] = @set[id][:fenestration ] if h[:fenestration ]
|
586
|
+
v[:doorhead ] = @set[id][:fenestration ] if h[:fenestration ]
|
587
|
+
v[:doorheadconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
588
|
+
v[:doorheadconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
589
|
+
v[:doorsill ] = @set[id][:fenestration ] if h[:fenestration ]
|
590
|
+
v[:doorsillconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
591
|
+
v[:doorsillconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
592
|
+
v[:doorjamb ] = @set[id][:fenestration ] if h[:fenestration ]
|
593
|
+
v[:doorjambconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
594
|
+
v[:doorjambconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
595
|
+
v[:skylight ] = @set[id][:fenestration ] if h[:fenestration ]
|
596
|
+
v[:skylighthead ] = @set[id][:fenestration ] if h[:fenestration ]
|
597
|
+
v[:skylightheadconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
598
|
+
v[:skylightheadconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
599
|
+
v[:skylightsill ] = @set[id][:fenestration ] if h[:fenestration ]
|
600
|
+
v[:skylightsillconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
601
|
+
v[:skylightsillconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
602
|
+
v[:skylightjamb ] = @set[id][:fenestration ] if h[:fenestration ]
|
603
|
+
v[:skylightjambconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
604
|
+
v[:skylightjambconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
605
|
+
v[:door ] = @set[id][:door ] if h[:door ]
|
606
|
+
v[:doorhead ] = @set[id][:door ] if h[:door ]
|
607
|
+
v[:doorheadconcave ] = @set[id][:door ] if h[:door ]
|
608
|
+
v[:doorheadconvex ] = @set[id][:door ] if h[:door ]
|
609
|
+
v[:doorsill ] = @set[id][:door ] if h[:door ]
|
610
|
+
v[:doorsillconcave ] = @set[id][:door ] if h[:door ]
|
611
|
+
v[:doorsillconvex ] = @set[id][:door ] if h[:door ]
|
612
|
+
v[:doorjamb ] = @set[id][:door ] if h[:door ]
|
613
|
+
v[:doorjambconcave ] = @set[id][:door ] if h[:door ]
|
614
|
+
v[:doorjambconvex ] = @set[id][:door ] if h[:door ]
|
615
|
+
v[:skylight ] = @set[id][:skylight ] if h[:skylight ]
|
616
|
+
v[:skylighthead ] = @set[id][:skylight ] if h[:skylight ]
|
617
|
+
v[:skylightheadconcave ] = @set[id][:skylight ] if h[:skylight ]
|
618
|
+
v[:skylightheadconvex ] = @set[id][:skylight ] if h[:skylight ]
|
619
|
+
v[:skylightsill ] = @set[id][:skylight ] if h[:skylight ]
|
620
|
+
v[:skylightsillconcave ] = @set[id][:skylight ] if h[:skylight ]
|
621
|
+
v[:skylightsillconvex ] = @set[id][:skylight ] if h[:skylight ]
|
622
|
+
v[:skylightjamb ] = @set[id][:skylight ] if h[:skylight ]
|
623
|
+
v[:skylightjambconcave ] = @set[id][:skylight ] if h[:skylight ]
|
624
|
+
v[:skylightjambconvex ] = @set[id][:skylight ] if h[:skylight ]
|
625
|
+
v[:head ] = @set[id][:head ] if h[:head ]
|
626
|
+
v[:headconcave ] = @set[id][:head ] if h[:head ]
|
627
|
+
v[:headconvex ] = @set[id][:head ] if h[:head ]
|
628
|
+
v[:sill ] = @set[id][:sill ] if h[:sill ]
|
629
|
+
v[:sillconcave ] = @set[id][:sill ] if h[:sill ]
|
630
|
+
v[:sillconvex ] = @set[id][:sill ] if h[:sill ]
|
631
|
+
v[:jamb ] = @set[id][:jamb ] if h[:jamb ]
|
632
|
+
v[:jambconcave ] = @set[id][:jamb ] if h[:jamb ]
|
633
|
+
v[:jambconvex ] = @set[id][:jamb ] if h[:jamb ]
|
634
|
+
v[:doorhead ] = @set[id][:doorhead ] if h[:doorhead ]
|
635
|
+
v[:doorheadconcave ] = @set[id][:doorhead ] if h[:doorhead ]
|
636
|
+
v[:doorheadconvex ] = @set[id][:doorhead ] if h[:doorhead ]
|
637
|
+
v[:doorsill ] = @set[id][:doorsill ] if h[:doorsill ]
|
638
|
+
v[:doorsillconcave ] = @set[id][:doorsill ] if h[:doorsill ]
|
639
|
+
v[:doorsillconvex ] = @set[id][:doorsill ] if h[:doorsill ]
|
640
|
+
v[:doorjamb ] = @set[id][:doorjamb ] if h[:doorjamb ]
|
641
|
+
v[:doorjambconcave ] = @set[id][:doorjamb ] if h[:doorjamb ]
|
642
|
+
v[:doorjambconvex ] = @set[id][:doorjamb ] if h[:doorjamb ]
|
643
|
+
v[:skylighthead ] = @set[id][:skylighthead ] if h[:skylighthead ]
|
644
|
+
v[:skylightheadconcave ] = @set[id][:skylighthead ] if h[:skylighthead ]
|
645
|
+
v[:skylightheadconvex ] = @set[id][:skylighthead ] if h[:skylighthead ]
|
646
|
+
v[:skylightsill ] = @set[id][:skylightsill ] if h[:skylightsill ]
|
647
|
+
v[:skylightsillconcave ] = @set[id][:skylightsill ] if h[:skylightsill ]
|
648
|
+
v[:skylightsillconvex ] = @set[id][:skylightsill ] if h[:skylightsill ]
|
649
|
+
v[:skylightjamb ] = @set[id][:skylightjamb ] if h[:skylightjamb ]
|
650
|
+
v[:skylightjambconcave ] = @set[id][:skylightjamb ] if h[:skylightjamb ]
|
651
|
+
v[:skylightjambconvex ] = @set[id][:skylightjamb ] if h[:skylightjamb ]
|
652
|
+
v[:headconcave ] = @set[id][:headconcave ] if h[:headconcave ]
|
653
|
+
v[:headconvex ] = @set[id][:headconvex ] if h[:headconvex ]
|
654
|
+
v[:sillconcave ] = @set[id][:sillconcave ] if h[:sillconcave ]
|
655
|
+
v[:sillconvex ] = @set[id][:sillconvex ] if h[:sillconvex ]
|
656
|
+
v[:jambconcave ] = @set[id][:jambconcave ] if h[:jambconcave ]
|
657
|
+
v[:jambconvex ] = @set[id][:jambconvex ] if h[:jambconvex ]
|
658
|
+
v[:doorheadconcave ] = @set[id][:doorheadconcave ] if h[:doorheadconcave ]
|
659
|
+
v[:doorheadconvex ] = @set[id][:doorheadconvex ] if h[:doorheadconvex ]
|
660
|
+
v[:doorsillconcave ] = @set[id][:doorsillconcave ] if h[:doorsillconcave ]
|
661
|
+
v[:doorsillconvex ] = @set[id][:doorsillconvex ] if h[:doorsillconvex ]
|
662
|
+
v[:doorjambconcave ] = @set[id][:doorjambconcave ] if h[:doorjambconcave ]
|
663
|
+
v[:doorjambconvex ] = @set[id][:doorjambconvex ] if h[:doorjambconvex ]
|
664
|
+
v[:skylightheadconcave ] = @set[id][:skylightheadconcave ] if h[:skylightheadconcave ]
|
665
|
+
v[:skylightheadconvex ] = @set[id][:skylightheadconvex ] if h[:skylightheadconvex ]
|
666
|
+
v[:skylightsillconcave ] = @set[id][:skylightsillconcave ] if h[:skylightsillconcave ]
|
667
|
+
v[:skylightsillconvex ] = @set[id][:skylightsillconvex ] if h[:skylightsillconvex ]
|
668
|
+
v[:skylightjambconcave ] = @set[id][:skylightjambconcave ] if h[:skylightjambconcave ]
|
669
|
+
v[:skylightjambconvex ] = @set[id][:skylightjambconvex ] if h[:skylightjambconvex ]
|
670
|
+
v[:spandrel ] = @set[id][:spandrel ] if h[:spandrel ]
|
671
|
+
v[:spandrelconcave ] = @set[id][:spandrel ] if h[:spandrel ]
|
672
|
+
v[:spandrelconvex ] = @set[id][:spandrel ] if h[:spandrel ]
|
673
|
+
v[:spandrelconcave ] = @set[id][:spandrelconcave ] if h[:spandrelconcave ]
|
674
|
+
v[:spandrelconvex ] = @set[id][:spandrelconvex ] if h[:spandrelconvex ]
|
675
|
+
v[:corner ] = @set[id][:corner ] if h[:corner ]
|
676
|
+
v[:cornerconcave ] = @set[id][:corner ] if h[:corner ]
|
677
|
+
v[:cornerconvex ] = @set[id][:corner ] if h[:corner ]
|
678
|
+
v[:cornerconcave ] = @set[id][:cornerconcave ] if h[:cornerconcave ]
|
679
|
+
v[:cornerconvex ] = @set[id][:cornerconvex ] if h[:cornerconvex ]
|
680
|
+
v[:parapet ] = @set[id][:roof ] if h[:roof ]
|
681
|
+
v[:parapetconcave ] = @set[id][:roof ] if h[:roof ]
|
682
|
+
v[:parapetconvex ] = @set[id][:roof ] if h[:roof ]
|
683
|
+
v[:parapetconcave ] = @set[id][:roofconcave ] if h[:roofconcave ]
|
684
|
+
v[:parapetconvex ] = @set[id][:roofconvex ] if h[:roofconvex ]
|
685
|
+
v[:parapet ] = @set[id][:parapet ] if h[:parapet ]
|
686
|
+
v[:parapetconcave ] = @set[id][:parapet ] if h[:parapet ]
|
687
|
+
v[:parapetconvex ] = @set[id][:parapet ] if h[:parapet ]
|
688
|
+
v[:parapetconcave ] = @set[id][:parapetconcave ] if h[:parapetconcave ]
|
689
|
+
v[:parapetconvex ] = @set[id][:parapetconvex ] if h[:parapetconvex ]
|
690
|
+
v[:roof ] = @set[id][:parapet ] if h[:parapet ]
|
691
|
+
v[:roofconcave ] = @set[id][:parapet ] if h[:parapet ]
|
692
|
+
v[:roofconvex ] = @set[id][:parapet ] if h[:parapet ]
|
693
|
+
v[:roofconcave ] = @set[id][:parapetconcave ] if h[:parapetconcave ]
|
694
|
+
v[:roofconvex ] = @set[id][:parapetxonvex ] if h[:parapetconvex ]
|
695
|
+
v[:roof ] = @set[id][:roof ] if h[:roof ]
|
696
|
+
v[:roofconcave ] = @set[id][:roof ] if h[:roof ]
|
697
|
+
v[:roofconvex ] = @set[id][:roof ] if h[:roof ]
|
698
|
+
v[:roofconcave ] = @set[id][:roofconcave ] if h[:roofconcave ]
|
699
|
+
v[:roofconvex ] = @set[id][:roofconvex ] if h[:roofconvex ]
|
700
|
+
v[:party ] = @set[id][:party ] if h[:party ]
|
701
|
+
v[:partyconcave ] = @set[id][:party ] if h[:party ]
|
702
|
+
v[:partyconvex ] = @set[id][:party ] if h[:party ]
|
703
|
+
v[:partyconcave ] = @set[id][:partyconcave ] if h[:partyconcave ]
|
704
|
+
v[:partyconvex ] = @set[id][:partyconvex ] if h[:partyconvex ]
|
705
|
+
v[:grade ] = @set[id][:grade ] if h[:grade ]
|
706
|
+
v[:gradeconcave ] = @set[id][:grade ] if h[:grade ]
|
707
|
+
v[:gradeconvex ] = @set[id][:grade ] if h[:grade ]
|
708
|
+
v[:gradeconcave ] = @set[id][:gradeconcave ] if h[:gradeconcave ]
|
709
|
+
v[:gradeconvex ] = @set[id][:gradeconvex ] if h[:gradeconvex ]
|
710
|
+
v[:balcony ] = @set[id][:balcony ] if h[:balcony ]
|
711
|
+
v[:balconyconcave ] = @set[id][:balcony ] if h[:balcony ]
|
712
|
+
v[:balconyconvex ] = @set[id][:balcony ] if h[:balcony ]
|
713
|
+
v[:balconyconcave ] = @set[id][:balconyconcave ] if h[:balconyconcave ]
|
714
|
+
v[:balconyconvex ] = @set[id][:balconyconvex ] if h[:balconyconvex ]
|
715
|
+
v[:balconysill ] = @set[id][:fenestration ] if h[:fenestration ]
|
716
|
+
v[:balconysillconcave ] = @set[id][:fenestration ] if h[:fenestration ]
|
717
|
+
v[:balconysillconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
718
|
+
v[:balconydoorsill ] = @set[id][:fenestration ] if h[:fenestration ]
|
719
|
+
v[:balconydoorsillconcave] = @set[id][:fenestration ] if h[:fenestration ]
|
720
|
+
v[:balconydoorsillconvex ] = @set[id][:fenestration ] if h[:fenestration ]
|
721
|
+
v[:balconysill ] = @set[id][:sill ] if h[:sill ]
|
722
|
+
v[:balconysillconcave ] = @set[id][:sill ] if h[:sill ]
|
723
|
+
v[:balconysillconvex ] = @set[id][:sill ] if h[:sill ]
|
724
|
+
v[:balconysillconcave ] = @set[id][:sillconcave ] if h[:sillconcave ]
|
725
|
+
v[:balconysillconvex ] = @set[id][:sillconvex ] if h[:sillconvex ]
|
726
|
+
v[:balconydoorsill ] = @set[id][:sill ] if h[:sill ]
|
727
|
+
v[:balconydoorsillconcave] = @set[id][:sill ] if h[:sill ]
|
728
|
+
v[:balconydoorsillconvex ] = @set[id][:sill ] if h[:sill ]
|
729
|
+
v[:balconydoorsillconcave] = @set[id][:sillconcave ] if h[:sillconcave ]
|
730
|
+
v[:balconydoorsillconvex ] = @set[id][:sillconvex ] if h[:sillconvex ]
|
731
|
+
v[:balconysill ] = @set[id][:balcony ] if h[:balcony ]
|
732
|
+
v[:balconysillconcave ] = @set[id][:balcony ] if h[:balcony ]
|
733
|
+
v[:balconysillconvex ] = @set[id][:balcony ] if h[:balcony ]
|
734
|
+
v[:balconysillconcave ] = @set[id][:balconyconcave ] if h[:balconyconcave ]
|
735
|
+
v[:balconysillconvex ] = @set[id][:balconyconvex ] if h[:balconycinvex ]
|
736
|
+
v[:balconydoorsill ] = @set[id][:balcony ] if h[:balcony ]
|
737
|
+
v[:balconydoorsillconcave] = @set[id][:balcony ] if h[:balcony ]
|
738
|
+
v[:balconydoorsillconvex ] = @set[id][:balcony ] if h[:balcony ]
|
739
|
+
v[:balconydoorsillconcave] = @set[id][:balconyconcave ] if h[:balconyconcave ]
|
740
|
+
v[:balconydoorsillconvex ] = @set[id][:balconyconvex ] if h[:balconycinvex ]
|
741
|
+
v[:balconysill ] = @set[id][:balconysill ] if h[:balconysill ]
|
742
|
+
v[:balconysillconcave ] = @set[id][:balconysill ] if h[:balconysill ]
|
743
|
+
v[:balconysillconvex ] = @set[id][:balconysill ] if h[:balconysill ]
|
744
|
+
v[:balconysillconcave ] = @set[id][:balconysillconcave ] if h[:balconysillconcave ]
|
745
|
+
v[:balconysillconvex ] = @set[id][:balconysillconvex ] if h[:balconysillconvex ]
|
746
|
+
v[:balconydoorsill ] = @set[id][:balconysill ] if h[:balconysill ]
|
747
|
+
v[:balconydoorsillconcave] = @set[id][:balconysill ] if h[:balconysill ]
|
748
|
+
v[:balconydoorsillconvex ] = @set[id][:balconysill ] if h[:balconysill ]
|
749
|
+
v[:balconydoorsillconcave] = @set[id][:balconysillconcave ] if h[:balconysillconcave ]
|
750
|
+
v[:balconydoorsillconvex ] = @set[id][:balconysillconvex ] if h[:balconysillconvex ]
|
751
|
+
v[:balconydoorsill ] = @set[id][:balconydoorsill ] if h[:balconydoorsill ]
|
752
|
+
v[:balconydoorsillconcave] = @set[id][:balconydoorsill ] if h[:balconydoorsill ]
|
753
|
+
v[:balconydoorsillconvex ] = @set[id][:balconydoorsill ] if h[:balconydoorsill ]
|
754
|
+
v[:balconydoorsillconcave] = @set[id][:balconydoorsillconcave] if h[:balconydoorsillconcave]
|
755
|
+
v[:balconydoorsillconvex ] = @set[id][:balconydoorsillconvex ] if h[:balconydoorsillconvex ]
|
756
|
+
v[:rimjoist ] = @set[id][:rimjoist ] if h[:rimjoist ]
|
757
|
+
v[:rimjoistconcave ] = @set[id][:rimjoist ] if h[:rimjoist ]
|
758
|
+
v[:rimjoistconvex ] = @set[id][:rimjoist ] if h[:rimjoist ]
|
759
|
+
v[:rimjoistconcave ] = @set[id][:rimjoistconcave ] if h[:rimjoistconcave ]
|
760
|
+
v[:rimjoistconvex ] = @set[id][:rimjoistconvex ] if h[:rimjoistconvex ]
|
362
761
|
|
363
762
|
max = [v[:parapetconcave], v[:parapetconvex]].max
|
364
763
|
v[:parapet] = max unless @has[:parapet]
|
764
|
+
|
765
|
+
max = [v[:roofconcave], v[:roofconvex]].max
|
766
|
+
v[:roof] = max unless @has[:roof]
|
365
767
|
@val[id] = v
|
366
768
|
|
367
769
|
true
|
368
770
|
end
|
369
771
|
|
370
772
|
##
|
371
|
-
#
|
372
|
-
# requires a valid, unique :id.
|
773
|
+
# Appends a new PSI set.
|
373
774
|
#
|
374
|
-
# @param
|
775
|
+
# @param [Hash] set a new PSI set
|
776
|
+
# @option set [#to_s] :id PSI set identifier
|
777
|
+
# @option set [#to_f] :rimjoist intermediate floor-to-wall intersection
|
778
|
+
# @option set [#to_f] :rimjoistconcave basilaire variant
|
779
|
+
# @option set [#to_f] :rimjoistconvex cantilever variant
|
780
|
+
# @option set [#to_f] :parapet roof-to-wall intersection
|
781
|
+
# @option set [#to_f] :parapetconcave basilaire variant
|
782
|
+
# @option set [#to_f] :parapetconvex typical
|
783
|
+
# @option set [#to_f] :roof roof-to-wall intersection
|
784
|
+
# @option set [#to_f] :roofconcave basilaire variant
|
785
|
+
# @option set [#to_f] :roofconvex typical
|
786
|
+
# @option set [#to_f] :fenestration head/sill/jamb interface
|
787
|
+
# @option set [#to_f] :head (fenestrated) header interface
|
788
|
+
# @option set [#to_f] :headconcave (fenestrated) basilaire variant
|
789
|
+
# @option set [#to_f] :headconvex (fenestrated) parapet variant
|
790
|
+
# @option set [#to_f] :sill (fenestrated) threshold/sill interface
|
791
|
+
# @option set [#to_f] :sillconcave (fenestrated) basilaire variant
|
792
|
+
# @option set [#to_f] :sillconvex (fenestrated) cantilever variant
|
793
|
+
# @option set [#to_f] :jamb (fenestrated) side jamb interface
|
794
|
+
# @option set [#to_f] :jambconcave (fenestrated) interior corner variant
|
795
|
+
# @option set [#to_f] :jambconvex (fenestrated) exterior corner variant
|
796
|
+
# @option set [#to_f] :door (opaque) head/sill/jamb interface
|
797
|
+
# @option set [#to_f] :doorhead (opaque) header interface
|
798
|
+
# @option set [#to_f] :doorheadconcave (opaque) basilaire variant
|
799
|
+
# @option set [#to_f] :doorheadconvex (opaque) parapet variant
|
800
|
+
# @option set [#to_f] :doorsill (opaque) threshold interface
|
801
|
+
# @option set [#to_f] :doorsillconcave (opaque) basilaire variant
|
802
|
+
# @option set [#to_f] :doorsillconvex (opaque) cantilever variant
|
803
|
+
# @option set [#to_f] :doorjamb (opaque) side jamb interface
|
804
|
+
# @option set [#to_f] :doorjambconcave (opaque) interior corner variant
|
805
|
+
# @option set [#to_f] :doorjambconvex (opaque) exterior corner variant
|
806
|
+
# @option set [#to_f] :skylight to roof interface
|
807
|
+
# @option set [#to_f] :skylighthead header interface
|
808
|
+
# @option set [#to_f] :skylightheadconcave basilaire variant
|
809
|
+
# @option set [#to_f] :skylightheadconvex parapet variant
|
810
|
+
# @option set [#to_f] :skylightsill sill interface
|
811
|
+
# @option set [#to_f] :skylightsillconcave basilaire variant
|
812
|
+
# @option set [#to_f] :skylightsillconvex cantilever variant
|
813
|
+
# @option set [#to_f] :skylightjamb side jamb interface
|
814
|
+
# @option set [#to_f] :skylightjambconcave (opaque) interior corner variant
|
815
|
+
# @option set [#to_f] :skylightjambconvex (opaque) parapet variant
|
816
|
+
# @option set [#to_f] :spandrel spandrel/other interface
|
817
|
+
# @option set [#to_f] :spandrelconcave interior corner variant
|
818
|
+
# @option set [#to_f] :spandrelconvex exterior corner variant
|
819
|
+
# @option set [#to_f] :corner corner intersection
|
820
|
+
# @option set [#to_f] :cornerconcave interior corner variant
|
821
|
+
# @option set [#to_f] :cornerconvex exterior corner variant
|
822
|
+
# @option set [#to_f] :balcony intermediate floor-balcony intersection
|
823
|
+
# @option set [#to_f] :balconyconcave basilaire variant
|
824
|
+
# @option set [#to_f] :balconyconvex cantilever variant
|
825
|
+
# @option set [#to_f] :balconysill intermediate floor-balcony-fenestration intersection
|
826
|
+
# @option set [#to_f] :balconysilloncave basilaire variant
|
827
|
+
# @option set [#to_f] :balconysillconvex cantilever variant
|
828
|
+
# @option set [#to_f] :balconydoorsill intermediate floor-balcony-door intersection
|
829
|
+
# @option set [#to_f] :balconydoorsilloncave basilaire variant
|
830
|
+
# @option set [#to_f] :balconydoorsillconvex cantilever variant
|
831
|
+
# @option set [#to_f] :party demising surface intersection
|
832
|
+
# @option set [#to_f] :partyconcave interior corner or basilaire variant
|
833
|
+
# @option set [#to_f] :partyconvex exterior corner or cantilever variant
|
834
|
+
# @option set [#to_f] :grade foundation wall or slab-on-grade intersection
|
835
|
+
# @option set [#to_f] :gradeconcave cantilever variant
|
836
|
+
# @option set [#to_f] :gradeconvex basilaire variant
|
837
|
+
# @option set [#to_f] :joint strong ~coplanar joint
|
838
|
+
# @option set [#to_f] :transition mild ~coplanar transition
|
375
839
|
#
|
376
|
-
# @return [Bool]
|
377
|
-
# @return [
|
840
|
+
# @return [Bool] whether PSI set is successfully appended
|
841
|
+
# @return [false] if invalid input (see logs)
|
378
842
|
def append(set = {})
|
379
843
|
mth = "TBD::#{__callee__}"
|
380
844
|
a = false
|
845
|
+
s = {}
|
846
|
+
return mismatch("set" , set, Hash, mth, DBG, a) unless set.is_a?(Hash)
|
847
|
+
return hashkey("set id", set, :id , mth, DBG, a) unless set.key?(:id)
|
381
848
|
|
382
|
-
|
383
|
-
return
|
849
|
+
id = trim(set[:id])
|
850
|
+
return mismatch("set ID", set[:id], String, mth, ERR, a) if id.empty?
|
384
851
|
|
385
|
-
|
386
|
-
|
387
|
-
|
852
|
+
if @set.key?(id)
|
853
|
+
log(ERR, "'#{id}': existing PSI set (#{mth})")
|
854
|
+
return a
|
855
|
+
end
|
388
856
|
|
389
|
-
s = {}
|
390
857
|
# Most PSI types have concave and convex variants, depending on the polar
|
391
858
|
# position of deratable surfaces about an edge-as-thermal-bridge. One
|
392
859
|
# exception is :fenestration, which TBD later breaks down into :head,
|
@@ -394,64 +861,97 @@ module TBD
|
|
394
861
|
# type that is not autoassigned to an edge (i.e., only via a TBD JSON
|
395
862
|
# input file). Finally, transitions are autoassigned by TBD when an edge
|
396
863
|
# is "flat", i.e, no noticeable polar angle difference between surfaces.
|
397
|
-
s[:rimjoist
|
398
|
-
s[:rimjoistconcave] = set[:rimjoistconcave] if set.key?(:rimjoistconcave)
|
399
|
-
s[:rimjoistconvex
|
400
|
-
s[:parapet
|
401
|
-
s[:parapetconcave
|
402
|
-
s[:parapetconvex
|
403
|
-
s[:
|
404
|
-
s[:
|
405
|
-
s[:
|
406
|
-
s[:
|
407
|
-
s[:
|
408
|
-
s[:
|
409
|
-
s[:
|
410
|
-
s[:
|
411
|
-
s[:
|
412
|
-
s[:
|
413
|
-
s[:
|
414
|
-
s[:
|
415
|
-
s[:
|
416
|
-
s[:
|
417
|
-
s[:
|
418
|
-
s[:
|
419
|
-
s[:
|
420
|
-
s[:
|
421
|
-
s[:
|
422
|
-
s[:
|
423
|
-
s[:
|
424
|
-
s[:
|
425
|
-
s[:
|
426
|
-
s[:
|
427
|
-
|
428
|
-
s[:
|
429
|
-
s[:
|
430
|
-
|
431
|
-
|
432
|
-
|
864
|
+
s[:rimjoist ] = set[:rimjoist ] if set.key?(:rimjoist)
|
865
|
+
s[:rimjoistconcave ] = set[:rimjoistconcave ] if set.key?(:rimjoistconcave)
|
866
|
+
s[:rimjoistconvex ] = set[:rimjoistconvex ] if set.key?(:rimjoistconvex)
|
867
|
+
s[:parapet ] = set[:parapet ] if set.key?(:parapet)
|
868
|
+
s[:parapetconcave ] = set[:parapetconcave ] if set.key?(:parapetconcave)
|
869
|
+
s[:parapetconvex ] = set[:parapetconvex ] if set.key?(:parapetconvex)
|
870
|
+
s[:roof ] = set[:roof ] if set.key?(:roof)
|
871
|
+
s[:roofconcave ] = set[:roofconcave ] if set.key?(:roofconcave)
|
872
|
+
s[:roofconvex ] = set[:roofconvex ] if set.key?(:roofconvex)
|
873
|
+
s[:fenestration ] = set[:fenestration ] if set.key?(:fenestration)
|
874
|
+
s[:head ] = set[:head ] if set.key?(:head)
|
875
|
+
s[:headconcave ] = set[:headconcave ] if set.key?(:headconcave)
|
876
|
+
s[:headconvex ] = set[:headconvex ] if set.key?(:headconvex)
|
877
|
+
s[:sill ] = set[:sill ] if set.key?(:sill)
|
878
|
+
s[:sillconcave ] = set[:sillconcave ] if set.key?(:sillconcave)
|
879
|
+
s[:sillconvex ] = set[:sillconvex ] if set.key?(:sillconvex)
|
880
|
+
s[:jamb ] = set[:jamb ] if set.key?(:jamb)
|
881
|
+
s[:jambconcave ] = set[:jambconcave ] if set.key?(:jambconcave)
|
882
|
+
s[:jambconvex ] = set[:jambconvex ] if set.key?(:jambconvex)
|
883
|
+
s[:door ] = set[:door ] if set.key?(:door)
|
884
|
+
s[:doorhead ] = set[:doorhead ] if set.key?(:doorhead)
|
885
|
+
s[:doorheadconcave ] = set[:doorheadconcave ] if set.key?(:doorheadconcave)
|
886
|
+
s[:doorheadconvex ] = set[:doorheadconvex ] if set.key?(:doorheadconvex)
|
887
|
+
s[:doorsill ] = set[:doorsill ] if set.key?(:doorsill)
|
888
|
+
s[:doorsillconcave ] = set[:doorsillconcave ] if set.key?(:doorsillconcave)
|
889
|
+
s[:doorsillconvex ] = set[:doorsillconvex ] if set.key?(:doorsillconvex)
|
890
|
+
s[:doorjamb ] = set[:doorjamb ] if set.key?(:doorjamb)
|
891
|
+
s[:doorjambconcave ] = set[:doorjambconcave ] if set.key?(:doorjambconcave)
|
892
|
+
s[:doorjambconvex ] = set[:doorjambconvex ] if set.key?(:doorjambconvex)
|
893
|
+
s[:skylight ] = set[:skylight ] if set.key?(:skylight)
|
894
|
+
s[:skylighthead ] = set[:skylighthead ] if set.key?(:skylighthead)
|
895
|
+
s[:skylightheadconcave ] = set[:skylightheadconcave ] if set.key?(:skylightheadconcave)
|
896
|
+
s[:skylightheadconvex ] = set[:skylightheadconvex ] if set.key?(:skylightheadconvex)
|
897
|
+
s[:skylightsill ] = set[:skylightsill ] if set.key?(:skylightsill)
|
898
|
+
s[:skylightsillconcave ] = set[:skylightsillconcave ] if set.key?(:skylightsillconcave)
|
899
|
+
s[:skylightsillconvex ] = set[:skylightsillconvex ] if set.key?(:skylightsillconvex)
|
900
|
+
s[:skylightjamb ] = set[:skylightjamb ] if set.key?(:skylightjamb)
|
901
|
+
s[:skylightjambconcave ] = set[:skylightjambconcave ] if set.key?(:skylightjambconcave)
|
902
|
+
s[:skylightjambconvex ] = set[:skylightjambconvex ] if set.key?(:skylightjambconvex)
|
903
|
+
s[:spandrel ] = set[:spandrel ] if set.key?(:spandrel)
|
904
|
+
s[:spandrelconcave ] = set[:spandrelconcave ] if set.key?(:spandrelconcave)
|
905
|
+
s[:spandrelconvex ] = set[:spandrelconvex ] if set.key?(:spandrelconvex)
|
906
|
+
s[:corner ] = set[:corner ] if set.key?(:corner)
|
907
|
+
s[:cornerconcave ] = set[:cornerconcave ] if set.key?(:cornerconcave)
|
908
|
+
s[:cornerconvex ] = set[:cornerconvex ] if set.key?(:cornerconvex)
|
909
|
+
s[:balcony ] = set[:balcony ] if set.key?(:balcony)
|
910
|
+
s[:balconyconcave ] = set[:balconyconcave ] if set.key?(:balconyconcave)
|
911
|
+
s[:balconyconvex ] = set[:balconyconvex ] if set.key?(:balconyconvex)
|
912
|
+
s[:balconysill ] = set[:balconysill ] if set.key?(:balconysill)
|
913
|
+
s[:balconysillconcave ] = set[:balconysillconcave ] if set.key?(:balconysillconcave)
|
914
|
+
s[:balconysillconvex ] = set[:balconysillconvex ] if set.key?(:balconysillconvex)
|
915
|
+
s[:balconydoorsill ] = set[:balconydoorsill ] if set.key?(:balconydoorsill)
|
916
|
+
s[:balconydoorsillconcave] = set[:balconydoorsillconcave] if set.key?(:balconydoorsillconcave)
|
917
|
+
s[:balconydoorsillconvex ] = set[:balconydoorsillconvex ] if set.key?(:balconydoorsillconvex)
|
918
|
+
s[:party ] = set[:party ] if set.key?(:party)
|
919
|
+
s[:partyconcave ] = set[:partyconcave ] if set.key?(:partyconcave)
|
920
|
+
s[:partyconvex ] = set[:partyconvex ] if set.key?(:partyconvex)
|
921
|
+
s[:grade ] = set[:grade ] if set.key?(:grade)
|
922
|
+
s[:gradeconcave ] = set[:gradeconcave ] if set.key?(:gradeconcave)
|
923
|
+
s[:gradeconvex ] = set[:gradeconvex ] if set.key?(:gradeconvex)
|
924
|
+
s[:joint ] = set[:joint ] if set.key?(:joint)
|
925
|
+
s[:transition ] = set[:transition ] if set.key?(:transition)
|
926
|
+
|
927
|
+
s[:joint ] = 0.000 unless set.key?(:joint)
|
928
|
+
s[:transition ] = 0.000 unless set.key?(:transition)
|
929
|
+
|
930
|
+
@set[id] = s
|
931
|
+
self.gen(id)
|
433
932
|
|
434
933
|
true
|
435
934
|
end
|
436
935
|
|
437
936
|
##
|
438
|
-
#
|
439
|
-
# of true/false (values) for any admissible PSI type (keys), and val: a
|
440
|
-
# of PSI-values for any admissible PSI type (
|
937
|
+
# Returns PSI set shorthands. The return Hash holds 2 keys, has: a Hash
|
938
|
+
# of true/false (values) for any admissible PSI type (keys), and val: a
|
939
|
+
# Hash of PSI-factors (values) for any admissible PSI type (keys).
|
940
|
+
# PSI-factors default to 0 W/K per linear meter if missing from set.
|
441
941
|
#
|
442
|
-
# @param id [
|
942
|
+
# @param id [#to_s] PSI set identifier
|
943
|
+
# @example intermediate floor slab intersection
|
944
|
+
# shorthands("A901")
|
443
945
|
#
|
444
|
-
# @return [Hash] has: Hash
|
445
|
-
# @return [Hash] has: empty Hash, val: empty Hash (if invalid/missing set)
|
946
|
+
# @return [Hash] has: Hash (Bool), val: Hash (PSI factors) see logs if empty
|
446
947
|
def shorthands(id = "")
|
447
948
|
mth = "TBD::#{__callee__}"
|
448
|
-
cl = String
|
449
949
|
sh = { has: {}, val: {} }
|
450
|
-
|
451
|
-
return
|
452
|
-
return
|
453
|
-
return
|
454
|
-
return
|
950
|
+
id = trim(id)
|
951
|
+
return mismatch("set ID", id, String, mth, ERR, a) if id.empty?
|
952
|
+
return hashkey(id, @set , id, mth, ERR, sh) unless @set.key?(id)
|
953
|
+
return hashkey(id, @has , id, mth, ERR, sh) unless @has.key?(id)
|
954
|
+
return hashkey(id, @val , id, mth, ERR, sh) unless @val.key?(id)
|
455
955
|
|
456
956
|
sh[:has] = @has[id]
|
457
957
|
sh[:val] = @val[id]
|
@@ -460,81 +960,98 @@ module TBD
|
|
460
960
|
end
|
461
961
|
|
462
962
|
##
|
463
|
-
#
|
963
|
+
# Validates whether a given PSI set has a complete list of PSI type:values.
|
464
964
|
#
|
465
|
-
# @param id [
|
965
|
+
# @param id [#to_s] PSI set identifier
|
466
966
|
#
|
467
|
-
# @return [Bool]
|
468
|
-
# @return [
|
967
|
+
# @return [Bool] whether provided PSI set is held in memory and is complete
|
968
|
+
# @return [false] if invalid input (see logs)
|
469
969
|
def complete?(id = "")
|
470
970
|
mth = "TBD::#{__callee__}"
|
471
971
|
a = false
|
472
|
-
|
473
|
-
return
|
474
|
-
return
|
475
|
-
return
|
476
|
-
return
|
972
|
+
id = trim(id)
|
973
|
+
return mismatch("set ID", id, String, mth, ERR, a) if id.empty?
|
974
|
+
return hashkey(id, @set , id, mth, ERR, a) unless @set.key?(id)
|
975
|
+
return hashkey(id, @has , id, mth, ERR, a) unless @has.key?(id)
|
976
|
+
return hashkey(id, @val , id, mth, ERR, a) unless @val.key?(id)
|
477
977
|
|
478
978
|
holes = []
|
479
|
-
holes << :head
|
480
|
-
holes << :sill
|
481
|
-
holes << :jamb
|
979
|
+
holes << :head if @has[id][:head ]
|
980
|
+
holes << :sill if @has[id][:sill ]
|
981
|
+
holes << :jamb if @has[id][:jamb ]
|
482
982
|
ok = holes.size == 3
|
483
|
-
ok = true
|
484
|
-
return false
|
983
|
+
ok = true if @has[id][:fenestration ]
|
984
|
+
return false unless ok
|
485
985
|
|
486
986
|
corners = []
|
487
|
-
corners << :concave
|
488
|
-
corners << :convex
|
987
|
+
corners << :concave if @has[id][:cornerconcave ]
|
988
|
+
corners << :convex if @has[id][:cornerconvex ]
|
489
989
|
ok = corners.size == 2
|
490
|
-
ok = true
|
491
|
-
return false
|
990
|
+
ok = true if @has[id][:corner ]
|
991
|
+
return false unless ok
|
492
992
|
|
493
993
|
parapets = []
|
494
|
-
|
495
|
-
parapets << :
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
return false
|
994
|
+
roofs = []
|
995
|
+
parapets << :concave if @has[id][:parapetconcave]
|
996
|
+
parapets << :convex if @has[id][:parapetconvex ]
|
997
|
+
roofs << :concave if @has[id][:roofconcave ]
|
998
|
+
parapets << :convex if @has[id][:roofconvex ]
|
999
|
+
ok = parapets.size == 2 || roofs.size == 2
|
1000
|
+
ok = true if @has[id][:parapet ]
|
1001
|
+
ok = true if @has[id][:roof ]
|
1002
|
+
return false unless ok
|
1003
|
+
return false unless @has[id][:party ]
|
1004
|
+
return false unless @has[id][:grade ]
|
1005
|
+
return false unless @has[id][:balcony ]
|
1006
|
+
return false unless @has[id][:rimjoist ]
|
503
1007
|
|
504
1008
|
ok
|
505
1009
|
end
|
506
1010
|
|
507
1011
|
##
|
508
|
-
#
|
1012
|
+
# Returns safe PSI type if missing from PSI set (based on inheritance).
|
509
1013
|
#
|
510
|
-
# @param id [
|
511
|
-
# @param type [
|
1014
|
+
# @param id [#to_s] PSI set identifier
|
1015
|
+
# @param type [#to_sym] PSI type
|
1016
|
+
# @example intermediate floor slab intersection
|
1017
|
+
# safe("90.1.22|wood.fr|unmitigated", :rimjoistconcave)
|
512
1018
|
#
|
513
1019
|
# @return [Symbol] safe PSI type
|
514
|
-
# @return [
|
1020
|
+
# @return [nil] if invalid inputs (see logs)
|
515
1021
|
def safe(id = "", type = nil)
|
516
1022
|
mth = "TBD::#{__callee__}"
|
517
|
-
|
518
|
-
|
1023
|
+
id = trim(id)
|
1024
|
+
ck1 = id.empty?
|
1025
|
+
ck2 = type.respond_to?(:to_sym)
|
1026
|
+
return mismatch("set ID", id, String, mth) if ck1
|
1027
|
+
return mismatch("type", type, Symbol, mth) unless ck2
|
1028
|
+
return hashkey(id, @set, id, mth, ERR) unless @set.key?(id)
|
1029
|
+
return hashkey(id, @has, id, mth, ERR) unless @has.key?(id)
|
519
1030
|
|
520
|
-
|
521
|
-
return TBD.mismatch("type", type, cl2, mth, ERR) unless type.is_a?(cl2)
|
522
|
-
return TBD.hashkey(id, @set, id, mth, ERR) unless @set.key?(id)
|
523
|
-
return TBD.hashkey(id, @has, id, mth, ERR) unless @has.key?(id)
|
1031
|
+
safer = type.to_sym
|
524
1032
|
|
525
|
-
safer
|
1033
|
+
unless @has[id][safer]
|
1034
|
+
concave = safer.to_s.include?("concave")
|
1035
|
+
convex = safer.to_s.include?("convex")
|
1036
|
+
safer = safer.to_s.chomp("concave").to_sym if concave
|
1037
|
+
safer = safer.to_s.chomp("convex").to_sym if convex
|
1038
|
+
end
|
526
1039
|
|
527
1040
|
unless @has[id][safer]
|
528
|
-
|
529
|
-
|
530
|
-
safer =
|
531
|
-
safer =
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
1041
|
+
safer = :fenestration if safer == :head
|
1042
|
+
safer = :fenestration if safer == :sill
|
1043
|
+
safer = :fenestration if safer == :jamb
|
1044
|
+
safer = :door if safer == :doorhead
|
1045
|
+
safer = :door if safer == :doorsill
|
1046
|
+
safer = :door if safer == :doorjamb
|
1047
|
+
safer = :skylight if safer == :skylighthead
|
1048
|
+
safer = :skylight if safer == :skylightsill
|
1049
|
+
safer = :skylight if safer == :skylightjamb
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
unless @has[id][safer]
|
1053
|
+
safer = :fenestration if safer == :skylight
|
1054
|
+
safer = :fenestration if safer == :door
|
538
1055
|
end
|
539
1056
|
|
540
1057
|
return safer if @has[id][safer]
|
@@ -544,30 +1061,41 @@ module TBD
|
|
544
1061
|
end
|
545
1062
|
|
546
1063
|
##
|
547
|
-
#
|
548
|
-
# and retrieved corresponding Topolys model surface/edge
|
549
|
-
# inputs allow customization of default assumptions and
|
550
|
-
# If successful, "edges" (input) may inherit additional
|
551
|
-
# edge-specific PSI set (defined in TBD JSON file),
|
552
|
-
# (e.g. "corner", defined in TBD JSON file),
|
553
|
-
# from TBD JSON file).
|
1064
|
+
# Processes TBD JSON inputs, after TBD has preprocessed OpenStudio model
|
1065
|
+
# variables and retrieved corresponding Topolys model surface/edge
|
1066
|
+
# properties. TBD user inputs allow customization of default assumptions and
|
1067
|
+
# inferred values. If successful, "edges" (input) may inherit additional
|
1068
|
+
# properties, e.g.: edge-specific PSI set (defined in TBD JSON file),
|
1069
|
+
# edge-specific PSI type (e.g. "corner", defined in TBD JSON file),
|
1070
|
+
# project-wide PSI set (if absent from TBD JSON file).
|
554
1071
|
#
|
555
|
-
# @param
|
556
|
-
# @
|
557
|
-
# @
|
1072
|
+
# @param [Hash] s TBD surfaces (keys: Openstudio surface names)
|
1073
|
+
# @option s [Hash] :windows TBD surface-specific windows e.g. s[][:windows]
|
1074
|
+
# @option s [Hash] :doors TBD surface-specific doors
|
1075
|
+
# @option s [Hash] :skylights TBD surface-specific skylights
|
1076
|
+
# @option s [OpenStudio::Model::BuildingStory] :story OpenStudio story
|
1077
|
+
# @option s ["Wall", "RoofCeiling", "Floor"] :stype OpenStudio surface type
|
1078
|
+
# @option s [OpenStudio::Model::Space] :space OpenStudio space
|
1079
|
+
# @param [Hash] e TBD edges (keys: Topolys edge identifiers)
|
1080
|
+
# @option e [Hash] :surfaces linked TBD surfaces e.g. e[][:surfaces]
|
1081
|
+
# @option e [#to_f] :length edge length in m
|
1082
|
+
# @option e [Topolys::Point3D] :v0 origin vertex
|
1083
|
+
# @option e [Topolys::Point3D] :v1 terminal vertex
|
1084
|
+
# @param [Hash] argh TBD arguments
|
1085
|
+
# @option argh [#to_s] :option selected PSI set
|
1086
|
+
# @option argh [#to_s] :io_path tbd.json input file path
|
1087
|
+
# @option argh [#to_s] :schema_path TBD JSON schema file path
|
558
1088
|
#
|
559
|
-
# @return [Hash] io:
|
560
|
-
# @return [Hash] io: empty Hash if invalid input
|
1089
|
+
# @return [Hash] io: (Hash), psi:/khi: enriched sets (see logs if empty)
|
561
1090
|
def inputs(s = {}, e = {}, argh = {})
|
562
1091
|
mth = "TBD::#{__callee__}"
|
563
1092
|
opt = :option
|
564
1093
|
ipt = { io: {}, psi: PSI.new, khi: KHI.new }
|
565
1094
|
io = {}
|
566
|
-
|
567
|
-
return mismatch("
|
568
|
-
return mismatch("
|
569
|
-
return
|
570
|
-
return hashkey("argh", argh, opt, mth, DBG, ipt) unless argh.key?(opt)
|
1095
|
+
return mismatch("s" , s , Hash, mth, DBG, ipt) unless s.is_a?(Hash)
|
1096
|
+
return mismatch("e" , e , Hash, mth, DBG, ipt) unless e.is_a?(Hash)
|
1097
|
+
return mismatch("argh", argh, Hash, mth, DBG, ipt) unless argh.is_a?(Hash)
|
1098
|
+
return hashkey("argh" , argh, opt , mth, DBG, ipt) unless argh.key?(opt)
|
571
1099
|
|
572
1100
|
argh[:io_path ] = nil unless argh.key?(:io_path)
|
573
1101
|
argh[:schema_path] = nil unless argh.key?(:schema_path)
|
@@ -580,41 +1108,43 @@ module TBD
|
|
580
1108
|
io = pth
|
581
1109
|
else
|
582
1110
|
return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
|
1111
|
+
|
583
1112
|
io = File.read(pth)
|
584
1113
|
io = JSON.parse(io, symbolize_names: true)
|
585
1114
|
return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
|
586
1115
|
end
|
587
1116
|
|
588
1117
|
# Schema validation is not yet supported in the OpenStudio Application.
|
589
|
-
#
|
590
|
-
# online linter, prior to using TBD. The following checks focus on
|
591
|
-
# - ignoring bad JSON input otherwise caught via JSON validation.
|
1118
|
+
# It is nonetheless recommended that users rely on the json-schema gem,
|
1119
|
+
# or an online linter, prior to using TBD. The following checks focus on
|
1120
|
+
# content - ignoring bad JSON input otherwise caught via JSON validation.
|
592
1121
|
#
|
593
1122
|
# A side note: JSON validation relies on case-senitive string comparisons
|
594
1123
|
# (e.g. OpenStudio space or surface names, vs corresponding TBD JSON
|
595
|
-
# identifiers). So "Space-1" doesn't match "SPACE-1"
|
1124
|
+
# identifiers). So "Space-1" doesn't match "SPACE-1" ... head's up!
|
596
1125
|
if sch
|
597
1126
|
require "json-schema"
|
1127
|
+
return invalid("JSON schema", mth, 3, FTL, ipt) unless File.exist?(sch)
|
1128
|
+
return empty("JSON schema" , mth, FTL, ipt) if File.zero?(sch)
|
598
1129
|
|
599
|
-
return invalid("JSON schema", mth, 0, FTL, ipt) unless File.exist?(sch)
|
600
|
-
return empty("JSON schema", mth, FTL, ipt) if File.zero?(sch)
|
601
1130
|
schema = File.read(sch)
|
602
1131
|
schema = JSON.parse(schema, symbolize_names: true)
|
603
1132
|
valid = JSON::Validator.validate!(schema, io)
|
604
|
-
return invalid("JSON schema validation", mth,
|
1133
|
+
return invalid("JSON schema validation", mth, 3, FTL, ipt) unless valid
|
605
1134
|
end
|
606
1135
|
|
607
1136
|
# Append JSON entries to library of linear & point thermal bridges.
|
608
|
-
io[:psis].each { |psi| ipt[:psi].append(psi) }
|
609
|
-
io[:khis].each { |khi| ipt[:khi].append(khi) }
|
1137
|
+
io[:psis].each { |psi| ipt[:psi].append(psi) } if io.key?(:psis)
|
1138
|
+
io[:khis].each { |khi| ipt[:khi].append(khi) } if io.key?(:khis)
|
610
1139
|
|
611
1140
|
# JSON-defined or user-selected, building PSI set must be complete/valid.
|
612
1141
|
io[:building] = { psi: argh[opt] } unless io.key?(:building)
|
613
1142
|
bdg = io[:building]
|
614
|
-
ok
|
615
|
-
return hashkey("Building PSI", bdg, :psi, mth, FTL, ipt)
|
1143
|
+
ok = bdg.key?(:psi)
|
1144
|
+
return hashkey("Building PSI", bdg, :psi, mth, FTL, ipt) unless ok
|
1145
|
+
|
616
1146
|
ok = ipt[:psi].complete?(bdg[:psi])
|
617
|
-
return invalid("Complete building PSI", mth,
|
1147
|
+
return invalid("Complete building PSI", mth, 3, FTL, ipt) unless ok
|
618
1148
|
|
619
1149
|
# Validate remaining (optional) JSON entries.
|
620
1150
|
[:stories, :spacetypes, :spaces].each do |types|
|
@@ -625,14 +1155,16 @@ module TBD
|
|
625
1155
|
if io.key?(types)
|
626
1156
|
io[types].each do |type|
|
627
1157
|
next unless type.key?(:psi)
|
628
|
-
next unless type.key?(:id)
|
1158
|
+
next unless type.key?(:id )
|
1159
|
+
|
629
1160
|
s1 = "JSON/OSM '#{type[:id]}' (#{mth})"
|
630
1161
|
s2 = "JSON/PSI '#{type[:id]}' set (#{mth})"
|
631
1162
|
match = false
|
632
1163
|
|
633
|
-
s.values.each do |props|
|
634
|
-
break
|
1164
|
+
s.values.each do |props| # TBD surface linked to type?
|
1165
|
+
break if match
|
635
1166
|
next unless props.key?(key)
|
1167
|
+
|
636
1168
|
match = type[:id] == props[key].nameString
|
637
1169
|
end
|
638
1170
|
|
@@ -645,6 +1177,7 @@ module TBD
|
|
645
1177
|
if io.key?(:surfaces)
|
646
1178
|
io[:surfaces].each do |surface|
|
647
1179
|
next unless surface.key?(:id)
|
1180
|
+
|
648
1181
|
s1 = "JSON/OSM surface '#{surface[:id]}' (#{mth})"
|
649
1182
|
log(ERR, s1) unless s.key?(surface[:id])
|
650
1183
|
|
@@ -657,6 +1190,7 @@ module TBD
|
|
657
1190
|
if surface.key?(:khis)
|
658
1191
|
surface[:khis].each do |khi|
|
659
1192
|
next unless khi.key?(:id)
|
1193
|
+
|
660
1194
|
s3 = "JSON/KHI surface '#{surface[:id]}' '#{khi[:id]}' (#{mth})"
|
661
1195
|
log(ERR, s3) unless ipt[:khi].point.key?(khi[:id])
|
662
1196
|
end
|
@@ -668,6 +1202,7 @@ module TBD
|
|
668
1202
|
io[:subsurfaces].each do |sub|
|
669
1203
|
next unless sub.key?(:id)
|
670
1204
|
next unless sub.key?(:usi)
|
1205
|
+
|
671
1206
|
match = false
|
672
1207
|
|
673
1208
|
s.each do |id, surface|
|
@@ -677,6 +1212,7 @@ module TBD
|
|
677
1212
|
if surface.key?(holes)
|
678
1213
|
surface[holes].keys.each do |id|
|
679
1214
|
break if match
|
1215
|
+
|
680
1216
|
match = sub[:id] == id
|
681
1217
|
end
|
682
1218
|
end
|
@@ -691,30 +1227,33 @@ module TBD
|
|
691
1227
|
io[:edges].each do |edge|
|
692
1228
|
next unless edge.key?(:type)
|
693
1229
|
next unless edge.key?(:surfaces)
|
694
|
-
|
695
|
-
|
696
|
-
|
1230
|
+
|
1231
|
+
surfaces = edge[:surfaces]
|
1232
|
+
type = edge[:type].to_sym
|
1233
|
+
safer = ipt[:psi].safe(bdg[:psi], type) # fallback
|
697
1234
|
log(ERR, "Skipping invalid edge PSI '#{type}' (#{mth})") unless safer
|
698
1235
|
next unless safer
|
1236
|
+
|
699
1237
|
valid = true
|
700
1238
|
|
701
|
-
surfaces.each do |surface|
|
702
|
-
e.values.each do |ee|
|
703
|
-
break unless valid
|
704
|
-
next if ee.key?(:io_type)
|
1239
|
+
surfaces.each do |surface| # TBD edge's surfaces on file
|
1240
|
+
e.values.each do |ee| # TBD edges in memory
|
1241
|
+
break unless valid # if previous anomaly detected
|
1242
|
+
next if ee.key?(:io_type) # validated from previous loop
|
705
1243
|
next unless ee.key?(:surfaces)
|
1244
|
+
|
706
1245
|
surfs = ee[:surfaces]
|
707
1246
|
next unless surfs.key?(surface)
|
708
1247
|
|
709
1248
|
# An edge on file is valid if ALL of its listed surfaces together
|
710
|
-
# connect at least
|
711
|
-
# Each of the latter may connect e.g.
|
712
|
-
# but the list of surfaces on file may be shorter, e.g. only
|
1249
|
+
# connect at least 1 or more TBD/Topolys model edges in memory.
|
1250
|
+
# Each of the latter may connect e.g. 3 TBD/Topolys surfaces,
|
1251
|
+
# but the list of surfaces on file may be shorter, e.g. only 2.
|
713
1252
|
match = true
|
714
1253
|
surfaces.each { |id| match = false unless surfs.key?(id) }
|
715
1254
|
next unless match
|
716
1255
|
|
717
|
-
if edge.key?(:length)
|
1256
|
+
if edge.key?(:length) # optional
|
718
1257
|
next unless (ee[:length] - edge[:length]).abs < TOL
|
719
1258
|
end
|
720
1259
|
|
@@ -724,7 +1263,6 @@ module TBD
|
|
724
1263
|
|
725
1264
|
unless edge.key?(:v0x) && edge.key?(:v0y) && edge.key?(:v0z) &&
|
726
1265
|
edge.key?(:v1x) && edge.key?(:v1y) && edge.key?(:v1z)
|
727
|
-
|
728
1266
|
log(ERR, "Mismatch '#{surface}' edge vertices (#{mth})")
|
729
1267
|
valid = false
|
730
1268
|
next
|
@@ -743,17 +1281,17 @@ module TBD
|
|
743
1281
|
next unless matches?(e1, e2)
|
744
1282
|
end
|
745
1283
|
|
746
|
-
if edge.key?(:psi)
|
1284
|
+
if edge.key?(:psi) # optional
|
747
1285
|
set = edge[:psi]
|
748
1286
|
|
749
1287
|
if ipt[:psi].set.key?(set)
|
750
1288
|
saferr = ipt[:psi].safe(set, type)
|
751
|
-
ee[:io_set ] = set
|
752
|
-
ee[:io_type] = type
|
753
|
-
log(ERR, "Invalid
|
754
|
-
valid = false
|
1289
|
+
ee[:io_set ] = set if saferr
|
1290
|
+
ee[:io_type] = type if saferr
|
1291
|
+
log(ERR, "Invalid #{set}: #{type} (#{mth})") unless saferr
|
1292
|
+
valid = false unless saferr
|
755
1293
|
else
|
756
|
-
log(ERR, "Missing edge PSI
|
1294
|
+
log(ERR, "Missing edge PSI #{set} (#{mth})")
|
757
1295
|
valid = false
|
758
1296
|
end
|
759
1297
|
else
|
@@ -767,10 +1305,12 @@ module TBD
|
|
767
1305
|
# No (optional) user-defined TBD JSON input file. In such cases, provided
|
768
1306
|
# argh[:option] must refer to a valid PSI set. If valid, all edges inherit
|
769
1307
|
# a default PSI set (without KHI entries).
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
1308
|
+
msg = "Incomplete building PSI set '#{argh[opt]}' (#{mth})"
|
1309
|
+
ok = ipt[:psi].complete?(argh[opt])
|
1310
|
+
|
1311
|
+
io[:building] = { psi: argh[opt] } if ok
|
1312
|
+
log(FTL, msg) unless ok
|
1313
|
+
return ipt unless ok
|
774
1314
|
end
|
775
1315
|
|
776
1316
|
ipt[:io] = io
|
@@ -779,86 +1319,83 @@ module TBD
|
|
779
1319
|
end
|
780
1320
|
|
781
1321
|
##
|
782
|
-
# Thermally
|
1322
|
+
# Thermally derates insulating material within construction.
|
783
1323
|
#
|
784
|
-
# @param
|
785
|
-
# @param
|
786
|
-
# @
|
1324
|
+
# @param id [#to_s] surface identifier
|
1325
|
+
# @param [Hash] s TBD surface parameters
|
1326
|
+
# @option s [#to_f] :heatloss heat loss from major thermal bridging, in W/K
|
1327
|
+
# @option s [#to_f] :net surface net area, in m2
|
1328
|
+
# @option s [:massless, :standard] :ltype indexed layer type
|
1329
|
+
# @option s [#to_i] :index deratable construction layer index
|
1330
|
+
# @option s [#to_f] :r deratable layer Rsi-factor, in m2•K/W
|
787
1331
|
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
|
788
1332
|
#
|
789
1333
|
# @return [OpenStudio::Model::Material] derated (cloned) material
|
790
|
-
# @return [
|
791
|
-
def derate(
|
1334
|
+
# @return [nil] if invalid input (see logs)
|
1335
|
+
def derate(id = "", s = {}, lc = nil)
|
792
1336
|
mth = "TBD::#{__callee__}"
|
793
1337
|
m = nil
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
log(WRN, "Won't derate '#{id}': already derated (#{mth})") if derated
|
835
|
-
return m if derated
|
836
|
-
|
837
|
-
index = s[:index]
|
838
|
-
ltype = s[:ltype]
|
839
|
-
r = s[:r]
|
840
|
-
u = s[:heatloss] / s[:net]
|
1338
|
+
id = trim(id)
|
1339
|
+
kys = [:heatloss, :net, :ltype, :index, :r]
|
1340
|
+
ck1 = s.is_a?(Hash)
|
1341
|
+
ck2 = lc.is_a?(OpenStudio::Model::LayeredConstruction)
|
1342
|
+
return mismatch("id" , id, cl6, mth) if id.empty?
|
1343
|
+
return mismatch("#{id} surface" , s , cl1, mth) unless ck1
|
1344
|
+
return mismatch("#{id} construction", lc, cl2, mth) unless ck2
|
1345
|
+
|
1346
|
+
kys.each do |k|
|
1347
|
+
tag = "#{id} #{k}"
|
1348
|
+
return hashkey(tag, s, k, mth, ERR) unless s.key?(k)
|
1349
|
+
|
1350
|
+
case k
|
1351
|
+
when :heatloss
|
1352
|
+
return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_f)
|
1353
|
+
return zero(tag, mth, WRN) if s[k].to_f.abs < 0.001
|
1354
|
+
when :net, :r
|
1355
|
+
return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_f)
|
1356
|
+
return negative(tag, mth, 2, ERR) if s[k].to_f < 0
|
1357
|
+
return zero(tag, mth, WRN) if s[k].to_f.abs < 0.001
|
1358
|
+
when :index
|
1359
|
+
return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_i)
|
1360
|
+
return negative(tag, mth, 2, ERR) if s[k].to_f < 0
|
1361
|
+
else # :ltype
|
1362
|
+
next if [:massless, :standard].include?(s[k])
|
1363
|
+
return invalid(tag, mth, 2, ERR)
|
1364
|
+
end
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
if lc.nameString.downcase.include?(" tbd")
|
1368
|
+
log(WRN, "Won't derate '#{id}': tagged as derated (#{mth})")
|
1369
|
+
return m
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
model = lc.model
|
1373
|
+
ltype = s[:ltype ]
|
1374
|
+
index = s[:index ].to_i
|
1375
|
+
net = s[:net ].to_f
|
1376
|
+
r = s[:r ].to_f
|
1377
|
+
u = s[:heatloss].to_f / net
|
841
1378
|
loss = 0
|
842
|
-
de_u = 1 / r + u
|
843
|
-
de_r = 1 / de_u
|
1379
|
+
de_u = 1 / r + u # derated U
|
1380
|
+
de_r = 1 / de_u # derated R
|
844
1381
|
|
845
1382
|
if ltype == :massless
|
846
1383
|
m = lc.getLayer(index).to_MasslessOpaqueMaterial
|
847
|
-
return invalid("
|
1384
|
+
return invalid("#{id} massless layer?", mth, 0) if m.empty?
|
848
1385
|
m = m.get
|
849
1386
|
up = ""
|
850
|
-
up = "uprated "
|
1387
|
+
up = "uprated " if m.nameString.downcase.include?(" uprated")
|
851
1388
|
m = m.clone(model).to_MasslessOpaqueMaterial.get
|
852
1389
|
m.setName("#{id} #{up}m tbd")
|
853
|
-
de_r = 0.001
|
854
|
-
loss = (de_u - 1 / de_r) *
|
1390
|
+
de_r = 0.001 unless de_r > 0.001
|
1391
|
+
loss = (de_u - 1 / de_r) * net unless de_r > 0.001
|
855
1392
|
m.setThermalResistance(de_r)
|
856
1393
|
else
|
857
1394
|
m = lc.getLayer(index).to_StandardOpaqueMaterial
|
858
|
-
return invalid("
|
1395
|
+
return invalid("#{id} standard layer?", mth, 0) if m.empty?
|
859
1396
|
m = m.get
|
860
1397
|
up = ""
|
861
|
-
up = "uprated "
|
1398
|
+
up = "uprated " if m.nameString.downcase.include?(" uprated")
|
862
1399
|
m = m.clone(model).to_StandardOpaqueMaterial.get
|
863
1400
|
m.setName("#{id} #{up}m tbd")
|
864
1401
|
k = m.thermalConductivity
|
@@ -869,14 +1406,14 @@ module TBD
|
|
869
1406
|
unless d > 0.003
|
870
1407
|
d = 0.003
|
871
1408
|
k = d / de_r
|
872
|
-
k = 3
|
873
|
-
loss = (de_u - k / d) *
|
1409
|
+
k = 3 unless k < 3
|
1410
|
+
loss = (de_u - k / d) * net unless k < 3
|
874
1411
|
end
|
875
|
-
else
|
1412
|
+
else # de_r < 0.001 m2•K/W
|
876
1413
|
d = 0.001 * k
|
877
|
-
d = 0.003
|
878
|
-
k = d / 0.001
|
879
|
-
loss = (de_u - k / d) *
|
1414
|
+
d = 0.003 unless d > 0.003
|
1415
|
+
k = d / 0.001 unless d > 0.003
|
1416
|
+
loss = (de_u - k / d) * net
|
880
1417
|
end
|
881
1418
|
|
882
1419
|
m.setThickness(d)
|
@@ -885,50 +1422,78 @@ module TBD
|
|
885
1422
|
|
886
1423
|
if m && loss > TOL
|
887
1424
|
s[:r_heatloss] = loss
|
888
|
-
|
889
|
-
log(WRN, "Won't assign #{
|
1425
|
+
hl = format "%.3f", s[:r_heatloss]
|
1426
|
+
log(WRN, "Won't assign #{hl} W/K to '#{id}': too conductive (#{mth})")
|
890
1427
|
end
|
891
1428
|
|
892
1429
|
m
|
893
1430
|
end
|
894
1431
|
|
895
1432
|
##
|
896
|
-
#
|
897
|
-
# and
|
898
|
-
# within surface multilayered constructions
|
899
|
-
#
|
900
|
-
# surfaces: derated TBD surfaces.
|
1433
|
+
# Processes TBD objects, based on an OpenStudio and generated Topolys model,
|
1434
|
+
# and derates admissible envelope surfaces by substituting insulating
|
1435
|
+
# materials with derated clones, within surface multilayered constructions.
|
1436
|
+
# Returns a Hash holding 2 key:value pairs; io: objects for JSON
|
1437
|
+
# serialization, and surfaces: derated TBD surfaces (see exit method).
|
901
1438
|
#
|
902
1439
|
# @param model [OpenStudio::Model::Model] a model
|
903
|
-
# @param
|
1440
|
+
# @param [Hash] argh TBD arguments
|
1441
|
+
# @option argh [#to_s] :option selected PSI set
|
1442
|
+
# @option argh [#to_s] :io_path tbd.json input file path
|
1443
|
+
# @option argh [#to_s] :schema_path TBD JSON schema file path
|
1444
|
+
# @option argh [Bool] :parapet (true) wall-roof edge as parapet
|
1445
|
+
# @option argh [Bool] :uprate_walls whether to uprate walls
|
1446
|
+
# @option argh [Bool] :uprate_roofs whether to uprate roofs
|
1447
|
+
# @option argh [Bool] :uprate_floors whether to uprate floors
|
1448
|
+
# @option argh [Bool] :wall_ut uprated wall Ut target in W/m2•K
|
1449
|
+
# @option argh [Bool] :roof_ut uprated roof Ut target in W/m2•K
|
1450
|
+
# @option argh [Bool] :floor_ut uprated floor Ut target in W/m2•K
|
1451
|
+
# @option argh [#to_s] :wall_option wall construction to uprate (or "all")
|
1452
|
+
# @option argh [#to_s] :roof_option roof construction to uprate (or "all")
|
1453
|
+
# @option argh [#to_s] :floor_option floor construction to uprate (or "all")
|
1454
|
+
# @option argh [Bool] :gen_ua whether to generate a UA' report
|
1455
|
+
# @option argh [#to_s] :ua_ref selected UA' ruleset
|
1456
|
+
# @option argh [Bool] :gen_kiva whether to generate KIVA inputs
|
1457
|
+
# @option argh [#to_f] :sub_tol proximity tolerance between edges in m
|
904
1458
|
#
|
905
1459
|
# @return [Hash] io: (Hash), surfaces: (Hash)
|
906
|
-
# @return [Hash] io: nil, surfaces: nil
|
1460
|
+
# @return [Hash] io: nil, surfaces: nil if invalid input (see logs)
|
907
1461
|
def process(model = nil, argh = {})
|
908
1462
|
mth = "TBD::#{__callee__}"
|
909
1463
|
cl = OpenStudio::Model::Model
|
910
1464
|
tbd = { io: nil, surfaces: {} }
|
911
|
-
|
912
1465
|
return mismatch("model", model, cl, mth, DBG, tbd) unless model.is_a?(cl)
|
913
1466
|
return mismatch("argh", argh, Hash, mth, DBG, tbd) unless argh.is_a?(Hash)
|
914
1467
|
|
915
|
-
argh = {}
|
916
|
-
argh[:
|
917
|
-
argh[:
|
918
|
-
argh[:
|
919
|
-
argh[:
|
920
|
-
argh[:uprate_walls ] = false
|
921
|
-
argh[:uprate_roofs ] = false
|
922
|
-
argh[:uprate_floors] = false
|
923
|
-
argh[:wall_ut ] = 0
|
924
|
-
argh[:roof_ut ] = 0
|
925
|
-
argh[:floor_ut ] = 0
|
926
|
-
argh[:wall_option ] = ""
|
927
|
-
argh[:roof_option ] = ""
|
928
|
-
argh[:floor_option ] = ""
|
929
|
-
argh[:gen_ua ] = false
|
930
|
-
argh[:ua_ref ] = ""
|
931
|
-
argh[:gen_kiva ] = false
|
1468
|
+
argh = {} if argh.empty?
|
1469
|
+
argh[:option ] = "" unless argh.key?(:option)
|
1470
|
+
argh[:io_path ] = nil unless argh.key?(:io_path)
|
1471
|
+
argh[:schema_path ] = nil unless argh.key?(:schema_path)
|
1472
|
+
argh[:parapet ] = true unless argh.key?(:parapet)
|
1473
|
+
argh[:uprate_walls ] = false unless argh.key?(:uprate_walls)
|
1474
|
+
argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs)
|
1475
|
+
argh[:uprate_floors] = false unless argh.key?(:uprate_floors)
|
1476
|
+
argh[:wall_ut ] = 0 unless argh.key?(:wall_ut)
|
1477
|
+
argh[:roof_ut ] = 0 unless argh.key?(:roof_ut)
|
1478
|
+
argh[:floor_ut ] = 0 unless argh.key?(:floor_ut)
|
1479
|
+
argh[:wall_option ] = "" unless argh.key?(:wall_option)
|
1480
|
+
argh[:roof_option ] = "" unless argh.key?(:roof_option)
|
1481
|
+
argh[:floor_option ] = "" unless argh.key?(:floor_option)
|
1482
|
+
argh[:gen_ua ] = false unless argh.key?(:gen_ua)
|
1483
|
+
argh[:ua_ref ] = "" unless argh.key?(:ua_ref)
|
1484
|
+
argh[:gen_kiva ] = false unless argh.key?(:gen_kiva)
|
1485
|
+
argh[:reset_kiva ] = false unless argh.key?(:reset_kiva)
|
1486
|
+
argh[:sub_tol ] = TBD::TOL unless argh.key?(:sub_tol)
|
1487
|
+
|
1488
|
+
# Ensure true or false: whether to generate KIVA inputs.
|
1489
|
+
unless [true, false].include?(argh[:gen_kiva])
|
1490
|
+
return invalid("generate KIVA option", mth, 0, DBG, tbd)
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
# Ensure true or false: whether to first purge (existing) KIVA inputs.
|
1494
|
+
unless [true, false].include?(argh[:reset_kiva])
|
1495
|
+
return invalid("reset KIVA option", mth, 0, DBG, tbd)
|
1496
|
+
end
|
932
1497
|
|
933
1498
|
# Create the Topolys Model.
|
934
1499
|
t_model = Topolys::Model.new
|
@@ -936,51 +1501,16 @@ module TBD
|
|
936
1501
|
# "true" if any space/zone holds valid setpoint temperatures. With invalid
|
937
1502
|
# inputs, these 2x methods return "false", ignoring any
|
938
1503
|
# setpoint-based logic, e.g. semi-heated spaces (DEBUG errors are logged).
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
# "true" if any space/zone is part of an HVAC air loop. With invalid inputs,
|
943
|
-
# the method returns "false", ignoring any air-loop related logic, e.g.
|
944
|
-
# plenum zones as HVAC objects (DEBUG errors are logged).
|
945
|
-
airloops = airLoopsHVAC?(model)
|
1504
|
+
heated = heatingTemperatureSetpoints?(model)
|
1505
|
+
cooled = coolingTemperatureSetpoints?(model)
|
1506
|
+
argh[:setpoints] = heated || cooled
|
946
1507
|
|
947
1508
|
model.getSurfaces.sort_by { |s| s.nameString }.each do |s|
|
948
|
-
# Fetch key attributes of opaque surfaces
|
949
|
-
#
|
950
|
-
surface = properties(
|
951
|
-
|
952
|
-
|
953
|
-
# Similar to "setpoints?" methods above, the boolean methods below also
|
954
|
-
# return "false" with invalid inputs, ignoring any space/zone
|
955
|
-
# conditioning-based logic (e.g. semi-heated spaces, mislabelling a
|
956
|
-
# plenum as an unconditioned zone).
|
957
|
-
if setpoints
|
958
|
-
if surface[:space].thermalZone.empty?
|
959
|
-
plenum = plenum?(surface[:space], airloops, setpoints)
|
960
|
-
surface[:conditioned] = false unless plenum
|
961
|
-
else
|
962
|
-
zone = surface[:space].thermalZone.get
|
963
|
-
heat = maxHeatScheduledSetpoint(zone)
|
964
|
-
cool = minCoolScheduledSetpoint(zone)
|
965
|
-
|
966
|
-
unless heat[:spt] || cool[:spt]
|
967
|
-
plenum = plenum?(surface[:space], airloops, setpoints)
|
968
|
-
heat[:spt] = 21 if plenum
|
969
|
-
cool[:spt] = 24 if plenum
|
970
|
-
surface[:conditioned] = false unless plenum
|
971
|
-
end
|
972
|
-
|
973
|
-
free = heat[:spt] && heat[:spt] < -40 && cool[:spt] && cool[:spt] > 40
|
974
|
-
surface[:conditioned] = false if free
|
975
|
-
end
|
976
|
-
end
|
977
|
-
|
978
|
-
# Recover if valid setpoints.
|
979
|
-
surface[:heating] = heat[:spt] if heat && heat[:spt]
|
980
|
-
surface[:cooling] = cool[:spt] if cool && cool[:spt]
|
981
|
-
|
982
|
-
tbd[:surfaces][s.nameString] = surface
|
983
|
-
end # (opaque) surfaces populated
|
1509
|
+
# Fetch key attributes of opaque surfaces (and any linked sub surfaces).
|
1510
|
+
# Method returns nil with invalid input (see logs); TBD ignores them.
|
1511
|
+
surface = properties(s, argh)
|
1512
|
+
tbd[:surfaces][s.nameString] = surface unless surface.nil?
|
1513
|
+
end
|
984
1514
|
|
985
1515
|
return empty("TBD surfaces", mth, ERR, tbd) if tbd[:surfaces].empty?
|
986
1516
|
|
@@ -988,74 +1518,77 @@ module TBD
|
|
988
1518
|
# ... if facing outdoors or facing UNENCLOSED/UNCONDITIONED spaces.
|
989
1519
|
tbd[:surfaces].each do |id, surface|
|
990
1520
|
surface[:deratable] = false
|
991
|
-
|
992
1521
|
next unless surface[:conditioned]
|
993
|
-
next if surface[:ground]
|
1522
|
+
next if surface[:ground ]
|
994
1523
|
|
995
1524
|
unless surface[:boundary].downcase == "outdoors"
|
996
1525
|
next unless tbd[:surfaces].key?(surface[:boundary])
|
997
|
-
next
|
1526
|
+
next if tbd[:surfaces][surface[:boundary]][:conditioned]
|
998
1527
|
end
|
999
1528
|
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1529
|
+
if surface.key?(:index)
|
1530
|
+
surface[:deratable] = true
|
1531
|
+
else
|
1532
|
+
log(ERR, "Skipping '#{id}': insulating layer? (#{mth})")
|
1533
|
+
end
|
1003
1534
|
end
|
1004
1535
|
|
1005
|
-
|
1536
|
+
# Sort subsurfaces before processing.
|
1537
|
+
[:windows, :doors, :skylights].each do |holes|
|
1006
1538
|
tbd[:surfaces].values.each do |surface|
|
1007
|
-
|
1008
|
-
|
1539
|
+
next unless surface.key?(holes)
|
1540
|
+
|
1541
|
+
surface[holes] = surface[holes].sort_by { |_, s| s[:minz] }.to_h
|
1009
1542
|
end
|
1010
1543
|
end
|
1011
1544
|
|
1012
1545
|
# Split "surfaces" hash into "floors", "ceilings" and "walls" hashes.
|
1013
|
-
floors = tbd[:surfaces].select { |_, s| s[:type] == :floor
|
1014
|
-
ceilings = tbd[:surfaces].select { |_, s| s[:type] == :ceiling
|
1015
|
-
walls = tbd[:surfaces].select { |_, s| s[:type] == :wall
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1546
|
+
floors = tbd[:surfaces].select { |_, s| s[:type] == :floor }
|
1547
|
+
ceilings = tbd[:surfaces].select { |_, s| s[:type] == :ceiling }
|
1548
|
+
walls = tbd[:surfaces].select { |_, s| s[:type] == :wall }
|
1549
|
+
|
1550
|
+
floors = floors.sort_by { |_, s| [s[:minz], s[:space]] }.to_h
|
1551
|
+
ceilings = ceilings.sort_by { |_, s| [s[:minz], s[:space]] }.to_h
|
1552
|
+
walls = walls.sort_by { |_, s| [s[:minz], s[:space]] }.to_h
|
1019
1553
|
|
1020
1554
|
# Fetch OpenStudio shading surfaces & key attributes.
|
1021
1555
|
shades = {}
|
1022
1556
|
|
1023
1557
|
model.getShadingSurfaces.each do |s|
|
1024
|
-
id
|
1025
|
-
|
1026
|
-
log(ERR, "Can't process '#{id}' transformation (#{mth})")
|
1027
|
-
next
|
1028
|
-
|
1029
|
-
|
1030
|
-
tr = transforms(
|
1031
|
-
|
1032
|
-
t = tr[:t]
|
1033
|
-
log(FTL, "Can't process '#{id}' transformation (#{mth})") unless ok
|
1034
|
-
return tbd unless ok
|
1035
|
-
|
1036
|
-
unless shading.empty?
|
1037
|
-
empty = shading.get.space.empty?
|
1038
|
-
tr[:r] += shading.get.space.get.directionofRelativeNorth unless empty
|
1039
|
-
end
|
1558
|
+
id = s.nameString
|
1559
|
+
group = s.shadingSurfaceGroup
|
1560
|
+
log(ERR, "Can't process '#{id}' transformation (#{mth})") if group.empty?
|
1561
|
+
next if group.empty?
|
1562
|
+
|
1563
|
+
group = group.get
|
1564
|
+
tr = transforms(group)
|
1565
|
+
t = tr[:t] if tr[:t] && tr[:r]
|
1040
1566
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1567
|
+
log(FTL, "Can't process '#{id}' transformation (#{mth})") unless t
|
1568
|
+
return tbd unless t
|
1569
|
+
|
1570
|
+
space = group.space
|
1571
|
+
tr[:r] += space.get.directionofRelativeNorth unless space.empty?
|
1572
|
+
n = trueNormal(s, tr[:r])
|
1573
|
+
log(FTL, "Can't process '#{id}' true normal (#{mth})") unless n
|
1574
|
+
return tbd unless n
|
1044
1575
|
|
1045
1576
|
points = (t * s.vertices).map { |v| Topolys::Point3D.new(v.x, v.y, v.z) }
|
1577
|
+
|
1046
1578
|
minz = ( points.map { |p| p.z } ).min
|
1047
|
-
|
1048
|
-
|
1579
|
+
|
1580
|
+
shades[id] = { group: group, points: points, minz: minz, n: n }
|
1581
|
+
end
|
1049
1582
|
|
1050
1583
|
# Mutually populate TBD & Topolys surfaces. Keep track of created "holes".
|
1051
1584
|
holes = {}
|
1052
|
-
floor_holes = dads(t_model, floors
|
1585
|
+
floor_holes = dads(t_model, floors)
|
1053
1586
|
ceiling_holes = dads(t_model, ceilings)
|
1054
|
-
wall_holes = dads(t_model, walls
|
1587
|
+
wall_holes = dads(t_model, walls)
|
1055
1588
|
|
1056
|
-
holes.merge!(floor_holes
|
1589
|
+
holes.merge!(floor_holes)
|
1057
1590
|
holes.merge!(ceiling_holes)
|
1058
|
-
holes.merge!(wall_holes
|
1591
|
+
holes.merge!(wall_holes)
|
1059
1592
|
dads(t_model, shades)
|
1060
1593
|
|
1061
1594
|
# Loop through Topolys edges and populate TBD edge hash. Initially, there
|
@@ -1063,25 +1596,44 @@ module TBD
|
|
1063
1596
|
# objects. Use Topolys-generated identifiers as unique edge hash keys.
|
1064
1597
|
edges = {}
|
1065
1598
|
|
1066
|
-
|
1599
|
+
# Start with hole edges.
|
1600
|
+
holes.each do |id, wire|
|
1067
1601
|
wire.edges.each do |e|
|
1068
|
-
i
|
1069
|
-
l
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1602
|
+
i = e.id
|
1603
|
+
l = e.length
|
1604
|
+
ex = edges.key?(i)
|
1605
|
+
|
1606
|
+
edges[i] = { length: l, v0: e.v0, v1: e.v1, surfaces: {} } unless ex
|
1607
|
+
|
1608
|
+
next if edges[i][:surfaces].key?(wire.attributes[:id])
|
1609
|
+
|
1610
|
+
edges[i][:surfaces][wire.attributes[:id]] = { wire: wire.id }
|
1074
1611
|
end
|
1075
1612
|
end
|
1076
1613
|
|
1077
1614
|
# Next, floors, ceilings & walls; then shades.
|
1078
|
-
faces(floors, edges
|
1615
|
+
faces(floors , edges)
|
1079
1616
|
faces(ceilings, edges)
|
1080
|
-
faces(walls, edges
|
1081
|
-
faces(shades, edges
|
1617
|
+
faces(walls , edges)
|
1618
|
+
faces(shades , edges)
|
1619
|
+
|
1620
|
+
# Purge existing KIVA objects from model.
|
1621
|
+
if argh[:reset_kiva]
|
1622
|
+
kva = false
|
1623
|
+
kva = true unless model.getSurfacePropertyExposedFoundationPerimeters.empty?
|
1624
|
+
kva = true unless model.getFoundationKivas.empty?
|
1625
|
+
|
1626
|
+
if kva
|
1627
|
+
if argh[:gen_kiva]
|
1628
|
+
resetKIVA(model, "Foundation")
|
1629
|
+
else
|
1630
|
+
resetKIVA(model, "Ground")
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
end
|
1082
1634
|
|
1083
1635
|
# Generate OSM Kiva settings and objects if foundation-facing floors.
|
1084
|
-
#
|
1636
|
+
# Returns false if partial failure (log failure eventually).
|
1085
1637
|
kiva(model, walls, floors, edges) if argh[:gen_kiva]
|
1086
1638
|
|
1087
1639
|
# Thermal bridging characteristics of edges are determined - in part - by
|
@@ -1113,8 +1665,10 @@ module TBD
|
|
1113
1665
|
vertical = dx < TOL && dy < TOL
|
1114
1666
|
edge_V = terminal - origin
|
1115
1667
|
|
1116
|
-
|
1117
|
-
|
1668
|
+
if edge_V.magnitude < TOL
|
1669
|
+
invalid("1x edge length < TOL", mth, 0, ERROR)
|
1670
|
+
next
|
1671
|
+
end
|
1118
1672
|
|
1119
1673
|
edge_plane = Topolys::Plane3D.new(origin, edge_V)
|
1120
1674
|
|
@@ -1122,7 +1676,7 @@ module TBD
|
|
1122
1676
|
reference_V = north.dup
|
1123
1677
|
elsif horizontal
|
1124
1678
|
reference_V = zenith.dup
|
1125
|
-
else
|
1679
|
+
else # project zenith vector unto edge plane
|
1126
1680
|
reference = edge_plane.project(origin + zenith)
|
1127
1681
|
reference_V = reference - origin
|
1128
1682
|
end
|
@@ -1131,12 +1685,13 @@ module TBD
|
|
1131
1685
|
# Loop through each linked wire and determine farthest point from
|
1132
1686
|
# edge while ensuring candidate point is not aligned with edge.
|
1133
1687
|
t_model.wires.each do |wire|
|
1134
|
-
next unless surface[:wire] == wire.id
|
1688
|
+
next unless surface[:wire] == wire.id # should be a unique match
|
1689
|
+
|
1135
1690
|
normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
|
1136
1691
|
normal = holes[id].attributes[:n] if holes.key?(id)
|
1137
1692
|
normal = shades[id][:n] if shades.key?(id)
|
1138
1693
|
farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
|
1139
|
-
farthest_V = farthest - origin
|
1694
|
+
farthest_V = farthest - origin # zero magnitude, initially
|
1140
1695
|
inverted = false
|
1141
1696
|
i_origin = wire.points.index(origin)
|
1142
1697
|
i_terminal = wire.points.index(terminal)
|
@@ -1167,9 +1722,10 @@ module TBD
|
|
1167
1722
|
plane = Topolys::Plane3D.from_points(origin, terminal, point)
|
1168
1723
|
end
|
1169
1724
|
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1725
|
+
dnx = (normal.x - plane.normal.x).abs
|
1726
|
+
dny = (normal.y - plane.normal.y).abs
|
1727
|
+
dnz = (normal.z - plane.normal.z).abs
|
1728
|
+
next unless dnx < TOL && dny < TOL && dnz < TOL
|
1173
1729
|
|
1174
1730
|
farther = point_V_magnitude > farthest_V.magnitude
|
1175
1731
|
farthest = point if farther
|
@@ -1180,16 +1736,18 @@ module TBD
|
|
1180
1736
|
invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
|
1181
1737
|
angle = 0 if angle.nil?
|
1182
1738
|
|
1183
|
-
adjust = false
|
1739
|
+
adjust = false # adjust angle [180°, 360°] if necessary
|
1184
1740
|
|
1185
1741
|
if vertical
|
1186
1742
|
adjust = true if east.dot(farthest_V) < -TOL
|
1187
1743
|
else
|
1188
|
-
|
1189
|
-
|
1744
|
+
dN = north.dot(farthest_V)
|
1745
|
+
dN1 = north.dot(farthest_V).abs - 1
|
1746
|
+
|
1747
|
+
if dN.abs < TOL || dN1.abs < TOL
|
1190
1748
|
adjust = true if east.dot(farthest_V) < -TOL
|
1191
1749
|
else
|
1192
|
-
adjust = true if
|
1750
|
+
adjust = true if dN < -TOL
|
1193
1751
|
end
|
1194
1752
|
end
|
1195
1753
|
|
@@ -1199,13 +1757,13 @@ module TBD
|
|
1199
1757
|
farthest_V.normalize!
|
1200
1758
|
surface[:polar ] = farthest_V
|
1201
1759
|
surface[:normal] = normal
|
1202
|
-
end
|
1203
|
-
end
|
1760
|
+
end # end of edge-linked, surface-to-wire loop
|
1761
|
+
end # end of edge-linked surface loop
|
1204
1762
|
|
1205
1763
|
edge[:horizontal] = horizontal
|
1206
1764
|
edge[:vertical ] = vertical
|
1207
|
-
edge[:surfaces ] = edge[:surfaces].sort_by{ |
|
1208
|
-
end
|
1765
|
+
edge[:surfaces ] = edge[:surfaces].sort_by{ |_, p| p[:angle] }.to_h
|
1766
|
+
end # end of edge loop
|
1209
1767
|
|
1210
1768
|
# Topolys edges may constitute thermal bridges (and therefore thermally
|
1211
1769
|
# derate linked OpenStudio opaque surfaces), depending on a number of
|
@@ -1251,23 +1809,27 @@ module TBD
|
|
1251
1809
|
# EnergyPlus simulation). This is similar to accessing an invalid .osm file.
|
1252
1810
|
return tbd if fatal?
|
1253
1811
|
|
1254
|
-
psi = json[:io][:building][:psi]
|
1812
|
+
psi = json[:io][:building][:psi] # default building PSI on file
|
1255
1813
|
shorts = json[:psi].shorthands(psi)
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1814
|
+
|
1815
|
+
if shorts[:has].empty? || shorts[:val].empty?
|
1816
|
+
log(FTL, "Invalid or incomplete building PSI set (#{mth})")
|
1817
|
+
return tbd
|
1818
|
+
end
|
1259
1819
|
|
1260
1820
|
edges.values.each do |edge|
|
1261
1821
|
next unless edge.key?(:surfaces)
|
1822
|
+
|
1262
1823
|
deratables = []
|
1824
|
+
set = {}
|
1263
1825
|
|
1264
1826
|
edge[:surfaces].keys.each do |id|
|
1265
1827
|
next unless tbd[:surfaces].key?(id)
|
1828
|
+
|
1266
1829
|
deratables << id if tbd[:surfaces][id][:deratable]
|
1267
1830
|
end
|
1268
1831
|
|
1269
1832
|
next if deratables.empty?
|
1270
|
-
set = {}
|
1271
1833
|
|
1272
1834
|
if edge.key?(:io_type)
|
1273
1835
|
bdg = json[:psi].safe(psi, edge[:io_type]) # building safe type fallback
|
@@ -1290,97 +1852,226 @@ module TBD
|
|
1290
1852
|
next unless deratables.include?(id)
|
1291
1853
|
|
1292
1854
|
# Evaluate current set content before processing a new linked surface.
|
1293
|
-
is
|
1294
|
-
is[:head
|
1295
|
-
is[:sill
|
1296
|
-
is[:jamb
|
1297
|
-
is[:
|
1298
|
-
is[:
|
1299
|
-
is[:
|
1300
|
-
is[:
|
1301
|
-
is[:
|
1302
|
-
is[:
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1855
|
+
is = {}
|
1856
|
+
is[:head ] = set.keys.to_s.include?("head")
|
1857
|
+
is[:sill ] = set.keys.to_s.include?("sill")
|
1858
|
+
is[:jamb ] = set.keys.to_s.include?("jamb")
|
1859
|
+
is[:doorhead ] = set.keys.to_s.include?("doorhead")
|
1860
|
+
is[:doorsill ] = set.keys.to_s.include?("doorsill")
|
1861
|
+
is[:doorjamb ] = set.keys.to_s.include?("doorjamb")
|
1862
|
+
is[:skylighthead ] = set.keys.to_s.include?("skylighthead")
|
1863
|
+
is[:skylightsill ] = set.keys.to_s.include?("skylightsill")
|
1864
|
+
is[:skylightjamb ] = set.keys.to_s.include?("skylightjamb")
|
1865
|
+
is[:spandrel ] = set.keys.to_s.include?("spandrel")
|
1866
|
+
is[:corner ] = set.keys.to_s.include?("corner")
|
1867
|
+
is[:parapet ] = set.keys.to_s.include?("parapet")
|
1868
|
+
is[:roof ] = set.keys.to_s.include?("roof")
|
1869
|
+
is[:party ] = set.keys.to_s.include?("party")
|
1870
|
+
is[:grade ] = set.keys.to_s.include?("grade")
|
1871
|
+
is[:balcony ] = set.keys.to_s.include?("balcony")
|
1872
|
+
is[:balconysill ] = set.keys.to_s.include?("balconysill")
|
1873
|
+
is[:balconydoorsill ] = set.keys.to_s.include?("balconydoorsill")
|
1874
|
+
is[:rimjoist ] = set.keys.to_s.include?("rimjoist")
|
1875
|
+
|
1876
|
+
# Label edge as ...
|
1877
|
+
# :head, :sill, :jamb (vertical fenestration)
|
1878
|
+
# :doorhead, :doorsill, :doorjamb (opaque door)
|
1879
|
+
# :skylighthead, :skylightsill, :skylightjamb (all other cases)
|
1880
|
+
#
|
1881
|
+
# ... if linked to:
|
1882
|
+
# 1x subsurface (vertical or non-vertical)
|
1306
1883
|
edge[:surfaces].keys.each do |i|
|
1307
|
-
break if is[:head]
|
1884
|
+
break if is[:head ]
|
1885
|
+
break if is[:sill ]
|
1886
|
+
break if is[:jamb ]
|
1887
|
+
break if is[:doorhead ]
|
1888
|
+
break if is[:doorsill ]
|
1889
|
+
break if is[:doorjamb ]
|
1890
|
+
break if is[:skylighthead]
|
1891
|
+
break if is[:skylightsill]
|
1892
|
+
break if is[:skylightjamb]
|
1308
1893
|
next if deratables.include?(i)
|
1309
1894
|
next unless holes.key?(i)
|
1310
1895
|
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
gardian =
|
1337
|
-
|
1896
|
+
# In most cases, subsurface edges simply delineate the rough opening
|
1897
|
+
# of its base surface (here, a "gardian"). Door sills, corner windows,
|
1898
|
+
# as well as a subsurface header aligned with a plenum "floor"
|
1899
|
+
# (ceiling tiles), are common instances where a subsurface edge links
|
1900
|
+
# 2x (opaque) surfaces. Deratable surface "id" may not be the gardian
|
1901
|
+
# of subsurface "i" - the latter may be a neighbour. The single
|
1902
|
+
# surface to derate is not the gardian in such cases.
|
1903
|
+
gardian = deratables.size == 1 ? id : ""
|
1904
|
+
target = gardian
|
1905
|
+
|
1906
|
+
# Retrieve base surface's subsurfaces.
|
1907
|
+
windows = tbd[:surfaces][id].key?(:windows)
|
1908
|
+
doors = tbd[:surfaces][id].key?(:doors)
|
1909
|
+
skylights = tbd[:surfaces][id].key?(:skylights)
|
1910
|
+
|
1911
|
+
windows = windows ? tbd[:surfaces][id][:windows ] : {}
|
1912
|
+
doors = doors ? tbd[:surfaces][id][:doors ] : {}
|
1913
|
+
skylights = skylights ? tbd[:surfaces][id][:skylights] : {}
|
1914
|
+
|
1915
|
+
# The gardian is "id" if subsurface "ids" holds "i".
|
1916
|
+
ids = windows.keys + doors.keys + skylights.keys
|
1917
|
+
|
1918
|
+
if gardian.empty?
|
1919
|
+
other = deratables.first == id ? deratables.last : deratables.first
|
1920
|
+
|
1921
|
+
gardian = ids.include?(i) ? id : other
|
1922
|
+
target = ids.include?(i) ? other : id
|
1923
|
+
|
1924
|
+
windows = tbd[:surfaces][gardian].key?(:windows)
|
1925
|
+
doors = tbd[:surfaces][gardian].key?(:doors)
|
1926
|
+
skylights = tbd[:surfaces][gardian].key?(:skylights)
|
1927
|
+
|
1928
|
+
windows = windows ? tbd[:surfaces][gardian][:windows ] : {}
|
1929
|
+
doors = doors ? tbd[:surfaces][gardian][:doors ] : {}
|
1930
|
+
skylights = skylights ? tbd[:surfaces][gardian][:skylights] : {}
|
1931
|
+
|
1932
|
+
ids = windows.keys + doors.keys + skylights.keys
|
1338
1933
|
end
|
1339
1934
|
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1935
|
+
unless ids.include?(i)
|
1936
|
+
log(ERR, "Orphaned subsurface #{i} (mth)")
|
1937
|
+
next
|
1938
|
+
end
|
1939
|
+
|
1940
|
+
window = windows.key?(i) ? windows[i] : {}
|
1941
|
+
door = doors.key?(i) ? doors[i] : {}
|
1942
|
+
skylight = skylights.key?(i) ? skylights[i] : {}
|
1943
|
+
|
1944
|
+
sub = window unless window.empty?
|
1945
|
+
sub = door unless door.empty?
|
1946
|
+
sub = skylight unless skylight.empty?
|
1947
|
+
|
1948
|
+
window = sub[:type] == :window
|
1949
|
+
door = sub[:type] == :door
|
1950
|
+
glazed = door && sub.key?(:glazed) && sub[:glazed]
|
1951
|
+
|
1952
|
+
s1 = edge[:surfaces][target]
|
1953
|
+
s2 = edge[:surfaces][i ]
|
1343
1954
|
concave = concave?(s1, s2)
|
1344
1955
|
convex = convex?(s1, s2)
|
1345
1956
|
flat = !concave && !convex
|
1346
1957
|
|
1347
|
-
# Subsurface edges are tagged as
|
1348
|
-
#
|
1349
|
-
# :fenestration, then its
|
1350
|
-
#
|
1351
|
-
# concave or convex variants also inherit from base type.
|
1958
|
+
# Subsurface edges are tagged as head, sill or jamb, regardless of
|
1959
|
+
# building PSI set subsurface-related tags. If the latter is simply
|
1960
|
+
# :fenestration, then its single PSI factor is systematically
|
1961
|
+
# assigned to e.g. a window's :head, :sill & :jamb edges.
|
1352
1962
|
#
|
1353
|
-
#
|
1354
|
-
#
|
1355
|
-
#
|
1356
|
-
#
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
else
|
1363
|
-
if edge[:horizontal]
|
1364
|
-
if s2[:polar].dot(zenith) < 0
|
1365
|
-
set[:head ] = shorts[:val][:head ] if flat
|
1366
|
-
set[:headconcave] = shorts[:val][:headconcave] if concave
|
1367
|
-
set[:headconvex ] = shorts[:val][:headconvex ] if convex
|
1368
|
-
is[:head ] = true
|
1369
|
-
else
|
1370
|
-
set[:sill ] = shorts[:val][:sill ] if flat
|
1371
|
-
set[:sillconcave] = shorts[:val][:sillconcave] if concave
|
1372
|
-
set[:sillconvex ] = shorts[:val][:sillconvex ] if convex
|
1373
|
-
is[:sill ] = true
|
1374
|
-
end
|
1375
|
-
else
|
1963
|
+
# Additionally, concave or convex variants also inherit from the base
|
1964
|
+
# type if undefined in the PSI set.
|
1965
|
+
#
|
1966
|
+
# If a subsurface is not horizontal, TBD tags any horizontal edge as
|
1967
|
+
# either :head or :sill based on the polar angle of the subsurface
|
1968
|
+
# around the edge vs sky zenith. Otherwise, all other subsurface edges
|
1969
|
+
# are tagged as :jamb.
|
1970
|
+
if ((s2[:normal].dot(zenith)).abs - 1).abs < TOL # horizontal surface
|
1971
|
+
if glazed || window
|
1376
1972
|
set[:jamb ] = shorts[:val][:jamb ] if flat
|
1377
1973
|
set[:jambconcave] = shorts[:val][:jambconcave] if concave
|
1378
1974
|
set[:jambconvex ] = shorts[:val][:jambconvex ] if convex
|
1379
1975
|
is[:jamb ] = true
|
1976
|
+
elsif door
|
1977
|
+
set[:doorjamb ] = shorts[:val][:doorjamb ] if flat
|
1978
|
+
set[:doorjambconcave] = shorts[:val][:doorjambconcave] if concave
|
1979
|
+
set[:doorjambconvex ] = shorts[:val][:doorjambconvex ] if convex
|
1980
|
+
is[:doorjamb ] = true
|
1981
|
+
else
|
1982
|
+
set[:skylightjamb ] = shorts[:val][:skylightjamb ] if flat
|
1983
|
+
set[:skylightjambconcave] = shorts[:val][:skylightjambconcave] if concave
|
1984
|
+
set[:skylightjambconvex ] = shorts[:val][:skylightjambconvex ] if convex
|
1985
|
+
is[:skylightjamb ] = true
|
1986
|
+
end
|
1987
|
+
else
|
1988
|
+
if glazed || window
|
1989
|
+
if edge[:horizontal]
|
1990
|
+
if s2[:polar].dot(zenith) < 0
|
1991
|
+
set[:head ] = shorts[:val][:head ] if flat
|
1992
|
+
set[:headconcave] = shorts[:val][:headconcave] if concave
|
1993
|
+
set[:headconvex ] = shorts[:val][:headconvex ] if convex
|
1994
|
+
is[:head ] = true
|
1995
|
+
else
|
1996
|
+
set[:sill ] = shorts[:val][:sill ] if flat
|
1997
|
+
set[:sillconcave] = shorts[:val][:sillconcave] if concave
|
1998
|
+
set[:sillconvex ] = shorts[:val][:sillconvex ] if convex
|
1999
|
+
is[:sill ] = true
|
2000
|
+
end
|
2001
|
+
else
|
2002
|
+
set[:jamb ] = shorts[:val][:jamb ] if flat
|
2003
|
+
set[:jambconcave] = shorts[:val][:jambconcave] if concave
|
2004
|
+
set[:jambconvex ] = shorts[:val][:jambconvex ] if convex
|
2005
|
+
is[:jamb ] = true
|
2006
|
+
end
|
2007
|
+
elsif door
|
2008
|
+
if edge[:horizontal]
|
2009
|
+
if s2[:polar].dot(zenith) < 0
|
2010
|
+
|
2011
|
+
set[:doorhead ] = shorts[:val][:doorhead ] if flat
|
2012
|
+
set[:doorheadconcave] = shorts[:val][:doorheadconcave] if concave
|
2013
|
+
set[:doorheadconvex ] = shorts[:val][:doorheadconvex ] if convex
|
2014
|
+
is[:doorhead ] = true
|
2015
|
+
else
|
2016
|
+
set[:doorsill ] = shorts[:val][:doorsill ] if flat
|
2017
|
+
set[:doorsillconcave] = shorts[:val][:doorsillconcave] if concave
|
2018
|
+
set[:doorsillconvex ] = shorts[:val][:doorsillconvex ] if convex
|
2019
|
+
is[:doorsill ] = true
|
2020
|
+
end
|
2021
|
+
else
|
2022
|
+
set[:doorjamb ] = shorts[:val][:doorjamb ] if flat
|
2023
|
+
set[:doorjambconcave] = shorts[:val][:doorjambconcave] if concave
|
2024
|
+
set[:doorjambconvex ] = shorts[:val][:doorjambconvex ] if convex
|
2025
|
+
is[:doorjamb ] = true
|
2026
|
+
end
|
2027
|
+
else
|
2028
|
+
if edge[:horizontal]
|
2029
|
+
if s2[:polar].dot(zenith) < 0
|
2030
|
+
set[:skylighthead ] = shorts[:val][:skylighthead ] if flat
|
2031
|
+
set[:skylightheadconcave] = shorts[:val][:skylightheadconcave] if concave
|
2032
|
+
set[:skylightheadconvex ] = shorts[:val][:skylightheadconvex ] if convex
|
2033
|
+
is[:skylighthead ] = true
|
2034
|
+
else
|
2035
|
+
set[:skylightsill ] = shorts[:val][:skylightsill ] if flat
|
2036
|
+
set[:skylightsillconcave] = shorts[:val][:skylightsillconcave] if concave
|
2037
|
+
set[:skylightsillconvex ] = shorts[:val][:skylightsillconvex ] if convex
|
2038
|
+
is[:skylightsill ] = true
|
2039
|
+
end
|
2040
|
+
else
|
2041
|
+
set[:skylightjamb ] = shorts[:val][:skylightjamb ] if flat
|
2042
|
+
set[:skylightjambconcave] = shorts[:val][:skylightjambconcave] if concave
|
2043
|
+
set[:skylightjambconvex ] = shorts[:val][:skylightjambconvex ] if convex
|
2044
|
+
is[:skylightjamb ] = true
|
2045
|
+
end
|
1380
2046
|
end
|
1381
2047
|
end
|
1382
2048
|
end
|
1383
2049
|
|
2050
|
+
# Label edge as :spandrel if linked to:
|
2051
|
+
# 1x deratable, non-spandrel wall
|
2052
|
+
# 1x deratable, spandrel wall
|
2053
|
+
edge[:surfaces].keys.each do |i|
|
2054
|
+
break if is[:spandrel]
|
2055
|
+
break unless deratables.size == 2
|
2056
|
+
break unless walls.key?(id)
|
2057
|
+
break unless walls[id][:spandrel]
|
2058
|
+
next if i == id
|
2059
|
+
next unless deratables.include?(i)
|
2060
|
+
next unless walls.key?(i)
|
2061
|
+
next if walls[i][:spandrel]
|
2062
|
+
|
2063
|
+
s1 = edge[:surfaces][id]
|
2064
|
+
s2 = edge[:surfaces][i ]
|
2065
|
+
concave = concave?(s1, s2)
|
2066
|
+
convex = convex?(s1, s2)
|
2067
|
+
flat = !concave && !convex
|
2068
|
+
|
2069
|
+
set[:spandrel ] = shorts[:val][:spandrel ] if flat
|
2070
|
+
set[:spandrelconcave] = shorts[:val][:spandrelconcave] if concave
|
2071
|
+
set[:spandrelconvex ] = shorts[:val][:spandrelconvex ] if convex
|
2072
|
+
is[:spandrel ] = true
|
2073
|
+
end
|
2074
|
+
|
1384
2075
|
# Label edge as :cornerconcave or :cornerconvex if linked to:
|
1385
2076
|
# 2x deratable walls & f(relative polar wall vectors around edge)
|
1386
2077
|
edge[:surfaces].keys.each do |i|
|
@@ -1388,8 +2079,8 @@ module TBD
|
|
1388
2079
|
break unless deratables.size == 2
|
1389
2080
|
break unless walls.key?(id)
|
1390
2081
|
next if i == id
|
1391
|
-
next
|
1392
|
-
next
|
2082
|
+
next unless deratables.include?(i)
|
2083
|
+
next unless walls.key?(i)
|
1393
2084
|
|
1394
2085
|
s1 = edge[:surfaces][id]
|
1395
2086
|
s2 = edge[:surfaces][i]
|
@@ -1401,11 +2092,12 @@ module TBD
|
|
1401
2092
|
is[:corner ] = true
|
1402
2093
|
end
|
1403
2094
|
|
1404
|
-
# Label edge as :parapet if linked to:
|
2095
|
+
# Label edge as :parapet/:roof if linked to:
|
1405
2096
|
# 1x deratable wall
|
1406
2097
|
# 1x deratable ceiling
|
1407
2098
|
edge[:surfaces].keys.each do |i|
|
1408
2099
|
break if is[:parapet]
|
2100
|
+
break if is[:roof ]
|
1409
2101
|
break unless deratables.size == 2
|
1410
2102
|
break unless ceilings.key?(id)
|
1411
2103
|
next if i == id
|
@@ -1413,15 +2105,22 @@ module TBD
|
|
1413
2105
|
next unless walls.key?(i)
|
1414
2106
|
|
1415
2107
|
s1 = edge[:surfaces][id]
|
1416
|
-
s2 = edge[:surfaces][i]
|
2108
|
+
s2 = edge[:surfaces][i ]
|
1417
2109
|
concave = concave?(s1, s2)
|
1418
2110
|
convex = convex?(s1, s2)
|
1419
2111
|
flat = !concave && !convex
|
1420
2112
|
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
2113
|
+
if argh[:parapet]
|
2114
|
+
set[:parapet ] = shorts[:val][:parapet ] if flat
|
2115
|
+
set[:parapetconcave] = shorts[:val][:parapetconcave] if concave
|
2116
|
+
set[:parapetconvex ] = shorts[:val][:parapetconvex ] if convex
|
2117
|
+
is[:parapet ] = true
|
2118
|
+
else
|
2119
|
+
set[:roof ] = shorts[:val][:roof ] if flat
|
2120
|
+
set[:roofconcave] = shorts[:val][:roofconcave] if concave
|
2121
|
+
set[:roofconvex ] = shorts[:val][:roofconvex ] if convex
|
2122
|
+
is[:roof ] = true
|
2123
|
+
end
|
1425
2124
|
end
|
1426
2125
|
|
1427
2126
|
# Label edge as :party if linked to:
|
@@ -1439,7 +2138,7 @@ module TBD
|
|
1439
2138
|
next unless facing == "othersidecoefficients"
|
1440
2139
|
|
1441
2140
|
s1 = edge[:surfaces][id]
|
1442
|
-
s2 = edge[:surfaces][i]
|
2141
|
+
s2 = edge[:surfaces][i ]
|
1443
2142
|
concave = concave?(s1, s2)
|
1444
2143
|
convex = convex?(s1, s2)
|
1445
2144
|
flat = !concave && !convex
|
@@ -1473,30 +2172,106 @@ module TBD
|
|
1473
2172
|
is[:grade ] = true
|
1474
2173
|
end
|
1475
2174
|
|
1476
|
-
# Label edge as :rimjoist
|
2175
|
+
# Label edge as :rimjoist, :balcony, :balconysill or :balconydoorsill if linked to:
|
1477
2176
|
# 1x deratable surface
|
1478
2177
|
# 1x CONDITIONED floor
|
1479
2178
|
# 1x shade (optional)
|
1480
|
-
|
2179
|
+
# 1x subsurface (optional)
|
2180
|
+
#
|
2181
|
+
# Despite referring to 'sill' or 'doorsill', a 'balconysill' or
|
2182
|
+
# 'balconydoorsill' edge may instead link (rarer) cases of balcony and a
|
2183
|
+
# fenestratio/door head. ASHRAE 90.1 2022 does not make the distinction
|
2184
|
+
# between sill vs head when intermediatre floor, balcony and vertical
|
2185
|
+
# fenestration meet. 'Sills' are simply the most common occurrence.
|
2186
|
+
balcony = false
|
2187
|
+
balconysill = false # vertical fenestration
|
2188
|
+
balconydoorsill = false # opaque door
|
1481
2189
|
|
1482
2190
|
edge[:surfaces].keys.each do |i|
|
1483
|
-
break
|
1484
|
-
next
|
1485
|
-
|
2191
|
+
break if balcony
|
2192
|
+
next if i == id
|
2193
|
+
|
2194
|
+
balcony = shades.key?(i)
|
1486
2195
|
end
|
1487
2196
|
|
1488
2197
|
edge[:surfaces].keys.each do |i|
|
1489
|
-
break
|
2198
|
+
break unless balcony
|
2199
|
+
break if balconysill
|
2200
|
+
break if balconydoorsill
|
2201
|
+
next if i == id
|
2202
|
+
next unless holes.key?(i)
|
2203
|
+
|
2204
|
+
# Deratable surface "id" may not be the gardian of "i" (see sills).
|
2205
|
+
gardian = deratables.size == 1 ? id : ""
|
2206
|
+
target = gardian
|
2207
|
+
|
2208
|
+
# Retrieve base surface's subsurfaces.
|
2209
|
+
windows = tbd[:surfaces][id].key?(:windows)
|
2210
|
+
doors = tbd[:surfaces][id].key?(:doors)
|
2211
|
+
skylights = tbd[:surfaces][id].key?(:skylights)
|
2212
|
+
|
2213
|
+
windows = windows ? tbd[:surfaces][id][:windows ] : {}
|
2214
|
+
doors = doors ? tbd[:surfaces][id][:doors ] : {}
|
2215
|
+
skylights = skylights ? tbd[:surfaces][id][:skylights] : {}
|
2216
|
+
|
2217
|
+
# The gardian is "id" if subsurface "ids" holds "i".
|
2218
|
+
ids = windows.keys + doors.keys + skylights.keys
|
2219
|
+
|
2220
|
+
if gardian.empty?
|
2221
|
+
other = deratables.first == id ? deratables.last : deratables.first
|
2222
|
+
|
2223
|
+
gardian = ids.include?(i) ? id : other
|
2224
|
+
target = ids.include?(i) ? other : id
|
2225
|
+
|
2226
|
+
windows = tbd[:surfaces][gardian].key?(:windows)
|
2227
|
+
doors = tbd[:surfaces][gardian].key?(:doors)
|
2228
|
+
skylights = tbd[:surfaces][gardian].key?(:skylights)
|
2229
|
+
|
2230
|
+
windows = windows ? tbd[:surfaces][gardian][:windows ] : {}
|
2231
|
+
doors = doors ? tbd[:surfaces][gardian][:doors ] : {}
|
2232
|
+
skylights = skylights ? tbd[:surfaces][gardian][:skylights] : {}
|
2233
|
+
|
2234
|
+
ids = windows.keys + doors.keys + skylights.keys
|
2235
|
+
end
|
2236
|
+
|
2237
|
+
unless ids.include?(i)
|
2238
|
+
log(ERR, "Balcony sill: orphaned subsurface #{i} (mth)")
|
2239
|
+
next
|
2240
|
+
end
|
2241
|
+
|
2242
|
+
window = windows.key?(i) ? windows[i] : {}
|
2243
|
+
door = doors.key?(i) ? doors[i] : {}
|
2244
|
+
skylight = skylights.key?(i) ? skylights[i] : {}
|
2245
|
+
|
2246
|
+
sub = window unless window.empty?
|
2247
|
+
sub = door unless door.empty?
|
2248
|
+
sub = skylight unless skylight.empty?
|
2249
|
+
|
2250
|
+
window = sub[:type] == :window
|
2251
|
+
door = sub[:type] == :door
|
2252
|
+
glazed = door && sub.key?(:glazed) && sub[:glazed]
|
2253
|
+
|
2254
|
+
if window || glazed
|
2255
|
+
balconysill = true
|
2256
|
+
elsif door
|
2257
|
+
balconydoorsill = true
|
2258
|
+
end
|
2259
|
+
end
|
2260
|
+
|
2261
|
+
edge[:surfaces].keys.each do |i|
|
2262
|
+
break if is[:rimjoist ] || is[:balcony ] ||
|
2263
|
+
is[:balconysill] || is[:balconydoorsill]
|
1490
2264
|
break unless deratables.size > 0
|
1491
2265
|
break if floors.key?(id)
|
1492
2266
|
next if i == id
|
1493
2267
|
next unless floors.key?(i)
|
1494
2268
|
next unless floors[i].key?(:conditioned)
|
1495
2269
|
next unless floors[i][:conditioned]
|
1496
|
-
next if floors[i][:ground]
|
2270
|
+
next if floors[i][:ground ]
|
1497
2271
|
|
1498
2272
|
other = deratables.first unless deratables.first == id
|
1499
2273
|
other = deratables.last unless deratables.last == id
|
2274
|
+
other = id if deratables.size == 1
|
1500
2275
|
|
1501
2276
|
s1 = edge[:surfaces][id]
|
1502
2277
|
s2 = edge[:surfaces][other]
|
@@ -1504,38 +2279,50 @@ module TBD
|
|
1504
2279
|
convex = convex?(s1, s2)
|
1505
2280
|
flat = !concave && !convex
|
1506
2281
|
|
1507
|
-
if
|
1508
|
-
set[:
|
1509
|
-
set[:
|
1510
|
-
set[:
|
1511
|
-
is[:
|
2282
|
+
if balconydoorsill
|
2283
|
+
set[:balconydoorsill ] = shorts[:val][:balconydoorsill ] if flat
|
2284
|
+
set[:balconydoorsillconcave] = shorts[:val][:balconydoorsillconcave] if concave
|
2285
|
+
set[:balconydoorsillconvex ] = shorts[:val][:balconydoorsillconvex ] if convex
|
2286
|
+
is[:balconydoorsill ] = true
|
2287
|
+
elsif balconysill
|
2288
|
+
set[:balconysill ] = shorts[:val][:balconysill ] if flat
|
2289
|
+
set[:balconysillconcave ] = shorts[:val][:balconysillconcave ] if concave
|
2290
|
+
set[:balconysillconvex ] = shorts[:val][:balconysillconvex ] if convex
|
2291
|
+
is[:balconysill ] = true
|
2292
|
+
elsif balcony
|
2293
|
+
set[:balcony ] = shorts[:val][:balcony ] if flat
|
2294
|
+
set[:balconyconcave ] = shorts[:val][:balconyconcave ] if concave
|
2295
|
+
set[:balconyconvex ] = shorts[:val][:balconyconvex ] if convex
|
2296
|
+
is[:balcony ] = true
|
1512
2297
|
else
|
1513
|
-
set[:rimjoist
|
1514
|
-
set[:rimjoistconcave] = shorts[:val][:rimjoistconcave] if concave
|
1515
|
-
set[:rimjoistconvex
|
1516
|
-
is[:rimjoist
|
2298
|
+
set[:rimjoist ] = shorts[:val][:rimjoist ] if flat
|
2299
|
+
set[:rimjoistconcave ] = shorts[:val][:rimjoistconcave ] if concave
|
2300
|
+
set[:rimjoistconvex ] = shorts[:val][:rimjoistconvex ] if convex
|
2301
|
+
is[:rimjoist ] = true
|
1517
2302
|
end
|
1518
2303
|
end
|
1519
|
-
end
|
2304
|
+
end # edge's surfaces loop
|
1520
2305
|
|
1521
2306
|
edge[:psi] = set unless set.empty?
|
1522
2307
|
edge[:set] = psi unless set.empty?
|
1523
|
-
end
|
2308
|
+
end # edge loop
|
1524
2309
|
|
1525
2310
|
# Tracking (mild) transitions between deratable surfaces around edges that
|
1526
2311
|
# have not been previously tagged.
|
1527
2312
|
edges.values.each do |edge|
|
2313
|
+
deratable = false
|
1528
2314
|
next if edge.key?(:psi)
|
1529
2315
|
next unless edge.key?(:surfaces)
|
1530
|
-
deratable = false
|
1531
2316
|
|
1532
2317
|
edge[:surfaces].keys.each do |id|
|
1533
2318
|
next unless tbd[:surfaces].key?(id)
|
1534
2319
|
next unless tbd[:surfaces][id][:deratable]
|
2320
|
+
|
1535
2321
|
deratable = tbd[:surfaces][id][:deratable]
|
1536
2322
|
end
|
1537
2323
|
|
1538
2324
|
next unless deratable
|
2325
|
+
|
1539
2326
|
edge[:psi] = { transition: 0.000 }
|
1540
2327
|
edge[:set] = json[:io][:building][:psi]
|
1541
2328
|
end
|
@@ -1547,6 +2334,7 @@ module TBD
|
|
1547
2334
|
next if edge.key?(:psi)
|
1548
2335
|
next unless edge.key?(:surfaces)
|
1549
2336
|
next unless edge[:surfaces].size == 1
|
2337
|
+
|
1550
2338
|
id = edge[:surfaces].first.first
|
1551
2339
|
next unless holes.key?(id)
|
1552
2340
|
next unless holes[id].attributes.key?(:unhinged)
|
@@ -1554,9 +2342,11 @@ module TBD
|
|
1554
2342
|
|
1555
2343
|
subsurface = model.getSubSurfaceByName(id)
|
1556
2344
|
next if subsurface.empty?
|
2345
|
+
|
1557
2346
|
subsurface = subsurface.get
|
1558
2347
|
surface = subsurface.surface
|
1559
2348
|
next if surface.empty?
|
2349
|
+
|
1560
2350
|
nom = surface.get.nameString
|
1561
2351
|
next unless tbd[:surfaces].key?(nom)
|
1562
2352
|
next unless tbd[:surfaces][nom].key?(:conditioned)
|
@@ -1570,90 +2360,197 @@ module TBD
|
|
1570
2360
|
edge[:set] = json[:io][:building][:psi]
|
1571
2361
|
end
|
1572
2362
|
|
1573
|
-
# A priori, TBD applies (default) :building PSI types and values to
|
1574
|
-
# individual edges. If a TBD JSON input file holds custom PSI sets for:
|
1575
|
-
# :stories
|
1576
|
-
# :spacetypes
|
1577
|
-
# :surfaces
|
1578
|
-
# :edges
|
1579
|
-
# ... that may apply to individual edges, then the default :building PSI
|
1580
|
-
# types and/or values are overridden, as follows:
|
1581
|
-
# custom :stories PSI sets trump :building PSI sets
|
1582
|
-
# custom :spacetypes PSI sets trump aforementioned PSI sets
|
1583
|
-
# custom :spaces PSI sets trump aforementioned PSI sets
|
1584
|
-
# custom :surfaces PSI sets trump aforementioned PSI sets
|
1585
|
-
# custom :edges PSI sets trump aforementioned PSI sets
|
1586
2363
|
if json[:io]
|
1587
|
-
|
2364
|
+
# Reset subsurface U-factors (if on file).
|
2365
|
+
if json[:io].key?(:subsurfaces)
|
1588
2366
|
json[:io][:subsurfaces].each do |sub|
|
2367
|
+
match = false
|
1589
2368
|
next unless sub.key?(:id)
|
1590
2369
|
next unless sub.key?(:usi)
|
1591
|
-
match = false
|
1592
2370
|
|
1593
2371
|
tbd[:surfaces].values.each do |surface|
|
1594
2372
|
break if match
|
1595
2373
|
|
1596
2374
|
[:windows, :doors, :skylights].each do |types|
|
1597
|
-
if
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
2375
|
+
break if match
|
2376
|
+
next unless surface.key?(types)
|
2377
|
+
|
2378
|
+
surface[types].each do |id, opening|
|
2379
|
+
break if match
|
2380
|
+
next unless opening.key?(:u)
|
2381
|
+
next unless sub[:id] == id
|
2382
|
+
|
2383
|
+
opening[:u] = sub[:usi]
|
2384
|
+
match = true
|
1604
2385
|
end
|
1605
2386
|
end
|
1606
2387
|
end
|
1607
2388
|
end
|
1608
2389
|
end
|
1609
2390
|
|
2391
|
+
# Reset wall-to-roof intersection type (if on file) ... per group.
|
2392
|
+
[:stories, :spacetypes, :spaces].each do |groups|
|
2393
|
+
key = :story
|
2394
|
+
key = :stype if groups == :spacetypes
|
2395
|
+
key = :space if groups == :spaces
|
2396
|
+
next unless json[:io].key?(groups)
|
2397
|
+
|
2398
|
+
json[:io][groups].each do |group|
|
2399
|
+
next unless group.key?(:id)
|
2400
|
+
next unless group.key?(:parapet)
|
2401
|
+
|
2402
|
+
edges.values.each do |edge|
|
2403
|
+
match = false
|
2404
|
+
next unless edge.key?(:psi)
|
2405
|
+
next unless edge.key?(:surfaces)
|
2406
|
+
next if edge.key?(:io_type)
|
2407
|
+
|
2408
|
+
edge[:surfaces].keys.each do |id|
|
2409
|
+
break if match
|
2410
|
+
next unless tbd[:surfaces].key?(id)
|
2411
|
+
next unless tbd[:surfaces][id].key?(key)
|
2412
|
+
|
2413
|
+
match = group[:id] == tbd[:surfaces][id][key].nameString
|
2414
|
+
end
|
2415
|
+
|
2416
|
+
next unless match
|
2417
|
+
|
2418
|
+
parapets = edge[:psi].keys.select {|ty| ty.to_s.include?("parapet")}
|
2419
|
+
roofs = edge[:psi].keys.select {|ty| ty.to_s.include?("roof")}
|
2420
|
+
|
2421
|
+
if group[:parapet]
|
2422
|
+
next unless parapets.empty?
|
2423
|
+
next if roofs.empty?
|
2424
|
+
|
2425
|
+
type = :parapet
|
2426
|
+
type = :parapetconcave if roofs.first.to_s.include?("concave")
|
2427
|
+
type = :parapetconvex if roofs.first.to_s.include?("convex")
|
2428
|
+
|
2429
|
+
edge[:psi][type] = shorts[:val][type]
|
2430
|
+
roofs.each {|ty| edge[:psi].delete(ty)}
|
2431
|
+
else
|
2432
|
+
next unless roofs.empty?
|
2433
|
+
next if parapets.empty?
|
2434
|
+
|
2435
|
+
type = :roof
|
2436
|
+
type = :roofconcave if parapets.first.to_s.include?("concave")
|
2437
|
+
type = :roofconvex if parapets.first.to_s.include?("convex")
|
2438
|
+
|
2439
|
+
edge[:psi][type] = shorts[:val][type]
|
2440
|
+
|
2441
|
+
parapets.each { |ty| edge[:psi].delete(ty) }
|
2442
|
+
end
|
2443
|
+
end
|
2444
|
+
end
|
2445
|
+
end
|
2446
|
+
|
2447
|
+
# Reset wall-to-roof intersection type (if on file) - individual surfaces.
|
2448
|
+
if json[:io].key?(:surfaces)
|
2449
|
+
json[:io][:surfaces].each do |surface|
|
2450
|
+
next unless surface.key?(:parapet)
|
2451
|
+
next unless surface.key?(:id)
|
2452
|
+
|
2453
|
+
edges.values.each do |edge|
|
2454
|
+
next if edge.key?(:io_type)
|
2455
|
+
next unless edge.key?(:psi)
|
2456
|
+
next unless edge.key?(:surfaces)
|
2457
|
+
next unless edge[:surfaces].keys.include?(surface[:id])
|
2458
|
+
|
2459
|
+
parapets = edge[:psi].keys.select {|ty| ty.to_s.include?("parapet")}
|
2460
|
+
roofs = edge[:psi].keys.select {|ty| ty.to_s.include?("roof")}
|
2461
|
+
|
2462
|
+
|
2463
|
+
if surface[:parapet]
|
2464
|
+
next unless parapets.empty?
|
2465
|
+
next if roofs.empty?
|
2466
|
+
|
2467
|
+
type = :parapet
|
2468
|
+
type = :parapetconcave if roofs.first.to_s.include?("concave")
|
2469
|
+
type = :parapetconvex if roofs.first.to_s.include?("convex")
|
2470
|
+
|
2471
|
+
edge[:psi][type] = shorts[:val][type]
|
2472
|
+
roofs.each {|ty| edge[:psi].delete(ty)}
|
2473
|
+
else
|
2474
|
+
next unless roofs.empty?
|
2475
|
+
next if parapets.empty?
|
2476
|
+
|
2477
|
+
type = :roof
|
2478
|
+
type = :roofconcave if parapets.first.to_s.include?("concave")
|
2479
|
+
type = :roofconvex if parapets.first.to_s.include?("convex")
|
2480
|
+
|
2481
|
+
edge[:psi][type] = shorts[:val][type]
|
2482
|
+
parapets.each {|ty| edge[:psi].delete(ty)}
|
2483
|
+
end
|
2484
|
+
end
|
2485
|
+
end
|
2486
|
+
end
|
2487
|
+
|
2488
|
+
# A priori, TBD applies (default) :building PSI types and values to
|
2489
|
+
# individual edges. If a TBD JSON input file holds custom PSI sets for:
|
2490
|
+
# :stories
|
2491
|
+
# :spacetypes
|
2492
|
+
# :surfaces
|
2493
|
+
# :edges
|
2494
|
+
# ... that may apply to individual edges, then the default :building PSI
|
2495
|
+
# types and/or values are overridden, as follows:
|
2496
|
+
# custom :stories PSI sets trump :building PSI sets
|
2497
|
+
# custom :spacetypes PSI sets trump aforementioned PSI sets
|
2498
|
+
# custom :spaces PSI sets trump aforementioned PSI sets
|
2499
|
+
# custom :surfaces PSI sets trump aforementioned PSI sets
|
2500
|
+
# custom :edges PSI sets trump aforementioned PSI sets
|
1610
2501
|
[:stories, :spacetypes, :spaces].each do |groups|
|
1611
2502
|
key = :story
|
1612
2503
|
key = :stype if groups == :spacetypes
|
1613
2504
|
key = :space if groups == :spaces
|
1614
|
-
next
|
2505
|
+
next unless json[:io].key?(groups)
|
1615
2506
|
|
1616
2507
|
json[:io][groups].each do |group|
|
1617
2508
|
next unless group.key?(:id)
|
1618
2509
|
next unless group.key?(:psi)
|
1619
2510
|
next unless json[:psi].set.key?(group[:psi])
|
1620
|
-
|
1621
|
-
|
2511
|
+
|
2512
|
+
sh = json[:psi].shorthands(group[:psi])
|
2513
|
+
next if sh[:val].empty?
|
1622
2514
|
|
1623
2515
|
edges.values.each do |edge|
|
1624
|
-
|
2516
|
+
match = false
|
1625
2517
|
next unless edge.key?(:psi)
|
1626
2518
|
next unless edge.key?(:surfaces)
|
2519
|
+
next if edge.key?(:io_set)
|
1627
2520
|
|
1628
2521
|
edge[:surfaces].keys.each do |id|
|
2522
|
+
break if match
|
1629
2523
|
next unless tbd[:surfaces].key?(id)
|
1630
2524
|
next unless tbd[:surfaces][id].key?(key)
|
1631
|
-
next unless group[:id] == tbd[:surfaces][id][key].nameString
|
1632
2525
|
|
1633
|
-
|
1634
|
-
|
1635
|
-
set = {}
|
2526
|
+
match = group[:id] == tbd[:surfaces][id][key].nameString
|
2527
|
+
end
|
1636
2528
|
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
safer = json[:psi].safe(group[:psi], type)
|
1643
|
-
set[type] = sh[:val][safer] if safer
|
1644
|
-
end
|
1645
|
-
end
|
2529
|
+
next unless match
|
2530
|
+
|
2531
|
+
set = {}
|
2532
|
+
edge[groups] = {} unless edge.key?(groups)
|
2533
|
+
edge[groups][group[:psi]] = {}
|
1646
2534
|
|
1647
|
-
|
2535
|
+
if edge.key?(:io_type)
|
2536
|
+
safer = json[:psi].safe(group[:psi], edge[:io_type])
|
2537
|
+
set[edge[:io_type]] = sh[:val][safer] if safer
|
2538
|
+
else
|
2539
|
+
edge[:psi].keys.each do |type|
|
2540
|
+
safer = json[:psi].safe(group[:psi], type)
|
2541
|
+
set[type] = sh[:val][safer] if safer
|
2542
|
+
end
|
1648
2543
|
end
|
2544
|
+
|
2545
|
+
edge[groups][group[:psi]] = set unless set.empty?
|
1649
2546
|
end
|
1650
2547
|
end
|
1651
2548
|
|
1652
2549
|
# TBD/Topolys edges will generally be linked to more than one surface
|
1653
|
-
# and hence to more than one
|
1654
|
-
# to hold 2x
|
1655
|
-
# common to both
|
1656
|
-
# PSI type/value from either
|
2550
|
+
# and hence to more than one group. It is possible for a TBD JSON file
|
2551
|
+
# to hold 2x group PSI sets that end up targetting one or more edges
|
2552
|
+
# common to both groups. In such cases, TBD retains the most conductive
|
2553
|
+
# PSI type/value from either group PSI set.
|
1657
2554
|
edges.values.each do |edge|
|
1658
2555
|
next unless edge.key?(:psi)
|
1659
2556
|
next unless edge.key?(groups)
|
@@ -1662,13 +2559,15 @@ module TBD
|
|
1662
2559
|
vals = {}
|
1663
2560
|
|
1664
2561
|
edge[groups].keys.each do |set|
|
1665
|
-
sh
|
1666
|
-
next if
|
2562
|
+
sh = json[:psi].shorthands(set)
|
2563
|
+
next if sh[:val].empty?
|
2564
|
+
|
1667
2565
|
safer = json[:psi].safe(set, type)
|
1668
2566
|
vals[set] = sh[:val][safer] if safer
|
1669
2567
|
end
|
1670
2568
|
|
1671
|
-
next if
|
2569
|
+
next if vals.empty?
|
2570
|
+
|
1672
2571
|
edge[:psi ][type] = vals.values.max
|
1673
2572
|
edge[:sets] = {} unless edge.key?(:sets)
|
1674
2573
|
edge[:sets][type] = vals.key(vals.values.max)
|
@@ -1678,35 +2577,37 @@ module TBD
|
|
1678
2577
|
|
1679
2578
|
if json[:io].key?(:surfaces)
|
1680
2579
|
json[:io][:surfaces].each do |surface|
|
1681
|
-
next unless surface.key?(:id)
|
1682
2580
|
next unless surface.key?(:psi)
|
2581
|
+
next unless surface.key?(:id)
|
2582
|
+
next unless tbd[:surfaces].key?(surface[:id ])
|
1683
2583
|
next unless json[:psi].set.key?(surface[:psi])
|
1684
|
-
|
1685
|
-
|
2584
|
+
|
2585
|
+
sh = json[:psi].shorthands(surface[:psi])
|
2586
|
+
next if sh[:val].empty?
|
1686
2587
|
|
1687
2588
|
edges.values.each do |edge|
|
1688
2589
|
next if edge.key?(:io_set)
|
1689
2590
|
next unless edge.key?(:psi)
|
1690
2591
|
next unless edge.key?(:surfaces)
|
2592
|
+
next unless edge[:surfaces].keys.include?(surface[:id])
|
1691
2593
|
|
1692
|
-
edge[:surfaces]
|
1693
|
-
|
1694
|
-
next unless surface[:id] == id
|
1695
|
-
set = {}
|
2594
|
+
s = edge[:surfaces][surface[:id]]
|
2595
|
+
set = {}
|
1696
2596
|
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
end
|
2597
|
+
if edge.key?(:io_type)
|
2598
|
+
safer = json[:psi].safe(surface[:psi], edge[:io_type])
|
2599
|
+
set[:io_type] = sh[:val][safer] if safer
|
2600
|
+
else
|
2601
|
+
edge[:psi].keys.each do |type|
|
2602
|
+
safer = json[:psi].safe(surface[:psi], type)
|
2603
|
+
set[type] = sh[:val][safer] if safer
|
1705
2604
|
end
|
1706
|
-
|
1707
|
-
s[:psi] = set unless set.empty?
|
1708
|
-
s[:set] = surface[:psi] unless set.empty?
|
1709
2605
|
end
|
2606
|
+
|
2607
|
+
next if set.empty?
|
2608
|
+
|
2609
|
+
s[:psi] = set
|
2610
|
+
s[:set] = surface[:psi]
|
1710
2611
|
end
|
1711
2612
|
end
|
1712
2613
|
|
@@ -1721,17 +2622,20 @@ module TBD
|
|
1721
2622
|
vals = {}
|
1722
2623
|
|
1723
2624
|
edge[:surfaces].each do |id, s|
|
1724
|
-
next
|
1725
|
-
next
|
1726
|
-
next
|
1727
|
-
|
1728
|
-
|
2625
|
+
next unless s.key?(:psi)
|
2626
|
+
next unless s.key?(:set)
|
2627
|
+
next if s[:set].empty?
|
2628
|
+
|
2629
|
+
sh = json[:psi].shorthands(s[:set])
|
2630
|
+
next if sh[:val].empty?
|
2631
|
+
|
1729
2632
|
safer = json[:psi].safe(s[:set], type)
|
1730
2633
|
vals[s[:set]] = sh[:val][safer] if safer
|
1731
2634
|
end
|
1732
2635
|
|
1733
|
-
next
|
1734
|
-
|
2636
|
+
next if vals.empty?
|
2637
|
+
|
2638
|
+
edge[:psi ][type] = vals.values.max
|
1735
2639
|
edge[:sets] = {} unless edge.key?(:sets)
|
1736
2640
|
edge[:sets][type] = vals.key(vals.values.max)
|
1737
2641
|
end
|
@@ -1746,15 +2650,18 @@ module TBD
|
|
1746
2650
|
|
1747
2651
|
if edge.key?(:io_set)
|
1748
2652
|
next unless json[:psi].set.key?(edge[:io_set])
|
2653
|
+
|
1749
2654
|
set = edge[:io_set]
|
1750
2655
|
else
|
1751
2656
|
next unless edge[:sets].key?(edge[:io_type])
|
1752
2657
|
next unless json[:psi].set.key?(edge[:sets][edge[:io_type]])
|
2658
|
+
|
1753
2659
|
set = edge[:sets][edge[:io_type]]
|
1754
2660
|
end
|
1755
2661
|
|
1756
2662
|
sh = json[:psi].shorthands(set)
|
1757
2663
|
next if sh[:val].empty?
|
2664
|
+
|
1758
2665
|
safer = json[:psi].safe(set, edge[:io_type])
|
1759
2666
|
next unless safer
|
1760
2667
|
|
@@ -1772,9 +2679,10 @@ module TBD
|
|
1772
2679
|
|
1773
2680
|
# Fetch edge multipliers for subsurfaces, if applicable.
|
1774
2681
|
edges.values.each do |edge|
|
1775
|
-
next if edge.key?(:mult)
|
2682
|
+
next if edge.key?(:mult) # skip if already assigned
|
1776
2683
|
next unless edge.key?(:surfaces)
|
1777
2684
|
next unless edge.key?(:psi)
|
2685
|
+
|
1778
2686
|
ok = false
|
1779
2687
|
|
1780
2688
|
edge[:psi].keys.each do |k|
|
@@ -1786,10 +2694,10 @@ module TBD
|
|
1786
2694
|
ok = jamb || sill || head
|
1787
2695
|
end
|
1788
2696
|
|
1789
|
-
next unless ok
|
2697
|
+
next unless ok # if OK, edge links subsurface(s) ... yet which one(s)?
|
1790
2698
|
|
1791
2699
|
edge[:surfaces].each do |id, surface|
|
1792
|
-
next unless tbd[:surfaces].key?(id)
|
2700
|
+
next unless tbd[:surfaces].key?(id) # look up parent (opaque) surface
|
1793
2701
|
|
1794
2702
|
[:windows, :doors, :skylights].each do |subtypes|
|
1795
2703
|
next unless tbd[:surfaces][id].key?(subtypes)
|
@@ -1801,7 +2709,7 @@ module TBD
|
|
1801
2709
|
# An edge may be tagged with (potentially conflicting) multipliers.
|
1802
2710
|
# This is only possible if the edge links 2 subsurfaces, e.g. a
|
1803
2711
|
# shared jamb between window & door. By default, TBD tags common
|
1804
|
-
# subsurface edges as (mild) "transitions" (i.e. PSI 0 W/K
|
2712
|
+
# subsurface edges as (mild) "transitions" (i.e. PSI 0 W/K•m), so
|
1805
2713
|
# there would be no point in assigning an edge multiplier. Users
|
1806
2714
|
# can however reset an edge type via a TBD JSON input file (e.g.
|
1807
2715
|
# "joint" instead of "transition"). It would be a very odd choice,
|
@@ -1820,9 +2728,9 @@ module TBD
|
|
1820
2728
|
# edges' origin and terminal vertices must be in close proximity. Edges
|
1821
2729
|
# of unhinged subsurfaces are ignored.
|
1822
2730
|
edges.each do |id, edge|
|
1823
|
-
nb = 0
|
2731
|
+
nb = 0 # linked subsurfaces (i.e. "holes")
|
1824
2732
|
match = false
|
1825
|
-
next if edge.key?(:io_type)
|
2733
|
+
next if edge.key?(:io_type) # skip if set in JSON
|
1826
2734
|
next unless edge.key?(:v0)
|
1827
2735
|
next unless edge.key?(:v1)
|
1828
2736
|
next unless edge.key?(:psi)
|
@@ -1879,6 +2787,7 @@ module TBD
|
|
1879
2787
|
# Loop through each edge and assign heat loss to linked surfaces.
|
1880
2788
|
edges.each do |identifier, edge|
|
1881
2789
|
next unless edge.key?(:psi)
|
2790
|
+
|
1882
2791
|
rsi = 0
|
1883
2792
|
max = edge[:psi ].values.max
|
1884
2793
|
type = edge[:psi ].key(max)
|
@@ -1896,11 +2805,12 @@ module TBD
|
|
1896
2805
|
edge[:surfaces].each do |id, s|
|
1897
2806
|
next unless tbd[:surfaces].key?(id)
|
1898
2807
|
next unless tbd[:surfaces][id][:deratable]
|
2808
|
+
|
1899
2809
|
deratables[id] = s
|
1900
2810
|
end
|
1901
2811
|
|
1902
2812
|
edge[:surfaces].each { |id, s| apertures[id] = s if holes.key?(id) }
|
1903
|
-
next if apertures.size > 1
|
2813
|
+
next if apertures.size > 1 # edge links 2x openings
|
1904
2814
|
|
1905
2815
|
# Prune dad if edge links an opening, its dad and an uncle.
|
1906
2816
|
if deratables.size > 1 && apertures.size > 0
|
@@ -1920,6 +2830,7 @@ module TBD
|
|
1920
2830
|
# Sum RSI of targeted insulating layer from each deratable surface.
|
1921
2831
|
deratables.each do |id, deratable|
|
1922
2832
|
next unless tbd[:surfaces][id].key?(:r)
|
2833
|
+
|
1923
2834
|
rsi += tbd[:surfaces][id][:r]
|
1924
2835
|
end
|
1925
2836
|
|
@@ -1938,8 +2849,10 @@ module TBD
|
|
1938
2849
|
# Assign thermal bridging heat loss [in W/K] to each deratable surface.
|
1939
2850
|
tbd[:surfaces].each do |id, surface|
|
1940
2851
|
next unless surface.key?(:edges)
|
2852
|
+
|
1941
2853
|
surface[:heatloss] = 0
|
1942
2854
|
e = surface[:edges].values
|
2855
|
+
|
1943
2856
|
e.each { |edge| surface[:heatloss] += edge[:psi] * edge[:length] }
|
1944
2857
|
end
|
1945
2858
|
|
@@ -1959,9 +2872,11 @@ module TBD
|
|
1959
2872
|
next unless k.key?(:count)
|
1960
2873
|
next unless json[:khi].point.key?(k[:id])
|
1961
2874
|
next unless json[:khi].point[k[:id]] > 0.001
|
1962
|
-
|
1963
|
-
s[:heatloss]
|
1964
|
-
s[:
|
2875
|
+
|
2876
|
+
s[:heatloss] = 0 unless s.key?(:heatloss)
|
2877
|
+
s[:heatloss] += json[:khi].point[k[:id]] * k[:count]
|
2878
|
+
s[:pts ] = {} unless s.key?(:pts)
|
2879
|
+
|
1965
2880
|
s[:pts][k[:id]] = { val: json[:khi].point[k[:id]], n: k[:count] }
|
1966
2881
|
end
|
1967
2882
|
end
|
@@ -1970,8 +2885,8 @@ module TBD
|
|
1970
2885
|
# If user has selected a Ut to meet, e.g. argh'ments:
|
1971
2886
|
# :uprate_walls
|
1972
2887
|
# :wall_ut
|
1973
|
-
# :wall_option
|
1974
|
-
#
|
2888
|
+
# :wall_option ... (same triple arguments for roofs and exposed floors)
|
2889
|
+
#
|
1975
2890
|
# ... first 'uprate' targeted insulation layers (see ua.rb) before derating.
|
1976
2891
|
# Check for new argh keys [:wall_uo], [:roof_uo] and/or [:floor_uo].
|
1977
2892
|
up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
|
@@ -1985,74 +2900,75 @@ module TBD
|
|
1985
2900
|
# (or rather layered materials) having " tbd" in their OpenStudio name.
|
1986
2901
|
tbd[:surfaces].each do |id, surface|
|
1987
2902
|
next unless surface.key?(:construction)
|
1988
|
-
next unless surface.key?(:index
|
1989
|
-
next unless surface.key?(:ltype
|
1990
|
-
next unless surface.key?(:r
|
1991
|
-
next unless surface.key?(:edges
|
1992
|
-
next unless surface.key?(:heatloss
|
2903
|
+
next unless surface.key?(:index)
|
2904
|
+
next unless surface.key?(:ltype)
|
2905
|
+
next unless surface.key?(:r)
|
2906
|
+
next unless surface.key?(:edges)
|
2907
|
+
next unless surface.key?(:heatloss)
|
1993
2908
|
next unless surface[:heatloss].abs > TOL
|
1994
2909
|
|
1995
|
-
model.
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2910
|
+
s = model.getSurfaceByName(id)
|
2911
|
+
next if s.empty?
|
2912
|
+
|
2913
|
+
s = s.get
|
2914
|
+
|
2915
|
+
index = surface[:index ]
|
2916
|
+
current_c = surface[:construction]
|
2917
|
+
c = current_c.clone(model).to_LayeredConstruction.get
|
2918
|
+
m = nil
|
2919
|
+
m = derate(id, surface, c) if index
|
2920
|
+
# m may be nilled simply because the targeted construction has already
|
2921
|
+
# been derated, i.e. holds " tbd" in its name. Names of cloned/derated
|
2922
|
+
# constructions (due to TBD) include the surface name (since derated
|
2923
|
+
# constructions are now unique to each surface) and the suffix " c tbd".
|
2924
|
+
if m
|
2925
|
+
c.setLayer(index, m)
|
2926
|
+
c.setName("#{id} c tbd")
|
2927
|
+
current_R = rsi(current_c, s.filmResistance)
|
2928
|
+
|
2929
|
+
# In principle, the derated "ratio" could be calculated simply by
|
2930
|
+
# accessing a surface's uFactor. Yet air layers within constructions
|
2931
|
+
# (not air films) are ignored in OpenStudio's uFactor calculation.
|
2932
|
+
# An example would be 25mm-50mm pressure-equalized air gaps behind
|
2933
|
+
# brick veneer. This is not always compliant to some energy codes.
|
2934
|
+
# TBD currently factors-in air gap (and exterior cladding) R-values.
|
2935
|
+
#
|
2936
|
+
# If one comments out the following loop (3 lines), tested surfaces
|
2937
|
+
# with air layers will generate discrepencies between the calculed RSi
|
2938
|
+
# value above and the inverse of the uFactor. All other surface
|
2939
|
+
# constructions pass the test.
|
2940
|
+
#
|
2941
|
+
# if ((1/current_R) - s.uFactor.to_f).abs > 0.005
|
2942
|
+
# puts "#{s.nameString} - Usi:#{1/current_R} UFactor: #{s.uFactor}"
|
2943
|
+
# end
|
2944
|
+
s.setConstruction(c)
|
2945
|
+
|
2946
|
+
# If the derated surface construction separates CONDITIONED space from
|
2947
|
+
# UNCONDITIONED or UNENCLOSED space, then derate the adjacent surface
|
2948
|
+
# construction as well (unless defaulted).
|
2949
|
+
if s.outsideBoundaryCondition.downcase == "surface"
|
2950
|
+
unless s.adjacentSurface.empty?
|
2951
|
+
adjacent = s.adjacentSurface.get
|
2952
|
+
nom = adjacent.nameString
|
2953
|
+
default = adjacent.isConstructionDefaulted == false
|
2954
|
+
|
2955
|
+
if default && tbd[:surfaces].key?(nom)
|
2956
|
+
current_cc = tbd[:surfaces][nom][:construction]
|
2957
|
+
cc = current_cc.clone(model).to_LayeredConstruction.get
|
2958
|
+
cc.setLayer(tbd[:surfaces][nom][:index], m)
|
2959
|
+
cc.setName("#{nom} c tbd")
|
2960
|
+
adjacent.setConstruction(cc)
|
2045
2961
|
end
|
2046
2962
|
end
|
2963
|
+
end
|
2047
2964
|
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2965
|
+
# Compute updated RSi value from layers.
|
2966
|
+
updated_c = s.construction.get.to_LayeredConstruction.get
|
2967
|
+
updated_R = rsi(updated_c, s.filmResistance)
|
2968
|
+
ratio = -(current_R - updated_R) * 100 / current_R
|
2052
2969
|
|
2053
|
-
|
2054
|
-
|
2055
|
-
end
|
2970
|
+
surface[:ratio] = ratio if ratio.abs > TOL
|
2971
|
+
surface[:u ] = 1 / current_R # un-derated U-factors (for UA')
|
2056
2972
|
end
|
2057
2973
|
end
|
2058
2974
|
|
@@ -2061,9 +2977,12 @@ module TBD
|
|
2061
2977
|
next unless surface[:deratable]
|
2062
2978
|
next unless surface.key?(:construction)
|
2063
2979
|
next if surface.key?(:u)
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2980
|
+
|
2981
|
+
s = model.getSurfaceByName(id)
|
2982
|
+
msg = "Skipping missing surface '#{id}' (#{mth})"
|
2983
|
+
log(ERR, msg) if s.empty?
|
2984
|
+
next if s.empty?
|
2985
|
+
|
2067
2986
|
surface[:u] = 1.0 / rsi(surface[:construction], s.get.filmResistance)
|
2068
2987
|
end
|
2069
2988
|
|
@@ -2075,14 +2994,16 @@ module TBD
|
|
2075
2994
|
# 4. edge origin & end vertices
|
2076
2995
|
# 5. array of linked outside- or ground-facing surfaces
|
2077
2996
|
edges.values.each do |e|
|
2078
|
-
next
|
2079
|
-
next
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
l
|
2085
|
-
|
2997
|
+
next unless e.key?(:psi)
|
2998
|
+
next unless e.key?(:set)
|
2999
|
+
|
3000
|
+
v = e[:psi].values.max
|
3001
|
+
set = e[:set]
|
3002
|
+
t = e[:psi].key(v)
|
3003
|
+
l = e[:length]
|
3004
|
+
l *= e[:mult] if e.key?(:mult)
|
3005
|
+
edge = { psi: set, type: t, length: l, surfaces: e[:surfaces].keys }
|
3006
|
+
|
2086
3007
|
edge[:v0x] = e[:v0].point.x
|
2087
3008
|
edge[:v0y] = e[:v0].point.y
|
2088
3009
|
edge[:v0z] = e[:v0].point.z
|
@@ -2093,42 +3014,71 @@ module TBD
|
|
2093
3014
|
json[:io][:edges] << edge
|
2094
3015
|
end
|
2095
3016
|
|
2096
|
-
|
2097
|
-
|
2098
|
-
|
2099
|
-
|
3017
|
+
if json[:io][:edges].empty?
|
3018
|
+
json[:io].delete(:edges)
|
3019
|
+
else
|
3020
|
+
json[:io][:edges].sort_by { |e| [ e[:v0x], e[:v0y], e[:v0z],
|
3021
|
+
e[:v1x], e[:v1y], e[:v1z] ] }
|
3022
|
+
end
|
2100
3023
|
|
2101
3024
|
# Populate UA' trade-off reference values (optional).
|
2102
|
-
|
2103
|
-
|
3025
|
+
if argh[:gen_ua] && argh[:ua_ref]
|
3026
|
+
case argh[:ua_ref]
|
3027
|
+
when "code (Quebec)"
|
3028
|
+
qc33(tbd[:surfaces], json[:psi], argh[:setpoints])
|
3029
|
+
end
|
3030
|
+
end
|
2104
3031
|
|
2105
|
-
tbd[:io] = json[:io]
|
3032
|
+
tbd[:io ] = json[:io ]
|
3033
|
+
argh[:io ] = tbd[:io ]
|
3034
|
+
argh[:surfaces] = tbd[:surfaces]
|
3035
|
+
argh[:version ] = model.getVersion.versionIdentifier
|
2106
3036
|
|
2107
3037
|
tbd
|
2108
3038
|
end
|
2109
3039
|
|
2110
3040
|
##
|
2111
|
-
# TBD
|
2112
|
-
#
|
2113
|
-
# (see tbd.out.json).
|
3041
|
+
# Exits TBD Measures. Writes out TBD model content and results if requested.
|
3042
|
+
# Always writes out minimal logs (see "tbd.out.json" file).
|
2114
3043
|
#
|
2115
3044
|
# @param runner [Runner] OpenStudio Measure runner
|
2116
|
-
# @param
|
3045
|
+
# @param [Hash] argh TBD arguments
|
3046
|
+
# @option argh [Hash] :io TBD input/output variables (see TBD JSON schema)
|
3047
|
+
# @option argh [Hash] :surfaces TBD surfaces (keys: Openstudio surface names)
|
3048
|
+
# @option argh [#to_s] :seed OpenStudio file, e.g. "school23.osm"
|
3049
|
+
# @option argh [#to_s] :version :version OpenStudio SDK, e.g. "3.6.1"
|
3050
|
+
# @option argh [Bool] :gen_ua whether to generate a UA' report
|
3051
|
+
# @option argh [#to_s] :ua_ref selected UA' ruleset
|
3052
|
+
# @option argh [Bool] :setpoints whether OpenStudio model holds setpoints
|
3053
|
+
# @option argh [Bool] :write_tbd whether to output a JSON file
|
3054
|
+
# @option argh [Bool] :uprate_walls whether to uprate walls
|
3055
|
+
# @option argh [Bool] :uprate_roofs whether to uprate roofs
|
3056
|
+
# @option argh [Bool] :uprate_floors whether to uprate floors
|
3057
|
+
# @option argh [#to_f] :wall_ut uprated wall Ut target in W/m2•K
|
3058
|
+
# @option argh [#to_f] :roof_ut uprated roof Ut target in W/m2•K
|
3059
|
+
# @option argh [#to_f] :floor_ut uprated floor Ut target in W/m2•K
|
3060
|
+
# @option argh [#to_s] :wall_option wall construction to uprate (or "all")
|
3061
|
+
# @option argh [#to_s] :roof_option roof construction to uprate (or "all")
|
3062
|
+
# @option argh [#to_s] :floor_option floor construction to uprate (or "all")
|
3063
|
+
# @option argh [#to_f] :wall_uo required wall Uo to achieve Ut in W/m2•K
|
3064
|
+
# @option argh [#to_f] :roof_uo required roof Uo to achieve Ut in W/m2•K
|
3065
|
+
# @option argh [#to_f] :floor_uo required floor Uo to achieve Ut in W/m2•K
|
2117
3066
|
#
|
2118
|
-
# @return [Bool]
|
3067
|
+
# @return [Bool] whether TBD Measure is successful (see logs)
|
2119
3068
|
def exit(runner = nil, argh = {})
|
2120
3069
|
# Generated files target a design context ( >= WARN ) ... change TBD log
|
2121
3070
|
# level for debugging purposes. By default, log status is set < DBG
|
2122
3071
|
# while log level is set @INF.
|
2123
|
-
|
2124
|
-
state
|
3072
|
+
groups = { wall: {}, roof: {}, floor: {} }
|
3073
|
+
state = msg(status)
|
3074
|
+
state = msg(INF) if status.zero?
|
2125
3075
|
argh = {} unless argh.is_a?(Hash)
|
2126
3076
|
argh[:io ] = nil unless argh.key?(:io)
|
2127
3077
|
argh[:surfaces] = nil unless argh.key?(:surfaces)
|
2128
3078
|
|
2129
3079
|
unless argh[:io] && argh[:surfaces]
|
2130
3080
|
state = "Halting all TBD processes, yet running OpenStudio"
|
2131
|
-
state = "Halting all TBD processes, and halting OpenStudio"
|
3081
|
+
state = "Halting all TBD processes, and halting OpenStudio" if fatal?
|
2132
3082
|
end
|
2133
3083
|
|
2134
3084
|
argh[:io ] = {} unless argh[:io]
|
@@ -2151,7 +3101,6 @@ module TBD
|
|
2151
3101
|
argh[:roof_uo ] = nil unless argh.key?(:roof_ut )
|
2152
3102
|
argh[:floor_uo ] = nil unless argh.key?(:floor_ut )
|
2153
3103
|
|
2154
|
-
groups = { wall: {}, roof: {}, floor: {} }
|
2155
3104
|
groups[:wall ][:up] = argh[:uprate_walls ]
|
2156
3105
|
groups[:roof ][:up] = argh[:uprate_roofs ]
|
2157
3106
|
groups[:floor][:up] = argh[:uprate_floors]
|
@@ -2184,7 +3133,7 @@ module TBD
|
|
2184
3133
|
|
2185
3134
|
uo = format("%.3f", g[:uo])
|
2186
3135
|
ut = format("%.3f", g[:ut])
|
2187
|
-
output = "An initial #{label.to_s} Uo of #{uo} W/m2•K is required to "
|
3136
|
+
output = "An initial #{label.to_s} Uo of #{uo} W/m2•K is required to " \
|
2188
3137
|
"achieve an overall Ut of #{ut} W/m2•K for #{g[:op]}"
|
2189
3138
|
u_t << output
|
2190
3139
|
runner.registerInfo(output)
|
@@ -2195,16 +3144,16 @@ module TBD
|
|
2195
3144
|
ua_md_fr = nil
|
2196
3145
|
ua = nil
|
2197
3146
|
ok = argh[:surfaces] && argh[:gen_ua]
|
2198
|
-
ua = ua_summary(tbd_log[:date], argh)
|
3147
|
+
ua = ua_summary(tbd_log[:date], argh) if ok
|
2199
3148
|
|
2200
3149
|
unless fatal? || ua.nil? || ua.empty?
|
2201
3150
|
if ua.key?(:en)
|
2202
3151
|
if ua[:en].key?(:b1) || ua[:en].key?(:b2)
|
3152
|
+
tbd_log[:ua] = {}
|
2203
3153
|
runner.registerInfo("-")
|
2204
3154
|
runner.registerInfo(ua[:model])
|
2205
|
-
|
2206
|
-
|
2207
|
-
ua_md_fr = ua_md(ua, :fr)
|
3155
|
+
ua_md_en = ua_md(ua, :en)
|
3156
|
+
ua_md_fr = ua_md(ua, :fr)
|
2208
3157
|
end
|
2209
3158
|
|
2210
3159
|
if ua[:en].key?(:b1) && ua[:en][:b1].key?(:summary)
|
@@ -2237,9 +3186,9 @@ module TBD
|
|
2237
3186
|
argh[:surfaces].each do |id, surface|
|
2238
3187
|
next if fatal?
|
2239
3188
|
next unless surface.key?(:ratio)
|
2240
|
-
ratio = format("%4.1f", surface[:ratio])
|
2241
|
-
output = "RSi derated by #{ratio}% : #{id}"
|
2242
3189
|
|
3190
|
+
ratio = format("%4.1f", surface[:ratio])
|
3191
|
+
output = "RSi derated by #{ratio}% : #{id}"
|
2243
3192
|
results << output
|
2244
3193
|
runner.registerInfo(output)
|
2245
3194
|
end
|
@@ -2285,14 +3234,14 @@ module TBD
|
|
2285
3234
|
file_paths = runner.workflow.absoluteFilePaths
|
2286
3235
|
|
2287
3236
|
# 'Apply Measure Now' won't cp files from 1st path back to generated_files.
|
2288
|
-
match1 = /WorkingFiles/.match(file_paths[1].to_s)
|
2289
|
-
match2 = /files/.match(file_paths[1].to_s)
|
3237
|
+
match1 = /WorkingFiles/.match(file_paths[1].to_s.strip)
|
3238
|
+
match2 = /files/.match(file_paths[1].to_s.strip)
|
2290
3239
|
match = match1 || match2
|
2291
3240
|
|
2292
|
-
if file_paths.size >= 2 && File.exists?(file_paths[1].to_s) && match
|
2293
|
-
out_dir = file_paths[1].to_s
|
2294
|
-
elsif !file_paths.empty? && File.exists?(file_paths.first.to_s)
|
2295
|
-
out_dir = file_paths.first.to_s
|
3241
|
+
if file_paths.size >= 2 && File.exists?(file_paths[1].to_s.strip) && match
|
3242
|
+
out_dir = file_paths[1].to_s.strip
|
3243
|
+
elsif !file_paths.empty? && File.exists?(file_paths.first.to_s.strip)
|
3244
|
+
out_dir = file_paths.first.to_s.strip
|
2296
3245
|
end
|
2297
3246
|
|
2298
3247
|
out_path = File.join(out_dir, "tbd.out.json")
|
@@ -2307,7 +3256,7 @@ module TBD
|
|
2307
3256
|
end
|
2308
3257
|
end
|
2309
3258
|
|
2310
|
-
unless
|
3259
|
+
unless fatal? || ua.nil? || ua.empty?
|
2311
3260
|
unless ua_md_en.nil? || ua_md_en.empty?
|
2312
3261
|
ua_path = File.join(out_dir, "ua_en.md")
|
2313
3262
|
|