tbd 3.0.2 → 3.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5922a435bc3f8d97b6aa78588346e59108b6455d27c72e0aeecdd792079f33f
4
- data.tar.gz: 5f683e96a3f1c33e0bb782bbb545b713d9d55beaf2359d3983f9b4d010716ce0
3
+ metadata.gz: 3f5a7947fb5c2f96807c924dcb4350198119a9033b35cef4d113e35221b0e61f
4
+ data.tar.gz: caf6856e314609d3d13134c2bcc86a78292ea4deb1c8ab453e3ca386a5bf56e2
5
5
  SHA512:
6
- metadata.gz: 7284058dc4ec681c102435484b8af994fd027ea666aa4ed760f0e178cfcef297de033454c0a2c7cd60c95f13b71308bdcc6c4fa0a846819ec92edaa46007af08
7
- data.tar.gz: 78e74d0942d5ad735c9b696c5cdaa72b5ad8a92fbf34a3c8843e41dce41f1de0eddfda3b53ef8db08c3f9c87a73b92b3ad8bb89400a58283c1805d7d660d83f7
6
+ metadata.gz: 024b47e38db346d15524df93119a49c93c3a183c063d5635cd823ad21d8a2c6892680cc4b2f76b0299177a1479265650c6efc241d9037cb35043f1395ed312a8
7
+ data.tar.gz: da0b7e03bc5142adc8df613427838114377e5b406c36ab2b9ec506eaf72e972478490d45437018a2a94aeac45547ded1c70babfcec677420594c3dd3f776a867
@@ -3,11 +3,11 @@ name: Pull Request CI
3
3
  on:
4
4
  pull_request:
5
5
  branches:
6
- - master
6
+ - develop
7
7
 
8
8
  jobs:
9
9
  test_300x:
10
- runs-on: ubuntu-20.04
10
+ runs-on: ubuntu-22.04
11
11
  steps:
12
12
  - name: Check out repository
13
13
  uses: actions/checkout@v2
@@ -23,7 +23,7 @@ jobs:
23
23
  docker exec -t test bundle exec rake
24
24
  docker kill test
25
25
  test_321x:
26
- runs-on: ubuntu-20.04
26
+ runs-on: ubuntu-22.04
27
27
  steps:
28
28
  - name: Check out repository
29
29
  uses: actions/checkout@v2
@@ -39,7 +39,7 @@ jobs:
39
39
  docker exec -t test bundle exec rake
40
40
  docker kill test
41
41
  test_330x:
42
- runs-on: ubuntu-20.04
42
+ runs-on: ubuntu-22.04
43
43
  steps:
44
44
  - name: Check out repository
45
45
  uses: actions/checkout@v2
@@ -55,7 +55,7 @@ jobs:
55
55
  docker exec -t test bundle exec rake
56
56
  docker kill test
57
57
  test_340x:
58
- runs-on: ubuntu-20.04
58
+ runs-on: ubuntu-22.04
59
59
  steps:
60
60
  - name: Check out repository
61
61
  uses: actions/checkout@v2
data/json/tbd_seb_n2.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "schema": "https://github.com/rd2/tbd/blob/master/tbd.schema.json",
3
3
  "description": "testing JSON surface KHI entries",
4
- "psis": [{
4
+ "psis": [
5
+ {
5
6
  "id": "good",
6
7
  "parapet": 0.5,
7
8
  "party": 0.9
@@ -17,7 +18,8 @@
17
18
  "grade": 0.45
18
19
  }
19
20
  ],
20
- "khis": [{
21
+ "khis": [
22
+ {
21
23
  "id": "column",
22
24
  "point": 0.5
23
25
  },
@@ -26,16 +28,19 @@
26
28
  "point": 0.5
27
29
  }
28
30
  ],
29
- "surfaces": [{
30
- "id": "Entryway Wall 5",
31
- "khis": [{
32
- "id": "column",
33
- "count": 3
34
- },
35
- {
36
- "id": "support",
37
- "count": 4
38
- }
39
- ]
40
- }]
31
+ "surfaces": [
32
+ {
33
+ "id": "Entryway Wall 5",
34
+ "khis": [
35
+ {
36
+ "id": "column",
37
+ "count": 3
38
+ },
39
+ {
40
+ "id": "support",
41
+ "count": 4
42
+ }
43
+ ]
44
+ }
45
+ ]
41
46
  }
@@ -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>65ac80d9-ef1e-4c41-9a62-68e2e1e6c5db</version_id>
7
- <version_modified>20220918T225251Z</version_modified>
6
+ <version_id>4501c26f-1cd4-45d8-8f0e-f1ea907c27df</version_id>
7
+ <version_modified>20221125T130635Z</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>
@@ -432,12 +432,6 @@
432
432
  <usage_type>test</usage_type>
433
433
  <checksum>6ED9AF88</checksum>
434
434
  </file>
435
- <file>
436
- <filename>psi.rb</filename>
437
- <filetype>rb</filetype>
438
- <usage_type>resource</usage_type>
439
- <checksum>655E8232</checksum>
440
- </file>
441
435
  <file>
442
436
  <filename>README.md</filename>
443
437
  <filetype>md</filetype>
@@ -448,13 +442,19 @@
448
442
  <filename>geo.rb</filename>
449
443
  <filetype>rb</filetype>
450
444
  <usage_type>resource</usage_type>
451
- <checksum>8A794826</checksum>
445
+ <checksum>D999D942</checksum>
446
+ </file>
447
+ <file>
448
+ <filename>psi.rb</filename>
449
+ <filetype>rb</filetype>
450
+ <usage_type>resource</usage_type>
451
+ <checksum>AFC8A549</checksum>
452
452
  </file>
453
453
  <file>
454
454
  <filename>ua.rb</filename>
455
455
  <filetype>rb</filetype>
456
456
  <usage_type>resource</usage_type>
457
- <checksum>7873E62A</checksum>
457
+ <checksum>B39B3BB0</checksum>
458
458
  </file>
459
459
  </files>
460
460
  </measure>
@@ -84,20 +84,21 @@ module TBD
84
84
  # vertices and wire.
85
85
  #
86
86
  # @param model [Topolys::Model] a model
87
- # @param pts [Array] a 1D array of 3D Topolys points (min 2x)
87
+ # @param pts [Array] a 1D array of 3D Topolys points (min 3x)
88
88
  #
89
89
  # @return [Hash] vx: 3D Topolys vertices Array; w: corresponding Topolys::Wire
90
90
  # @return [Hash] vx: nil; w: nil (if invalid input)
91
91
  def objects(model = nil, pts = [])
92
- mth = "OSut::#{__callee__}"
92
+ mth = "TBD::#{__callee__}"
93
93
  cl = Topolys::Model
94
94
  obj = { vx: nil, w: nil }
95
95
 
96
96
  return mismatch("model", model, cl, mth, DBG, obj) unless model.is_a?(cl)
97
97
  return mismatch("points", pts, Array, mth, DBG, obj) unless pts.is_a?(Array)
98
98
 
99
- log(DBG, "#{pts.size}? need +2 Topolys points (#{mth})") unless pts.size > 2
99
+ log(DBG, "#{pts.size}? need +3 Topolys points (#{mth})") unless pts.size > 2
100
100
  return obj unless pts.size > 2
101
+
101
102
  obj[:vx] = model.get_vertices(pts)
102
103
  obj[:w ] = model.get_wire(obj[:vx])
103
104
 
@@ -109,14 +110,14 @@ module TBD
109
110
  # As a side effect, it will - if successful - also populate a Topolys 'model'
110
111
  # with Topolys vertices, wires, holes. In rare cases such as domes of tubular
111
112
  # daylighting devices (TDDs), kids may be 'unhinged', i.e. not on same 3D
112
- # plane as 'dad(s)' - TBD corrects auch cases elsewhere.
113
+ # plane as 'dad(s)' - TBD corrects such cases elsewhere.
113
114
  #
114
115
  # @param model [Topolys::Model] a model
115
116
  # @param boys [Hash] a collection of TBD subsurfaces
116
117
  #
117
118
  # @return [Array] 3D Topolys wires of 'holes' (made by kids)
118
119
  def kids(model = nil, boys = {})
119
- mth = "OSut::#{__callee__}"
120
+ mth = "TBD::#{__callee__}"
120
121
  cl = Topolys::Model
121
122
  holes = []
122
123
 
@@ -146,7 +147,7 @@ module TBD
146
147
  #
147
148
  # @return [Array] 3D Topolys wires of 'holes' (made by kids)
148
149
  def dads(model = nil, pops = {})
149
- mth = "OSut::#{__callee__}"
150
+ mth = "TBD::#{__callee__}"
150
151
  cl = Topolys::Model
151
152
  holes = {}
152
153
 
@@ -183,7 +184,7 @@ module TBD
183
184
  # @return [Bool] true if successful
184
185
  # @return [Bool] false if invalid input
185
186
  def faces(s = {}, e = {})
186
- mth = "OSut::#{__callee__}"
187
+ mth = "TBD::#{__callee__}"
187
188
 
188
189
  return mismatch("surfaces", s, Hash, mth, DBG, false) unless s.is_a?(Hash)
189
190
  return mismatch("edges", e, Hash, mth, DBG, false) unless e.is_a?(Hash)
@@ -211,6 +212,42 @@ module TBD
211
212
  true
212
213
  end
213
214
 
215
+ ##
216
+ # Validate whether an OpenStudio planar surface is safe for TBD to process.
217
+ #
218
+ # @param s [OpenStudio::Model::PlanarSurface] a surface
219
+ #
220
+ # @return [Bool] true if valid surface
221
+ def validate(s = nil)
222
+ mth = "TBD::#{__callee__}"
223
+ cl = OpenStudio::Model::PlanarSurface
224
+
225
+ return mismatch("surface", s, cl, mth, DBG, false) unless s.is_a?(cl)
226
+
227
+ id = s.nameString
228
+ size = s.vertices.size
229
+ last = size - 1
230
+
231
+ log(ERR, "#{id} #{size} vertices? need +3 (#{mth})") unless size > 2
232
+ return false unless size > 2
233
+
234
+ [0, last].each do |i|
235
+ v1 = s.vertices[i]
236
+ v2 = s.vertices[i + 1] unless i == last
237
+ v2 = s.vertices.first if i == last
238
+ vector = v2 - v1
239
+ bad = vector.length < TOL
240
+
241
+ # As is, this comparison also catches collinear vertices (< 10mm apart)
242
+ # along an edge. Should avoid red-flagging such cases. TO DO.
243
+ log(ERR, "#{id}: < #{TOL}m (#{mth})") if bad
244
+ return false if bad
245
+ end
246
+
247
+ # Add as many extra tests as needed ...
248
+ true
249
+ end
250
+
214
251
  ##
215
252
  # Return site-specific (or true) Topolys normal vector of OpenStudio surface.
216
253
  #
@@ -250,6 +287,8 @@ module TBD
250
287
  return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
251
288
  return mismatch("surface", surface, cl2, mth) unless surface.is_a?(cl2)
252
289
 
290
+ return nil unless validate(surface)
291
+
253
292
  nom = surface.nameString
254
293
  surf = {}
255
294
  subs = {}
@@ -307,6 +346,8 @@ module TBD
307
346
  surf[:gross ] = surface.grossArea
308
347
 
309
348
  surface.subSurfaces.sort_by { |s| s.nameString }.each do |s|
349
+ next unless validate(s)
350
+
310
351
  id = s.nameString
311
352
  valid = s.vertices.size == 3 || s.vertices.size == 4
312
353
  log(ERR, "Skipping '#{id}': vertex # 3 or 4 (#{mth})") unless valid
@@ -334,7 +375,7 @@ module TBD
334
375
  log(ERR, "Skipping '#{id}': missing construction (#{mth})") if c.empty?
335
376
  next if c.empty?
336
377
  c = c.get.to_LayeredConstruction
337
- log(ERR, "Skipping '#{id}': must be a #{cl3} (#{mth})") if c.empty?
378
+ log(WRN, "Skipping '#{id}': subs limited to #{cl3} (#{mth})") if c.empty?
338
379
  next if c.empty?
339
380
  c = c.get
340
381
 
@@ -630,7 +671,7 @@ module TBD
630
671
  end
631
672
 
632
673
  foundation = OpenStudio::Model::FoundationKiva.new(model)
633
- foundation.setName("KIVA Foundation Floor '#{id}'")
674
+ foundation.setName("KIVA Foundation Floor #{id}")
634
675
 
635
676
  floor = model.getSurfaceByName(id)
636
677
  kiva = false if floor.empty?
@@ -205,7 +205,7 @@ module TBD
205
205
  rimjoist: 0.300, # *
206
206
  parapet: 0.325, # *
207
207
  fenestration: 0.200, # *
208
- corner: 0.300, # ** "regular (BETBG)", adj. for ext. dimensions
208
+ corner: 0.300, # ** not explicitely stated
209
209
  balcony: 0.500, # *
210
210
  party: 0.450, # *
211
211
  grade: 0.450, # *
@@ -219,7 +219,7 @@ module TBD
219
219
  rimjoist: 0.850, # *
220
220
  parapet: 0.800, # *
221
221
  fenestration: 0.500, # *
222
- corner: 0.850, # ** ... not stated
222
+ corner: 0.850, # ** not explicitely stated
223
223
  balcony: 1.000, # *
224
224
  party: 0.850, # *
225
225
  grade: 0.850, # *
@@ -569,16 +569,21 @@ module TBD
569
569
  return mismatch("argh", s, Hash, mth, DBG, ipt) unless argh.is_a?(Hash)
570
570
  return hashkey("argh", argh, opt, mth, DBG, ipt) unless argh.key?(opt)
571
571
 
572
- argh[:io_path] = nil unless argh.key?(:io_path)
572
+ argh[:io_path ] = nil unless argh.key?(:io_path)
573
573
  argh[:schema_path] = nil unless argh.key?(:schema_path)
574
- pth = argh[:io_path]
574
+
575
+ pth = argh[:io_path ]
575
576
  sch = argh[:schema_path]
576
577
 
577
- if pth
578
- return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
579
- io = File.read(pth)
580
- io = JSON.parse(io, symbolize_names: true)
581
- return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
578
+ if pth && (pth.is_a?(String) || pth.is_a?(Hash))
579
+ if pth.is_a?(Hash)
580
+ io = pth
581
+ else
582
+ return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
583
+ io = File.read(pth)
584
+ io = JSON.parse(io, symbolize_names: true)
585
+ return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
586
+ end
582
587
 
583
588
  # Schema validation is not yet supported in the OpenStudio Application.
584
589
  # We nonetheless recommend that users rely on the json-schema gem, or an
@@ -844,7 +849,7 @@ module TBD
844
849
  up = ""
845
850
  up = "uprated " if m.nameString.include?(" uprated")
846
851
  m = m.clone(model).to_MasslessOpaqueMaterial.get
847
- m.setName("'#{id}' #{up}m tbd")
852
+ m.setName("#{id} #{up}m tbd")
848
853
  de_r = 0.001 unless de_r > 0.001
849
854
  loss = (de_u - 1 / de_r) * s[:net] unless de_r > 0.001
850
855
  m.setThermalResistance(de_r)
@@ -855,7 +860,7 @@ module TBD
855
860
  up = ""
856
861
  up = "uprated " if m.nameString.include?(" uprated")
857
862
  m = m.clone(model).to_StandardOpaqueMaterial.get
858
- m.setName("'#{id}' #{up}m tbd")
863
+ m.setName("#{id} #{up}m tbd")
859
864
  k = m.thermalConductivity
860
865
 
861
866
  if de_r > 0.001
@@ -1105,6 +1110,10 @@ module TBD
1105
1110
  horizontal = dz.abs < TOL
1106
1111
  vertical = dx < TOL && dy < TOL
1107
1112
  edge_V = terminal - origin
1113
+
1114
+ invalid("1x edge length < TOL", mth, 0, ERROR) if edge_V.magnitude < TOL
1115
+ next if edge_V.magnitude < TOL
1116
+
1108
1117
  edge_plane = Topolys::Plane3D.new(origin, edge_V)
1109
1118
 
1110
1119
  if vertical
@@ -1120,71 +1129,77 @@ module TBD
1120
1129
  # Loop through each linked wire and determine farthest point from
1121
1130
  # edge while ensuring candidate point is not aligned with edge.
1122
1131
  t_model.wires.each do |wire|
1123
- if surface[:wire] == wire.id # there should be a unique match
1124
- normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
1125
- normal = holes[id].attributes[:n] if holes.key?(id)
1126
- normal = shades[id][:n] if shades.key?(id)
1127
- farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
1128
- farthest_V = farthest - origin # zero magnitude, initially
1129
- inverted = false
1130
- i_origin = wire.points.index(origin)
1131
- i_terminal = wire.points.index(terminal)
1132
- i_last = wire.points.size - 1
1133
-
1134
- if i_terminal == 0
1135
- inverted = true unless i_origin == i_last
1136
- elsif i_origin == i_last
1137
- inverted = true unless i_terminal == 0
1132
+ next unless surface[:wire] == wire.id # should be a unique match
1133
+ normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
1134
+ normal = holes[id].attributes[:n] if holes.key?(id)
1135
+ normal = shades[id][:n] if shades.key?(id)
1136
+ farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
1137
+ farthest_V = farthest - origin # zero magnitude, initially
1138
+ inverted = false
1139
+ i_origin = wire.points.index(origin)
1140
+ i_terminal = wire.points.index(terminal)
1141
+ i_last = wire.points.size - 1
1142
+
1143
+ if i_terminal == 0
1144
+ inverted = true unless i_origin == i_last
1145
+ elsif i_origin == i_last
1146
+ inverted = true unless i_terminal == 0
1147
+ else
1148
+ inverted = true unless i_terminal - i_origin == 1
1149
+ end
1150
+
1151
+ wire.points.each do |point|
1152
+ next if point == origin
1153
+ next if point == terminal
1154
+
1155
+ point_on_plane = edge_plane.project(point)
1156
+ origin_point_V = point_on_plane - origin
1157
+ point_V_magnitude = origin_point_V.magnitude
1158
+ next unless point_V_magnitude > TOL
1159
+
1160
+ # Generate a plane between origin, terminal & point. Only consider
1161
+ # planes that share the same normal as wire.
1162
+ if inverted
1163
+ plane = Topolys::Plane3D.from_points(terminal, origin, point)
1138
1164
  else
1139
- inverted = true unless i_terminal - i_origin == 1
1165
+ plane = Topolys::Plane3D.from_points(origin, terminal, point)
1140
1166
  end
1141
1167
 
1142
- wire.points.each do |point|
1143
- next if point == origin
1144
- next if point == terminal
1145
- point_on_plane = edge_plane.project(point)
1146
- origin_point_V = point_on_plane - origin
1147
- point_V_magnitude = origin_point_V.magnitude
1148
- next unless point_V_magnitude > TOL
1149
-
1150
- # Generate a plane between origin, terminal & point. Only consider
1151
- # planes that share the same normal as wire.
1152
- if inverted
1153
- plane = Topolys::Plane3D.from_points(terminal, origin, point)
1154
- else
1155
- plane = Topolys::Plane3D.from_points(origin, terminal, point)
1156
- end
1168
+ next unless (normal.x - plane.normal.x).abs < TOL &&
1169
+ (normal.y - plane.normal.y).abs < TOL &&
1170
+ (normal.z - plane.normal.z).abs < TOL
1157
1171
 
1158
- next unless (normal.x - plane.normal.x).abs < TOL &&
1159
- (normal.y - plane.normal.y).abs < TOL &&
1160
- (normal.z - plane.normal.z).abs < TOL
1172
+ farther = point_V_magnitude > farthest_V.magnitude
1173
+ farthest = point if farther
1174
+ farthest_V = origin_point_V if farther
1175
+ end
1161
1176
 
1162
- farther = point_V_magnitude > farthest_V.magnitude
1163
- farthest = point if farther
1164
- farthest_V = origin_point_V if farther
1165
- end
1177
+ puts "ADDITION!!" if id == "ADDITION"
1178
+ puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
1166
1179
 
1167
- angle = reference_V.angle(farthest_V)
1168
- adjust = false # adjust angle [180°, 360°] if necessary
1180
+ angle = reference_V.angle(farthest_V)
1181
+ invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
1182
+ angle = 0 if angle.nil?
1169
1183
 
1170
- if vertical
1184
+ adjust = false # adjust angle [180°, 360°] if necessary
1185
+
1186
+ if vertical
1187
+ adjust = true if east.dot(farthest_V) < -TOL
1188
+ else
1189
+ if north.dot(farthest_V).abs < TOL ||
1190
+ (north.dot(farthest_V).abs - 1).abs < TOL
1171
1191
  adjust = true if east.dot(farthest_V) < -TOL
1172
1192
  else
1173
- if north.dot(farthest_V).abs < TOL ||
1174
- (north.dot(farthest_V).abs - 1).abs < TOL
1175
- adjust = true if east.dot(farthest_V) < -TOL
1176
- else
1177
- adjust = true if north.dot(farthest_V) < -TOL
1178
- end
1193
+ adjust = true if north.dot(farthest_V) < -TOL
1179
1194
  end
1180
-
1181
- angle = 2 * Math::PI - angle if adjust
1182
- angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
1183
- surface[:angle] = angle
1184
- farthest_V.normalize!
1185
- surface[:polar] = farthest_V
1186
- surface[:normal] = normal
1187
1195
  end
1196
+
1197
+ angle = 2 * Math::PI - angle if adjust
1198
+ angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
1199
+ surface[:angle ] = angle
1200
+ farthest_V.normalize!
1201
+ surface[:polar ] = farthest_V
1202
+ surface[:normal] = normal
1188
1203
  end # end of edge-linked, surface-to-wire loop
1189
1204
  end # end of edge-linked surface loop
1190
1205
 
@@ -1993,8 +2008,6 @@ module TBD
1993
2008
  #
1994
2009
  # @return [Bool] true if TBD Measure is successful
1995
2010
  def exit(runner = nil, argh = {})
1996
- mth = "TBD::#{__callee__}"
1997
-
1998
2011
  # Generated files target a design context ( >= WARN ) ... change TBD log
1999
2012
  # level for debugging purposes. By default, log status is set < DBG
2000
2013
  # while log level is set @INF.
@@ -62,30 +62,33 @@ module TBD
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.
65
- rt = 1 / ut # target construction Rt
66
- ro = rsi(lc, film) # current construction Ro
67
- new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
68
- new_u = 1 / new_r
65
+ rt = 1 / ut # target construction Rt
66
+ ro = rsi(lc, film) # current construction Ro
67
+ new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
68
+ new_u = 1 / new_r
69
69
 
70
70
  # Then, uprate (if possible) to counter expected thermal bridging effects.
71
- u_psi = hloss / area # from psi & khi
72
- new_u = new_u - u_psi # uprated layer USi to counter psi & khi
73
- new_r = 1 / new_u # uprated layer RSi to counter psi & khi
71
+ u_psi = hloss / area # from psi & khi
72
+ new_u -= u_psi # uprated layer USi to counter psi & khi
73
+ new_r = 1 / new_u # uprated layer RSi to counter psi & khi
74
74
 
75
75
  return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
76
- loss = 0.0 # residual heatloss (not assigned) [W/K]
76
+
77
+ loss = 0.0 # residual heatloss (not assigned) [W/K]
77
78
 
78
79
  if lyr[:type] == :massless
79
80
  m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
80
81
  return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
82
+
81
83
  m = m.get.clone(model).to_MasslessOpaqueMaterial.get
82
84
  m.setName("#{id} uprated")
83
85
  new_r = 0.001 unless new_r > 0.001
84
86
  loss = (new_u - 1 / new_r) * area unless new_r > 0.001
85
87
  m.setThermalResistance(new_r)
86
88
  else # type == :standard
87
- m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
89
+ m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
88
90
  return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
91
+
89
92
  m = m.get.clone(model).to_StandardOpaqueMaterial.get
90
93
  m.setName("#{id} uprated")
91
94
  k = m.thermalConductivity
@@ -108,10 +111,12 @@ module TBD
108
111
 
109
112
  ok = m.setThickness(d)
110
113
  return invalid("Can't uprate '#{id}': > 3m", mth, 0, ERR, res) unless ok
114
+
111
115
  m.setThermalConductivity(k) if ok
112
116
  end
113
117
 
114
118
  return invalid("", mth, 0, ERR, res) unless m
119
+
115
120
  lc.setLayer(lyr[:index], m)
116
121
  uo = 1 / rsi(lc, film)
117
122
 
@@ -174,7 +179,6 @@ module TBD
174
179
  area = 0
175
180
  film = 100000000000000
176
181
  lc = nil
177
- uo = nil
178
182
  id = ""
179
183
  all = g[:op].downcase == "all wall constructions" ||
180
184
  g[:op].downcase == "all roof constructions" ||
@@ -188,6 +192,7 @@ module TBD
188
192
  next unless sss.outsideBoundaryCondition.downcase == "outdoors"
189
193
  next if sss.construction.empty?
190
194
  next if sss.construction.get.to_LayeredConstruction.empty?
195
+
191
196
  c = sss.construction.get.to_LayeredConstruction.get
192
197
  i = c.nameString
193
198
 
@@ -236,7 +241,7 @@ module TBD
236
241
  end
237
242
 
238
243
  if coll.empty?
239
- log(ERR, "No construction to uprate - skipping (#{mth})")
244
+ log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
240
245
  next
241
246
  elsif lc # valid layered construction - good to uprate!
242
247
  # Ensure lc is referenced by surface types == label.
@@ -948,7 +953,7 @@ module TBD
948
953
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
949
954
  model += " (v#{ua[:version]})" if ua.key?(:version)
950
955
  report << model unless model.empty?
951
- report << "* TBD : v3.0.2"
956
+ report << "* TBD : v3.1.0"
952
957
  report << "* date : #{ua[:date]}"
953
958
 
954
959
  if lang == :en
data/lib/tbd/geo.rb CHANGED
@@ -84,20 +84,21 @@ module TBD
84
84
  # vertices and wire.
85
85
  #
86
86
  # @param model [Topolys::Model] a model
87
- # @param pts [Array] a 1D array of 3D Topolys points (min 2x)
87
+ # @param pts [Array] a 1D array of 3D Topolys points (min 3x)
88
88
  #
89
89
  # @return [Hash] vx: 3D Topolys vertices Array; w: corresponding Topolys::Wire
90
90
  # @return [Hash] vx: nil; w: nil (if invalid input)
91
91
  def objects(model = nil, pts = [])
92
- mth = "OSut::#{__callee__}"
92
+ mth = "TBD::#{__callee__}"
93
93
  cl = Topolys::Model
94
94
  obj = { vx: nil, w: nil }
95
95
 
96
96
  return mismatch("model", model, cl, mth, DBG, obj) unless model.is_a?(cl)
97
97
  return mismatch("points", pts, Array, mth, DBG, obj) unless pts.is_a?(Array)
98
98
 
99
- log(DBG, "#{pts.size}? need +2 Topolys points (#{mth})") unless pts.size > 2
99
+ log(DBG, "#{pts.size}? need +3 Topolys points (#{mth})") unless pts.size > 2
100
100
  return obj unless pts.size > 2
101
+
101
102
  obj[:vx] = model.get_vertices(pts)
102
103
  obj[:w ] = model.get_wire(obj[:vx])
103
104
 
@@ -109,14 +110,14 @@ module TBD
109
110
  # As a side effect, it will - if successful - also populate a Topolys 'model'
110
111
  # with Topolys vertices, wires, holes. In rare cases such as domes of tubular
111
112
  # daylighting devices (TDDs), kids may be 'unhinged', i.e. not on same 3D
112
- # plane as 'dad(s)' - TBD corrects auch cases elsewhere.
113
+ # plane as 'dad(s)' - TBD corrects such cases elsewhere.
113
114
  #
114
115
  # @param model [Topolys::Model] a model
115
116
  # @param boys [Hash] a collection of TBD subsurfaces
116
117
  #
117
118
  # @return [Array] 3D Topolys wires of 'holes' (made by kids)
118
119
  def kids(model = nil, boys = {})
119
- mth = "OSut::#{__callee__}"
120
+ mth = "TBD::#{__callee__}"
120
121
  cl = Topolys::Model
121
122
  holes = []
122
123
 
@@ -146,7 +147,7 @@ module TBD
146
147
  #
147
148
  # @return [Array] 3D Topolys wires of 'holes' (made by kids)
148
149
  def dads(model = nil, pops = {})
149
- mth = "OSut::#{__callee__}"
150
+ mth = "TBD::#{__callee__}"
150
151
  cl = Topolys::Model
151
152
  holes = {}
152
153
 
@@ -183,7 +184,7 @@ module TBD
183
184
  # @return [Bool] true if successful
184
185
  # @return [Bool] false if invalid input
185
186
  def faces(s = {}, e = {})
186
- mth = "OSut::#{__callee__}"
187
+ mth = "TBD::#{__callee__}"
187
188
 
188
189
  return mismatch("surfaces", s, Hash, mth, DBG, false) unless s.is_a?(Hash)
189
190
  return mismatch("edges", e, Hash, mth, DBG, false) unless e.is_a?(Hash)
@@ -211,6 +212,42 @@ module TBD
211
212
  true
212
213
  end
213
214
 
215
+ ##
216
+ # Validate whether an OpenStudio planar surface is safe for TBD to process.
217
+ #
218
+ # @param s [OpenStudio::Model::PlanarSurface] a surface
219
+ #
220
+ # @return [Bool] true if valid surface
221
+ def validate(s = nil)
222
+ mth = "TBD::#{__callee__}"
223
+ cl = OpenStudio::Model::PlanarSurface
224
+
225
+ return mismatch("surface", s, cl, mth, DBG, false) unless s.is_a?(cl)
226
+
227
+ id = s.nameString
228
+ size = s.vertices.size
229
+ last = size - 1
230
+
231
+ log(ERR, "#{id} #{size} vertices? need +3 (#{mth})") unless size > 2
232
+ return false unless size > 2
233
+
234
+ [0, last].each do |i|
235
+ v1 = s.vertices[i]
236
+ v2 = s.vertices[i + 1] unless i == last
237
+ v2 = s.vertices.first if i == last
238
+ vector = v2 - v1
239
+ bad = vector.length < TOL
240
+
241
+ # As is, this comparison also catches collinear vertices (< 10mm apart)
242
+ # along an edge. Should avoid red-flagging such cases. TO DO.
243
+ log(ERR, "#{id}: < #{TOL}m (#{mth})") if bad
244
+ return false if bad
245
+ end
246
+
247
+ # Add as many extra tests as needed ...
248
+ true
249
+ end
250
+
214
251
  ##
215
252
  # Return site-specific (or true) Topolys normal vector of OpenStudio surface.
216
253
  #
@@ -250,6 +287,8 @@ module TBD
250
287
  return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
251
288
  return mismatch("surface", surface, cl2, mth) unless surface.is_a?(cl2)
252
289
 
290
+ return nil unless validate(surface)
291
+
253
292
  nom = surface.nameString
254
293
  surf = {}
255
294
  subs = {}
@@ -307,6 +346,8 @@ module TBD
307
346
  surf[:gross ] = surface.grossArea
308
347
 
309
348
  surface.subSurfaces.sort_by { |s| s.nameString }.each do |s|
349
+ next unless validate(s)
350
+
310
351
  id = s.nameString
311
352
  valid = s.vertices.size == 3 || s.vertices.size == 4
312
353
  log(ERR, "Skipping '#{id}': vertex # 3 or 4 (#{mth})") unless valid
@@ -334,7 +375,7 @@ module TBD
334
375
  log(ERR, "Skipping '#{id}': missing construction (#{mth})") if c.empty?
335
376
  next if c.empty?
336
377
  c = c.get.to_LayeredConstruction
337
- log(ERR, "Skipping '#{id}': must be a #{cl3} (#{mth})") if c.empty?
378
+ log(WRN, "Skipping '#{id}': subs limited to #{cl3} (#{mth})") if c.empty?
338
379
  next if c.empty?
339
380
  c = c.get
340
381
 
@@ -630,7 +671,7 @@ module TBD
630
671
  end
631
672
 
632
673
  foundation = OpenStudio::Model::FoundationKiva.new(model)
633
- foundation.setName("KIVA Foundation Floor '#{id}'")
674
+ foundation.setName("KIVA Foundation Floor #{id}")
634
675
 
635
676
  floor = model.getSurfaceByName(id)
636
677
  kiva = false if floor.empty?
data/lib/tbd/psi.rb CHANGED
@@ -205,7 +205,7 @@ module TBD
205
205
  rimjoist: 0.300, # *
206
206
  parapet: 0.325, # *
207
207
  fenestration: 0.200, # *
208
- corner: 0.300, # ** "regular (BETBG)", adj. for ext. dimensions
208
+ corner: 0.300, # ** not explicitely stated
209
209
  balcony: 0.500, # *
210
210
  party: 0.450, # *
211
211
  grade: 0.450, # *
@@ -219,7 +219,7 @@ module TBD
219
219
  rimjoist: 0.850, # *
220
220
  parapet: 0.800, # *
221
221
  fenestration: 0.500, # *
222
- corner: 0.850, # ** ... not stated
222
+ corner: 0.850, # ** not explicitely stated
223
223
  balcony: 1.000, # *
224
224
  party: 0.850, # *
225
225
  grade: 0.850, # *
@@ -569,16 +569,21 @@ module TBD
569
569
  return mismatch("argh", s, Hash, mth, DBG, ipt) unless argh.is_a?(Hash)
570
570
  return hashkey("argh", argh, opt, mth, DBG, ipt) unless argh.key?(opt)
571
571
 
572
- argh[:io_path] = nil unless argh.key?(:io_path)
572
+ argh[:io_path ] = nil unless argh.key?(:io_path)
573
573
  argh[:schema_path] = nil unless argh.key?(:schema_path)
574
- pth = argh[:io_path]
574
+
575
+ pth = argh[:io_path ]
575
576
  sch = argh[:schema_path]
576
577
 
577
- if pth
578
- return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
579
- io = File.read(pth)
580
- io = JSON.parse(io, symbolize_names: true)
581
- return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
578
+ if pth && (pth.is_a?(String) || pth.is_a?(Hash))
579
+ if pth.is_a?(Hash)
580
+ io = pth
581
+ else
582
+ return empty("JSON file", mth, FTL, ipt) unless File.size?(pth)
583
+ io = File.read(pth)
584
+ io = JSON.parse(io, symbolize_names: true)
585
+ return mismatch("io", io, Hash, mth, FTL, ipt) unless io.is_a?(Hash)
586
+ end
582
587
 
583
588
  # Schema validation is not yet supported in the OpenStudio Application.
584
589
  # We nonetheless recommend that users rely on the json-schema gem, or an
@@ -844,7 +849,7 @@ module TBD
844
849
  up = ""
845
850
  up = "uprated " if m.nameString.include?(" uprated")
846
851
  m = m.clone(model).to_MasslessOpaqueMaterial.get
847
- m.setName("'#{id}' #{up}m tbd")
852
+ m.setName("#{id} #{up}m tbd")
848
853
  de_r = 0.001 unless de_r > 0.001
849
854
  loss = (de_u - 1 / de_r) * s[:net] unless de_r > 0.001
850
855
  m.setThermalResistance(de_r)
@@ -855,7 +860,7 @@ module TBD
855
860
  up = ""
856
861
  up = "uprated " if m.nameString.include?(" uprated")
857
862
  m = m.clone(model).to_StandardOpaqueMaterial.get
858
- m.setName("'#{id}' #{up}m tbd")
863
+ m.setName("#{id} #{up}m tbd")
859
864
  k = m.thermalConductivity
860
865
 
861
866
  if de_r > 0.001
@@ -1105,6 +1110,10 @@ module TBD
1105
1110
  horizontal = dz.abs < TOL
1106
1111
  vertical = dx < TOL && dy < TOL
1107
1112
  edge_V = terminal - origin
1113
+
1114
+ invalid("1x edge length < TOL", mth, 0, ERROR) if edge_V.magnitude < TOL
1115
+ next if edge_V.magnitude < TOL
1116
+
1108
1117
  edge_plane = Topolys::Plane3D.new(origin, edge_V)
1109
1118
 
1110
1119
  if vertical
@@ -1120,71 +1129,77 @@ module TBD
1120
1129
  # Loop through each linked wire and determine farthest point from
1121
1130
  # edge while ensuring candidate point is not aligned with edge.
1122
1131
  t_model.wires.each do |wire|
1123
- if surface[:wire] == wire.id # there should be a unique match
1124
- normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
1125
- normal = holes[id].attributes[:n] if holes.key?(id)
1126
- normal = shades[id][:n] if shades.key?(id)
1127
- farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
1128
- farthest_V = farthest - origin # zero magnitude, initially
1129
- inverted = false
1130
- i_origin = wire.points.index(origin)
1131
- i_terminal = wire.points.index(terminal)
1132
- i_last = wire.points.size - 1
1133
-
1134
- if i_terminal == 0
1135
- inverted = true unless i_origin == i_last
1136
- elsif i_origin == i_last
1137
- inverted = true unless i_terminal == 0
1132
+ next unless surface[:wire] == wire.id # should be a unique match
1133
+ normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
1134
+ normal = holes[id].attributes[:n] if holes.key?(id)
1135
+ normal = shades[id][:n] if shades.key?(id)
1136
+ farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
1137
+ farthest_V = farthest - origin # zero magnitude, initially
1138
+ inverted = false
1139
+ i_origin = wire.points.index(origin)
1140
+ i_terminal = wire.points.index(terminal)
1141
+ i_last = wire.points.size - 1
1142
+
1143
+ if i_terminal == 0
1144
+ inverted = true unless i_origin == i_last
1145
+ elsif i_origin == i_last
1146
+ inverted = true unless i_terminal == 0
1147
+ else
1148
+ inverted = true unless i_terminal - i_origin == 1
1149
+ end
1150
+
1151
+ wire.points.each do |point|
1152
+ next if point == origin
1153
+ next if point == terminal
1154
+
1155
+ point_on_plane = edge_plane.project(point)
1156
+ origin_point_V = point_on_plane - origin
1157
+ point_V_magnitude = origin_point_V.magnitude
1158
+ next unless point_V_magnitude > TOL
1159
+
1160
+ # Generate a plane between origin, terminal & point. Only consider
1161
+ # planes that share the same normal as wire.
1162
+ if inverted
1163
+ plane = Topolys::Plane3D.from_points(terminal, origin, point)
1138
1164
  else
1139
- inverted = true unless i_terminal - i_origin == 1
1165
+ plane = Topolys::Plane3D.from_points(origin, terminal, point)
1140
1166
  end
1141
1167
 
1142
- wire.points.each do |point|
1143
- next if point == origin
1144
- next if point == terminal
1145
- point_on_plane = edge_plane.project(point)
1146
- origin_point_V = point_on_plane - origin
1147
- point_V_magnitude = origin_point_V.magnitude
1148
- next unless point_V_magnitude > TOL
1149
-
1150
- # Generate a plane between origin, terminal & point. Only consider
1151
- # planes that share the same normal as wire.
1152
- if inverted
1153
- plane = Topolys::Plane3D.from_points(terminal, origin, point)
1154
- else
1155
- plane = Topolys::Plane3D.from_points(origin, terminal, point)
1156
- end
1168
+ next unless (normal.x - plane.normal.x).abs < TOL &&
1169
+ (normal.y - plane.normal.y).abs < TOL &&
1170
+ (normal.z - plane.normal.z).abs < TOL
1157
1171
 
1158
- next unless (normal.x - plane.normal.x).abs < TOL &&
1159
- (normal.y - plane.normal.y).abs < TOL &&
1160
- (normal.z - plane.normal.z).abs < TOL
1172
+ farther = point_V_magnitude > farthest_V.magnitude
1173
+ farthest = point if farther
1174
+ farthest_V = origin_point_V if farther
1175
+ end
1161
1176
 
1162
- farther = point_V_magnitude > farthest_V.magnitude
1163
- farthest = point if farther
1164
- farthest_V = origin_point_V if farther
1165
- end
1177
+ puts "ADDITION!!" if id == "ADDITION"
1178
+ puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
1166
1179
 
1167
- angle = reference_V.angle(farthest_V)
1168
- adjust = false # adjust angle [180°, 360°] if necessary
1180
+ angle = reference_V.angle(farthest_V)
1181
+ invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
1182
+ angle = 0 if angle.nil?
1169
1183
 
1170
- if vertical
1184
+ adjust = false # adjust angle [180°, 360°] if necessary
1185
+
1186
+ if vertical
1187
+ adjust = true if east.dot(farthest_V) < -TOL
1188
+ else
1189
+ if north.dot(farthest_V).abs < TOL ||
1190
+ (north.dot(farthest_V).abs - 1).abs < TOL
1171
1191
  adjust = true if east.dot(farthest_V) < -TOL
1172
1192
  else
1173
- if north.dot(farthest_V).abs < TOL ||
1174
- (north.dot(farthest_V).abs - 1).abs < TOL
1175
- adjust = true if east.dot(farthest_V) < -TOL
1176
- else
1177
- adjust = true if north.dot(farthest_V) < -TOL
1178
- end
1193
+ adjust = true if north.dot(farthest_V) < -TOL
1179
1194
  end
1180
-
1181
- angle = 2 * Math::PI - angle if adjust
1182
- angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
1183
- surface[:angle] = angle
1184
- farthest_V.normalize!
1185
- surface[:polar] = farthest_V
1186
- surface[:normal] = normal
1187
1195
  end
1196
+
1197
+ angle = 2 * Math::PI - angle if adjust
1198
+ angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
1199
+ surface[:angle ] = angle
1200
+ farthest_V.normalize!
1201
+ surface[:polar ] = farthest_V
1202
+ surface[:normal] = normal
1188
1203
  end # end of edge-linked, surface-to-wire loop
1189
1204
  end # end of edge-linked surface loop
1190
1205
 
@@ -1993,8 +2008,6 @@ module TBD
1993
2008
  #
1994
2009
  # @return [Bool] true if TBD Measure is successful
1995
2010
  def exit(runner = nil, argh = {})
1996
- mth = "TBD::#{__callee__}"
1997
-
1998
2011
  # Generated files target a design context ( >= WARN ) ... change TBD log
1999
2012
  # level for debugging purposes. By default, log status is set < DBG
2000
2013
  # while log level is set @INF.
data/lib/tbd/ua.rb CHANGED
@@ -62,30 +62,33 @@ module TBD
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.
65
- rt = 1 / ut # target construction Rt
66
- ro = rsi(lc, film) # current construction Ro
67
- new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
68
- new_u = 1 / new_r
65
+ rt = 1 / ut # target construction Rt
66
+ ro = rsi(lc, film) # current construction Ro
67
+ new_r = lyr[:r] + (rt - ro) # new, un-derated layer RSi
68
+ new_u = 1 / new_r
69
69
 
70
70
  # Then, uprate (if possible) to counter expected thermal bridging effects.
71
- u_psi = hloss / area # from psi & khi
72
- new_u = new_u - u_psi # uprated layer USi to counter psi & khi
73
- new_r = 1 / new_u # uprated layer RSi to counter psi & khi
71
+ u_psi = hloss / area # from psi & khi
72
+ new_u -= u_psi # uprated layer USi to counter psi & khi
73
+ new_r = 1 / new_u # uprated layer RSi to counter psi & khi
74
74
 
75
75
  return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
76
- loss = 0.0 # residual heatloss (not assigned) [W/K]
76
+
77
+ loss = 0.0 # residual heatloss (not assigned) [W/K]
77
78
 
78
79
  if lyr[:type] == :massless
79
80
  m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
80
81
  return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
82
+
81
83
  m = m.get.clone(model).to_MasslessOpaqueMaterial.get
82
84
  m.setName("#{id} uprated")
83
85
  new_r = 0.001 unless new_r > 0.001
84
86
  loss = (new_u - 1 / new_r) * area unless new_r > 0.001
85
87
  m.setThermalResistance(new_r)
86
88
  else # type == :standard
87
- m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
89
+ m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
88
90
  return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
91
+
89
92
  m = m.get.clone(model).to_StandardOpaqueMaterial.get
90
93
  m.setName("#{id} uprated")
91
94
  k = m.thermalConductivity
@@ -108,10 +111,12 @@ module TBD
108
111
 
109
112
  ok = m.setThickness(d)
110
113
  return invalid("Can't uprate '#{id}': > 3m", mth, 0, ERR, res) unless ok
114
+
111
115
  m.setThermalConductivity(k) if ok
112
116
  end
113
117
 
114
118
  return invalid("", mth, 0, ERR, res) unless m
119
+
115
120
  lc.setLayer(lyr[:index], m)
116
121
  uo = 1 / rsi(lc, film)
117
122
 
@@ -174,7 +179,6 @@ module TBD
174
179
  area = 0
175
180
  film = 100000000000000
176
181
  lc = nil
177
- uo = nil
178
182
  id = ""
179
183
  all = g[:op].downcase == "all wall constructions" ||
180
184
  g[:op].downcase == "all roof constructions" ||
@@ -188,6 +192,7 @@ module TBD
188
192
  next unless sss.outsideBoundaryCondition.downcase == "outdoors"
189
193
  next if sss.construction.empty?
190
194
  next if sss.construction.get.to_LayeredConstruction.empty?
195
+
191
196
  c = sss.construction.get.to_LayeredConstruction.get
192
197
  i = c.nameString
193
198
 
@@ -236,7 +241,7 @@ module TBD
236
241
  end
237
242
 
238
243
  if coll.empty?
239
- log(ERR, "No construction to uprate - skipping (#{mth})")
244
+ log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
240
245
  next
241
246
  elsif lc # valid layered construction - good to uprate!
242
247
  # Ensure lc is referenced by surface types == label.
@@ -948,7 +953,7 @@ module TBD
948
953
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
949
954
  model += " (v#{ua[:version]})" if ua.key?(:version)
950
955
  report << model unless model.empty?
951
- report << "* TBD : v3.0.2"
956
+ report << "* TBD : v3.1.0"
952
957
  report << "* date : #{ua[:date]}"
953
958
 
954
959
  if lang == :en
data/lib/tbd/version.rb CHANGED
@@ -21,5 +21,5 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  module TBD
24
- VERSION = "3.0.2".freeze
24
+ VERSION = "3.1.0".freeze
25
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tbd
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Bourgeois & Dan Macumber
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-19 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: topolys
@@ -124,13 +124,11 @@ files:
124
124
  - LICENSE.md
125
125
  - README.md
126
126
  - Rakefile
127
- - json/midrise.json
128
127
  - json/tbd_5ZoneNoHVAC.json
129
128
  - json/tbd_5ZoneNoHVAC_btap.json
130
129
  - json/tbd_seb_n2.json
131
130
  - json/tbd_seb_n4.json
132
131
  - json/tbd_warehouse10.json
133
- - json/tbd_warehouse5.json
134
132
  - lib/measures/tbd/LICENSE.md
135
133
  - lib/measures/tbd/README.md
136
134
  - lib/measures/tbd/README.md.erb
@@ -163,7 +161,7 @@ licenses:
163
161
  - MIT
164
162
  metadata:
165
163
  homepage_uri: https://github.com/rd2/tbd
166
- source_code_uri: https://github.com/rd2/tbd/tree/v3.0.2
164
+ source_code_uri: https://github.com/rd2/tbd/tree/v3.1.0
167
165
  bug_tracker_uri: https://github.com/rd2/tbd/issues
168
166
  post_install_message:
169
167
  rdoc_options: []
data/json/midrise.json DELETED
@@ -1,64 +0,0 @@
1
- {
2
- "schema": "https://github.com/rd2/tbd/blob/master/tbd.schema.json",
3
- "description": "sorting out multiple story PSI sets (midrise)",
4
- "psis": [{
5
- "id": "compliant",
6
- "rimjoist": 0.3,
7
- "parapet": 0.325,
8
- "fenestration": 0.35,
9
- "corner": 0.45,
10
- "balcony": 0.5,
11
- "party": 0.5,
12
- "grade": 0.45
13
- },
14
- {
15
- "id": "Building Story 1",
16
- "rimjoist": 0.501,
17
- "parapet": 0.511,
18
- "fenestration": 0.521,
19
- "cornerconcave": 0.531,
20
- "cornerconvex": 0.541,
21
- "balcony": 0.551,
22
- "party": 0.561,
23
- "grade": 0.571
24
- },
25
- {
26
- "id": "Building Story 2",
27
- "rimjoist": 0.401,
28
- "parapet": 0.411,
29
- "fenestration": 0.421,
30
- "cornerconcave": 0.431,
31
- "cornerconvex": 0.441,
32
- "balcony": 0.451,
33
- "party": 0.461,
34
- "grade": 0.471
35
- },
36
- {
37
- "id": "Building Story 3",
38
- "rimjoist": 0.301,
39
- "parapet": 0.311,
40
- "fenestration": 0.321,
41
- "cornerconcave": 0.331,
42
- "cornerconvex": 0.341,
43
- "balcony": 0.351,
44
- "party": 0.361,
45
- "grade": 0.371
46
- }
47
- ],
48
- "building": {
49
- "psi": "compliant"
50
- },
51
- "stories": [{
52
- "id": "Building Story 1",
53
- "psi": "Building Story 1"
54
- },
55
- {
56
- "id": "Building Story 2",
57
- "psi": "Building Story 2"
58
- },
59
- {
60
- "id": "Building Story 3",
61
- "psi": "Building Story 3"
62
- }
63
- ]
64
- }
@@ -1,37 +0,0 @@
1
- {
2
- "schema": "https://github.com/rd2/tbd/blob/master/tbd.schema.json",
3
- "description": "test_warehouse.osm with spacetype PSI values",
4
- "psis": [{
5
- "id": "Warehouse Office",
6
- "rimjoist": 0.300,
7
- "parapet": 0.300,
8
- "fenestration": 0.300,
9
- "corner": 0.300,
10
- "balcony": 0.300,
11
- "party": 0.300,
12
- "grade": 0.300
13
- },
14
- {
15
- "id": "Warehouse Fine",
16
- "rimjoist": 0.500,
17
- "parapet": 0.500,
18
- "fenestration": 0.500,
19
- "corner": 0.500,
20
- "balcony": 0.500,
21
- "party": 0.500,
22
- "grade": 0.500
23
- }
24
- ],
25
- "building": {
26
- "psi": "regular (BETBG)"
27
- },
28
- "spacetypes": [{
29
- "id": "Warehouse Office",
30
- "psi": "Warehouse Office"
31
- },
32
- {
33
- "id": "Warehouse Fine",
34
- "psi": "Warehouse Fine"
35
- }
36
- ]
37
- }