tbd 3.1.1 → 3.2.1
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/README.md +3 -7
- data/Rakefile +8 -7
- 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 +38 -29
- data/lib/measures/tbd/resources/geo.rb +56 -16
- data/lib/measures/tbd/resources/model.rb +0 -6
- data/lib/measures/tbd/resources/oslog.rb +17 -7
- data/lib/measures/tbd/resources/psi.rb +117 -10
- 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/resources/version.rb +1 -1
- data/lib/measures/tbd/tests/tbd_tests.rb +117 -87
- data/lib/tbd/geo.rb +56 -16
- data/lib/tbd/psi.rb +117 -10
- data/lib/tbd/ua.rb +36 -69
- data/lib/tbd/version.rb +2 -2
- data/lib/tbd.rb +9 -9
- 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
|
@@ -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 )
|
@@ -974,8 +975,9 @@ module TBD
|
|
974
975
|
end
|
975
976
|
end
|
976
977
|
|
977
|
-
|
978
|
-
surface[:
|
978
|
+
# Recover if valid setpoints.
|
979
|
+
surface[:heating] = heat[:spt] if heat && heat[:spt]
|
980
|
+
surface[:cooling] = cool[:spt] if cool && cool[:spt]
|
979
981
|
|
980
982
|
tbd[:surfaces][s.nameString] = surface
|
981
983
|
end # (opaque) surfaces populated
|
@@ -1174,9 +1176,6 @@ module TBD
|
|
1174
1176
|
farthest_V = origin_point_V if farther
|
1175
1177
|
end
|
1176
1178
|
|
1177
|
-
puts "ADDITION!!" if id == "ADDITION"
|
1178
|
-
puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
|
1179
|
-
|
1180
1179
|
angle = reference_V.angle(farthest_V)
|
1181
1180
|
invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
|
1182
1181
|
angle = 0 if angle.nil?
|
@@ -1488,7 +1487,7 @@ module TBD
|
|
1488
1487
|
|
1489
1488
|
edge[:surfaces].keys.each do |i|
|
1490
1489
|
break if is[:rimjoist] || is[:balcony]
|
1491
|
-
break unless deratables.size
|
1490
|
+
break unless deratables.size > 0
|
1492
1491
|
break if floors.key?(id)
|
1493
1492
|
next if i == id
|
1494
1493
|
next unless floors.key?(i)
|
@@ -1771,13 +1770,120 @@ module TBD
|
|
1771
1770
|
end
|
1772
1771
|
end
|
1773
1772
|
|
1773
|
+
# Fetch edge multipliers for subsurfaces, if applicable.
|
1774
|
+
edges.values.each do |edge|
|
1775
|
+
next if edge.key?(:mult) # skip if already assigned
|
1776
|
+
next unless edge.key?(:surfaces)
|
1777
|
+
next unless edge.key?(:psi)
|
1778
|
+
ok = false
|
1779
|
+
|
1780
|
+
edge[:psi].keys.each do |k|
|
1781
|
+
break if ok
|
1782
|
+
|
1783
|
+
jamb = k.to_s.include?("jamb")
|
1784
|
+
sill = k.to_s.include?("sill")
|
1785
|
+
head = k.to_s.include?("head")
|
1786
|
+
ok = jamb || sill || head
|
1787
|
+
end
|
1788
|
+
|
1789
|
+
next unless ok # if OK, edge links subsurface(s) ... yet which one(s)?
|
1790
|
+
|
1791
|
+
edge[:surfaces].each do |id, surface|
|
1792
|
+
next unless tbd[:surfaces].key?(id) # look up parent (opaque) surface
|
1793
|
+
|
1794
|
+
[:windows, :doors, :skylights].each do |subtypes|
|
1795
|
+
next unless tbd[:surfaces][id].key?(subtypes)
|
1796
|
+
|
1797
|
+
tbd[:surfaces][id][subtypes].each do |nom, sub|
|
1798
|
+
next unless edge[:surfaces].key?(nom)
|
1799
|
+
next unless sub[:mult] > 1
|
1800
|
+
|
1801
|
+
# An edge may be tagged with (potentially conflicting) multipliers.
|
1802
|
+
# This is only possible if the edge links 2 subsurfaces, e.g. a
|
1803
|
+
# shared jamb between window & door. By default, TBD tags common
|
1804
|
+
# subsurface edges as (mild) "transitions" (i.e. PSI 0 W/K.m), so
|
1805
|
+
# there would be no point in assigning an edge multiplier. Users
|
1806
|
+
# can however reset an edge type via a TBD JSON input file (e.g.
|
1807
|
+
# "joint" instead of "transition"). It would be a very odd choice,
|
1808
|
+
# but TBD doesn't prohibit it. If linked subsurfaces have different
|
1809
|
+
# multipliers (e.g. 2 vs 3), TBD tracks the highest value.
|
1810
|
+
edge[:mult] = sub[:mult] unless edge.key?(:mult)
|
1811
|
+
edge[:mult] = sub[:mult] if sub[:mult] > edge[:mult]
|
1812
|
+
end
|
1813
|
+
end
|
1814
|
+
end
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
# Unless a user has set the thermal bridge type of an individual edge via
|
1818
|
+
# JSON input, reset any subsurface's head, sill or jamb edges as (mild)
|
1819
|
+
# transitions when in close proximity to another subsurface edge. Both
|
1820
|
+
# edges' origin and terminal vertices must be in close proximity. Edges
|
1821
|
+
# of unhinged subsurfaces are ignored.
|
1822
|
+
edges.each do |id, edge|
|
1823
|
+
nb = 0 # linked subsurfaces (i.e. "holes")
|
1824
|
+
match = false
|
1825
|
+
next if edge.key?(:io_type) # skip if set in JSON
|
1826
|
+
next unless edge.key?(:v0)
|
1827
|
+
next unless edge.key?(:v1)
|
1828
|
+
next unless edge.key?(:psi)
|
1829
|
+
next unless edge.key?(:surfaces)
|
1830
|
+
|
1831
|
+
edge[:surfaces].keys.each do |identifier|
|
1832
|
+
break if match
|
1833
|
+
next unless holes.key?(identifier)
|
1834
|
+
|
1835
|
+
if holes[identifier].attributes.key?(:unhinged)
|
1836
|
+
nb = 0 if holes[identifier].attributes[:unhinged]
|
1837
|
+
break if holes[identifier].attributes[:unhinged]
|
1838
|
+
end
|
1839
|
+
|
1840
|
+
nb += 1
|
1841
|
+
match = true if nb > 1
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
if nb == 1 # linking 1x subsurface, search for 1x other.
|
1845
|
+
e1 = { v0: edge[:v0].point, v1: edge[:v1].point }
|
1846
|
+
|
1847
|
+
edges.each do |nom, e|
|
1848
|
+
nb = 0
|
1849
|
+
break if match
|
1850
|
+
next if nom == id
|
1851
|
+
next if e.key?(:io_type)
|
1852
|
+
next unless e.key?(:psi)
|
1853
|
+
next unless e.key?(:surfaces)
|
1854
|
+
|
1855
|
+
e[:surfaces].keys.each do |identifier|
|
1856
|
+
next unless holes.key?(identifier)
|
1857
|
+
|
1858
|
+
if holes[identifier].attributes.key?(:unhinged)
|
1859
|
+
nb = 0 if holes[identifier].attributes[:unhinged]
|
1860
|
+
break if holes[identifier].attributes[:unhinged]
|
1861
|
+
end
|
1862
|
+
|
1863
|
+
nb += 1
|
1864
|
+
end
|
1865
|
+
|
1866
|
+
next unless nb == 1 # only process edge if linking 1x subsurface
|
1867
|
+
|
1868
|
+
e2 = { v0: e[:v0].point, v1: e[:v1].point }
|
1869
|
+
match = matches?(e1, e2, argh[:sub_tol])
|
1870
|
+
end
|
1871
|
+
end
|
1872
|
+
|
1873
|
+
next unless match
|
1874
|
+
|
1875
|
+
edge[:psi] = { transition: 0.000 }
|
1876
|
+
edge[:set] = json[:io][:building][:psi]
|
1877
|
+
end
|
1878
|
+
|
1774
1879
|
# Loop through each edge and assign heat loss to linked surfaces.
|
1775
1880
|
edges.each do |identifier, edge|
|
1776
1881
|
next unless edge.key?(:psi)
|
1777
1882
|
rsi = 0
|
1778
|
-
max = edge[:psi].values.max
|
1779
|
-
type = edge[:psi].key(max)
|
1883
|
+
max = edge[:psi ].values.max
|
1884
|
+
type = edge[:psi ].key(max)
|
1780
1885
|
length = edge[:length]
|
1886
|
+
length *= edge[:mult ] if edge.key?(:mult)
|
1781
1887
|
bridge = { psi: max, type: type, length: length }
|
1782
1888
|
deratables = {}
|
1783
1889
|
apertures = {}
|
@@ -1869,7 +1975,7 @@ module TBD
|
|
1869
1975
|
# ... first 'uprate' targeted insulation layers (see ua.rb) before derating.
|
1870
1976
|
# Check for new argh keys [:wall_uo], [:roof_uo] and/or [:floor_uo].
|
1871
1977
|
up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
|
1872
|
-
uprate(model, tbd[:surfaces], argh)
|
1978
|
+
uprate(model, tbd[:surfaces], argh) if up
|
1873
1979
|
|
1874
1980
|
# Derated (cloned) constructions are unique to each deratable surface.
|
1875
1981
|
# Unique construction names are prefixed with the surface name,
|
@@ -1975,6 +2081,7 @@ module TBD
|
|
1975
2081
|
set = e[:set]
|
1976
2082
|
t = e[:psi].key(v)
|
1977
2083
|
l = e[:length]
|
2084
|
+
l *= e[:mult] if e.key?(:mult)
|
1978
2085
|
edge = { psi: set, type: t, length: l, surfaces: e[:surfaces].keys }
|
1979
2086
|
edge[:v0x] = e[:v0].point.x
|
1980
2087
|
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.1"
|
957
924
|
report << "* date : #{ua[:date]}"
|
958
925
|
|
959
926
|
if lang == :en
|