tbd 3.4.5 → 3.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '088489dd7b5163709bef87c140b053593cca33f27fff736a553ec2c734ace460'
4
- data.tar.gz: 0e5a5ed2af0d1d4c9d0b50d625147bd519b5944b923ae12d0ef414d88818c0a9
3
+ metadata.gz: 551371a237284715450e716fba2b269ae37d1f54f348311acbf50245c8df0b29
4
+ data.tar.gz: ea3e0c6489c60e354518c3a38516fb70fda77d9250772e5db2995c32e6bc17c5
5
5
  SHA512:
6
- metadata.gz: 708b50fde62e4990344e51eb46390607ee193338d59e3539c61f80fd53850acfb37dc6659bfe77ef23a3820f1339266fa02425ad719f9f940b948b6564bc6d22
7
- data.tar.gz: 69c846ef8d3663436b726566dfc3b78e44e6a9ba30431d00b724c6c5778150a8e6b950abdbf18af6bf5e8ed0276d0fa008e683f1c6ee3656c2ca6436c0c74af3
6
+ metadata.gz: 2779d70ffa38269af0eea2cd7cd83bab7223f55b0c22a198fc2b0c612454441bda39eb9ef129c2480109a83060da6a84ee81901fad42c3b19691ef6bb297f1d1
7
+ data.tar.gz: 7cab12e47384c1aad5b86960a21c042f96b07b8912c910314925e235c817c3e2618c4d3a90155834294c31831943e3c4c3124178afe27c1dc540cb92b77aa825
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
3
+ Copyright (c) 2020-2026 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-2025 Denis Bourgeois & Dan Macumber
3
+ Copyright (c) 2020-2026 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-2025 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2026 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
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.1</schema_version>
4
4
  <name>tbd_measure</name>
5
5
  <uid>8890787b-8c25-4dc8-8641-b6be1b6c2357</uid>
6
- <version_id>7a2d773a-7a50-4c69-aa1f-93ed796e9ace</version_id>
7
- <version_modified>2025-08-15T13:39:31Z</version_modified>
6
+ <version_id>8649cb7e-2e58-4a6d-ba67-911b6e0a41a6</version_id>
7
+ <version_modified>2026-01-04T12:08:45Z</version_modified>
8
8
  <xml_checksum>99772807</xml_checksum>
9
9
  <class_name>TBDMeasure</class_name>
10
10
  <display_name>Thermal Bridging and Derating - TBD</display_name>
@@ -464,7 +464,7 @@
464
464
  <filename>LICENSE.md</filename>
465
465
  <filetype>md</filetype>
466
466
  <usage_type>license</usage_type>
467
- <checksum>3EBCA5DB</checksum>
467
+ <checksum>98D54646</checksum>
468
468
  </file>
469
469
  <file>
470
470
  <filename>README.md</filename>
@@ -493,13 +493,13 @@
493
493
  <filename>measure.rb</filename>
494
494
  <filetype>rb</filetype>
495
495
  <usage_type>script</usage_type>
496
- <checksum>3FBDA0C2</checksum>
496
+ <checksum>489D7CFA</checksum>
497
497
  </file>
498
498
  <file>
499
499
  <filename>geo.rb</filename>
500
500
  <filetype>rb</filetype>
501
501
  <usage_type>resource</usage_type>
502
- <checksum>EF3BA8F7</checksum>
502
+ <checksum>9CA80CEB</checksum>
503
503
  </file>
504
504
  <file>
505
505
  <filename>geometry.rb</filename>
@@ -523,13 +523,13 @@
523
523
  <filename>psi.rb</filename>
524
524
  <filetype>rb</filetype>
525
525
  <usage_type>resource</usage_type>
526
- <checksum>71AED953</checksum>
526
+ <checksum>B9FB5E02</checksum>
527
527
  </file>
528
528
  <file>
529
529
  <filename>tbd.rb</filename>
530
530
  <filetype>rb</filetype>
531
531
  <usage_type>resource</usage_type>
532
- <checksum>9E26251E</checksum>
532
+ <checksum>3919B9B0</checksum>
533
533
  </file>
534
534
  <file>
535
535
  <filename>transformation.rb</filename>
@@ -541,13 +541,13 @@
541
541
  <filename>ua.rb</filename>
542
542
  <filetype>rb</filetype>
543
543
  <usage_type>resource</usage_type>
544
- <checksum>022F6D10</checksum>
544
+ <checksum>69C2A886</checksum>
545
545
  </file>
546
546
  <file>
547
547
  <filename>utils.rb</filename>
548
548
  <filetype>rb</filetype>
549
549
  <usage_type>resource</usage_type>
550
- <checksum>3CD8019A</checksum>
550
+ <checksum>BDF50D67</checksum>
551
551
  </file>
552
552
  <file>
553
553
  <filename>version.rb</filename>
@@ -565,7 +565,7 @@
565
565
  <filename>tbd_tests.rb</filename>
566
566
  <filetype>rb</filetype>
567
567
  <usage_type>test</usage_type>
568
- <checksum>9C76CD98</checksum>
568
+ <checksum>90CE962F</checksum>
569
569
  </file>
570
570
  </files>
571
571
  </measure>
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2026 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
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2026 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 )
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
3
+ # Copyright (c) 2020-2026 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