tbd 3.0.2 → 3.0.3

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: fff7235e8a345417b5d6885b6b131f8a3ec91cdcad6cf06a691a5455ad04c842
4
+ data.tar.gz: 89b326a95347bcaf4415b6c5643e2ae0841aae2e238cdfc6326bc069fdff1134
5
5
  SHA512:
6
- metadata.gz: 7284058dc4ec681c102435484b8af994fd027ea666aa4ed760f0e178cfcef297de033454c0a2c7cd60c95f13b71308bdcc6c4fa0a846819ec92edaa46007af08
7
- data.tar.gz: 78e74d0942d5ad735c9b696c5cdaa72b5ad8a92fbf34a3c8843e41dce41f1de0eddfda3b53ef8db08c3f9c87a73b92b3ad8bb89400a58283c1805d7d660d83f7
6
+ metadata.gz: c38bdf41f39fccaaed05642f7d4b8e24f13833a120c9b4af152399a9b796a3594574961745de4728e4f4261a75567fb46b4e953ef061453cec94f413ec26583f
7
+ data.tar.gz: f4844f96093b176796d1c89c3af61dd8473c6417d1305a64b84abf96eee1328357ff6d34cd2726a4e6bd3617f45ca80c4a6af2309f63772f1acd2dc4dbfc15cb
@@ -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
@@ -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>3e0bc642-b37c-488f-96d5-e73f4c6d432b</version_id>
7
+ <version_modified>20221020T205858Z</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>
@@ -445,16 +439,22 @@
445
439
  <checksum>36FA11E3</checksum>
446
440
  </file>
447
441
  <file>
448
- <filename>geo.rb</filename>
442
+ <filename>ua.rb</filename>
449
443
  <filetype>rb</filetype>
450
444
  <usage_type>resource</usage_type>
451
- <checksum>8A794826</checksum>
445
+ <checksum>7264CA34</checksum>
452
446
  </file>
453
447
  <file>
454
- <filename>ua.rb</filename>
448
+ <filename>psi.rb</filename>
449
+ <filetype>rb</filetype>
450
+ <usage_type>resource</usage_type>
451
+ <checksum>F31F865A</checksum>
452
+ </file>
453
+ <file>
454
+ <filename>geo.rb</filename>
455
455
  <filetype>rb</filetype>
456
456
  <usage_type>resource</usage_type>
457
- <checksum>7873E62A</checksum>
457
+ <checksum>D999D942</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?
@@ -844,7 +844,7 @@ module TBD
844
844
  up = ""
845
845
  up = "uprated " if m.nameString.include?(" uprated")
846
846
  m = m.clone(model).to_MasslessOpaqueMaterial.get
847
- m.setName("'#{id}' #{up}m tbd")
847
+ m.setName("#{id} #{up}m tbd")
848
848
  de_r = 0.001 unless de_r > 0.001
849
849
  loss = (de_u - 1 / de_r) * s[:net] unless de_r > 0.001
850
850
  m.setThermalResistance(de_r)
@@ -855,7 +855,7 @@ module TBD
855
855
  up = ""
856
856
  up = "uprated " if m.nameString.include?(" uprated")
857
857
  m = m.clone(model).to_StandardOpaqueMaterial.get
858
- m.setName("'#{id}' #{up}m tbd")
858
+ m.setName("#{id} #{up}m tbd")
859
859
  k = m.thermalConductivity
860
860
 
861
861
  if de_r > 0.001
@@ -1105,6 +1105,10 @@ module TBD
1105
1105
  horizontal = dz.abs < TOL
1106
1106
  vertical = dx < TOL && dy < TOL
1107
1107
  edge_V = terminal - origin
1108
+
1109
+ invalid("1x edge length < TOL", mth, 0, ERROR) if edge_V.magnitude < TOL
1110
+ next if edge_V.magnitude < TOL
1111
+
1108
1112
  edge_plane = Topolys::Plane3D.new(origin, edge_V)
1109
1113
 
1110
1114
  if vertical
@@ -1120,71 +1124,77 @@ module TBD
1120
1124
  # Loop through each linked wire and determine farthest point from
1121
1125
  # edge while ensuring candidate point is not aligned with edge.
1122
1126
  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
1127
+ next unless surface[:wire] == wire.id # should be a unique match
1128
+ normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
1129
+ normal = holes[id].attributes[:n] if holes.key?(id)
1130
+ normal = shades[id][:n] if shades.key?(id)
1131
+ farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
1132
+ farthest_V = farthest - origin # zero magnitude, initially
1133
+ inverted = false
1134
+ i_origin = wire.points.index(origin)
1135
+ i_terminal = wire.points.index(terminal)
1136
+ i_last = wire.points.size - 1
1137
+
1138
+ if i_terminal == 0
1139
+ inverted = true unless i_origin == i_last
1140
+ elsif i_origin == i_last
1141
+ inverted = true unless i_terminal == 0
1142
+ else
1143
+ inverted = true unless i_terminal - i_origin == 1
1144
+ end
1145
+
1146
+ wire.points.each do |point|
1147
+ next if point == origin
1148
+ next if point == terminal
1149
+
1150
+ point_on_plane = edge_plane.project(point)
1151
+ origin_point_V = point_on_plane - origin
1152
+ point_V_magnitude = origin_point_V.magnitude
1153
+ next unless point_V_magnitude > TOL
1154
+
1155
+ # Generate a plane between origin, terminal & point. Only consider
1156
+ # planes that share the same normal as wire.
1157
+ if inverted
1158
+ plane = Topolys::Plane3D.from_points(terminal, origin, point)
1138
1159
  else
1139
- inverted = true unless i_terminal - i_origin == 1
1160
+ plane = Topolys::Plane3D.from_points(origin, terminal, point)
1140
1161
  end
1141
1162
 
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
1163
+ next unless (normal.x - plane.normal.x).abs < TOL &&
1164
+ (normal.y - plane.normal.y).abs < TOL &&
1165
+ (normal.z - plane.normal.z).abs < TOL
1157
1166
 
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
1167
+ farther = point_V_magnitude > farthest_V.magnitude
1168
+ farthest = point if farther
1169
+ farthest_V = origin_point_V if farther
1170
+ end
1161
1171
 
1162
- farther = point_V_magnitude > farthest_V.magnitude
1163
- farthest = point if farther
1164
- farthest_V = origin_point_V if farther
1165
- end
1172
+ puts "ADDITION!!" if id == "ADDITION"
1173
+ puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
1174
+
1175
+ angle = reference_V.angle(farthest_V)
1176
+ invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
1177
+ angle = 0 if angle.nil?
1166
1178
 
1167
- angle = reference_V.angle(farthest_V)
1168
- adjust = false # adjust angle [180°, 360°] if necessary
1179
+ adjust = false # adjust angle [180°, 360°] if necessary
1169
1180
 
1170
- if vertical
1181
+ if vertical
1182
+ adjust = true if east.dot(farthest_V) < -TOL
1183
+ else
1184
+ if north.dot(farthest_V).abs < TOL ||
1185
+ (north.dot(farthest_V).abs - 1).abs < TOL
1171
1186
  adjust = true if east.dot(farthest_V) < -TOL
1172
1187
  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
1188
+ adjust = true if north.dot(farthest_V) < -TOL
1179
1189
  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
1190
  end
1191
+
1192
+ angle = 2 * Math::PI - angle if adjust
1193
+ angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
1194
+ surface[:angle ] = angle
1195
+ farthest_V.normalize!
1196
+ surface[:polar ] = farthest_V
1197
+ surface[:normal] = normal
1188
1198
  end # end of edge-linked, surface-to-wire loop
1189
1199
  end # end of edge-linked surface loop
1190
1200
 
@@ -1993,8 +2003,6 @@ module TBD
1993
2003
  #
1994
2004
  # @return [Bool] true if TBD Measure is successful
1995
2005
  def exit(runner = nil, argh = {})
1996
- mth = "TBD::#{__callee__}"
1997
-
1998
2006
  # Generated files target a design context ( >= WARN ) ... change TBD log
1999
2007
  # level for debugging purposes. By default, log status is set < DBG
2000
2008
  # while log level is set @INF.
@@ -174,7 +174,6 @@ module TBD
174
174
  area = 0
175
175
  film = 100000000000000
176
176
  lc = nil
177
- uo = nil
178
177
  id = ""
179
178
  all = g[:op].downcase == "all wall constructions" ||
180
179
  g[:op].downcase == "all roof constructions" ||
@@ -236,7 +235,7 @@ module TBD
236
235
  end
237
236
 
238
237
  if coll.empty?
239
- log(ERR, "No construction to uprate - skipping (#{mth})")
238
+ log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
240
239
  next
241
240
  elsif lc # valid layered construction - good to uprate!
242
241
  # Ensure lc is referenced by surface types == label.
@@ -948,7 +947,7 @@ module TBD
948
947
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
949
948
  model += " (v#{ua[:version]})" if ua.key?(:version)
950
949
  report << model unless model.empty?
951
- report << "* TBD : v3.0.2"
950
+ report << "* TBD : v3.0.3"
952
951
  report << "* date : #{ua[:date]}"
953
952
 
954
953
  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
@@ -844,7 +844,7 @@ module TBD
844
844
  up = ""
845
845
  up = "uprated " if m.nameString.include?(" uprated")
846
846
  m = m.clone(model).to_MasslessOpaqueMaterial.get
847
- m.setName("'#{id}' #{up}m tbd")
847
+ m.setName("#{id} #{up}m tbd")
848
848
  de_r = 0.001 unless de_r > 0.001
849
849
  loss = (de_u - 1 / de_r) * s[:net] unless de_r > 0.001
850
850
  m.setThermalResistance(de_r)
@@ -855,7 +855,7 @@ module TBD
855
855
  up = ""
856
856
  up = "uprated " if m.nameString.include?(" uprated")
857
857
  m = m.clone(model).to_StandardOpaqueMaterial.get
858
- m.setName("'#{id}' #{up}m tbd")
858
+ m.setName("#{id} #{up}m tbd")
859
859
  k = m.thermalConductivity
860
860
 
861
861
  if de_r > 0.001
@@ -1105,6 +1105,10 @@ module TBD
1105
1105
  horizontal = dz.abs < TOL
1106
1106
  vertical = dx < TOL && dy < TOL
1107
1107
  edge_V = terminal - origin
1108
+
1109
+ invalid("1x edge length < TOL", mth, 0, ERROR) if edge_V.magnitude < TOL
1110
+ next if edge_V.magnitude < TOL
1111
+
1108
1112
  edge_plane = Topolys::Plane3D.new(origin, edge_V)
1109
1113
 
1110
1114
  if vertical
@@ -1120,71 +1124,77 @@ module TBD
1120
1124
  # Loop through each linked wire and determine farthest point from
1121
1125
  # edge while ensuring candidate point is not aligned with edge.
1122
1126
  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
1127
+ next unless surface[:wire] == wire.id # should be a unique match
1128
+ normal = tbd[:surfaces][id][:n] if tbd[:surfaces].key?(id)
1129
+ normal = holes[id].attributes[:n] if holes.key?(id)
1130
+ normal = shades[id][:n] if shades.key?(id)
1131
+ farthest = Topolys::Point3D.new(origin.x, origin.y, origin.z)
1132
+ farthest_V = farthest - origin # zero magnitude, initially
1133
+ inverted = false
1134
+ i_origin = wire.points.index(origin)
1135
+ i_terminal = wire.points.index(terminal)
1136
+ i_last = wire.points.size - 1
1137
+
1138
+ if i_terminal == 0
1139
+ inverted = true unless i_origin == i_last
1140
+ elsif i_origin == i_last
1141
+ inverted = true unless i_terminal == 0
1142
+ else
1143
+ inverted = true unless i_terminal - i_origin == 1
1144
+ end
1145
+
1146
+ wire.points.each do |point|
1147
+ next if point == origin
1148
+ next if point == terminal
1149
+
1150
+ point_on_plane = edge_plane.project(point)
1151
+ origin_point_V = point_on_plane - origin
1152
+ point_V_magnitude = origin_point_V.magnitude
1153
+ next unless point_V_magnitude > TOL
1154
+
1155
+ # Generate a plane between origin, terminal & point. Only consider
1156
+ # planes that share the same normal as wire.
1157
+ if inverted
1158
+ plane = Topolys::Plane3D.from_points(terminal, origin, point)
1138
1159
  else
1139
- inverted = true unless i_terminal - i_origin == 1
1160
+ plane = Topolys::Plane3D.from_points(origin, terminal, point)
1140
1161
  end
1141
1162
 
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
1163
+ next unless (normal.x - plane.normal.x).abs < TOL &&
1164
+ (normal.y - plane.normal.y).abs < TOL &&
1165
+ (normal.z - plane.normal.z).abs < TOL
1157
1166
 
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
1167
+ farther = point_V_magnitude > farthest_V.magnitude
1168
+ farthest = point if farther
1169
+ farthest_V = origin_point_V if farther
1170
+ end
1161
1171
 
1162
- farther = point_V_magnitude > farthest_V.magnitude
1163
- farthest = point if farther
1164
- farthest_V = origin_point_V if farther
1165
- end
1172
+ puts "ADDITION!!" if id == "ADDITION"
1173
+ puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
1174
+
1175
+ angle = reference_V.angle(farthest_V)
1176
+ invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
1177
+ angle = 0 if angle.nil?
1166
1178
 
1167
- angle = reference_V.angle(farthest_V)
1168
- adjust = false # adjust angle [180°, 360°] if necessary
1179
+ adjust = false # adjust angle [180°, 360°] if necessary
1169
1180
 
1170
- if vertical
1181
+ if vertical
1182
+ adjust = true if east.dot(farthest_V) < -TOL
1183
+ else
1184
+ if north.dot(farthest_V).abs < TOL ||
1185
+ (north.dot(farthest_V).abs - 1).abs < TOL
1171
1186
  adjust = true if east.dot(farthest_V) < -TOL
1172
1187
  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
1188
+ adjust = true if north.dot(farthest_V) < -TOL
1179
1189
  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
1190
  end
1191
+
1192
+ angle = 2 * Math::PI - angle if adjust
1193
+ angle -= 2 * Math::PI if (angle - 2 * Math::PI).abs < TOL
1194
+ surface[:angle ] = angle
1195
+ farthest_V.normalize!
1196
+ surface[:polar ] = farthest_V
1197
+ surface[:normal] = normal
1188
1198
  end # end of edge-linked, surface-to-wire loop
1189
1199
  end # end of edge-linked surface loop
1190
1200
 
@@ -1993,8 +2003,6 @@ module TBD
1993
2003
  #
1994
2004
  # @return [Bool] true if TBD Measure is successful
1995
2005
  def exit(runner = nil, argh = {})
1996
- mth = "TBD::#{__callee__}"
1997
-
1998
2006
  # Generated files target a design context ( >= WARN ) ... change TBD log
1999
2007
  # level for debugging purposes. By default, log status is set < DBG
2000
2008
  # while log level is set @INF.
data/lib/tbd/ua.rb CHANGED
@@ -174,7 +174,6 @@ module TBD
174
174
  area = 0
175
175
  film = 100000000000000
176
176
  lc = nil
177
- uo = nil
178
177
  id = ""
179
178
  all = g[:op].downcase == "all wall constructions" ||
180
179
  g[:op].downcase == "all roof constructions" ||
@@ -236,7 +235,7 @@ module TBD
236
235
  end
237
236
 
238
237
  if coll.empty?
239
- log(ERR, "No construction to uprate - skipping (#{mth})")
238
+ log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
240
239
  next
241
240
  elsif lc # valid layered construction - good to uprate!
242
241
  # Ensure lc is referenced by surface types == label.
@@ -948,7 +947,7 @@ module TBD
948
947
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
949
948
  model += " (v#{ua[:version]})" if ua.key?(:version)
950
949
  report << model unless model.empty?
951
- report << "* TBD : v3.0.2"
950
+ report << "* TBD : v3.0.3"
952
951
  report << "* date : #{ua[:date]}"
953
952
 
954
953
  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.0.3".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.0.3
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-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: topolys
@@ -163,7 +163,7 @@ licenses:
163
163
  - MIT
164
164
  metadata:
165
165
  homepage_uri: https://github.com/rd2/tbd
166
- source_code_uri: https://github.com/rd2/tbd/tree/v3.0.2
166
+ source_code_uri: https://github.com/rd2/tbd/tree/v3.0.3
167
167
  bug_tracker_uri: https://github.com/rd2/tbd/issues
168
168
  post_install_message:
169
169
  rdoc_options: []