tbd 3.4.4 → 3.5.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,3 +1,3 @@
1
- module Topolys
2
- VERSION = "0.6.2"
3
- end
1
+ module Topolys
2
+ VERSION = "0.6.2"
3
+ end
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2025 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
data/lib/tbd/geo.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2025 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
@@ -310,23 +310,24 @@ module TBD
310
310
  end
311
311
 
312
312
  unless surface.construction.empty?
313
- construction = surface.construction.get.to_LayeredConstruction
313
+ lc = surface.construction.get.to_LayeredConstruction
314
314
 
315
- unless construction.empty?
316
- construction = construction.get
317
- lyr = insulatingLayer(construction)
318
- lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
319
- lyr[:index] = nil unless lyr[:index] >= 0
320
- lyr[:index] = nil unless lyr[:index] < construction.layers.size
315
+ unless lc.empty?
316
+ lc = lc.get
317
+ lyr = insulatingLayer(lc)
321
318
 
322
- if lyr[:index]
323
- surf[:construction] = construction
319
+ if lyr[:index].is_a?(Integer) && lyr[:index].between?(0, lc.numLayers - 1)
320
+ surf[:construction] = lc
324
321
  # index: ... of layer/material (to derate) within construction
325
322
  # ltype: either :massless (RSi) or :standard (k + d)
326
323
  # r : initial RSi value of the indexed layer to derate
327
324
  surf[:index] = lyr[:index]
328
325
  surf[:ltype] = lyr[:type ]
329
326
  surf[:r ] = lyr[:r ]
327
+ else
328
+ surf[:index] = nil
329
+ surf[:ltype] = nil
330
+ surf[:r ] = 0.0
330
331
  end
331
332
  end
332
333
  end
data/lib/tbd/psi.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2025 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
@@ -1355,103 +1355,103 @@ module TBD
1355
1355
  ##
1356
1356
  # Thermally derates insulating material within construction.
1357
1357
  #
1358
- # @param id [#to_s] surface identifier
1358
+ # @param id [#to_sym] surface identifier
1359
1359
  # @param [Hash] s TBD surface parameters
1360
- # @option s [#to_f] :heatloss heat loss from major thermal bridging, in W/K
1361
- # @option s [#to_f] :net surface net area, in m2
1360
+ # @option s [Numeric] :heatloss heat loss from major thermal bridging, in W/K
1361
+ # @option s [Numeric] :net surface net area, in m2
1362
1362
  # @option s [:massless, :standard] :ltype indexed layer type
1363
- # @option s [#to_i] :index deratable construction layer index
1364
- # @option s [#to_f] :r deratable layer Rsi-factor, in m2•K/W
1363
+ # @option s [Integer] :index deratable construction layer index
1364
+ # @option s [Numeric] :r deratable layer Rsi-factor, in m2•K/W
1365
1365
  # @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
1366
1366
  #
1367
- # @return [OpenStudio::Model::Material] derated (cloned) material
1367
+ # @return [OpenStudio::Model::OpaqueMaterial] derated (cloned) material
1368
1368
  # @return [nil] if invalid input (see logs)
1369
1369
  def derate(id = "", s = {}, lc = nil)
1370
1370
  mth = "TBD::#{__callee__}"
1371
1371
  m = nil
1372
- id = trim(id)
1373
1372
  kys = [:heatloss, :net, :ltype, :index, :r]
1374
- ck1 = s.is_a?(Hash)
1375
- ck2 = lc.is_a?(OpenStudio::Model::LayeredConstruction)
1376
- return mismatch("id" , id, cl6, mth) if id.empty?
1377
- return mismatch("#{id} surface" , s , cl1, mth) unless ck1
1378
- return mismatch("#{id} construction", lc, cl2, mth) unless ck2
1373
+ cl = OpenStudio::Model::LayeredConstruction
1374
+ return mismatch("lc", lc, cl, mth) unless lc.respond_to?(NS)
1375
+ return mismatch("id", id, String, mth) unless id.respond_to?(:to_sym)
1376
+
1377
+ id = trim(id)
1378
+ nom = lc.nameString
1379
+ return invalid("id", mth, 1) if id.empty?
1380
+ return mismatch(nom, lc, cl, mth) unless lc.is_a?(cl)
1381
+ return mismatch("#{nom} surface", s, Hash, mth) unless s.is_a?(Hash)
1382
+
1383
+ if nom.downcase.include?(" tbd")
1384
+ log(WRN, "Won't derate '#{nom}': tagged as derated (#{mth})")
1385
+ return m
1386
+ end
1379
1387
 
1380
1388
  kys.each do |k|
1381
1389
  tag = "#{id} #{k}"
1382
- return hashkey(tag, s, k, mth, ERR) unless s.key?(k)
1390
+ return hashkey(tag, s, k, mth) unless s.key?(k)
1383
1391
 
1384
1392
  case k
1385
- when :heatloss
1386
- return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_f)
1387
- return zero(tag, mth, WRN) if s[k].to_f.abs < 0.001
1388
- when :net, :r
1389
- return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_f)
1390
- return negative(tag, mth, 2, ERR) if s[k].to_f < 0
1391
- return zero(tag, mth, WRN) if s[k].to_f.abs < 0.001
1392
- when :index
1393
- return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_i)
1394
- return negative(tag, mth, 2, ERR) if s[k].to_f < 0
1395
- else # :ltype
1393
+ when :ltype
1396
1394
  next if [:massless, :standard].include?(s[k])
1397
- return invalid(tag, mth, 2, ERR)
1398
- end
1399
- end
1395
+ return invalid(tag, mth, 2)
1396
+ when :index
1397
+ return mismatch(tag, s[k], Integer, mth) unless s[k].is_a?(Integer)
1398
+ return invalid(tag, mth, 2) unless s[k].between?(0, lc.numLayers - 1)
1399
+ else
1400
+ return mismatch(tag, s[k], Numeric, mth) unless s[k].is_a?(Numeric)
1401
+ next if k == :heatloss
1400
1402
 
1401
- if lc.nameString.downcase.include?(" tbd")
1402
- log(WRN, "Won't derate '#{id}': tagged as derated (#{mth})")
1403
- return m
1403
+ return negative(tag, mth, 2) if s[k] < 0
1404
+ return zero(tag, mth) if s[k].abs < 0.001
1405
+ end
1404
1406
  end
1405
1407
 
1406
1408
  model = lc.model
1407
- ltype = s[:ltype ]
1408
- index = s[:index ].to_i
1409
- net = s[:net ].to_f
1410
- r = s[:r ].to_f
1411
- u = s[:heatloss].to_f / net
1409
+ ltype = s[:ltype]
1410
+ index = s[:index]
1411
+ net = s[:net]
1412
+ r = s[:r]
1413
+ u = s[:heatloss] / net
1412
1414
  loss = 0
1413
- de_u = 1 / r + u # derated U
1414
- de_r = 1 / de_u # derated R
1415
+ de_u = 1 / r + u # derated insulating material U
1416
+ de_r = 1 / de_u # derated insulating material R
1415
1417
 
1416
1418
  if ltype == :massless
1417
- m = lc.getLayer(index).to_MasslessOpaqueMaterial
1419
+ m = lc.getLayer(index).to_MasslessOpaqueMaterial
1418
1420
  return invalid("#{id} massless layer?", mth, 0) if m.empty?
1419
- m = m.get
1420
- up = ""
1421
- up = "uprated " if m.nameString.downcase.include?(" uprated")
1422
- m = m.clone(model).to_MasslessOpaqueMaterial.get
1423
- m.setName("#{id} #{up}m tbd")
1424
- de_r = 0.001 unless de_r > 0.001
1425
- loss = (de_u - 1 / de_r) * net unless de_r > 0.001
1426
- m.setThermalResistance(de_r)
1421
+
1422
+ m = m.get
1423
+ up = m.nameString.downcase.include?(" uprated") ? "uprated " : ""
1424
+ m = m.clone(model).to_MasslessOpaqueMaterial.get
1425
+ m.setName("#{id} #{up}m tbd")
1426
+
1427
+ de_r = RMIN unless de_r > RMIN
1428
+ loss = (de_u - 1 / de_r) * net unless de_r > RMIN
1429
+
1430
+ unless m.setThermalResistance(de_r)
1431
+ return invalid("Can't derate #{id}: RSi#{de_r.round(2)}", mth)
1432
+ end
1427
1433
  else
1428
- m = lc.getLayer(index).to_StandardOpaqueMaterial
1434
+ m = lc.getLayer(index).to_StandardOpaqueMaterial
1429
1435
  return invalid("#{id} standard layer?", mth, 0) if m.empty?
1430
- m = m.get
1431
- up = ""
1432
- up = "uprated " if m.nameString.downcase.include?(" uprated")
1433
- m = m.clone(model).to_StandardOpaqueMaterial.get
1434
- m.setName("#{id} #{up}m tbd")
1435
- k = m.thermalConductivity
1436
-
1437
- if de_r > 0.001
1438
- d = de_r * k
1439
-
1440
- unless d > 0.003
1441
- d = 0.003
1442
- k = d / de_r
1443
- k = 3 unless k < 3
1444
- loss = (de_u - k / d) * net unless k < 3
1445
- end
1446
- else # de_r < 0.001 m2•K/W
1447
- d = 0.001 * k
1448
- d = 0.003 unless d > 0.003
1449
- k = d / 0.001 unless d > 0.003
1450
- loss = (de_u - k / d) * net
1436
+
1437
+ m = m.get
1438
+ up = m.nameString.downcase.include?(" uprated") ? "uprated " : ""
1439
+ m = m.clone(model).to_StandardOpaqueMaterial.get
1440
+ m.setName("#{id} #{up}m tbd")
1441
+
1442
+ d = m.thickness
1443
+ k = (d / de_r).clamp(KMIN, KMAX)
1444
+ d = (k * de_r).clamp(DMIN, DMAX)
1445
+
1446
+ loss = (de_u - k / d) * net unless d / k > RMIN
1447
+
1448
+ unless m.setThermalConductivity(k)
1449
+ return invalid("Can't derate #{id}: K#{k.round(3)}", mth)
1451
1450
  end
1452
1451
 
1453
- m.setThickness(d)
1454
- m.setThermalConductivity(k)
1452
+ unless m.setThickness(d)
1453
+ return invalid("Can't derate #{id}: #{(d*1000).to_i}mm", mth)
1454
+ end
1455
1455
  end
1456
1456
 
1457
1457
  if m && loss > TOL
@@ -1941,14 +1941,38 @@ module TBD
1941
1941
  ids = windows.keys + doors.keys + skylights.keys
1942
1942
  end
1943
1943
 
1944
- unless ids.include?(i)
1945
- log(ERR, "Orphaned subsurface #{i} (mth)")
1944
+ adj = nil
1945
+
1946
+ unless ids.include?(i) # adjacent sub surface?
1947
+ sb = model.getSubSurfaceByName(i)
1948
+
1949
+ if sb.empty?
1950
+ log(DBG, "Orphaned subsurface #{i} (#{mth})?")
1951
+ else
1952
+ sb = sb.get
1953
+ adj = sb.adjacentSubSurface
1954
+
1955
+ if adj.empty?
1956
+ log(DBG, "Orphaned sub #{i} (#{mth})?")
1957
+ end
1958
+ end
1959
+
1946
1960
  next
1947
1961
  end
1948
1962
 
1949
- window = windows.key?(i) ? windows[i] : {}
1950
- door = doors.key?(i) ? doors[i] : {}
1951
- skylight = skylights.key?(i) ? skylights[i] : {}
1963
+ if adj
1964
+ window = windows.key?(i) ? windows[i] : {}
1965
+ door = doors.key?(i) ? doors[i] : {}
1966
+ skylight = skylights.key?(i) ? skylights[i] : {}
1967
+ else
1968
+ window = windows.key?(i) ? windows[i] : {}
1969
+ door = doors.key?(i) ? doors[i] : {}
1970
+ skylight = skylights.key?(i) ? skylights[i] : {}
1971
+ end
1972
+
1973
+ # window = windows.key?(i) ? windows[i] : {}
1974
+ # door = doors.key?(i) ? doors[i] : {}
1975
+ # skylight = skylights.key?(i) ? skylights[i] : {}
1952
1976
 
1953
1977
  sub = window unless window.empty?
1954
1978
  sub = door unless door.empty?
@@ -2939,12 +2963,12 @@ module TBD
2939
2963
  up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
2940
2964
  uprate(model, tbd[:surfaces], argh) if up
2941
2965
 
2942
- # Derated (cloned) constructions are unique to each deratable surface.
2943
- # Unique construction names are prefixed with the surface name,
2944
- # and suffixed with " tbd", indicating that the construction is
2945
- # henceforth thermally derated. The " tbd" expression is also key in
2946
- # avoiding inadvertent derating - TBD will not derate constructions
2947
- # (or rather layered materials) having " tbd" in their OpenStudio name.
2966
+ # A derated (cloned) construction and (cloned) insulating layer are unique
2967
+ # to each deratable surface. Unique construction and material names are
2968
+ # prefixed with the surface name, and suffixed with " tbd", indicating that
2969
+ # the construction is henceforth thermally derated. The " tbd" expression
2970
+ # is also key in avoiding inadvertent sequential derating - TBD will not
2971
+ # derate a construction/material pair having " tbd" in their OpenStudio name.
2948
2972
  tbd[:surfaces].each do |id, surface|
2949
2973
  next unless surface.key?(:construction)
2950
2974
  next unless surface.key?(:index)
@@ -3138,9 +3162,9 @@ module TBD
3138
3162
  argh[:uprate_walls ] = false unless argh.key?(:uprate_walls )
3139
3163
  argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs )
3140
3164
  argh[:uprate_floors] = false unless argh.key?(:uprate_floors)
3141
- argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut )
3142
- argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut )
3143
- argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut )
3165
+ argh[:wall_ut ] = UMAX unless argh.key?(:wall_ut )
3166
+ argh[:roof_ut ] = UMAX unless argh.key?(:roof_ut )
3167
+ argh[:floor_ut ] = UMAX unless argh.key?(:floor_ut )
3144
3168
  argh[:wall_option ] = "" unless argh.key?(:wall_option )
3145
3169
  argh[:roof_option ] = "" unless argh.key?(:roof_option )
3146
3170
  argh[:floor_option ] = "" unless argh.key?(:floor_option )
data/lib/tbd/ua.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2025 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
@@ -54,12 +54,12 @@ module TBD
54
54
  lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
55
55
  lyr[:index] = nil unless lyr[:index] >= 0
56
56
  lyr[:index] = nil unless lyr[:index] < lc.layers.size
57
- return invalid("#{id} layer index", mth, 3, ERR, res) unless lyr[:index]
57
+ return invalid("#{id} layer index", mth, 3, WRN, res) unless lyr[:index]
58
58
  return zero("#{id}: heatloss" , mth, WRN, res) unless hloss > TOL
59
59
  return zero("#{id}: films" , mth, WRN, res) unless film > TOL
60
- return zero("#{id}: Ut" , mth, WRN, res) unless ut > TOL
61
- return invalid("#{id}: Ut" , mth, 6, WRN, res) unless ut < 5.678
62
- return zero("#{id}: net area (m2)", mth, ERR, res) unless area > TOL
60
+ return zero("#{id}: Ut" , mth, WRN, res) unless ut > UMIN
61
+ return invalid("#{id}: Ut" , mth, 6, WRN, res) unless ut < UMAX
62
+ return zero("#{id}: net area (m2)", mth, WRN, res) unless area > TOL
63
63
 
64
64
  # First, calculate initial layer RSi to initially meet Ut target.
65
65
  rt = 1 / ut # target construction Rt
@@ -71,56 +71,51 @@ module TBD
71
71
  u_psi = hloss / area # from psi+khi
72
72
  new_u -= u_psi # uprated layer USi to counter psi+khi
73
73
  new_r = 1 / new_u # uprated layer RSi to counter psi+khi
74
- return zero("#{id}: new Rsi", mth, ERR, res) unless new_r > 0.001
74
+ return zero("#{id}: new Rsi", mth, WRN, res) unless new_r > RMIN
75
75
 
76
76
  if lyr[:type] == :massless
77
- m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
78
- return invalid("#{id} massless layer?", mth, 0, DBG, res) if m.empty?
79
-
80
- m = m.get.clone(model).to_MasslessOpaqueMaterial.get
81
- m.setName("#{id} uprated")
82
- new_r = 0.001 unless new_r > 0.001
83
- loss = (new_u - 1 / new_r) * area unless new_r > 0.001
84
- m.setThermalResistance(new_r)
85
- else # type == :standard
86
- m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
87
- return invalid("#{id} standard layer?", mth, 0, DBG, res) if m.empty?
88
-
89
- m = m.get.clone(model).to_StandardOpaqueMaterial.get
90
- m.setName("#{id} uprated")
91
- k = m.thermalConductivity
92
-
93
- if new_r > 0.001
94
- d = new_r * k
95
-
96
- unless d > 0.003
97
- d = 0.003
98
- k = d / new_r
99
- k = 3.0 unless k < 3.0
100
- loss = (new_u - k / d) * area unless k < 3.0
101
- end
102
- else # new_r < 0.001 m2•K/W
103
- d = 0.001 * k
104
- d = 0.003 unless d > 0.003
105
- k = d / 0.001 unless d > 0.003
106
- loss = (new_u - k / d) * area
77
+ m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
78
+ return invalid("#{id} massless layer?", mth, 0, DBG, res) if m.empty?
79
+
80
+ m = m.get.clone(model).to_MasslessOpaqueMaterial.get
81
+ m.setName("#{id} uprated")
82
+
83
+ new_r = RMIN unless new_r > RMIN
84
+ loss = (new_u - 1 / new_r) * area unless new_r > RMIN
85
+
86
+ unless m.setThermalResistance(new_r)
87
+ return invalid("Can't uprate #{id}: RSi#{new_r.round(2)}", mth, 0, DBG, res)
107
88
  end
89
+ else
90
+ m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
91
+ return invalid("#{id} standard layer?", mth, 0, DBG, res) if m.empty?
108
92
 
109
- if m.setThickness(d)
110
- m.setThermalConductivity(k)
111
- else
112
- return invalid("Can't uprate #{id}: #{d} > 3m", mth, 0, ERR, res)
93
+ m = m.get.clone(model).to_StandardOpaqueMaterial.get
94
+ m.setName("#{id} uprated")
95
+
96
+ d = m.thickness
97
+ k = (d / new_r).clamp(KMIN, KMAX)
98
+ d = (k * new_r).clamp(DMIN, DMAX)
99
+
100
+ loss = (new_u - k / d) * area unless d / k > RMIN
101
+
102
+ unless m.setThermalConductivity(k)
103
+ return invalid("Can't uprate #{id}: K#{k.round(3)}", mth, 0, DBG, res)
104
+ end
105
+
106
+ unless m.setThickness(d)
107
+ return invalid("Can't uprate #{id}: #{(d*1000).to_i}mm", mth, 0, DBG, res)
113
108
  end
114
109
  end
115
110
 
116
- return invalid("Can't ID insulating layer", mth, 0, ERR, res) unless m
111
+ return invalid("Can't ID insulating layer", mth, 0, DBG, res) unless m
117
112
 
118
113
  lc.setLayer(lyr[:index], m)
119
114
  uo = 1 / rsi(lc, film)
120
115
 
121
116
  if loss > TOL
122
117
  h_loss = format "%.3f", loss
123
- return invalid("Can't assign #{h_loss} W/K to #{id}", mth, 0, ERR, res)
118
+ return invalid("Can't assign #{h_loss} W/K to #{id}", mth, 0, DBG, res)
124
119
  end
125
120
 
126
121
  res[:uo] = uo
@@ -145,9 +140,9 @@ module TBD
145
140
  # @option argh [Bool] :uprate_walls (false) whether to uprate walls
146
141
  # @option argh [Bool] :uprate_roofs (false) whether to uprate roofs
147
142
  # @option argh [Bool] :uprate_floors (false) whether to uprate floors
148
- # @option argh [#to_f] :wall_ut (5.678) uprated wall Usi-factor target
149
- # @option argh [#to_f] :roof_ut (5.678) uprated roof Usi-factor target
150
- # @option argh [#to_f] :floor_ut (5.678) uprated floor Usi-factor target
143
+ # @option argh [#to_f] :wall_ut (UMAX) uprated wall Usi-factor target
144
+ # @option argh [#to_f] :roof_ut (UMAX) uprated roof Usi-factor target
145
+ # @option argh [#to_f] :floor_ut (UMAX) uprated floor Usi-factor target
151
146
  # @option argh [#to_s] :wall_option ("") construction to uprate (or "all")
152
147
  # @option argh [#to_s] :roof_option ("") construction to uprate (or "all")
153
148
  # @option argh [#to_s] :floor_option ("") construction to uprate (or "all")
@@ -172,9 +167,9 @@ module TBD
172
167
  argh[:uprate_walls ] = false unless argh.key?(:uprate_walls)
173
168
  argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs)
174
169
  argh[:uprate_floors] = false unless argh.key?(:uprate_floors)
175
- argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut)
176
- argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut)
177
- argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut)
170
+ argh[:wall_ut ] = UMAX unless argh.key?(:wall_ut)
171
+ argh[:roof_ut ] = UMAX unless argh.key?(:roof_ut)
172
+ argh[:floor_ut ] = UMAX unless argh.key?(:floor_ut)
178
173
  argh[:wall_option ] = "" unless argh.key?(:wall_option)
179
174
  argh[:roof_option ] = "" unless argh.key?(:roof_option)
180
175
  argh[:floor_option ] = "" unless argh.key?(:floor_option)
@@ -197,7 +192,7 @@ module TBD
197
192
  groups.each do |type, g|
198
193
  next unless g[:up]
199
194
  next unless g[:ut].is_a?(Numeric)
200
- next unless g[:ut] < 5.678
195
+ next unless g[:ut] < UMAX
201
196
  next if g[:ut] < 0
202
197
 
203
198
  typ = type
@@ -211,7 +206,7 @@ module TBD
211
206
  all = tout.include?(op)
212
207
 
213
208
  if g[:op].empty?
214
- log(ERR, "Construction (#{type}) to uprate? (#{mth})")
209
+ log(WRN, "Construction (#{type}) to uprate? (#{mth})")
215
210
  elsif all
216
211
  s.each do |nom, surface|
217
212
  next unless surface.key?(:deratable )
@@ -247,11 +242,11 @@ module TBD
247
242
  else
248
243
  id = g[:op]
249
244
  lc = model.getConstructionByName(id)
250
- log(ERR, "Construction '#{id}'? (#{mth})") if lc.empty?
245
+ log(WRN, "Construction '#{id}'? (#{mth})") if lc.empty?
251
246
  next if lc.empty?
252
247
 
253
248
  lc = lc.get.to_LayeredConstruction
254
- log(ERR, "'#{id}' layered construction? (#{mth})") if lc.empty?
249
+ log(WRN, "'#{id}' layered construction? (#{mth})") if lc.empty?
255
250
  next if lc.empty?
256
251
 
257
252
  lc = lc.get
@@ -282,7 +277,7 @@ module TBD
282
277
  end
283
278
 
284
279
  if coll.empty?
285
- log(ERR, "No #{type} construction to uprate - skipping (#{mth})")
280
+ log(WRN, "No #{type} construction to uprate - skipping (#{mth})")
286
281
  next
287
282
  elsif lc
288
283
  # Valid layered construction - good to uprate!
@@ -291,7 +286,7 @@ module TBD
291
286
  lyr[:index] = nil unless lyr[:index] >= 0
292
287
  lyr[:index] = nil unless lyr[:index] < lc.layers.size
293
288
 
294
- log(ERR, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
289
+ log(WRN, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
295
290
  next unless lyr[:index]
296
291
 
297
292
  # Ensure lc is exclusively linked to deratable surfaces of right type.
@@ -302,10 +297,10 @@ module TBD
302
297
  next unless surface.key?(:construction)
303
298
  next unless surface[:construction].is_a?(cl3)
304
299
  next unless surface[:construction] == lc
300
+ next unless surface[:deratable]
305
301
 
306
302
  ok = true
307
- ok = false unless surface[:type ] == typ
308
- ok = false unless surface[:deratable]
303
+ ok = false unless surface[:type] == typ
309
304
  ok = false unless coll.key?(id)
310
305
  ok = false unless coll[id][:s].key?(nom)
311
306
 
@@ -346,13 +341,18 @@ module TBD
346
341
 
347
342
  if sss.isConstructionDefaulted
348
343
  set = defaultConstructionSet(sss) # building? story?
349
- constructions = set.defaultExteriorSurfaceConstructions
350
344
 
351
- unless constructions.empty?
352
- constructions = constructions.get
353
- constructions.setWallConstruction(lc) if typ == :wall
354
- constructions.setFloorConstruction(lc) if typ == :floor
355
- constructions.setRoofCeilingConstruction(lc) if typ == :ceiling
345
+ if set.nil?
346
+ sss.setConstruction(lc)
347
+ else
348
+ constructions = set.defaultExteriorSurfaceConstructions
349
+
350
+ unless constructions.empty?
351
+ constructions = constructions.get
352
+ constructions.setWallConstruction(lc) if typ == :wall
353
+ constructions.setFloorConstruction(lc) if typ == :floor
354
+ constructions.setRoofCeilingConstruction(lc) if typ == :ceiling
355
+ end
356
356
  end
357
357
  else
358
358
  sss.setConstruction(lc)
@@ -385,7 +385,7 @@ module TBD
385
385
  res = uo(model, lc, id, hloss, film, g[:ut])
386
386
 
387
387
  unless res[:uo] && res[:m]
388
- log(ERR, "Unable to uprate '#{id}' (#{mth})")
388
+ log(WRN, "Unable to uprate '#{id}' (#{mth})")
389
389
  next
390
390
  end
391
391
 
@@ -407,7 +407,7 @@ module TBD
407
407
  argh[:roof_uo ] = res[:uo] if typ == :ceiling
408
408
  argh[:floor_uo] = res[:uo] if typ == :floor
409
409
  else
410
- log(ERR, "Nilled construction to uprate - (#{mth})")
410
+ log(WRN, "Nilled construction to uprate - (#{mth})")
411
411
  return false
412
412
  end
413
413
  end
@@ -589,7 +589,7 @@ module TBD
589
589
  empty = shorts[:has].empty? && shorts[:val].empty?
590
590
  has = shorts[:has] unless empty
591
591
  val = shorts[:val] unless empty
592
- log(ERR, "Invalid UA' reference set (#{mth})") if empty
592
+ log(WRN, "Invalid UA' reference set (#{mth})") if empty
593
593
 
594
594
  unless empty
595
595
  ua[:model] += " : Design vs '#{ref}'"
@@ -1000,7 +1000,7 @@ module TBD
1000
1000
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
1001
1001
  model += " (v#{ua[:version]})" if ua.key?(:version)
1002
1002
  report << model unless model.empty?
1003
- report << "* TBD : v3.4.4"
1003
+ report << "* TBD : v3.5.0"
1004
1004
  report << "* date : #{ua[:date]}"
1005
1005
 
1006
1006
  if lang == :en
data/lib/tbd/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2025 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
@@ -21,5 +21,5 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  module TBD
24
- VERSION = "3.4.4".freeze # TBD release version
24
+ VERSION = "3.5.0".freeze
25
25
  end
data/lib/tbd.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2025 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
@@ -58,16 +58,22 @@ rescue LoadError
58
58
  end
59
59
 
60
60
  module TBD
61
- extend OSut # OpenStudio utilities
62
-
63
- TOL = OSut::TOL.dup # default distance tolerance (m)
64
- TOL2 = OSut::TOL2.dup # default area tolerance (m2)
65
- DBG = OSut::DEBUG.dup # github.com/rd2/oslg
66
- INF = OSut::INFO.dup # github.com/rd2/oslg
67
- WRN = OSut::WARN.dup # github.com/rd2/oslg
68
- ERR = OSut::ERR.dup # github.com/rd2/oslg
69
- FTL = OSut::FATAL.dup # github.com/rd2/oslg
70
- NS = OSut::NS.dup # OpenStudio IdfObject nameString method
71
-
61
+ extend OSut # OpenStudio utilities (github.com/rd2/oslg)
62
+ DBG = OSut::DEBUG.dup
63
+ INF = OSut::INFO.dup
64
+ WRN = OSut::WARN.dup
65
+ ERR = OSut::ERR.dup
66
+ FTL = OSut::FATAL.dup
67
+ NS = OSut::NS.dup
68
+ TOL = OSut::TOL.dup
69
+ TOL2 = OSut::TOL2.dup
70
+ DMIN = OSut::DMIN.dup
71
+ DMAX = OSut::DMAX.dup
72
+ KMIN = OSut::KMIN.dup
73
+ KMAX = OSut::KMAX.dup
74
+ UMAX = OSut::UMAX.dup
75
+ UMIN = OSut::UMIN.dup
76
+ RMIN = OSut::RMIN.dup
77
+ RMAX = OSut::RMAX.dup
72
78
  extend TBD
73
79
  end
data/tbd.gemspec CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
29
29
  s.metadata = {}
30
30
 
31
31
  s.add_dependency "topolys", "~> 0"
32
- s.add_dependency "osut", "~> 0"
32
+ s.add_dependency "osut", "~> 0.8.0"
33
33
  s.add_dependency "json-schema", "~> 4"
34
34
 
35
35
  s.add_development_dependency "bundler", "~> 2.1"