tbd 3.1.1 → 3.2.1

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
@@ -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
- surface[:heating] = heat[:spt] if heat[:spt] # if valid heating setpoints
978
- surface[:cooling] = cool[:spt] if cool[:spt] # if valid cooling setpoints
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 == 2
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) if up
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-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.1"
923
+ report << "* TBD : v3.2.1"
957
924
  report << "* date : #{ua[:date]}"
958
925
 
959
926
  if lang == :en