tbd 3.1.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/pull_request.yml +16 -0
- data/LICENSE.md +1 -1
- data/lib/measures/tbd/LICENSE.md +1 -1
- data/lib/measures/tbd/README.md +8 -0
- data/lib/measures/tbd/measure.rb +44 -2
- data/lib/measures/tbd/measure.xml +33 -24
- data/lib/measures/tbd/resources/geo.rb +56 -16
- data/lib/measures/tbd/resources/oslog.rb +17 -7
- data/lib/measures/tbd/resources/psi.rb +113 -7
- data/lib/measures/tbd/resources/tbd.rb +1 -1
- data/lib/measures/tbd/resources/ua.rb +36 -69
- data/lib/measures/tbd/resources/utils.rb +86 -28
- data/lib/measures/tbd/tests/tbd_tests.rb +117 -87
- data/lib/tbd/geo.rb +56 -16
- data/lib/tbd/psi.rb +113 -7
- data/lib/tbd/ua.rb +36 -69
- data/lib/tbd/version.rb +2 -2
- data/lib/tbd.rb +1 -1
- metadata +3 -3
@@ -1,6 +1,6 @@
|
|
1
1
|
# MIT License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2020-
|
3
|
+
# Copyright (c) 2020-2023 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
|
@@ -36,187 +36,217 @@ class TBDTest < Minitest::Test
|
|
36
36
|
# end
|
37
37
|
|
38
38
|
def test_number_of_arguments_and_argument_names
|
39
|
-
# create an instance of the measure
|
40
39
|
measure = TBDMeasure.new
|
41
|
-
# make an empty model
|
42
40
|
model = OpenStudio::Model::Model.new
|
43
|
-
|
44
|
-
# get arguments and test that they are what we are expecting
|
45
41
|
arguments = measure.arguments(model)
|
46
|
-
assert_equal(
|
42
|
+
assert_equal(15, arguments.size)
|
47
43
|
end
|
48
44
|
|
49
45
|
def test_no_load_tbd_json
|
50
|
-
# create an instance of the measure
|
51
46
|
measure = TBDMeasure.new
|
52
47
|
|
53
|
-
# Output
|
54
|
-
seed_dir = File.join(__dir__,
|
48
|
+
# Output directories.
|
49
|
+
seed_dir = File.join(__dir__, "output/no_load_tbd_json/")
|
55
50
|
FileUtils.mkdir_p(seed_dir)
|
56
|
-
seed_path = File.join(seed_dir,
|
51
|
+
seed_path = File.join(seed_dir, "in.osm")
|
57
52
|
|
58
|
-
#
|
53
|
+
# Create runner with empty OSW, and example test model.
|
59
54
|
osw = OpenStudio::WorkflowJSON.new
|
60
55
|
osw.setSeedFile(seed_path)
|
61
56
|
runner = OpenStudio::Measure::OSRunner.new(osw)
|
62
|
-
|
63
|
-
# create example test model
|
64
57
|
model = OpenStudio::Model::exampleModel
|
65
58
|
model.save(seed_path, true)
|
66
59
|
|
67
|
-
#
|
60
|
+
# Get measure arguments.
|
68
61
|
arguments = measure.arguments(model)
|
69
62
|
argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
|
70
63
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
|
81
|
-
# populate argument with specified hash value if specified
|
64
|
+
# Hash of argument values (defaults from measure.rb for other arguments).
|
65
|
+
argh = {}
|
66
|
+
argh["option" ] = "efficient (BETBG)"
|
67
|
+
argh["write_tbd_json"] = true
|
68
|
+
argh["gen_UA_report" ] = true
|
69
|
+
argh["wall_option" ] = "ALL wall constructions"
|
70
|
+
argh["wall_ut" ] = 0.5
|
71
|
+
|
72
|
+
# Populate arguments with specified hash value if specified.
|
82
73
|
arguments.each do |arg|
|
83
74
|
temp_arg_var = arg.clone
|
84
|
-
|
85
|
-
assert(temp_arg_var.setValue(args_hash[arg.name])) if gotit
|
75
|
+
assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
|
86
76
|
argument_map[arg.name] = temp_arg_var
|
87
77
|
end
|
88
78
|
|
89
|
-
#
|
79
|
+
# Run the measure and assert that it ran correctly.
|
90
80
|
Dir.chdir(seed_dir)
|
91
81
|
measure.run(model, runner, argument_map)
|
92
82
|
result = runner.result
|
93
|
-
|
94
|
-
# show the output
|
95
83
|
show_output(result)
|
96
|
-
|
97
|
-
# assert that it ran correctly
|
98
|
-
assert_equal('Success', result.value.valueName)
|
84
|
+
assert_equal("Success", result.value.valueName)
|
99
85
|
assert(result.warnings.empty?)
|
86
|
+
assert(result.errors.empty?)
|
100
87
|
|
101
|
-
#
|
102
|
-
|
103
|
-
|
88
|
+
# Save the model to test output directory.
|
89
|
+
output_path = File.join(seed_dir, "out.osm")
|
90
|
+
model.save(output_path, true)
|
104
91
|
end
|
105
92
|
|
106
93
|
def test_load_tbd_json
|
107
|
-
# create an instance of the measure
|
108
94
|
measure = TBDMeasure.new
|
109
95
|
|
110
|
-
# Output
|
96
|
+
# Output directories.
|
111
97
|
seed_dir = File.join(__dir__, "output/load_tbd_json/")
|
112
98
|
FileUtils.mkdir_p(seed_dir)
|
113
99
|
seed_path = File.join(seed_dir, "in.osm")
|
114
100
|
|
115
|
-
#
|
101
|
+
# Create runner with empty OSW, and example test model.
|
116
102
|
osw = OpenStudio::WorkflowJSON.new
|
117
103
|
osw.setSeedFile(seed_path)
|
118
104
|
runner = OpenStudio::Measure::OSRunner.new(osw)
|
119
|
-
|
120
|
-
# create example test model
|
121
105
|
model = OpenStudio::Model::exampleModel
|
122
106
|
model.save(seed_path, true)
|
123
107
|
|
124
|
-
#
|
108
|
+
# Copy tdb.json next to seed.
|
125
109
|
origin_pth = File.join(__dir__, "tbd_full_PSI.json")
|
126
110
|
target_pth = File.join(seed_dir, "tbd.json")
|
127
111
|
FileUtils.cp(origin_pth, target_pth)
|
128
112
|
|
129
|
-
#
|
113
|
+
# Get measure arguments.
|
130
114
|
arguments = measure.arguments(model)
|
131
115
|
argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
|
132
116
|
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
# using defaults values from measure.rb for other arguments
|
117
|
+
# Hash of argument values (defaults from measure.rb for other arguments).
|
118
|
+
argh = {}
|
119
|
+
argh["load_tbd_json" ] = true
|
137
120
|
|
138
|
-
#
|
121
|
+
# Populate arguments with specified hash value if specified.
|
139
122
|
arguments.each do |arg|
|
140
123
|
temp_arg_var = arg.clone
|
141
|
-
if
|
142
|
-
assert(temp_arg_var.setValue(args_hash[arg.name]))
|
143
|
-
end
|
124
|
+
assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
|
144
125
|
argument_map[arg.name] = temp_arg_var
|
145
126
|
end
|
146
127
|
|
147
|
-
#
|
128
|
+
# Run the measure and assert that it ran correctly.
|
148
129
|
Dir.chdir(seed_dir)
|
149
130
|
measure.run(model, runner, argument_map)
|
150
131
|
result = runner.result
|
151
|
-
|
152
|
-
# show the output
|
153
132
|
show_output(result)
|
154
|
-
|
155
|
-
# assert that it ran correctly
|
156
|
-
assert_equal('Success', result.value.valueName)
|
133
|
+
assert_equal("Success", result.value.valueName)
|
157
134
|
assert(result.warnings.empty?)
|
135
|
+
assert(result.errors.empty?)
|
158
136
|
|
159
|
-
#
|
160
|
-
|
161
|
-
|
137
|
+
# Save the model to test output directory.
|
138
|
+
output_path = File.join(seed_dir, "out.osm")
|
139
|
+
model.save(output_path, true)
|
162
140
|
end
|
163
141
|
|
164
142
|
def test_load_tbd_json_error
|
165
|
-
# create an instance of the measure
|
166
143
|
measure = TBDMeasure.new
|
167
144
|
|
168
|
-
# Output
|
145
|
+
# Output directories.
|
169
146
|
seed_dir = File.join(__dir__, "output/load_tbd_json_error/")
|
170
147
|
FileUtils.mkdir_p(seed_dir)
|
171
148
|
seed_path = File.join(seed_dir, "in.osm")
|
172
149
|
|
173
|
-
#
|
150
|
+
# Create runner with empty OSW, and example test model.
|
174
151
|
osw = OpenStudio::WorkflowJSON.new
|
175
152
|
osw.setSeedFile(seed_path)
|
176
153
|
runner = OpenStudio::Measure::OSRunner.new(osw)
|
177
|
-
|
178
|
-
# create example test model
|
179
154
|
model = OpenStudio::Model::exampleModel
|
180
155
|
model.save(seed_path, true)
|
181
156
|
|
182
|
-
#
|
157
|
+
# POSTULATED USER ERROR: Do not copy tdb.json next to seed.
|
183
158
|
|
184
|
-
#
|
159
|
+
# Get measure arguments.
|
185
160
|
arguments = measure.arguments(model)
|
186
161
|
argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
|
187
162
|
|
188
|
-
#
|
189
|
-
|
190
|
-
|
191
|
-
# using defaults values from measure.rb for other arguments
|
163
|
+
# Hash of argument values (defaults from measure.rb for other arguments).
|
164
|
+
argh = {}
|
165
|
+
argh["load_tbd_json" ] = true
|
192
166
|
|
193
|
-
#
|
167
|
+
# Populate argument with specified hash value if specified.
|
194
168
|
arguments.each do |arg|
|
195
169
|
temp_arg_var = arg.clone
|
196
|
-
if
|
197
|
-
assert(temp_arg_var.setValue(args_hash[arg.name]))
|
198
|
-
end
|
170
|
+
assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
|
199
171
|
argument_map[arg.name] = temp_arg_var
|
200
172
|
end
|
201
173
|
|
202
|
-
#
|
174
|
+
# Run the measure, assert that it did not run correctly.
|
203
175
|
Dir.chdir(seed_dir)
|
204
176
|
measure.run(model, runner, argument_map)
|
205
177
|
result = runner.result
|
206
|
-
|
207
|
-
# show the output
|
208
178
|
show_output(result)
|
179
|
+
assert_equal("Fail", result.value.valueName)
|
209
180
|
|
210
|
-
# assert that it ran correctly
|
211
|
-
assert_equal('Fail', result.value.valueName)
|
212
|
-
assert(result.errors.size == 1)
|
213
181
|
assert(result.warnings.size == 1)
|
214
|
-
|
215
|
-
|
216
|
-
assert(
|
182
|
+
message = result.warnings[0].logMessage
|
183
|
+
puts message
|
184
|
+
assert(message.include?("Can't find 'tbd.json' - simulation halted"))
|
185
|
+
|
186
|
+
assert(result.errors.size == 1)
|
187
|
+
message = result.errors[0].logMessage
|
188
|
+
puts message
|
189
|
+
assert(message.include?("Halting all TBD processes, "))
|
190
|
+
assert(message.include?("and halting OpenStudio - see 'tbd.out.json'"))
|
191
|
+
|
192
|
+
# Save the model to test output directory.
|
193
|
+
output_path = File.join(seed_dir, "out.osm")
|
194
|
+
model.save(output_path, true)
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_tbd_kiva_massless_error
|
198
|
+
measure = TBDMeasure.new
|
199
|
+
|
200
|
+
# Output directories.
|
201
|
+
seed_dir = File.join(__dir__, "output/tbd_kiva_massless_error/")
|
202
|
+
FileUtils.mkdir_p(seed_dir)
|
203
|
+
seed_path = File.join(seed_dir, "in.osm")
|
204
|
+
|
205
|
+
# Create runner with empty OSW, and example test model.
|
206
|
+
osw = OpenStudio::WorkflowJSON.new
|
207
|
+
osw.setSeedFile(seed_path)
|
208
|
+
runner = OpenStudio::Measure::OSRunner.new(osw)
|
209
|
+
model = OpenStudio::Model::exampleModel
|
210
|
+
model.save(seed_path, true)
|
211
|
+
|
212
|
+
# Copy tdb.json next to seed.
|
213
|
+
origin_pth = File.join(__dir__, "tbd_full_PSI.json")
|
214
|
+
target_pth = File.join(seed_dir, "tbd.json")
|
215
|
+
FileUtils.cp(origin_pth, target_pth)
|
216
|
+
|
217
|
+
# Get measure arguments.
|
218
|
+
arguments = measure.arguments(model)
|
219
|
+
argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
|
220
|
+
|
221
|
+
# Hash of argument values (defaults from measure.rb for other arguments).
|
222
|
+
argh = {}
|
223
|
+
argh["gen_kiva_force"] = true
|
224
|
+
|
225
|
+
# POSTULATED USER ERROR : Slab on grade construction holds a massless layer.
|
226
|
+
|
227
|
+
# Populate argument with specified hash value if specified.
|
228
|
+
arguments.each do |arg|
|
229
|
+
temp_arg_var = arg.clone
|
230
|
+
assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
|
231
|
+
argument_map[arg.name] = temp_arg_var
|
232
|
+
end
|
233
|
+
|
234
|
+
# Run the measure, assert that it did not run correctly.
|
235
|
+
Dir.chdir(seed_dir)
|
236
|
+
measure.run(model, runner, argument_map)
|
237
|
+
result = runner.result
|
238
|
+
show_output(result)
|
239
|
+
assert_equal("Fail", result.value.valueName)
|
240
|
+
assert(result.warnings.empty?)
|
241
|
+
assert(result.errors.size == 4)
|
242
|
+
|
243
|
+
result.errors.each do |error|
|
244
|
+
assert(error.logMessage.include?("KIVA requires standard materials ("))
|
245
|
+
end
|
217
246
|
|
218
|
-
#
|
219
|
-
#
|
220
|
-
|
247
|
+
# Save the model to test output directory. There should be neither instance
|
248
|
+
# of KIVA objects nor TBD derated materials/constructions.
|
249
|
+
output_path = File.join(seed_dir, "out.osm")
|
250
|
+
model.save(output_path, true)
|
221
251
|
end
|
222
252
|
end
|
data/lib/tbd/geo.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# MIT License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2020-
|
3
|
+
# Copyright (c) 2020-2023 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
|
@@ -22,24 +22,27 @@
|
|
22
22
|
|
23
23
|
module TBD
|
24
24
|
##
|
25
|
-
# Check for matching Topolys vertex pairs between edges
|
25
|
+
# Check for matching Topolys vertex pairs between edges.
|
26
26
|
#
|
27
27
|
# @param e1 [Hash] first edge
|
28
28
|
# @param e2 [Hash] second edge
|
29
|
+
# @param tol [Float] user-set tolerance (> TOL) in m
|
29
30
|
#
|
30
31
|
# @return [Bool] true if edges share vertex pairs
|
31
32
|
# @return [Bool] false if invalid input
|
32
|
-
def matches?(e1 = {}, e2 = {})
|
33
|
+
def matches?(e1 = {}, e2 = {}, tol = TOL)
|
33
34
|
mth = "TBD::#{__callee__}"
|
34
35
|
cl = Topolys::Point3D
|
35
36
|
a = false
|
36
37
|
|
37
38
|
return mismatch("e1", e1, Hash, mth, DBG, a) unless e1.is_a?(Hash)
|
38
39
|
return mismatch("e2", e2, Hash, mth, DBG, a) unless e2.is_a?(Hash)
|
40
|
+
|
39
41
|
return hashkey("e1", e1, :v0, mth, DBG, a) unless e1.key?(:v0)
|
40
42
|
return hashkey("e1", e1, :v1, mth, DBG, a) unless e1.key?(:v1)
|
41
43
|
return hashkey("e2", e2, :v0, mth, DBG, a) unless e2.key?(:v0)
|
42
44
|
return hashkey("e2", e2, :v1, mth, DBG, a) unless e2.key?(:v1)
|
45
|
+
|
43
46
|
return mismatch("e1 :v0", e1[:v0], cl, mth, DBG, a) unless e1[:v0].is_a?(cl)
|
44
47
|
return mismatch("e1 :v1", e1[:v1], cl, mth, DBG, a) unless e1[:v1].is_a?(cl)
|
45
48
|
return mismatch("e2 :v0", e2[:v0], cl, mth, DBG, a) unless e2[:v0].is_a?(cl)
|
@@ -51,26 +54,29 @@ module TBD
|
|
51
54
|
return zero("e1", mth, DBG, a) if e1_vector.magnitude < TOL
|
52
55
|
return zero("e2", mth, DBG, a) if e2_vector.magnitude < TOL
|
53
56
|
|
57
|
+
return mismatch("e1", e1, Hash, mth, DBG, a) unless tol.is_a?(Numeric)
|
58
|
+
return zero("tol", mth, DBG, a) if tol < TOL
|
59
|
+
|
54
60
|
return true if
|
55
61
|
(
|
56
62
|
(
|
57
|
-
( (e1[:v0].x - e2[:v0].x).abs <
|
58
|
-
(e1[:v0].y - e2[:v0].y).abs <
|
59
|
-
(e1[:v0].z - e2[:v0].z).abs <
|
63
|
+
( (e1[:v0].x - e2[:v0].x).abs < tol &&
|
64
|
+
(e1[:v0].y - e2[:v0].y).abs < tol &&
|
65
|
+
(e1[:v0].z - e2[:v0].z).abs < tol
|
60
66
|
) ||
|
61
|
-
( (e1[:v0].x - e2[:v1].x).abs <
|
62
|
-
(e1[:v0].y - e2[:v1].y).abs <
|
63
|
-
(e1[:v0].z - e2[:v1].z).abs <
|
67
|
+
( (e1[:v0].x - e2[:v1].x).abs < tol &&
|
68
|
+
(e1[:v0].y - e2[:v1].y).abs < tol &&
|
69
|
+
(e1[:v0].z - e2[:v1].z).abs < tol
|
64
70
|
)
|
65
71
|
) &&
|
66
72
|
(
|
67
|
-
( (e1[:v1].x - e2[:v0].x).abs <
|
68
|
-
(e1[:v1].y - e2[:v0].y).abs <
|
69
|
-
(e1[:v1].z - e2[:v0].z).abs <
|
73
|
+
( (e1[:v1].x - e2[:v0].x).abs < tol &&
|
74
|
+
(e1[:v1].y - e2[:v0].y).abs < tol &&
|
75
|
+
(e1[:v1].z - e2[:v0].z).abs < tol
|
70
76
|
) ||
|
71
|
-
( (e1[:v1].x - e2[:v1].x).abs <
|
72
|
-
(e1[:v1].y - e2[:v1].y).abs <
|
73
|
-
(e1[:v1].z - e2[:v1].z).abs <
|
77
|
+
( (e1[:v1].x - e2[:v1].x).abs < tol &&
|
78
|
+
(e1[:v1].y - e2[:v1].y).abs < tol &&
|
79
|
+
(e1[:v1].z - e2[:v1].z).abs < tol
|
74
80
|
)
|
75
81
|
)
|
76
82
|
)
|
@@ -354,6 +360,7 @@ module TBD
|
|
354
360
|
next unless valid
|
355
361
|
vec = s.vertices
|
356
362
|
area = s.grossArea
|
363
|
+
mult = s.multiplier
|
357
364
|
typ = s.subSurfaceType.downcase
|
358
365
|
type = :skylight
|
359
366
|
type = :window if typ.include?("window" )
|
@@ -442,6 +449,7 @@ module TBD
|
|
442
449
|
n: n,
|
443
450
|
gross: s.grossArea,
|
444
451
|
area: area,
|
452
|
+
mult: mult,
|
445
453
|
type: type,
|
446
454
|
u: u,
|
447
455
|
unhinged: unhinged }
|
@@ -478,7 +486,9 @@ module TBD
|
|
478
486
|
end
|
479
487
|
|
480
488
|
subarea = 0
|
481
|
-
|
489
|
+
|
490
|
+
subs.values.each { |sub| subarea += sub[:area] * sub[:mult] }
|
491
|
+
|
482
492
|
surf[:net] = surf[:gross] - subarea
|
483
493
|
|
484
494
|
# Tranform final Point 3D sets, and store.
|
@@ -604,6 +614,36 @@ module TBD
|
|
604
614
|
return mismatch("floors", floors, cl2, mth, DBG, a) unless floors.is_a?(cl2)
|
605
615
|
return mismatch("edges", edges, cl2, mth, DBG, a) unless edges.is_a?(cl2)
|
606
616
|
|
617
|
+
kva = true
|
618
|
+
|
619
|
+
# Pre-validate foundation-facing constructions.
|
620
|
+
model.getSurfaces.each do |s|
|
621
|
+
id = s.nameString
|
622
|
+
construction = s.construction
|
623
|
+
next unless s.outsideBoundaryCondition.downcase == "foundation"
|
624
|
+
|
625
|
+
if construction.empty?
|
626
|
+
log(ERR, "Invalid construction for KIVA (see #{id})")
|
627
|
+
kva = false if kva
|
628
|
+
else
|
629
|
+
construction = construction.get.to_LayeredConstruction
|
630
|
+
|
631
|
+
if construction.empty?
|
632
|
+
log(ERR, "KIVA requires layered constructions (see #{id})")
|
633
|
+
kva = false if kva
|
634
|
+
else
|
635
|
+
construction = construction.get
|
636
|
+
|
637
|
+
unless standardOpaqueLayers?(construction)
|
638
|
+
log(ERR, "KIVA requires standard materials (see #{id})")
|
639
|
+
kva = false if kva
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
return a unless kva
|
646
|
+
|
607
647
|
# Strictly relying on Kiva's total exposed perimeter approach.
|
608
648
|
arg = "TotalExposedPerimeter"
|
609
649
|
kiva = true
|
data/lib/tbd/psi.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# MIT License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2020-
|
3
|
+
# Copyright (c) 2020-2023 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
|
@@ -913,6 +913,7 @@ module TBD
|
|
913
913
|
return mismatch("argh", argh, Hash, mth, DBG, tbd) unless argh.is_a?(Hash)
|
914
914
|
|
915
915
|
argh = {} if argh.empty?
|
916
|
+
argh[:sub_tol ] = TBD::TOL unless argh.key?(:sub_tol )
|
916
917
|
argh[:option ] = "" unless argh.key?(:option )
|
917
918
|
argh[:io_path ] = nil unless argh.key?(:io_path )
|
918
919
|
argh[:schema_path ] = nil unless argh.key?(:schema_path )
|
@@ -1174,9 +1175,6 @@ module TBD
|
|
1174
1175
|
farthest_V = origin_point_V if farther
|
1175
1176
|
end
|
1176
1177
|
|
1177
|
-
puts "ADDITION!!" if id == "ADDITION"
|
1178
|
-
puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
|
1179
|
-
|
1180
1178
|
angle = reference_V.angle(farthest_V)
|
1181
1179
|
invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
|
1182
1180
|
angle = 0 if angle.nil?
|
@@ -1771,13 +1769,120 @@ module TBD
|
|
1771
1769
|
end
|
1772
1770
|
end
|
1773
1771
|
|
1772
|
+
# Fetch edge multipliers for subsurfaces, if applicable.
|
1773
|
+
edges.values.each do |edge|
|
1774
|
+
next if edge.key?(:mult) # skip if already assigned
|
1775
|
+
next unless edge.key?(:surfaces)
|
1776
|
+
next unless edge.key?(:psi)
|
1777
|
+
ok = false
|
1778
|
+
|
1779
|
+
edge[:psi].keys.each do |k|
|
1780
|
+
break if ok
|
1781
|
+
|
1782
|
+
jamb = k.to_s.include?("jamb")
|
1783
|
+
sill = k.to_s.include?("sill")
|
1784
|
+
head = k.to_s.include?("head")
|
1785
|
+
ok = jamb || sill || head
|
1786
|
+
end
|
1787
|
+
|
1788
|
+
next unless ok # if OK, edge links subsurface(s) ... yet which one(s)?
|
1789
|
+
|
1790
|
+
edge[:surfaces].each do |id, surface|
|
1791
|
+
next unless tbd[:surfaces].key?(id) # look up parent (opaque) surface
|
1792
|
+
|
1793
|
+
[:windows, :doors, :skylights].each do |subtypes|
|
1794
|
+
next unless tbd[:surfaces][id].key?(subtypes)
|
1795
|
+
|
1796
|
+
tbd[:surfaces][id][subtypes].each do |nom, sub|
|
1797
|
+
next unless edge[:surfaces].key?(nom)
|
1798
|
+
next unless sub[:mult] > 1
|
1799
|
+
|
1800
|
+
# An edge may be tagged with (potentially conflicting) multipliers.
|
1801
|
+
# This is only possible if the edge links 2 subsurfaces, e.g. a
|
1802
|
+
# shared jamb between window & door. By default, TBD tags common
|
1803
|
+
# subsurface edges as (mild) "transitions" (i.e. PSI 0 W/K.m), so
|
1804
|
+
# there would be no point in assigning an edge multiplier. Users
|
1805
|
+
# can however reset an edge type via a TBD JSON input file (e.g.
|
1806
|
+
# "joint" instead of "transition"). It would be a very odd choice,
|
1807
|
+
# but TBD doesn't prohibit it. If linked subsurfaces have different
|
1808
|
+
# multipliers (e.g. 2 vs 3), TBD tracks the highest value.
|
1809
|
+
edge[:mult] = sub[:mult] unless edge.key?(:mult)
|
1810
|
+
edge[:mult] = sub[:mult] if sub[:mult] > edge[:mult]
|
1811
|
+
end
|
1812
|
+
end
|
1813
|
+
end
|
1814
|
+
end
|
1815
|
+
|
1816
|
+
# Unless a user has set the thermal bridge type of an individual edge via
|
1817
|
+
# JSON input, reset any subsurface's head, sill or jamb edges as (mild)
|
1818
|
+
# transitions when in close proximity to another subsurface edge. Both
|
1819
|
+
# edges' origin and terminal vertices must be in close proximity. Edges
|
1820
|
+
# of unhinged subsurfaces are ignored.
|
1821
|
+
edges.each do |id, edge|
|
1822
|
+
nb = 0 # linked subsurfaces (i.e. "holes")
|
1823
|
+
match = false
|
1824
|
+
next if edge.key?(:io_type) # skip if set in JSON
|
1825
|
+
next unless edge.key?(:v0)
|
1826
|
+
next unless edge.key?(:v1)
|
1827
|
+
next unless edge.key?(:psi)
|
1828
|
+
next unless edge.key?(:surfaces)
|
1829
|
+
|
1830
|
+
edge[:surfaces].keys.each do |identifier|
|
1831
|
+
break if match
|
1832
|
+
next unless holes.key?(identifier)
|
1833
|
+
|
1834
|
+
if holes[identifier].attributes.key?(:unhinged)
|
1835
|
+
nb = 0 if holes[identifier].attributes[:unhinged]
|
1836
|
+
break if holes[identifier].attributes[:unhinged]
|
1837
|
+
end
|
1838
|
+
|
1839
|
+
nb += 1
|
1840
|
+
match = true if nb > 1
|
1841
|
+
end
|
1842
|
+
|
1843
|
+
if nb == 1 # linking 1x subsurface, search for 1x other.
|
1844
|
+
e1 = { v0: edge[:v0].point, v1: edge[:v1].point }
|
1845
|
+
|
1846
|
+
edges.each do |nom, e|
|
1847
|
+
nb = 0
|
1848
|
+
break if match
|
1849
|
+
next if nom == id
|
1850
|
+
next if e.key?(:io_type)
|
1851
|
+
next unless e.key?(:psi)
|
1852
|
+
next unless e.key?(:surfaces)
|
1853
|
+
|
1854
|
+
e[:surfaces].keys.each do |identifier|
|
1855
|
+
next unless holes.key?(identifier)
|
1856
|
+
|
1857
|
+
if holes[identifier].attributes.key?(:unhinged)
|
1858
|
+
nb = 0 if holes[identifier].attributes[:unhinged]
|
1859
|
+
break if holes[identifier].attributes[:unhinged]
|
1860
|
+
end
|
1861
|
+
|
1862
|
+
nb += 1
|
1863
|
+
end
|
1864
|
+
|
1865
|
+
next unless nb == 1 # only process edge if linking 1x subsurface
|
1866
|
+
|
1867
|
+
e2 = { v0: e[:v0].point, v1: e[:v1].point }
|
1868
|
+
match = matches?(e1, e2, argh[:sub_tol])
|
1869
|
+
end
|
1870
|
+
end
|
1871
|
+
|
1872
|
+
next unless match
|
1873
|
+
|
1874
|
+
edge[:psi] = { transition: 0.000 }
|
1875
|
+
edge[:set] = json[:io][:building][:psi]
|
1876
|
+
end
|
1877
|
+
|
1774
1878
|
# Loop through each edge and assign heat loss to linked surfaces.
|
1775
1879
|
edges.each do |identifier, edge|
|
1776
1880
|
next unless edge.key?(:psi)
|
1777
1881
|
rsi = 0
|
1778
|
-
max = edge[:psi].values.max
|
1779
|
-
type = edge[:psi].key(max)
|
1882
|
+
max = edge[:psi ].values.max
|
1883
|
+
type = edge[:psi ].key(max)
|
1780
1884
|
length = edge[:length]
|
1885
|
+
length *= edge[:mult ] if edge.key?(:mult)
|
1781
1886
|
bridge = { psi: max, type: type, length: length }
|
1782
1887
|
deratables = {}
|
1783
1888
|
apertures = {}
|
@@ -1869,7 +1974,7 @@ module TBD
|
|
1869
1974
|
# ... first 'uprate' targeted insulation layers (see ua.rb) before derating.
|
1870
1975
|
# Check for new argh keys [:wall_uo], [:roof_uo] and/or [:floor_uo].
|
1871
1976
|
up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
|
1872
|
-
uprate(model, tbd[:surfaces], argh)
|
1977
|
+
uprate(model, tbd[:surfaces], argh) if up
|
1873
1978
|
|
1874
1979
|
# Derated (cloned) constructions are unique to each deratable surface.
|
1875
1980
|
# Unique construction names are prefixed with the surface name,
|
@@ -1975,6 +2080,7 @@ module TBD
|
|
1975
2080
|
set = e[:set]
|
1976
2081
|
t = e[:psi].key(v)
|
1977
2082
|
l = e[:length]
|
2083
|
+
l *= e[:mult] if e.key?(:mult)
|
1978
2084
|
edge = { psi: set, type: t, length: l, surfaces: e[:surfaces].keys }
|
1979
2085
|
edge[:v0x] = e[:v0].point.x
|
1980
2086
|
edge[:v0y] = e[:v0].point.y
|