tbd 3.1.0 → 3.2.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 +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 +119 -11
- 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 +119 -11
- 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
|
@@ -211,7 +211,7 @@ module TBD
|
|
211
211
|
grade: 0.450, # *
|
212
212
|
joint: 0.200, # *
|
213
213
|
transition: 0.000
|
214
|
-
}.freeze
|
214
|
+
}.freeze
|
215
215
|
self.gen("code (Quebec)")
|
216
216
|
|
217
217
|
@set["uncompliant (Quebec)"] = # NECB-QC (non-code-compliant) defaults:
|
@@ -225,7 +225,7 @@ module TBD
|
|
225
225
|
grade: 0.850, # *
|
226
226
|
joint: 0.500, # *
|
227
227
|
transition: 0.000
|
228
|
-
}.freeze
|
228
|
+
}.freeze
|
229
229
|
self.gen("uncompliant (Quebec)")
|
230
230
|
|
231
231
|
@set["(non thermal bridging)"] = # ... would not derate surfaces:
|
@@ -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?
|
@@ -1426,7 +1424,7 @@ module TBD
|
|
1426
1424
|
end
|
1427
1425
|
|
1428
1426
|
# Label edge as :party if linked to:
|
1429
|
-
# 1x
|
1427
|
+
# 1x OtherSideCoefficients surface
|
1430
1428
|
# 1x (only) deratable surface
|
1431
1429
|
edge[:surfaces].keys.each do |i|
|
1432
1430
|
break if is[:party]
|
@@ -1435,7 +1433,9 @@ module TBD
|
|
1435
1433
|
next unless tbd[:surfaces].key?(i)
|
1436
1434
|
next if holes.key?(i)
|
1437
1435
|
next if shades.key?(i)
|
1438
|
-
|
1436
|
+
|
1437
|
+
facing = tbd[:surfaces][i][:boundary].downcase
|
1438
|
+
next unless facing == "othersidecoefficients"
|
1439
1439
|
|
1440
1440
|
s1 = edge[:surfaces][id]
|
1441
1441
|
s2 = edge[:surfaces][i]
|
@@ -1769,13 +1769,120 @@ module TBD
|
|
1769
1769
|
end
|
1770
1770
|
end
|
1771
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
|
+
|
1772
1878
|
# Loop through each edge and assign heat loss to linked surfaces.
|
1773
1879
|
edges.each do |identifier, edge|
|
1774
1880
|
next unless edge.key?(:psi)
|
1775
1881
|
rsi = 0
|
1776
|
-
max = edge[:psi].values.max
|
1777
|
-
type = edge[:psi].key(max)
|
1882
|
+
max = edge[:psi ].values.max
|
1883
|
+
type = edge[:psi ].key(max)
|
1778
1884
|
length = edge[:length]
|
1885
|
+
length *= edge[:mult ] if edge.key?(:mult)
|
1779
1886
|
bridge = { psi: max, type: type, length: length }
|
1780
1887
|
deratables = {}
|
1781
1888
|
apertures = {}
|
@@ -1867,7 +1974,7 @@ module TBD
|
|
1867
1974
|
# ... first 'uprate' targeted insulation layers (see ua.rb) before derating.
|
1868
1975
|
# Check for new argh keys [:wall_uo], [:roof_uo] and/or [:floor_uo].
|
1869
1976
|
up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
|
1870
|
-
uprate(model, tbd[:surfaces], argh)
|
1977
|
+
uprate(model, tbd[:surfaces], argh) if up
|
1871
1978
|
|
1872
1979
|
# Derated (cloned) constructions are unique to each deratable surface.
|
1873
1980
|
# Unique construction names are prefixed with the surface name,
|
@@ -1973,6 +2080,7 @@ module TBD
|
|
1973
2080
|
set = e[:set]
|
1974
2081
|
t = e[:psi].key(v)
|
1975
2082
|
l = e[:length]
|
2083
|
+
l *= e[:mult] if e.key?(:mult)
|
1976
2084
|
edge = { psi: set, type: t, length: l, surfaces: e[:surfaces].keys }
|
1977
2085
|
edge[:v0x] = e[:v0].point.x
|
1978
2086
|
edge[:v0y] = e[:v0].point.y
|
@@ -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
|
@@ -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
|
@@ -47,6 +47,8 @@ module TBD
|
|
47
47
|
return mismatch("film", film, cl3, mth, DBG, res) unless film.is_a?(cl3)
|
48
48
|
return mismatch("Ut", ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
|
49
49
|
|
50
|
+
loss = 0.0 # residual heatloss (not assigned) [W/K]
|
51
|
+
area = lc.getNetArea
|
50
52
|
lyr = insulatingLayer(lc)
|
51
53
|
lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
|
52
54
|
lyr[:index] = nil unless lyr[:index] >= 0
|
@@ -57,8 +59,6 @@ module TBD
|
|
57
59
|
return zero("'#{id}': films", mth, WRN, res) unless film > TOL
|
58
60
|
return zero("'#{id}': Ut", mth, WRN, res) unless ut > TOL
|
59
61
|
return invalid("'#{id}': Ut", mth, 0, WRN, res) unless ut < 5.678
|
60
|
-
|
61
|
-
area = lc.getNetArea
|
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.
|
@@ -74,8 +74,6 @@ module TBD
|
|
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]
|
78
|
-
|
79
77
|
if lyr[:type] == :massless
|
80
78
|
m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
|
81
79
|
return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
|
@@ -85,7 +83,7 @@ module TBD
|
|
85
83
|
new_r = 0.001 unless new_r > 0.001
|
86
84
|
loss = (new_u - 1 / new_r) * area unless new_r > 0.001
|
87
85
|
m.setThermalResistance(new_r)
|
88
|
-
else
|
86
|
+
else # type == :standard
|
89
87
|
m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
|
90
88
|
return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
|
91
89
|
|
@@ -595,77 +593,46 @@ module TBD
|
|
595
593
|
next unless surface[:net] > TOL
|
596
594
|
next unless surface.key?(:u)
|
597
595
|
next unless surface[:u] > TOL
|
598
|
-
heating
|
599
|
-
heating
|
600
|
-
bloc
|
601
|
-
bloc
|
602
|
-
|
596
|
+
heating = 21.0
|
597
|
+
heating = surface[:heating] if surface.key?(:heating)
|
598
|
+
bloc = b1
|
599
|
+
bloc = b2 if heating < 18
|
603
600
|
reference = surface.key?(:ref)
|
601
|
+
|
604
602
|
if type == :wall
|
605
603
|
areas[:walls][:net ] += surface[:net]
|
606
|
-
|
607
|
-
|
608
|
-
|
604
|
+
bloc[:pro][:walls ] += surface[:net] * surface[:u ]
|
605
|
+
bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
|
606
|
+
bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
|
609
607
|
elsif type == :ceiling
|
610
608
|
areas[:roofs][:net ] += surface[:net]
|
611
|
-
|
612
|
-
|
613
|
-
|
609
|
+
bloc[:pro][:roofs ] += surface[:net] * surface[:u ]
|
610
|
+
bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
|
611
|
+
bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
|
614
612
|
else
|
615
613
|
areas[:floors][:net] += surface[:net]
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
end
|
620
|
-
|
621
|
-
if surface.key?(:doors)
|
622
|
-
surface[:doors].values.each do |door|
|
623
|
-
next unless door.key?(:gross)
|
624
|
-
next unless door[:gross] > TOL
|
625
|
-
next unless door.key?(:u)
|
626
|
-
next unless door[:u] > TOL
|
627
|
-
areas[:walls][:subs ] += door[:gross] if type == :wall
|
628
|
-
areas[:roofs][:subs ] += door[:gross] if type == :ceiling
|
629
|
-
areas[:floors][:subs] += door[:gross] if type == :floor
|
630
|
-
bloc[:pro][:doors ] += door[:gross] * door[:u]
|
631
|
-
|
632
|
-
ok = door.key?(:ref)
|
633
|
-
bloc[:ref][:doors ] += door[:gross] * door[:ref] if ok
|
634
|
-
bloc[:ref][:doors ] += door[:gross] * door[:u ] unless ok
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
|
-
if surface.key?(:windows)
|
639
|
-
surface[:windows].values.each do |window|
|
640
|
-
next unless window.key?(:gross)
|
641
|
-
next unless window[:gross] > TOL
|
642
|
-
next unless window.key?(:u)
|
643
|
-
next unless window[:u] > TOL
|
644
|
-
areas[:walls][:subs ] += window[:gross] if type == :wall
|
645
|
-
areas[:roofs][:subs ] += window[:gross] if type == :ceiling
|
646
|
-
areas[:floors][:subs] += window[:gross] if type == :floor
|
647
|
-
bloc[:pro][:windows] += window[:gross] * window[:u]
|
648
|
-
|
649
|
-
ok = window.key?(:ref)
|
650
|
-
bloc[:ref][:windows ] += window[:gross] * window[:ref] if ok
|
651
|
-
bloc[:ref][:windows ] += window[:gross] * window[:u ] unless ok
|
652
|
-
end
|
614
|
+
bloc[:pro][:floors ] += surface[:net] * surface[:u ]
|
615
|
+
bloc[:ref][:floors ] += surface[:net] * surface[:ref] if reference
|
616
|
+
bloc[:ref][:floors ] += surface[:net] * surface[:u ] unless reference
|
653
617
|
end
|
654
618
|
|
655
|
-
|
656
|
-
surface
|
657
|
-
|
658
|
-
|
659
|
-
next unless
|
660
|
-
next unless
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
619
|
+
[:doors, :windows, :skylights].each do |subs|
|
620
|
+
next unless surface.key?(subs)
|
621
|
+
|
622
|
+
surface[subs].values.each do |sub|
|
623
|
+
next unless sub.key?(:gross)
|
624
|
+
next unless sub.key?(:u )
|
625
|
+
next unless sub[:gross] > TOL
|
626
|
+
next unless sub[:u ] > TOL
|
627
|
+
|
628
|
+
gross = sub[:gross]
|
629
|
+
gross *= sub[:mult ] if sub.key?(:mult)
|
630
|
+
areas[:walls ][:subs] += gross if type == :wall
|
631
|
+
areas[:roofs ][:subs] += gross if type == :ceiling
|
632
|
+
areas[:floors][:subs] += gross if type == :floor
|
633
|
+
bloc[:pro ][subs ] += gross * sub[:u ]
|
634
|
+
bloc[:ref ][subs ] += gross * sub[:ref] if sub.key?(:ref)
|
635
|
+
bloc[:ref ][subs ] += gross * sub[:u ] unless sub.key?(:ref)
|
669
636
|
end
|
670
637
|
end
|
671
638
|
|
@@ -953,7 +920,7 @@ module TBD
|
|
953
920
|
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
|
954
921
|
model += " (v#{ua[:version]})" if ua.key?(:version)
|
955
922
|
report << model unless model.empty?
|
956
|
-
report << "* TBD : v3.
|
923
|
+
report << "* TBD : v3.2.0"
|
957
924
|
report << "* date : #{ua[:date]}"
|
958
925
|
|
959
926
|
if lang == :en
|