tbd 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be8e76fa83e37a27c674adaeddee2cbf022d8b9e543f75e46ab62b4f1d2087c1
4
- data.tar.gz: 6699f65f798c2e615660c6e65f6df926a4baa9af0cb1f040fc2075b10100a4dc
3
+ metadata.gz: 92789b60b438d1518f82d2596a12314ea5c6903de2bd902f1cbbea1a2379fd1d
4
+ data.tar.gz: e0e17c13b6db89a38fe39a9f8a6f4e2249e09976c08049250da3d6546689064f
5
5
  SHA512:
6
- metadata.gz: cc594dc6093c99f18aa9b30e17c7efb10a011118d7dd4996c9798dabe9c9f84f2a47ca5250f0dfbc45467cfdf8f545bb08f4473b8ebdbbeb2ff197ff475fea45
7
- data.tar.gz: '099e13541b55a471309ba59dd212114ecb0e55839cb59b6b05bc971f0a5c4aa42e74f5bdd27dc7f61b5512f379cc0245be9038a23e3fb766a19a14349243a252'
6
+ metadata.gz: a054444e9871a6a6844c4535913b50700b94588f338434e47b8dd41ec6d73c78df8f2efd773a4bc1f86d25cd37216b7ebe48ba94e18a6f4e8547b2be987c5ecb
7
+ data.tar.gz: 360c811de3c35599d73741985d80df474379a29fbad16714a0afa553b4719d50b41415f18006462176f9adc26377980507e535c616a64b77f0398b24d3fa435c
@@ -70,3 +70,19 @@ jobs:
70
70
  docker exec -t test bundle update
71
71
  docker exec -t test bundle exec rake
72
72
  docker kill test
73
+ test_351x:
74
+ runs-on: ubuntu-22.04
75
+ steps:
76
+ - name: Check out repository
77
+ uses: actions/checkout@v2
78
+ - name: Run Tests
79
+ run: |
80
+ echo $(pwd)
81
+ echo $(ls)
82
+ docker pull nrel/openstudio:3.5.1
83
+ docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.5.1
84
+ docker exec -t test pwd
85
+ docker exec -t test ls
86
+ docker exec -t test bundle update
87
+ docker exec -t test bundle exec rake
88
+ docker kill test
data/LICENSE.md CHANGED
@@ -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
@@ -27,6 +27,14 @@ For EnergyPlus simulations, leave CHECKED. For iterative exploration with Apply
27
27
  **Required:** false,
28
28
  **Model Dependent:** false
29
29
 
30
+ ### Proximity tolerance (m)
31
+ Proximity tolerance (e.g. 0.100 m) between subsurface edges, e.g. between near-adjacent window jambs.
32
+ **Name:** sub_tol,
33
+ **Type:** Double,
34
+ **Units:** ,
35
+ **Required:** false,
36
+ **Model Dependent:** false
37
+
30
38
  ### Load 'tbd.json'
31
39
  Loads existing 'tbd.json' file (under '/files'), may override 'default thermal bridge' set.
32
40
  **Name:** load_tbd_json,
@@ -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
@@ -48,6 +48,15 @@ class TBDMeasure < OpenStudio::Measure::ModelMeasure
48
48
  alter.setDefaultValue(true)
49
49
  args << alter
50
50
 
51
+ arg = "sub_tol"
52
+ dsc = "Proximity tolerance (e.g. 0.100 m) between subsurface edges, e.g. " \
53
+ "between near-adjacent window jambs."
54
+ sub_tol = OpenStudio::Measure::OSArgument.makeDoubleArgument(arg, false)
55
+ sub_tol.setDisplayName("Proximity tolerance (m)")
56
+ sub_tol.setDescription(dsc)
57
+ sub_tol.setDefaultValue(TBD::TOL)
58
+ args << sub_tol
59
+
51
60
  arg = "load_tbd_json"
52
61
  dsc = "Loads existing 'tbd.json' file (under '/files'), may override " \
53
62
  "'default thermal bridge' set."
@@ -225,6 +234,7 @@ class TBDMeasure < OpenStudio::Measure::ModelMeasure
225
234
 
226
235
  argh = {}
227
236
  argh[:alter ] = runner.getBoolArgumentValue("alter_model", args)
237
+ argh[:sub_tol ] = runner.getDoubleArgumentValue("sub_tol", args)
228
238
  argh[:load_tbd ] = runner.getBoolArgumentValue("load_tbd_json", args)
229
239
  argh[:option ] = runner.getStringArgumentValue("option", args)
230
240
  argh[:write_tbd ] = runner.getBoolArgumentValue("write_tbd_json", args)
@@ -286,6 +296,38 @@ class TBDMeasure < OpenStudio::Measure::ModelMeasure
286
296
  end
287
297
  end
288
298
 
299
+ # Pre-validate ground-facing constructions for KIVA.
300
+ if argh[:kiva_force] || argh[:gen_kiva]
301
+ kva = true
302
+
303
+ mdl.getSurfaces.each do |s|
304
+ id = s.nameString
305
+ construction = s.construction
306
+ next unless s.isGroundSurface
307
+
308
+ if construction.empty?
309
+ runner.registerError("Invalid construction for KIVA (#{id})")
310
+ kva = false if kva
311
+ else
312
+ construction = construction.get.to_LayeredConstruction
313
+
314
+ if construction.empty?
315
+ runner.registerError("KIVA requires layered constructions (#{id})")
316
+ kva = false if kva
317
+ else
318
+ construction = construction.get
319
+
320
+ unless TBD.standardOpaqueLayers?(construction)
321
+ runner.registerError("KIVA requires standard materials (#{id})")
322
+ kva = false if kva
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ return false unless kva
329
+ end
330
+
289
331
  # Process all ground-facing surfaces as foundation-facing.
290
332
  if argh[:kiva_force]
291
333
  argh[:gen_kiva] = true
@@ -313,7 +355,7 @@ class TBDMeasure < OpenStudio::Measure::ModelMeasure
313
355
 
314
356
  argh[:version ] = model.getVersion.versionIdentifier
315
357
  tbd = TBD.process(model, argh)
316
- argh[:io ] = tbd[:io]
358
+ argh[:io ] = tbd[:io ]
317
359
  argh[:surfaces] = tbd[:surfaces]
318
360
  setpoints = TBD.heatingTemperatureSetpoints?(model)
319
361
  setpoints = TBD.coolingTemperatureSetpoints?(model) || setpoints
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.0</schema_version>
4
4
  <name>tbd_measure</name>
5
5
  <uid>8890787b-8c25-4dc8-8641-b6be1b6c2357</uid>
6
- <version_id>59363fb9-ebff-4985-834a-911020830cf1</version_id>
7
- <version_modified>20221213T122330Z</version_modified>
6
+ <version_id>216d4e2e-fab7-49c6-95e9-7262edbf3277</version_id>
7
+ <version_modified>20230111T223650Z</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>
@@ -30,6 +30,15 @@
30
30
  </choice>
31
31
  </choices>
32
32
  </argument>
33
+ <argument>
34
+ <name>sub_tol</name>
35
+ <display_name>Proximity tolerance (m)</display_name>
36
+ <description>Proximity tolerance (e.g. 0.100 m) between subsurface edges, e.g. between near-adjacent window jambs.</description>
37
+ <type>Double</type>
38
+ <required>false</required>
39
+ <model_dependent>false</model_dependent>
40
+ <default_value>0.01</default_value>
41
+ </argument>
33
42
  <argument>
34
43
  <name>load_tbd_json</name>
35
44
  <display_name>Load 'tbd.json'</display_name>
@@ -373,12 +382,6 @@
373
382
  <usage_type>doc</usage_type>
374
383
  <checksum>32D70693</checksum>
375
384
  </file>
376
- <file>
377
- <filename>LICENSE.md</filename>
378
- <filetype>md</filetype>
379
- <usage_type>license</usage_type>
380
- <checksum>CB393A2B</checksum>
381
- </file>
382
385
  <file>
383
386
  <filename>geometry.rb</filename>
384
387
  <filetype>rb</filetype>
@@ -397,23 +400,29 @@
397
400
  <usage_type>resource</usage_type>
398
401
  <checksum>05CC939E</checksum>
399
402
  </file>
403
+ <file>
404
+ <filename>LICENSE.md</filename>
405
+ <filetype>md</filetype>
406
+ <usage_type>license</usage_type>
407
+ <checksum>A91E64A0</checksum>
408
+ </file>
400
409
  <file>
401
410
  <filename>oslog.rb</filename>
402
411
  <filetype>rb</filetype>
403
412
  <usage_type>resource</usage_type>
404
- <checksum>6F3C0E99</checksum>
413
+ <checksum>E97617E3</checksum>
405
414
  </file>
406
415
  <file>
407
416
  <filename>tbd.rb</filename>
408
417
  <filetype>rb</filetype>
409
418
  <usage_type>resource</usage_type>
410
- <checksum>3776D81D</checksum>
419
+ <checksum>A4E8433C</checksum>
411
420
  </file>
412
421
  <file>
413
422
  <filename>utils.rb</filename>
414
423
  <filetype>rb</filetype>
415
424
  <usage_type>resource</usage_type>
416
- <checksum>54F57409</checksum>
425
+ <checksum>283C976C</checksum>
417
426
  </file>
418
427
  <file>
419
428
  <version>
@@ -424,37 +433,37 @@
424
433
  <filename>measure.rb</filename>
425
434
  <filetype>rb</filetype>
426
435
  <usage_type>script</usage_type>
427
- <checksum>3EE587D6</checksum>
436
+ <checksum>44B11139</checksum>
428
437
  </file>
429
438
  <file>
430
439
  <filename>tbd_tests.rb</filename>
431
440
  <filetype>rb</filetype>
432
441
  <usage_type>test</usage_type>
433
- <checksum>6ED9AF88</checksum>
442
+ <checksum>58ED6635</checksum>
434
443
  </file>
435
444
  <file>
436
- <filename>README.md</filename>
437
- <filetype>md</filetype>
438
- <usage_type>readme</usage_type>
439
- <checksum>36FA11E3</checksum>
445
+ <filename>ua.rb</filename>
446
+ <filetype>rb</filetype>
447
+ <usage_type>resource</usage_type>
448
+ <checksum>9EBACA60</checksum>
440
449
  </file>
441
450
  <file>
442
451
  <filename>geo.rb</filename>
443
452
  <filetype>rb</filetype>
444
453
  <usage_type>resource</usage_type>
445
- <checksum>D999D942</checksum>
454
+ <checksum>F447D8CE</checksum>
446
455
  </file>
447
456
  <file>
448
- <filename>psi.rb</filename>
449
- <filetype>rb</filetype>
450
- <usage_type>resource</usage_type>
451
- <checksum>4C4C35F9</checksum>
457
+ <filename>README.md</filename>
458
+ <filetype>md</filetype>
459
+ <usage_type>readme</usage_type>
460
+ <checksum>B836C43A</checksum>
452
461
  </file>
453
462
  <file>
454
- <filename>ua.rb</filename>
463
+ <filename>psi.rb</filename>
455
464
  <filetype>rb</filetype>
456
465
  <usage_type>resource</usage_type>
457
- <checksum>3438C6A8</checksum>
466
+ <checksum>E6ED8157</checksum>
458
467
  </file>
459
468
  </files>
460
469
  </measure>
@@ -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
@@ -22,24 +22,27 @@
22
22
 
23
23
  module TBD
24
24
  ##
25
- # Check for matching Topolys vertex pairs between edges (within TOL).
25
+ # Check for matching Topolys vertex pairs between edges.
26
26
  #
27
27
  # @param e1 [Hash] first edge
28
28
  # @param e2 [Hash] second edge
29
+ # @param tol [Float] user-set tolerance (> TOL) in m
29
30
  #
30
31
  # @return [Bool] true if edges share vertex pairs
31
32
  # @return [Bool] false if invalid input
32
- def matches?(e1 = {}, e2 = {})
33
+ def matches?(e1 = {}, e2 = {}, tol = TOL)
33
34
  mth = "TBD::#{__callee__}"
34
35
  cl = Topolys::Point3D
35
36
  a = false
36
37
 
37
38
  return mismatch("e1", e1, Hash, mth, DBG, a) unless e1.is_a?(Hash)
38
39
  return mismatch("e2", e2, Hash, mth, DBG, a) unless e2.is_a?(Hash)
40
+
39
41
  return hashkey("e1", e1, :v0, mth, DBG, a) unless e1.key?(:v0)
40
42
  return hashkey("e1", e1, :v1, mth, DBG, a) unless e1.key?(:v1)
41
43
  return hashkey("e2", e2, :v0, mth, DBG, a) unless e2.key?(:v0)
42
44
  return hashkey("e2", e2, :v1, mth, DBG, a) unless e2.key?(:v1)
45
+
43
46
  return mismatch("e1 :v0", e1[:v0], cl, mth, DBG, a) unless e1[:v0].is_a?(cl)
44
47
  return mismatch("e1 :v1", e1[:v1], cl, mth, DBG, a) unless e1[:v1].is_a?(cl)
45
48
  return mismatch("e2 :v0", e2[:v0], cl, mth, DBG, a) unless e2[:v0].is_a?(cl)
@@ -51,26 +54,29 @@ module TBD
51
54
  return zero("e1", mth, DBG, a) if e1_vector.magnitude < TOL
52
55
  return zero("e2", mth, DBG, a) if e2_vector.magnitude < TOL
53
56
 
57
+ return mismatch("e1", e1, Hash, mth, DBG, a) unless tol.is_a?(Numeric)
58
+ return zero("tol", mth, DBG, a) if tol < TOL
59
+
54
60
  return true if
55
61
  (
56
62
  (
57
- ( (e1[:v0].x - e2[:v0].x).abs < TOL &&
58
- (e1[:v0].y - e2[:v0].y).abs < TOL &&
59
- (e1[:v0].z - e2[:v0].z).abs < TOL
63
+ ( (e1[:v0].x - e2[:v0].x).abs < tol &&
64
+ (e1[:v0].y - e2[:v0].y).abs < tol &&
65
+ (e1[:v0].z - e2[:v0].z).abs < tol
60
66
  ) ||
61
- ( (e1[:v0].x - e2[:v1].x).abs < TOL &&
62
- (e1[:v0].y - e2[:v1].y).abs < TOL &&
63
- (e1[:v0].z - e2[:v1].z).abs < TOL
67
+ ( (e1[:v0].x - e2[:v1].x).abs < tol &&
68
+ (e1[:v0].y - e2[:v1].y).abs < tol &&
69
+ (e1[:v0].z - e2[:v1].z).abs < tol
64
70
  )
65
71
  ) &&
66
72
  (
67
- ( (e1[:v1].x - e2[:v0].x).abs < TOL &&
68
- (e1[:v1].y - e2[:v0].y).abs < TOL &&
69
- (e1[:v1].z - e2[:v0].z).abs < TOL
73
+ ( (e1[:v1].x - e2[:v0].x).abs < tol &&
74
+ (e1[:v1].y - e2[:v0].y).abs < tol &&
75
+ (e1[:v1].z - e2[:v0].z).abs < tol
70
76
  ) ||
71
- ( (e1[:v1].x - e2[:v1].x).abs < TOL &&
72
- (e1[:v1].y - e2[:v1].y).abs < TOL &&
73
- (e1[:v1].z - e2[:v1].z).abs < TOL
77
+ ( (e1[:v1].x - e2[:v1].x).abs < tol &&
78
+ (e1[:v1].y - e2[:v1].y).abs < tol &&
79
+ (e1[:v1].z - e2[:v1].z).abs < tol
74
80
  )
75
81
  )
76
82
  )
@@ -354,6 +360,7 @@ module TBD
354
360
  next unless valid
355
361
  vec = s.vertices
356
362
  area = s.grossArea
363
+ mult = s.multiplier
357
364
  typ = s.subSurfaceType.downcase
358
365
  type = :skylight
359
366
  type = :window if typ.include?("window" )
@@ -442,6 +449,7 @@ module TBD
442
449
  n: n,
443
450
  gross: s.grossArea,
444
451
  area: area,
452
+ mult: mult,
445
453
  type: type,
446
454
  u: u,
447
455
  unhinged: unhinged }
@@ -478,7 +486,9 @@ module TBD
478
486
  end
479
487
 
480
488
  subarea = 0
481
- subs.values.each { |sub| subarea += sub[:area] }
489
+
490
+ subs.values.each { |sub| subarea += sub[:area] * sub[:mult] }
491
+
482
492
  surf[:net] = surf[:gross] - subarea
483
493
 
484
494
  # Tranform final Point 3D sets, and store.
@@ -604,6 +614,36 @@ module TBD
604
614
  return mismatch("floors", floors, cl2, mth, DBG, a) unless floors.is_a?(cl2)
605
615
  return mismatch("edges", edges, cl2, mth, DBG, a) unless edges.is_a?(cl2)
606
616
 
617
+ kva = true
618
+
619
+ # Pre-validate foundation-facing constructions.
620
+ model.getSurfaces.each do |s|
621
+ id = s.nameString
622
+ construction = s.construction
623
+ next unless s.outsideBoundaryCondition.downcase == "foundation"
624
+
625
+ if construction.empty?
626
+ log(ERR, "Invalid construction for KIVA (see #{id})")
627
+ kva = false if kva
628
+ else
629
+ construction = construction.get.to_LayeredConstruction
630
+
631
+ if construction.empty?
632
+ log(ERR, "KIVA requires layered constructions (see #{id})")
633
+ kva = false if kva
634
+ else
635
+ construction = construction.get
636
+
637
+ unless standardOpaqueLayers?(construction)
638
+ log(ERR, "KIVA requires standard materials (see #{id})")
639
+ kva = false if kva
640
+ end
641
+ end
642
+ end
643
+ end
644
+
645
+ return a unless kva
646
+
607
647
  # Strictly relying on Kiva's total exposed perimeter approach.
608
648
  arg = "TotalExposedPerimeter"
609
649
  kiva = true
@@ -1,6 +1,6 @@
1
1
  # BSD 3-Clause License
2
2
  #
3
- # Copyright (c) 2022, Denis Bourgeois
3
+ # Copyright (c) 2022-2023, Denis Bourgeois
4
4
  # All rights reserved.
5
5
  #
6
6
  # Redistribution and use in source and binary forms, with or without
@@ -127,6 +127,7 @@ module OSlg
127
127
  # @return [String] "DEBUG", "INFO", "WARN", "ERROR" or "FATAL"
128
128
  def tag(level)
129
129
  return @@tag[level] if level >= DEBUG && level <= FATAL
130
+
130
131
  ""
131
132
  end
132
133
 
@@ -138,6 +139,7 @@ module OSlg
138
139
  # @return [String] preset OSlg message
139
140
  def msg(status)
140
141
  return @@msg[status] if status >= DEBUG && status <= FATAL
142
+
141
143
  ""
142
144
  end
143
145
 
@@ -163,6 +165,7 @@ module OSlg
163
165
  @@logs << {level: level, message: message}
164
166
  @@status = level if level > @@status
165
167
  end
168
+
166
169
  @@status
167
170
  end
168
171
 
@@ -194,10 +197,11 @@ module OSlg
194
197
  mth = mth[0...60] + " ..." if mth.length > 60
195
198
  return res if mth.empty?
196
199
 
197
- msg = "Invalid '#{id}' "
200
+ msg = "Invalid '#{id}' "
198
201
  msg += "arg ##{ord} " if ord > 0
199
202
  msg += "(#{mth})"
200
203
  log(lvl, msg) if lvl >= DEBUG && lvl <= FATAL
204
+
201
205
  res
202
206
  end
203
207
 
@@ -232,6 +236,7 @@ module OSlg
232
236
 
233
237
  msg = "'#{id}' #{obj.class}? expecting #{cl} (#{mth})"
234
238
  log(lvl, msg) if lvl >= DEBUG && lvl <= FATAL
239
+
235
240
  res
236
241
  end
237
242
 
@@ -264,8 +269,9 @@ module OSlg
264
269
  mth = mth[0...60] + " ..." if mth.length > 60
265
270
  return res if mth.empty?
266
271
 
267
- msg = "Missing '#{key}' key in '#{id}' Hash (#{mth})"
272
+ msg = "Missing '#{key}' key in '#{id}' Hash (#{mth})"
268
273
  log(lvl, msg) if lvl >= DEBUG && lvl <= FATAL
274
+
269
275
  res
270
276
  end
271
277
 
@@ -294,8 +300,9 @@ module OSlg
294
300
  mth = mth[0...60] + " ..." if mth.length > 60
295
301
  return res if mth.empty?
296
302
 
297
- msg = "Empty '#{id}' (#{mth})"
303
+ msg = "Empty '#{id}' (#{mth})"
298
304
  log(lvl, msg) if lvl >= DEBUG && lvl <= FATAL
305
+
299
306
  res
300
307
  end
301
308
 
@@ -325,8 +332,9 @@ module OSlg
325
332
  mth = mth[0...60] + " ..." if mth.length > 60
326
333
  return res if mth.empty?
327
334
 
328
- msg = "Zero '#{id}' (#{mth})"
335
+ msg = "Zero '#{id}' (#{mth})"
329
336
  log(lvl, msg) if lvl >= DEBUG && lvl <= FATAL
337
+
330
338
  res
331
339
  end
332
340
 
@@ -355,9 +363,10 @@ module OSlg
355
363
 
356
364
  mth = mth[0...60] + " ..." if mth.length > 60
357
365
  return res if mth.empty?
358
-
359
- msg = "Negative '#{id}' (#{mth})"
366
+
367
+ msg = "Negative '#{id}' (#{mth})"
360
368
  log(lvl, msg) if lvl >= DEBUG && lvl <= FATAL
369
+
361
370
  res
362
371
  end
363
372
 
@@ -368,6 +377,7 @@ module OSlg
368
377
  def clean!
369
378
  @@status = 0
370
379
  @@logs = []
380
+
371
381
  @@level
372
382
  end
373
383
 
@@ -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 )
@@ -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?
@@ -1771,13 +1769,120 @@ module TBD
1771
1769
  end
1772
1770
  end
1773
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
+
1774
1878
  # Loop through each edge and assign heat loss to linked surfaces.
1775
1879
  edges.each do |identifier, edge|
1776
1880
  next unless edge.key?(:psi)
1777
1881
  rsi = 0
1778
- max = edge[:psi].values.max
1779
- type = edge[:psi].key(max)
1882
+ max = edge[:psi ].values.max
1883
+ type = edge[:psi ].key(max)
1780
1884
  length = edge[:length]
1885
+ length *= edge[:mult ] if edge.key?(:mult)
1781
1886
  bridge = { psi: max, type: type, length: length }
1782
1887
  deratables = {}
1783
1888
  apertures = {}
@@ -1869,7 +1974,7 @@ module TBD
1869
1974
  # ... first 'uprate' targeted insulation layers (see ua.rb) before derating.
1870
1975
  # Check for new argh keys [:wall_uo], [:roof_uo] and/or [:floor_uo].
1871
1976
  up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
1872
- uprate(model, tbd[:surfaces], argh) if up
1977
+ uprate(model, tbd[:surfaces], argh) if up
1873
1978
 
1874
1979
  # Derated (cloned) constructions are unique to each deratable surface.
1875
1980
  # Unique construction names are prefixed with the surface name,
@@ -1975,6 +2080,7 @@ module TBD
1975
2080
  set = e[:set]
1976
2081
  t = e[:psi].key(v)
1977
2082
  l = e[:length]
2083
+ l *= e[:mult] if e.key?(:mult)
1978
2084
  edge = { psi: set, type: t, length: l, surfaces: e[:surfaces].keys }
1979
2085
  edge[:v0x] = e[:v0].point.x
1980
2086
  edge[:v0y] = e[:v0].point.y