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