tbd 3.1.1 → 3.2.1
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/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
|