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.
@@ -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