tbd 3.0.2 → 3.1.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 +5 -5
- data/json/tbd_seb_n2.json +19 -14
- data/lib/measures/tbd/measure.xml +10 -10
- data/lib/measures/tbd/resources/geo.rb +50 -9
- data/lib/measures/tbd/resources/psi.rb +80 -67
- data/lib/measures/tbd/resources/ua.rb +17 -12
- data/lib/tbd/geo.rb +50 -9
- data/lib/tbd/psi.rb +80 -67
- data/lib/tbd/ua.rb +17 -12
- data/lib/tbd/version.rb +1 -1
- metadata +3 -5
- data/json/midrise.json +0 -64
- data/json/tbd_warehouse5.json +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f5a7947fb5c2f96807c924dcb4350198119a9033b35cef4d113e35221b0e61f
|
4
|
+
data.tar.gz: caf6856e314609d3d13134c2bcc86a78292ea4deb1c8ab453e3ca386a5bf56e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 024b47e38db346d15524df93119a49c93c3a183c063d5635cd823ad21d8a2c6892680cc4b2f76b0299177a1479265650c6efc241d9037cb35043f1395ed312a8
|
7
|
+
data.tar.gz: da0b7e03bc5142adc8df613427838114377e5b406c36ab2b9ec506eaf72e972478490d45437018a2a94aeac45547ded1c70babfcec677420594c3dd3f776a867
|
@@ -3,11 +3,11 @@ name: Pull Request CI
|
|
3
3
|
on:
|
4
4
|
pull_request:
|
5
5
|
branches:
|
6
|
-
-
|
6
|
+
- develop
|
7
7
|
|
8
8
|
jobs:
|
9
9
|
test_300x:
|
10
|
-
runs-on: ubuntu-
|
10
|
+
runs-on: ubuntu-22.04
|
11
11
|
steps:
|
12
12
|
- name: Check out repository
|
13
13
|
uses: actions/checkout@v2
|
@@ -23,7 +23,7 @@ jobs:
|
|
23
23
|
docker exec -t test bundle exec rake
|
24
24
|
docker kill test
|
25
25
|
test_321x:
|
26
|
-
runs-on: ubuntu-
|
26
|
+
runs-on: ubuntu-22.04
|
27
27
|
steps:
|
28
28
|
- name: Check out repository
|
29
29
|
uses: actions/checkout@v2
|
@@ -39,7 +39,7 @@ jobs:
|
|
39
39
|
docker exec -t test bundle exec rake
|
40
40
|
docker kill test
|
41
41
|
test_330x:
|
42
|
-
runs-on: ubuntu-
|
42
|
+
runs-on: ubuntu-22.04
|
43
43
|
steps:
|
44
44
|
- name: Check out repository
|
45
45
|
uses: actions/checkout@v2
|
@@ -55,7 +55,7 @@ jobs:
|
|
55
55
|
docker exec -t test bundle exec rake
|
56
56
|
docker kill test
|
57
57
|
test_340x:
|
58
|
-
runs-on: ubuntu-
|
58
|
+
runs-on: ubuntu-22.04
|
59
59
|
steps:
|
60
60
|
- name: Check out repository
|
61
61
|
uses: actions/checkout@v2
|
data/json/tbd_seb_n2.json
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
{
|
2
2
|
"schema": "https://github.com/rd2/tbd/blob/master/tbd.schema.json",
|
3
3
|
"description": "testing JSON surface KHI entries",
|
4
|
-
"psis": [
|
4
|
+
"psis": [
|
5
|
+
{
|
5
6
|
"id": "good",
|
6
7
|
"parapet": 0.5,
|
7
8
|
"party": 0.9
|
@@ -17,7 +18,8 @@
|
|
17
18
|
"grade": 0.45
|
18
19
|
}
|
19
20
|
],
|
20
|
-
"khis": [
|
21
|
+
"khis": [
|
22
|
+
{
|
21
23
|
"id": "column",
|
22
24
|
"point": 0.5
|
23
25
|
},
|
@@ -26,16 +28,19 @@
|
|
26
28
|
"point": 0.5
|
27
29
|
}
|
28
30
|
],
|
29
|
-
"surfaces": [
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
31
|
+
"surfaces": [
|
32
|
+
{
|
33
|
+
"id": "Entryway Wall 5",
|
34
|
+
"khis": [
|
35
|
+
{
|
36
|
+
"id": "column",
|
37
|
+
"count": 3
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"id": "support",
|
41
|
+
"count": 4
|
42
|
+
}
|
43
|
+
]
|
44
|
+
}
|
45
|
+
]
|
41
46
|
}
|
@@ -3,8 +3,8 @@
|
|
3
3
|
<schema_version>3.0</schema_version>
|
4
4
|
<name>tbd_measure</name>
|
5
5
|
<uid>8890787b-8c25-4dc8-8641-b6be1b6c2357</uid>
|
6
|
-
<version_id>
|
7
|
-
<version_modified>
|
6
|
+
<version_id>4501c26f-1cd4-45d8-8f0e-f1ea907c27df</version_id>
|
7
|
+
<version_modified>20221125T130635Z</version_modified>
|
8
8
|
<xml_checksum>99772807</xml_checksum>
|
9
9
|
<class_name>TBDMeasure</class_name>
|
10
10
|
<display_name>Thermal Bridging and Derating - TBD</display_name>
|
@@ -432,12 +432,6 @@
|
|
432
432
|
<usage_type>test</usage_type>
|
433
433
|
<checksum>6ED9AF88</checksum>
|
434
434
|
</file>
|
435
|
-
<file>
|
436
|
-
<filename>psi.rb</filename>
|
437
|
-
<filetype>rb</filetype>
|
438
|
-
<usage_type>resource</usage_type>
|
439
|
-
<checksum>655E8232</checksum>
|
440
|
-
</file>
|
441
435
|
<file>
|
442
436
|
<filename>README.md</filename>
|
443
437
|
<filetype>md</filetype>
|
@@ -448,13 +442,19 @@
|
|
448
442
|
<filename>geo.rb</filename>
|
449
443
|
<filetype>rb</filetype>
|
450
444
|
<usage_type>resource</usage_type>
|
451
|
-
<checksum>
|
445
|
+
<checksum>D999D942</checksum>
|
446
|
+
</file>
|
447
|
+
<file>
|
448
|
+
<filename>psi.rb</filename>
|
449
|
+
<filetype>rb</filetype>
|
450
|
+
<usage_type>resource</usage_type>
|
451
|
+
<checksum>AFC8A549</checksum>
|
452
452
|
</file>
|
453
453
|
<file>
|
454
454
|
<filename>ua.rb</filename>
|
455
455
|
<filetype>rb</filetype>
|
456
456
|
<usage_type>resource</usage_type>
|
457
|
-
<checksum>
|
457
|
+
<checksum>B39B3BB0</checksum>
|
458
458
|
</file>
|
459
459
|
</files>
|
460
460
|
</measure>
|
@@ -84,20 +84,21 @@ module TBD
|
|
84
84
|
# vertices and wire.
|
85
85
|
#
|
86
86
|
# @param model [Topolys::Model] a model
|
87
|
-
# @param pts [Array] a 1D array of 3D Topolys points (min
|
87
|
+
# @param pts [Array] a 1D array of 3D Topolys points (min 3x)
|
88
88
|
#
|
89
89
|
# @return [Hash] vx: 3D Topolys vertices Array; w: corresponding Topolys::Wire
|
90
90
|
# @return [Hash] vx: nil; w: nil (if invalid input)
|
91
91
|
def objects(model = nil, pts = [])
|
92
|
-
mth = "
|
92
|
+
mth = "TBD::#{__callee__}"
|
93
93
|
cl = Topolys::Model
|
94
94
|
obj = { vx: nil, w: nil }
|
95
95
|
|
96
96
|
return mismatch("model", model, cl, mth, DBG, obj) unless model.is_a?(cl)
|
97
97
|
return mismatch("points", pts, Array, mth, DBG, obj) unless pts.is_a?(Array)
|
98
98
|
|
99
|
-
log(DBG, "#{pts.size}? need +
|
99
|
+
log(DBG, "#{pts.size}? need +3 Topolys points (#{mth})") unless pts.size > 2
|
100
100
|
return obj unless pts.size > 2
|
101
|
+
|
101
102
|
obj[:vx] = model.get_vertices(pts)
|
102
103
|
obj[:w ] = model.get_wire(obj[:vx])
|
103
104
|
|
@@ -109,14 +110,14 @@ module TBD
|
|
109
110
|
# As a side effect, it will - if successful - also populate a Topolys 'model'
|
110
111
|
# with Topolys vertices, wires, holes. In rare cases such as domes of tubular
|
111
112
|
# daylighting devices (TDDs), kids may be 'unhinged', i.e. not on same 3D
|
112
|
-
# plane as 'dad(s)' - TBD corrects
|
113
|
+
# plane as 'dad(s)' - TBD corrects such cases elsewhere.
|
113
114
|
#
|
114
115
|
# @param model [Topolys::Model] a model
|
115
116
|
# @param boys [Hash] a collection of TBD subsurfaces
|
116
117
|
#
|
117
118
|
# @return [Array] 3D Topolys wires of 'holes' (made by kids)
|
118
119
|
def kids(model = nil, boys = {})
|
119
|
-
mth = "
|
120
|
+
mth = "TBD::#{__callee__}"
|
120
121
|
cl = Topolys::Model
|
121
122
|
holes = []
|
122
123
|
|
@@ -146,7 +147,7 @@ module TBD
|
|
146
147
|
#
|
147
148
|
# @return [Array] 3D Topolys wires of 'holes' (made by kids)
|
148
149
|
def dads(model = nil, pops = {})
|
149
|
-
mth = "
|
150
|
+
mth = "TBD::#{__callee__}"
|
150
151
|
cl = Topolys::Model
|
151
152
|
holes = {}
|
152
153
|
|
@@ -183,7 +184,7 @@ module TBD
|
|
183
184
|
# @return [Bool] true if successful
|
184
185
|
# @return [Bool] false if invalid input
|
185
186
|
def faces(s = {}, e = {})
|
186
|
-
mth = "
|
187
|
+
mth = "TBD::#{__callee__}"
|
187
188
|
|
188
189
|
return mismatch("surfaces", s, Hash, mth, DBG, false) unless s.is_a?(Hash)
|
189
190
|
return mismatch("edges", e, Hash, mth, DBG, false) unless e.is_a?(Hash)
|
@@ -211,6 +212,42 @@ module TBD
|
|
211
212
|
true
|
212
213
|
end
|
213
214
|
|
215
|
+
##
|
216
|
+
# Validate whether an OpenStudio planar surface is safe for TBD to process.
|
217
|
+
#
|
218
|
+
# @param s [OpenStudio::Model::PlanarSurface] a surface
|
219
|
+
#
|
220
|
+
# @return [Bool] true if valid surface
|
221
|
+
def validate(s = nil)
|
222
|
+
mth = "TBD::#{__callee__}"
|
223
|
+
cl = OpenStudio::Model::PlanarSurface
|
224
|
+
|
225
|
+
return mismatch("surface", s, cl, mth, DBG, false) unless s.is_a?(cl)
|
226
|
+
|
227
|
+
id = s.nameString
|
228
|
+
size = s.vertices.size
|
229
|
+
last = size - 1
|
230
|
+
|
231
|
+
log(ERR, "#{id} #{size} vertices? need +3 (#{mth})") unless size > 2
|
232
|
+
return false unless size > 2
|
233
|
+
|
234
|
+
[0, last].each do |i|
|
235
|
+
v1 = s.vertices[i]
|
236
|
+
v2 = s.vertices[i + 1] unless i == last
|
237
|
+
v2 = s.vertices.first if i == last
|
238
|
+
vector = v2 - v1
|
239
|
+
bad = vector.length < TOL
|
240
|
+
|
241
|
+
# As is, this comparison also catches collinear vertices (< 10mm apart)
|
242
|
+
# along an edge. Should avoid red-flagging such cases. TO DO.
|
243
|
+
log(ERR, "#{id}: < #{TOL}m (#{mth})") if bad
|
244
|
+
return false if bad
|
245
|
+
end
|
246
|
+
|
247
|
+
# Add as many extra tests as needed ...
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
214
251
|
##
|
215
252
|
# Return site-specific (or true) Topolys normal vector of OpenStudio surface.
|
216
253
|
#
|
@@ -250,6 +287,8 @@ module TBD
|
|
250
287
|
return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
|
251
288
|
return mismatch("surface", surface, cl2, mth) unless surface.is_a?(cl2)
|
252
289
|
|
290
|
+
return nil unless validate(surface)
|
291
|
+
|
253
292
|
nom = surface.nameString
|
254
293
|
surf = {}
|
255
294
|
subs = {}
|
@@ -307,6 +346,8 @@ module TBD
|
|
307
346
|
surf[:gross ] = surface.grossArea
|
308
347
|
|
309
348
|
surface.subSurfaces.sort_by { |s| s.nameString }.each do |s|
|
349
|
+
next unless validate(s)
|
350
|
+
|
310
351
|
id = s.nameString
|
311
352
|
valid = s.vertices.size == 3 || s.vertices.size == 4
|
312
353
|
log(ERR, "Skipping '#{id}': vertex # 3 or 4 (#{mth})") unless valid
|
@@ -334,7 +375,7 @@ module TBD
|
|
334
375
|
log(ERR, "Skipping '#{id}': missing construction (#{mth})") if c.empty?
|
335
376
|
next if c.empty?
|
336
377
|
c = c.get.to_LayeredConstruction
|
337
|
-
log(
|
378
|
+
log(WRN, "Skipping '#{id}': subs limited to #{cl3} (#{mth})") if c.empty?
|
338
379
|
next if c.empty?
|
339
380
|
c = c.get
|
340
381
|
|
@@ -630,7 +671,7 @@ module TBD
|
|
630
671
|
end
|
631
672
|
|
632
673
|
foundation = OpenStudio::Model::FoundationKiva.new(model)
|
633
|
-
foundation.setName("KIVA Foundation Floor
|
674
|
+
foundation.setName("KIVA Foundation Floor #{id}")
|
634
675
|
|
635
676
|
floor = model.getSurfaceByName(id)
|
636
677
|
kiva = false if floor.empty?
|
@@ -205,7 +205,7 @@ module TBD
|
|
205
205
|
rimjoist: 0.300, # *
|
206
206
|
parapet: 0.325, # *
|
207
207
|
fenestration: 0.200, # *
|
208
|
-
corner: 0.300, # **
|
208
|
+
corner: 0.300, # ** not explicitely stated
|
209
209
|
balcony: 0.500, # *
|
210
210
|
party: 0.450, # *
|
211
211
|
grade: 0.450, # *
|
@@ -219,7 +219,7 @@ module TBD
|
|
219
219
|
rimjoist: 0.850, # *
|
220
220
|
parapet: 0.800, # *
|
221
221
|
fenestration: 0.500, # *
|
222
|
-
corner: 0.850, # **
|
222
|
+
corner: 0.850, # ** not explicitely stated
|
223
223
|
balcony: 1.000, # *
|
224
224
|
party: 0.850, # *
|
225
225
|
grade: 0.850, # *
|
@@ -569,16 +569,21 @@ module TBD
|
|
569
569
|
return mismatch("argh", s, Hash, mth, DBG, ipt) unless argh.is_a?(Hash)
|
570
570
|
return hashkey("argh", argh, opt, mth, DBG, ipt) unless argh.key?(opt)
|
571
571
|
|
572
|
-
argh[:io_path]
|
572
|
+
argh[:io_path ] = nil unless argh.key?(:io_path)
|
573
573
|
argh[:schema_path] = nil unless argh.key?(:schema_path)
|
574
|
-
|
574
|
+
|
575
|
+
pth = argh[:io_path ]
|
575
576
|
sch = argh[:schema_path]
|
576
577
|
|
577
|
-
if pth
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
578
|
+
if pth && (pth.is_a?(String) || pth.is_a?(Hash))
|
579
|
+
if pth.is_a?(Hash)
|
580
|
+
io = pth
|
581
|
+
else
|
582
|
+
return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
|
583
|
+
io = File.read(pth)
|
584
|
+
io = JSON.parse(io, symbolize_names: true)
|
585
|
+
return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
|
586
|
+
end
|
582
587
|
|
583
588
|
# Schema validation is not yet supported in the OpenStudio Application.
|
584
589
|
# We nonetheless recommend that users rely on the json-schema gem, or an
|
@@ -844,7 +849,7 @@ module TBD
|
|
844
849
|
up = ""
|
845
850
|
up = "uprated " if m.nameString.include?(" uprated")
|
846
851
|
m = m.clone(model).to_MasslessOpaqueMaterial.get
|
847
|
-
m.setName("
|
852
|
+
m.setName("#{id} #{up}m tbd")
|
848
853
|
de_r = 0.001 unless de_r > 0.001
|
849
854
|
loss = (de_u - 1 / de_r) * s[:net] unless de_r > 0.001
|
850
855
|
m.setThermalResistance(de_r)
|
@@ -855,7 +860,7 @@ module TBD
|
|
855
860
|
up = ""
|
856
861
|
up = "uprated " if m.nameString.include?(" uprated")
|
857
862
|
m = m.clone(model).to_StandardOpaqueMaterial.get
|
858
|
-
m.setName("
|
863
|
+
m.setName("#{id} #{up}m tbd")
|
859
864
|
k = m.thermalConductivity
|
860
865
|
|
861
866
|
if de_r > 0.001
|
@@ -1105,6 +1110,10 @@ module TBD
|
|
1105
1110
|
horizontal = dz.abs < TOL
|
1106
1111
|
vertical = dx < TOL && dy < TOL
|
1107
1112
|
edge_V = terminal - origin
|
1113
|
+
|
1114
|
+
invalid("1x edge length < TOL", mth, 0, ERROR) if edge_V.magnitude < TOL
|
1115
|
+
next if edge_V.magnitude < TOL
|
1116
|
+
|
1108
1117
|
edge_plane = Topolys::Plane3D.new(origin, edge_V)
|
1109
1118
|
|
1110
1119
|
if vertical
|
@@ -1120,71 +1129,77 @@ module TBD
|
|
1120
1129
|
# Loop through each linked wire and determine farthest point from
|
1121
1130
|
# edge while ensuring candidate point is not aligned with edge.
|
1122
1131
|
t_model.wires.each do |wire|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1132
|
+
next unless surface[:wire] == wire.id # should be a unique match
|
1133
|
+
normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
|
1134
|
+
normal = holes[id].attributes[:n] if holes.key?(id)
|
1135
|
+
normal = shades[id][:n] if shades.key?(id)
|
1136
|
+
farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
|
1137
|
+
farthest_V = farthest - origin # zero magnitude, initially
|
1138
|
+
inverted = false
|
1139
|
+
i_origin = wire.points.index(origin)
|
1140
|
+
i_terminal = wire.points.index(terminal)
|
1141
|
+
i_last = wire.points.size - 1
|
1142
|
+
|
1143
|
+
if i_terminal == 0
|
1144
|
+
inverted = true unless i_origin == i_last
|
1145
|
+
elsif i_origin == i_last
|
1146
|
+
inverted = true unless i_terminal == 0
|
1147
|
+
else
|
1148
|
+
inverted = true unless i_terminal - i_origin == 1
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
wire.points.each do |point|
|
1152
|
+
next if point == origin
|
1153
|
+
next if point == terminal
|
1154
|
+
|
1155
|
+
point_on_plane = edge_plane.project(point)
|
1156
|
+
origin_point_V = point_on_plane - origin
|
1157
|
+
point_V_magnitude = origin_point_V.magnitude
|
1158
|
+
next unless point_V_magnitude > TOL
|
1159
|
+
|
1160
|
+
# Generate a plane between origin, terminal & point. Only consider
|
1161
|
+
# planes that share the same normal as wire.
|
1162
|
+
if inverted
|
1163
|
+
plane = Topolys::Plane3D.from_points(terminal, origin, point)
|
1138
1164
|
else
|
1139
|
-
|
1165
|
+
plane = Topolys::Plane3D.from_points(origin, terminal, point)
|
1140
1166
|
end
|
1141
1167
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
point_on_plane = edge_plane.project(point)
|
1146
|
-
origin_point_V = point_on_plane - origin
|
1147
|
-
point_V_magnitude = origin_point_V.magnitude
|
1148
|
-
next unless point_V_magnitude > TOL
|
1149
|
-
|
1150
|
-
# Generate a plane between origin, terminal & point. Only consider
|
1151
|
-
# planes that share the same normal as wire.
|
1152
|
-
if inverted
|
1153
|
-
plane = Topolys::Plane3D.from_points(terminal, origin, point)
|
1154
|
-
else
|
1155
|
-
plane = Topolys::Plane3D.from_points(origin, terminal, point)
|
1156
|
-
end
|
1168
|
+
next unless (normal.x - plane.normal.x).abs < TOL &&
|
1169
|
+
(normal.y - plane.normal.y).abs < TOL &&
|
1170
|
+
(normal.z - plane.normal.z).abs < TOL
|
1157
1171
|
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1172
|
+
farther = point_V_magnitude > farthest_V.magnitude
|
1173
|
+
farthest = point if farther
|
1174
|
+
farthest_V = origin_point_V if farther
|
1175
|
+
end
|
1161
1176
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
farthest_V = origin_point_V if farther
|
1165
|
-
end
|
1177
|
+
puts "ADDITION!!" if id == "ADDITION"
|
1178
|
+
puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
|
1166
1179
|
|
1167
|
-
|
1168
|
-
|
1180
|
+
angle = reference_V.angle(farthest_V)
|
1181
|
+
invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
|
1182
|
+
angle = 0 if angle.nil?
|
1169
1183
|
|
1170
|
-
|
1184
|
+
adjust = false # adjust angle [180°, 360°] if necessary
|
1185
|
+
|
1186
|
+
if vertical
|
1187
|
+
adjust = true if east.dot(farthest_V) < -TOL
|
1188
|
+
else
|
1189
|
+
if north.dot(farthest_V).abs < TOL ||
|
1190
|
+
(north.dot(farthest_V).abs - 1).abs < TOL
|
1171
1191
|
adjust = true if east.dot(farthest_V) < -TOL
|
1172
1192
|
else
|
1173
|
-
if north.dot(farthest_V)
|
1174
|
-
(north.dot(farthest_V).abs - 1).abs < TOL
|
1175
|
-
adjust = true if east.dot(farthest_V) < -TOL
|
1176
|
-
else
|
1177
|
-
adjust = true if north.dot(farthest_V) < -TOL
|
1178
|
-
end
|
1193
|
+
adjust = true if north.dot(farthest_V) < -TOL
|
1179
1194
|
end
|
1180
|
-
|
1181
|
-
angle = 2 * Math::PI - angle if adjust
|
1182
|
-
angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
|
1183
|
-
surface[:angle] = angle
|
1184
|
-
farthest_V.normalize!
|
1185
|
-
surface[:polar] = farthest_V
|
1186
|
-
surface[:normal] = normal
|
1187
1195
|
end
|
1196
|
+
|
1197
|
+
angle = 2 * Math::PI - angle if adjust
|
1198
|
+
angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
|
1199
|
+
surface[:angle ] = angle
|
1200
|
+
farthest_V.normalize!
|
1201
|
+
surface[:polar ] = farthest_V
|
1202
|
+
surface[:normal] = normal
|
1188
1203
|
end # end of edge-linked, surface-to-wire loop
|
1189
1204
|
end # end of edge-linked surface loop
|
1190
1205
|
|
@@ -1993,8 +2008,6 @@ module TBD
|
|
1993
2008
|
#
|
1994
2009
|
# @return [Bool] true if TBD Measure is successful
|
1995
2010
|
def exit(runner = nil, argh = {})
|
1996
|
-
mth = "TBD::#{__callee__}"
|
1997
|
-
|
1998
2011
|
# Generated files target a design context ( >= WARN ) ... change TBD log
|
1999
2012
|
# level for debugging purposes. By default, log status is set < DBG
|
2000
2013
|
# while log level is set @INF.
|
@@ -62,30 +62,33 @@ module TBD
|
|
62
62
|
return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
|
63
63
|
|
64
64
|
# First, calculate initial layer RSi to initially meet Ut target.
|
65
|
-
rt
|
66
|
-
ro
|
67
|
-
new_r
|
68
|
-
new_u
|
65
|
+
rt = 1 / ut # target construction Rt
|
66
|
+
ro = rsi(lc, film) # current construction Ro
|
67
|
+
new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
|
68
|
+
new_u = 1 / new_r
|
69
69
|
|
70
70
|
# Then, uprate (if possible) to counter expected thermal bridging effects.
|
71
|
-
u_psi
|
72
|
-
new_u
|
73
|
-
new_r
|
71
|
+
u_psi = hloss / area # from psi & khi
|
72
|
+
new_u -= u_psi # uprated layer USi to counter psi & khi
|
73
|
+
new_r = 1 / new_u # uprated layer RSi to counter psi & khi
|
74
74
|
|
75
75
|
return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
|
76
|
-
|
76
|
+
|
77
|
+
loss = 0.0 # residual heatloss (not assigned) [W/K]
|
77
78
|
|
78
79
|
if lyr[:type] == :massless
|
79
80
|
m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
|
80
81
|
return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
|
82
|
+
|
81
83
|
m = m.get.clone(model).to_MasslessOpaqueMaterial.get
|
82
84
|
m.setName("#{id} uprated")
|
83
85
|
new_r = 0.001 unless new_r > 0.001
|
84
86
|
loss = (new_u - 1 / new_r) * area unless new_r > 0.001
|
85
87
|
m.setThermalResistance(new_r)
|
86
88
|
else # type == :standard
|
87
|
-
m
|
89
|
+
m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
|
88
90
|
return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
|
91
|
+
|
89
92
|
m = m.get.clone(model).to_StandardOpaqueMaterial.get
|
90
93
|
m.setName("#{id} uprated")
|
91
94
|
k = m.thermalConductivity
|
@@ -108,10 +111,12 @@ module TBD
|
|
108
111
|
|
109
112
|
ok = m.setThickness(d)
|
110
113
|
return invalid("Can't uprate '#{id}': > 3m", mth, 0, ERR, res) unless ok
|
114
|
+
|
111
115
|
m.setThermalConductivity(k) if ok
|
112
116
|
end
|
113
117
|
|
114
118
|
return invalid("", mth, 0, ERR, res) unless m
|
119
|
+
|
115
120
|
lc.setLayer(lyr[:index], m)
|
116
121
|
uo = 1 / rsi(lc, film)
|
117
122
|
|
@@ -174,7 +179,6 @@ module TBD
|
|
174
179
|
area = 0
|
175
180
|
film = 100000000000000
|
176
181
|
lc = nil
|
177
|
-
uo = nil
|
178
182
|
id = ""
|
179
183
|
all = g[:op].downcase == "all wall constructions" ||
|
180
184
|
g[:op].downcase == "all roof constructions" ||
|
@@ -188,6 +192,7 @@ module TBD
|
|
188
192
|
next unless sss.outsideBoundaryCondition.downcase == "outdoors"
|
189
193
|
next if sss.construction.empty?
|
190
194
|
next if sss.construction.get.to_LayeredConstruction.empty?
|
195
|
+
|
191
196
|
c = sss.construction.get.to_LayeredConstruction.get
|
192
197
|
i = c.nameString
|
193
198
|
|
@@ -236,7 +241,7 @@ module TBD
|
|
236
241
|
end
|
237
242
|
|
238
243
|
if coll.empty?
|
239
|
-
log(ERR, "No construction to uprate - skipping (#{mth})")
|
244
|
+
log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
|
240
245
|
next
|
241
246
|
elsif lc # valid layered construction - good to uprate!
|
242
247
|
# Ensure lc is referenced by surface types == label.
|
@@ -948,7 +953,7 @@ module TBD
|
|
948
953
|
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
|
949
954
|
model += " (v#{ua[:version]})" if ua.key?(:version)
|
950
955
|
report << model unless model.empty?
|
951
|
-
report << "* TBD : v3.0
|
956
|
+
report << "* TBD : v3.1.0"
|
952
957
|
report << "* date : #{ua[:date]}"
|
953
958
|
|
954
959
|
if lang == :en
|
data/lib/tbd/geo.rb
CHANGED
@@ -84,20 +84,21 @@ module TBD
|
|
84
84
|
# vertices and wire.
|
85
85
|
#
|
86
86
|
# @param model [Topolys::Model] a model
|
87
|
-
# @param pts [Array] a 1D array of 3D Topolys points (min
|
87
|
+
# @param pts [Array] a 1D array of 3D Topolys points (min 3x)
|
88
88
|
#
|
89
89
|
# @return [Hash] vx: 3D Topolys vertices Array; w: corresponding Topolys::Wire
|
90
90
|
# @return [Hash] vx: nil; w: nil (if invalid input)
|
91
91
|
def objects(model = nil, pts = [])
|
92
|
-
mth = "
|
92
|
+
mth = "TBD::#{__callee__}"
|
93
93
|
cl = Topolys::Model
|
94
94
|
obj = { vx: nil, w: nil }
|
95
95
|
|
96
96
|
return mismatch("model", model, cl, mth, DBG, obj) unless model.is_a?(cl)
|
97
97
|
return mismatch("points", pts, Array, mth, DBG, obj) unless pts.is_a?(Array)
|
98
98
|
|
99
|
-
log(DBG, "#{pts.size}? need +
|
99
|
+
log(DBG, "#{pts.size}? need +3 Topolys points (#{mth})") unless pts.size > 2
|
100
100
|
return obj unless pts.size > 2
|
101
|
+
|
101
102
|
obj[:vx] = model.get_vertices(pts)
|
102
103
|
obj[:w ] = model.get_wire(obj[:vx])
|
103
104
|
|
@@ -109,14 +110,14 @@ module TBD
|
|
109
110
|
# As a side effect, it will - if successful - also populate a Topolys 'model'
|
110
111
|
# with Topolys vertices, wires, holes. In rare cases such as domes of tubular
|
111
112
|
# daylighting devices (TDDs), kids may be 'unhinged', i.e. not on same 3D
|
112
|
-
# plane as 'dad(s)' - TBD corrects
|
113
|
+
# plane as 'dad(s)' - TBD corrects such cases elsewhere.
|
113
114
|
#
|
114
115
|
# @param model [Topolys::Model] a model
|
115
116
|
# @param boys [Hash] a collection of TBD subsurfaces
|
116
117
|
#
|
117
118
|
# @return [Array] 3D Topolys wires of 'holes' (made by kids)
|
118
119
|
def kids(model = nil, boys = {})
|
119
|
-
mth = "
|
120
|
+
mth = "TBD::#{__callee__}"
|
120
121
|
cl = Topolys::Model
|
121
122
|
holes = []
|
122
123
|
|
@@ -146,7 +147,7 @@ module TBD
|
|
146
147
|
#
|
147
148
|
# @return [Array] 3D Topolys wires of 'holes' (made by kids)
|
148
149
|
def dads(model = nil, pops = {})
|
149
|
-
mth = "
|
150
|
+
mth = "TBD::#{__callee__}"
|
150
151
|
cl = Topolys::Model
|
151
152
|
holes = {}
|
152
153
|
|
@@ -183,7 +184,7 @@ module TBD
|
|
183
184
|
# @return [Bool] true if successful
|
184
185
|
# @return [Bool] false if invalid input
|
185
186
|
def faces(s = {}, e = {})
|
186
|
-
mth = "
|
187
|
+
mth = "TBD::#{__callee__}"
|
187
188
|
|
188
189
|
return mismatch("surfaces", s, Hash, mth, DBG, false) unless s.is_a?(Hash)
|
189
190
|
return mismatch("edges", e, Hash, mth, DBG, false) unless e.is_a?(Hash)
|
@@ -211,6 +212,42 @@ module TBD
|
|
211
212
|
true
|
212
213
|
end
|
213
214
|
|
215
|
+
##
|
216
|
+
# Validate whether an OpenStudio planar surface is safe for TBD to process.
|
217
|
+
#
|
218
|
+
# @param s [OpenStudio::Model::PlanarSurface] a surface
|
219
|
+
#
|
220
|
+
# @return [Bool] true if valid surface
|
221
|
+
def validate(s = nil)
|
222
|
+
mth = "TBD::#{__callee__}"
|
223
|
+
cl = OpenStudio::Model::PlanarSurface
|
224
|
+
|
225
|
+
return mismatch("surface", s, cl, mth, DBG, false) unless s.is_a?(cl)
|
226
|
+
|
227
|
+
id = s.nameString
|
228
|
+
size = s.vertices.size
|
229
|
+
last = size - 1
|
230
|
+
|
231
|
+
log(ERR, "#{id} #{size} vertices? need +3 (#{mth})") unless size > 2
|
232
|
+
return false unless size > 2
|
233
|
+
|
234
|
+
[0, last].each do |i|
|
235
|
+
v1 = s.vertices[i]
|
236
|
+
v2 = s.vertices[i + 1] unless i == last
|
237
|
+
v2 = s.vertices.first if i == last
|
238
|
+
vector = v2 - v1
|
239
|
+
bad = vector.length < TOL
|
240
|
+
|
241
|
+
# As is, this comparison also catches collinear vertices (< 10mm apart)
|
242
|
+
# along an edge. Should avoid red-flagging such cases. TO DO.
|
243
|
+
log(ERR, "#{id}: < #{TOL}m (#{mth})") if bad
|
244
|
+
return false if bad
|
245
|
+
end
|
246
|
+
|
247
|
+
# Add as many extra tests as needed ...
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
214
251
|
##
|
215
252
|
# Return site-specific (or true) Topolys normal vector of OpenStudio surface.
|
216
253
|
#
|
@@ -250,6 +287,8 @@ module TBD
|
|
250
287
|
return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
|
251
288
|
return mismatch("surface", surface, cl2, mth) unless surface.is_a?(cl2)
|
252
289
|
|
290
|
+
return nil unless validate(surface)
|
291
|
+
|
253
292
|
nom = surface.nameString
|
254
293
|
surf = {}
|
255
294
|
subs = {}
|
@@ -307,6 +346,8 @@ module TBD
|
|
307
346
|
surf[:gross ] = surface.grossArea
|
308
347
|
|
309
348
|
surface.subSurfaces.sort_by { |s| s.nameString }.each do |s|
|
349
|
+
next unless validate(s)
|
350
|
+
|
310
351
|
id = s.nameString
|
311
352
|
valid = s.vertices.size == 3 || s.vertices.size == 4
|
312
353
|
log(ERR, "Skipping '#{id}': vertex # 3 or 4 (#{mth})") unless valid
|
@@ -334,7 +375,7 @@ module TBD
|
|
334
375
|
log(ERR, "Skipping '#{id}': missing construction (#{mth})") if c.empty?
|
335
376
|
next if c.empty?
|
336
377
|
c = c.get.to_LayeredConstruction
|
337
|
-
log(
|
378
|
+
log(WRN, "Skipping '#{id}': subs limited to #{cl3} (#{mth})") if c.empty?
|
338
379
|
next if c.empty?
|
339
380
|
c = c.get
|
340
381
|
|
@@ -630,7 +671,7 @@ module TBD
|
|
630
671
|
end
|
631
672
|
|
632
673
|
foundation = OpenStudio::Model::FoundationKiva.new(model)
|
633
|
-
foundation.setName("KIVA Foundation Floor
|
674
|
+
foundation.setName("KIVA Foundation Floor #{id}")
|
634
675
|
|
635
676
|
floor = model.getSurfaceByName(id)
|
636
677
|
kiva = false if floor.empty?
|
data/lib/tbd/psi.rb
CHANGED
@@ -205,7 +205,7 @@ module TBD
|
|
205
205
|
rimjoist: 0.300, # *
|
206
206
|
parapet: 0.325, # *
|
207
207
|
fenestration: 0.200, # *
|
208
|
-
corner: 0.300, # **
|
208
|
+
corner: 0.300, # ** not explicitely stated
|
209
209
|
balcony: 0.500, # *
|
210
210
|
party: 0.450, # *
|
211
211
|
grade: 0.450, # *
|
@@ -219,7 +219,7 @@ module TBD
|
|
219
219
|
rimjoist: 0.850, # *
|
220
220
|
parapet: 0.800, # *
|
221
221
|
fenestration: 0.500, # *
|
222
|
-
corner: 0.850, # **
|
222
|
+
corner: 0.850, # ** not explicitely stated
|
223
223
|
balcony: 1.000, # *
|
224
224
|
party: 0.850, # *
|
225
225
|
grade: 0.850, # *
|
@@ -569,16 +569,21 @@ module TBD
|
|
569
569
|
return mismatch("argh", s, Hash, mth, DBG, ipt) unless argh.is_a?(Hash)
|
570
570
|
return hashkey("argh", argh, opt, mth, DBG, ipt) unless argh.key?(opt)
|
571
571
|
|
572
|
-
argh[:io_path]
|
572
|
+
argh[:io_path ] = nil unless argh.key?(:io_path)
|
573
573
|
argh[:schema_path] = nil unless argh.key?(:schema_path)
|
574
|
-
|
574
|
+
|
575
|
+
pth = argh[:io_path ]
|
575
576
|
sch = argh[:schema_path]
|
576
577
|
|
577
|
-
if pth
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
578
|
+
if pth && (pth.is_a?(String) || pth.is_a?(Hash))
|
579
|
+
if pth.is_a?(Hash)
|
580
|
+
io = pth
|
581
|
+
else
|
582
|
+
return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
|
583
|
+
io = File.read(pth)
|
584
|
+
io = JSON.parse(io, symbolize_names: true)
|
585
|
+
return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
|
586
|
+
end
|
582
587
|
|
583
588
|
# Schema validation is not yet supported in the OpenStudio Application.
|
584
589
|
# We nonetheless recommend that users rely on the json-schema gem, or an
|
@@ -844,7 +849,7 @@ module TBD
|
|
844
849
|
up = ""
|
845
850
|
up = "uprated " if m.nameString.include?(" uprated")
|
846
851
|
m = m.clone(model).to_MasslessOpaqueMaterial.get
|
847
|
-
m.setName("
|
852
|
+
m.setName("#{id} #{up}m tbd")
|
848
853
|
de_r = 0.001 unless de_r > 0.001
|
849
854
|
loss = (de_u - 1 / de_r) * s[:net] unless de_r > 0.001
|
850
855
|
m.setThermalResistance(de_r)
|
@@ -855,7 +860,7 @@ module TBD
|
|
855
860
|
up = ""
|
856
861
|
up = "uprated " if m.nameString.include?(" uprated")
|
857
862
|
m = m.clone(model).to_StandardOpaqueMaterial.get
|
858
|
-
m.setName("
|
863
|
+
m.setName("#{id} #{up}m tbd")
|
859
864
|
k = m.thermalConductivity
|
860
865
|
|
861
866
|
if de_r > 0.001
|
@@ -1105,6 +1110,10 @@ module TBD
|
|
1105
1110
|
horizontal = dz.abs < TOL
|
1106
1111
|
vertical = dx < TOL && dy < TOL
|
1107
1112
|
edge_V = terminal - origin
|
1113
|
+
|
1114
|
+
invalid("1x edge length < TOL", mth, 0, ERROR) if edge_V.magnitude < TOL
|
1115
|
+
next if edge_V.magnitude < TOL
|
1116
|
+
|
1108
1117
|
edge_plane = Topolys::Plane3D.new(origin, edge_V)
|
1109
1118
|
|
1110
1119
|
if vertical
|
@@ -1120,71 +1129,77 @@ module TBD
|
|
1120
1129
|
# Loop through each linked wire and determine farthest point from
|
1121
1130
|
# edge while ensuring candidate point is not aligned with edge.
|
1122
1131
|
t_model.wires.each do |wire|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1132
|
+
next unless surface[:wire] == wire.id # should be a unique match
|
1133
|
+
normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
|
1134
|
+
normal = holes[id].attributes[:n] if holes.key?(id)
|
1135
|
+
normal = shades[id][:n] if shades.key?(id)
|
1136
|
+
farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
|
1137
|
+
farthest_V = farthest - origin # zero magnitude, initially
|
1138
|
+
inverted = false
|
1139
|
+
i_origin = wire.points.index(origin)
|
1140
|
+
i_terminal = wire.points.index(terminal)
|
1141
|
+
i_last = wire.points.size - 1
|
1142
|
+
|
1143
|
+
if i_terminal == 0
|
1144
|
+
inverted = true unless i_origin == i_last
|
1145
|
+
elsif i_origin == i_last
|
1146
|
+
inverted = true unless i_terminal == 0
|
1147
|
+
else
|
1148
|
+
inverted = true unless i_terminal - i_origin == 1
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
wire.points.each do |point|
|
1152
|
+
next if point == origin
|
1153
|
+
next if point == terminal
|
1154
|
+
|
1155
|
+
point_on_plane = edge_plane.project(point)
|
1156
|
+
origin_point_V = point_on_plane - origin
|
1157
|
+
point_V_magnitude = origin_point_V.magnitude
|
1158
|
+
next unless point_V_magnitude > TOL
|
1159
|
+
|
1160
|
+
# Generate a plane between origin, terminal & point. Only consider
|
1161
|
+
# planes that share the same normal as wire.
|
1162
|
+
if inverted
|
1163
|
+
plane = Topolys::Plane3D.from_points(terminal, origin, point)
|
1138
1164
|
else
|
1139
|
-
|
1165
|
+
plane = Topolys::Plane3D.from_points(origin, terminal, point)
|
1140
1166
|
end
|
1141
1167
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
point_on_plane = edge_plane.project(point)
|
1146
|
-
origin_point_V = point_on_plane - origin
|
1147
|
-
point_V_magnitude = origin_point_V.magnitude
|
1148
|
-
next unless point_V_magnitude > TOL
|
1149
|
-
|
1150
|
-
# Generate a plane between origin, terminal & point. Only consider
|
1151
|
-
# planes that share the same normal as wire.
|
1152
|
-
if inverted
|
1153
|
-
plane = Topolys::Plane3D.from_points(terminal, origin, point)
|
1154
|
-
else
|
1155
|
-
plane = Topolys::Plane3D.from_points(origin, terminal, point)
|
1156
|
-
end
|
1168
|
+
next unless (normal.x - plane.normal.x).abs < TOL &&
|
1169
|
+
(normal.y - plane.normal.y).abs < TOL &&
|
1170
|
+
(normal.z - plane.normal.z).abs < TOL
|
1157
1171
|
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1172
|
+
farther = point_V_magnitude > farthest_V.magnitude
|
1173
|
+
farthest = point if farther
|
1174
|
+
farthest_V = origin_point_V if farther
|
1175
|
+
end
|
1161
1176
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
farthest_V = origin_point_V if farther
|
1165
|
-
end
|
1177
|
+
puts "ADDITION!!" if id == "ADDITION"
|
1178
|
+
puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
|
1166
1179
|
|
1167
|
-
|
1168
|
-
|
1180
|
+
angle = reference_V.angle(farthest_V)
|
1181
|
+
invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
|
1182
|
+
angle = 0 if angle.nil?
|
1169
1183
|
|
1170
|
-
|
1184
|
+
adjust = false # adjust angle [180°, 360°] if necessary
|
1185
|
+
|
1186
|
+
if vertical
|
1187
|
+
adjust = true if east.dot(farthest_V) < -TOL
|
1188
|
+
else
|
1189
|
+
if north.dot(farthest_V).abs < TOL ||
|
1190
|
+
(north.dot(farthest_V).abs - 1).abs < TOL
|
1171
1191
|
adjust = true if east.dot(farthest_V) < -TOL
|
1172
1192
|
else
|
1173
|
-
if north.dot(farthest_V)
|
1174
|
-
(north.dot(farthest_V).abs - 1).abs < TOL
|
1175
|
-
adjust = true if east.dot(farthest_V) < -TOL
|
1176
|
-
else
|
1177
|
-
adjust = true if north.dot(farthest_V) < -TOL
|
1178
|
-
end
|
1193
|
+
adjust = true if north.dot(farthest_V) < -TOL
|
1179
1194
|
end
|
1180
|
-
|
1181
|
-
angle = 2 * Math::PI - angle if adjust
|
1182
|
-
angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
|
1183
|
-
surface[:angle] = angle
|
1184
|
-
farthest_V.normalize!
|
1185
|
-
surface[:polar] = farthest_V
|
1186
|
-
surface[:normal] = normal
|
1187
1195
|
end
|
1196
|
+
|
1197
|
+
angle = 2 * Math::PI - angle if adjust
|
1198
|
+
angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
|
1199
|
+
surface[:angle ] = angle
|
1200
|
+
farthest_V.normalize!
|
1201
|
+
surface[:polar ] = farthest_V
|
1202
|
+
surface[:normal] = normal
|
1188
1203
|
end # end of edge-linked, surface-to-wire loop
|
1189
1204
|
end # end of edge-linked surface loop
|
1190
1205
|
|
@@ -1993,8 +2008,6 @@ module TBD
|
|
1993
2008
|
#
|
1994
2009
|
# @return [Bool] true if TBD Measure is successful
|
1995
2010
|
def exit(runner = nil, argh = {})
|
1996
|
-
mth = "TBD::#{__callee__}"
|
1997
|
-
|
1998
2011
|
# Generated files target a design context ( >= WARN ) ... change TBD log
|
1999
2012
|
# level for debugging purposes. By default, log status is set < DBG
|
2000
2013
|
# while log level is set @INF.
|
data/lib/tbd/ua.rb
CHANGED
@@ -62,30 +62,33 @@ module TBD
|
|
62
62
|
return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
|
63
63
|
|
64
64
|
# First, calculate initial layer RSi to initially meet Ut target.
|
65
|
-
rt
|
66
|
-
ro
|
67
|
-
new_r
|
68
|
-
new_u
|
65
|
+
rt = 1 / ut # target construction Rt
|
66
|
+
ro = rsi(lc, film) # current construction Ro
|
67
|
+
new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
|
68
|
+
new_u = 1 / new_r
|
69
69
|
|
70
70
|
# Then, uprate (if possible) to counter expected thermal bridging effects.
|
71
|
-
u_psi
|
72
|
-
new_u
|
73
|
-
new_r
|
71
|
+
u_psi = hloss / area # from psi & khi
|
72
|
+
new_u -= u_psi # uprated layer USi to counter psi & khi
|
73
|
+
new_r = 1 / new_u # uprated layer RSi to counter psi & khi
|
74
74
|
|
75
75
|
return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
|
76
|
-
|
76
|
+
|
77
|
+
loss = 0.0 # residual heatloss (not assigned) [W/K]
|
77
78
|
|
78
79
|
if lyr[:type] == :massless
|
79
80
|
m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
|
80
81
|
return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
|
82
|
+
|
81
83
|
m = m.get.clone(model).to_MasslessOpaqueMaterial.get
|
82
84
|
m.setName("#{id} uprated")
|
83
85
|
new_r = 0.001 unless new_r > 0.001
|
84
86
|
loss = (new_u - 1 / new_r) * area unless new_r > 0.001
|
85
87
|
m.setThermalResistance(new_r)
|
86
88
|
else # type == :standard
|
87
|
-
m
|
89
|
+
m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
|
88
90
|
return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
|
91
|
+
|
89
92
|
m = m.get.clone(model).to_StandardOpaqueMaterial.get
|
90
93
|
m.setName("#{id} uprated")
|
91
94
|
k = m.thermalConductivity
|
@@ -108,10 +111,12 @@ module TBD
|
|
108
111
|
|
109
112
|
ok = m.setThickness(d)
|
110
113
|
return invalid("Can't uprate '#{id}': > 3m", mth, 0, ERR, res) unless ok
|
114
|
+
|
111
115
|
m.setThermalConductivity(k) if ok
|
112
116
|
end
|
113
117
|
|
114
118
|
return invalid("", mth, 0, ERR, res) unless m
|
119
|
+
|
115
120
|
lc.setLayer(lyr[:index], m)
|
116
121
|
uo = 1 / rsi(lc, film)
|
117
122
|
|
@@ -174,7 +179,6 @@ module TBD
|
|
174
179
|
area = 0
|
175
180
|
film = 100000000000000
|
176
181
|
lc = nil
|
177
|
-
uo = nil
|
178
182
|
id = ""
|
179
183
|
all = g[:op].downcase == "all wall constructions" ||
|
180
184
|
g[:op].downcase == "all roof constructions" ||
|
@@ -188,6 +192,7 @@ module TBD
|
|
188
192
|
next unless sss.outsideBoundaryCondition.downcase == "outdoors"
|
189
193
|
next if sss.construction.empty?
|
190
194
|
next if sss.construction.get.to_LayeredConstruction.empty?
|
195
|
+
|
191
196
|
c = sss.construction.get.to_LayeredConstruction.get
|
192
197
|
i = c.nameString
|
193
198
|
|
@@ -236,7 +241,7 @@ module TBD
|
|
236
241
|
end
|
237
242
|
|
238
243
|
if coll.empty?
|
239
|
-
log(ERR, "No construction to uprate - skipping (#{mth})")
|
244
|
+
log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
|
240
245
|
next
|
241
246
|
elsif lc # valid layered construction - good to uprate!
|
242
247
|
# Ensure lc is referenced by surface types == label.
|
@@ -948,7 +953,7 @@ module TBD
|
|
948
953
|
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
|
949
954
|
model += " (v#{ua[:version]})" if ua.key?(:version)
|
950
955
|
report << model unless model.empty?
|
951
|
-
report << "* TBD : v3.0
|
956
|
+
report << "* TBD : v3.1.0"
|
952
957
|
report << "* date : #{ua[:date]}"
|
953
958
|
|
954
959
|
if lang == :en
|
data/lib/tbd/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tbd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Bourgeois & Dan Macumber
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: topolys
|
@@ -124,13 +124,11 @@ files:
|
|
124
124
|
- LICENSE.md
|
125
125
|
- README.md
|
126
126
|
- Rakefile
|
127
|
-
- json/midrise.json
|
128
127
|
- json/tbd_5ZoneNoHVAC.json
|
129
128
|
- json/tbd_5ZoneNoHVAC_btap.json
|
130
129
|
- json/tbd_seb_n2.json
|
131
130
|
- json/tbd_seb_n4.json
|
132
131
|
- json/tbd_warehouse10.json
|
133
|
-
- json/tbd_warehouse5.json
|
134
132
|
- lib/measures/tbd/LICENSE.md
|
135
133
|
- lib/measures/tbd/README.md
|
136
134
|
- lib/measures/tbd/README.md.erb
|
@@ -163,7 +161,7 @@ licenses:
|
|
163
161
|
- MIT
|
164
162
|
metadata:
|
165
163
|
homepage_uri: https://github.com/rd2/tbd
|
166
|
-
source_code_uri: https://github.com/rd2/tbd/tree/v3.0
|
164
|
+
source_code_uri: https://github.com/rd2/tbd/tree/v3.1.0
|
167
165
|
bug_tracker_uri: https://github.com/rd2/tbd/issues
|
168
166
|
post_install_message:
|
169
167
|
rdoc_options: []
|
data/json/midrise.json
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"schema": "https://github.com/rd2/tbd/blob/master/tbd.schema.json",
|
3
|
-
"description": "sorting out multiple story PSI sets (midrise)",
|
4
|
-
"psis": [{
|
5
|
-
"id": "compliant",
|
6
|
-
"rimjoist": 0.3,
|
7
|
-
"parapet": 0.325,
|
8
|
-
"fenestration": 0.35,
|
9
|
-
"corner": 0.45,
|
10
|
-
"balcony": 0.5,
|
11
|
-
"party": 0.5,
|
12
|
-
"grade": 0.45
|
13
|
-
},
|
14
|
-
{
|
15
|
-
"id": "Building Story 1",
|
16
|
-
"rimjoist": 0.501,
|
17
|
-
"parapet": 0.511,
|
18
|
-
"fenestration": 0.521,
|
19
|
-
"cornerconcave": 0.531,
|
20
|
-
"cornerconvex": 0.541,
|
21
|
-
"balcony": 0.551,
|
22
|
-
"party": 0.561,
|
23
|
-
"grade": 0.571
|
24
|
-
},
|
25
|
-
{
|
26
|
-
"id": "Building Story 2",
|
27
|
-
"rimjoist": 0.401,
|
28
|
-
"parapet": 0.411,
|
29
|
-
"fenestration": 0.421,
|
30
|
-
"cornerconcave": 0.431,
|
31
|
-
"cornerconvex": 0.441,
|
32
|
-
"balcony": 0.451,
|
33
|
-
"party": 0.461,
|
34
|
-
"grade": 0.471
|
35
|
-
},
|
36
|
-
{
|
37
|
-
"id": "Building Story 3",
|
38
|
-
"rimjoist": 0.301,
|
39
|
-
"parapet": 0.311,
|
40
|
-
"fenestration": 0.321,
|
41
|
-
"cornerconcave": 0.331,
|
42
|
-
"cornerconvex": 0.341,
|
43
|
-
"balcony": 0.351,
|
44
|
-
"party": 0.361,
|
45
|
-
"grade": 0.371
|
46
|
-
}
|
47
|
-
],
|
48
|
-
"building": {
|
49
|
-
"psi": "compliant"
|
50
|
-
},
|
51
|
-
"stories": [{
|
52
|
-
"id": "Building Story 1",
|
53
|
-
"psi": "Building Story 1"
|
54
|
-
},
|
55
|
-
{
|
56
|
-
"id": "Building Story 2",
|
57
|
-
"psi": "Building Story 2"
|
58
|
-
},
|
59
|
-
{
|
60
|
-
"id": "Building Story 3",
|
61
|
-
"psi": "Building Story 3"
|
62
|
-
}
|
63
|
-
]
|
64
|
-
}
|
data/json/tbd_warehouse5.json
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"schema": "https://github.com/rd2/tbd/blob/master/tbd.schema.json",
|
3
|
-
"description": "test_warehouse.osm with spacetype PSI values",
|
4
|
-
"psis": [{
|
5
|
-
"id": "Warehouse Office",
|
6
|
-
"rimjoist": 0.300,
|
7
|
-
"parapet": 0.300,
|
8
|
-
"fenestration": 0.300,
|
9
|
-
"corner": 0.300,
|
10
|
-
"balcony": 0.300,
|
11
|
-
"party": 0.300,
|
12
|
-
"grade": 0.300
|
13
|
-
},
|
14
|
-
{
|
15
|
-
"id": "Warehouse Fine",
|
16
|
-
"rimjoist": 0.500,
|
17
|
-
"parapet": 0.500,
|
18
|
-
"fenestration": 0.500,
|
19
|
-
"corner": 0.500,
|
20
|
-
"balcony": 0.500,
|
21
|
-
"party": 0.500,
|
22
|
-
"grade": 0.500
|
23
|
-
}
|
24
|
-
],
|
25
|
-
"building": {
|
26
|
-
"psi": "regular (BETBG)"
|
27
|
-
},
|
28
|
-
"spacetypes": [{
|
29
|
-
"id": "Warehouse Office",
|
30
|
-
"psi": "Warehouse Office"
|
31
|
-
},
|
32
|
-
{
|
33
|
-
"id": "Warehouse Fine",
|
34
|
-
"psi": "Warehouse Fine"
|
35
|
-
}
|
36
|
-
]
|
37
|
-
}
|