tbd 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2022 Denis Bourgeois & Dan Macumber
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 # based on EXTERIOR dimensions (art. 3.1.1.6)
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 # based on EXTERIOR dimensions (art. 3.1.1.6)
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 adiabatic surface
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
- next unless tbd[:surfaces][i][:boundary].downcase == "adiabatic"
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) if up
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-2022 Denis Bourgeois & Dan Macumber
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-2022 Denis Bourgeois & Dan Macumber
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 # type == :standard
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 = 21.0
599
- heating = surface[:heating] if surface.key?(:heating)
600
- bloc = b1
601
- bloc = b2 if heating < 18
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
- bloc[:pro][:walls ] += surface[:net] * surface[:u ]
607
- bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
608
- bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
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
- bloc[:pro][:roofs ] += surface[:net] * surface[:u ]
612
- bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
613
- bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
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
- bloc[:pro][:floors] += surface[:net] * surface[:u ]
617
- bloc[:ref][:floors] += surface[:net] * surface[:ref] if reference
618
- bloc[:ref][:floors] += surface[:net] * surface[:u ] unless reference
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
- if surface.key?(:skylights)
656
- surface[:skylights].values.each do |sky|
657
- next unless sky.key?(:gross)
658
- next unless sky[:gross] > TOL
659
- next unless sky.key?(:u)
660
- next unless sky[:u] > TOL
661
- areas[:walls][:subs ] += sky[:gross] if type == :wall
662
- areas[:roofs][:subs ] += sky[:gross] if type == :ceiling
663
- areas[:floors][:subs ] += sky[:gross] if type == :floor
664
- bloc[:pro][:skylights] += sky[:gross] * sky[:u]
665
-
666
- ok = sky.key?(:ref)
667
- bloc[:ref][:skylights] += sky[:gross] * sky[:ref] if ok
668
- bloc[:ref][:skylights] += sky[:gross] * sky[:u ] unless ok
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.1.0"
923
+ report << "* TBD : v3.2.0"
957
924
  report << "* date : #{ua[:date]}"
958
925
 
959
926
  if lang == :en