tbd 3.0.2 → 3.1.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: 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
- }