osut 0.2.3 → 0.2.7

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/osut/utils.rb +337 -81
  3. data/lib/osut/version.rb +1 -1
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be4500de4b7207ef0f1bb50342c4f63e12714bf22a17779f928b51adba0b862f
4
- data.tar.gz: 3b4aa51dff5771c31fe5edc89d1326555447d2eaa7844b4944ff43b84b01cad2
3
+ metadata.gz: 6bbbc6887949cfd0d1aed5c460d993196fa86d1a13f366f7f852e418aa9457a0
4
+ data.tar.gz: b38eaa81d84a79655adb10a06bb1fa299ffff60051d187f2b36bad38fa4d78e0
5
5
  SHA512:
6
- metadata.gz: 47c84a2d136ec1b6552436cdcc555469813987485f64a2a4ddfa99788f5ee1d5b99d4b369b50766652b028f39204d3efccf7793276400e256d81492bbb85127f
7
- data.tar.gz: af28329f47ff40ac971ec3b9333c0e21cb0ef293f1c2697aeebe96d0009acba34696a1f43c9bf07828b854ae8bd110b95b295bcb849dc8e0ff3e21236e4de308
6
+ metadata.gz: 1eb2cd49987ce2a4bf7a6a9fe0ed7048bc78dce644aa88e68173861ce4e52fc2bfe59bb3c4a7b353426a1bfa30f45af102ca3e509ac91945b82673545f1a4d4d
7
+ data.tar.gz: bb8075d22d660b3f177716463baf3bc2b0e788269dc05f60086272dc5fec193b93a207f94500fa26cf8d786fdcd3810c0d3defb50eca4c39b3a05cf7c8d2412c
data/lib/osut/utils.rb CHANGED
@@ -304,19 +304,9 @@ module OSut
304
304
  end
305
305
  end
306
306
 
307
- # unless equip.to_ZoneHVACLowTemperatureRadiantElectric.empty?
308
- # equip = equip.to_ZoneHVACLowTemperatureRadiantElectric.get
309
- #
310
- # unless equip.heatingSetpointTemperatureSchedule.empty?
311
- # sched = equip.heatingSetpointTemperatureSchedule.get
312
- # end
313
- # end
314
-
315
307
  unless equip.to_ZoneHVACLowTemperatureRadiantElectric.empty?
316
308
  equip = equip.to_ZoneHVACLowTemperatureRadiantElectric.get
317
- unless equip.heatingSetpointTemperatureSchedule.empty?
318
- sched = equip.heatingSetpointTemperatureSchedule.get
319
- end
309
+ sched = equip.heatingSetpointTemperatureSchedule
320
310
  end
321
311
 
322
312
  unless equip.to_ZoneHVACLowTempRadiantConstFlow.empty?
@@ -638,7 +628,7 @@ module OSut
638
628
  mth = "OSut::#{__callee__}"
639
629
  cl = OpenStudio::Model::Model
640
630
 
641
- return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
631
+ return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
642
632
 
643
633
  model.getThermalZones.each do |zone|
644
634
  return true if minCoolScheduledSetpoint(zone)[:spt]
@@ -658,12 +648,12 @@ module OSut
658
648
  mth = "OSut::#{__callee__}"
659
649
  cl = OpenStudio::Model::Model
660
650
 
661
- return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
651
+ return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
662
652
 
663
653
  model.getThermalZones.each do |zone|
664
- next if zone.canBePlenum
665
- return true unless zone.airLoopHVACs.empty?
666
- return true if zone.isPlenum
654
+ next if zone.canBePlenum
655
+ return true unless zone.airLoopHVACs.empty?
656
+ return true if zone.isPlenum
667
657
  end
668
658
 
669
659
  false
@@ -688,7 +678,7 @@ module OSut
688
678
  # A space may be tagged as a plenum if:
689
679
  #
690
680
  # CASE A: its zone's "isPlenum" == true (SDK method) for a fully-developed
691
- # OpenStudio model (complete with HVAC air loops);
681
+ # OpenStudio model (complete with HVAC air loops); OR
692
682
  #
693
683
  # CASE B: (IN ABSENCE OF HVAC AIRLOOPS) if it's excluded from a building's
694
684
  # total floor area yet linked to a zone holding an 'inactive'
@@ -747,8 +737,8 @@ module OSut
747
737
  cl = OpenStudio::Model::Model
748
738
  limits = nil
749
739
 
750
- return mismatch("model", model, cl, mth) unless model.is_a?(cl)
751
- return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_s)
740
+ return mismatch("model", model, cl, mth) unless model.is_a?(cl)
741
+ return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_s)
752
742
 
753
743
  # Either fetch availability ScheduleTypeLimits object, or create one.
754
744
  model.getScheduleTypeLimitss.each do |l|
@@ -779,9 +769,9 @@ module OSut
779
769
  off = OpenStudio::Model::ScheduleDay.new(model, 0)
780
770
 
781
771
  # Seasonal availability start/end dates.
782
- year = model.yearDescription
783
- return empty("yearDescription", mth, ERR) if year.empty?
784
- year = year.get
772
+ year = model.yearDescription
773
+ return empty("yearDescription", mth, ERR) if year.empty?
774
+ year = year.get
785
775
  may01 = year.makeDate(OpenStudio::MonthOfYear.new("May"), 1)
786
776
  oct31 = year.makeDate(OpenStudio::MonthOfYear.new("Oct"), 31)
787
777
 
@@ -967,17 +957,17 @@ module OSut
967
957
  cl1 = OpenStudio::Model::Model
968
958
  cl2 = OpenStudio::Model::Surface
969
959
 
970
- return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
971
- return invalid("s", mth, 2) unless s.respond_to?(NS)
972
- id = s.nameString
973
- return mismatch(id, s, cl2, mth) unless s.is_a?(cl2)
960
+ return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
961
+ return invalid("s", mth, 2) unless s.respond_to?(NS)
962
+ id = s.nameString
963
+ return mismatch(id, s, cl2, mth) unless s.is_a?(cl2)
974
964
 
975
965
  ok = s.isConstructionDefaulted
976
966
  log(ERR, "'#{id}' construction not defaulted (#{mth})") unless ok
977
967
  return nil unless ok
978
- return empty("'#{id}' construction", mth, ERR) if s.construction.empty?
968
+ return empty("'#{id}' construction", mth, ERR) if s.construction.empty?
979
969
  base = s.construction.get
980
- return empty("'#{id}' space", mth, ERR) if s.space.empty?
970
+ return empty("'#{id}' space", mth, ERR) if s.space.empty?
981
971
  space = s.space.get
982
972
  type = s.surfaceType
983
973
  ground = false
@@ -991,7 +981,7 @@ module OSut
991
981
 
992
982
  unless space.defaultConstructionSet.empty?
993
983
  set = space.defaultConstructionSet.get
994
- return set if holdsConstruction?(set, base, ground, exterior, type)
984
+ return set if holdsConstruction?(set, base, ground, exterior, type)
995
985
  end
996
986
 
997
987
  unless space.spaceType.empty?
@@ -999,7 +989,7 @@ module OSut
999
989
 
1000
990
  unless spacetype.defaultConstructionSet.empty?
1001
991
  set = spacetype.defaultConstructionSet.get
1002
- return set if holdsConstruction?(set, base, ground, exterior, type)
992
+ return set if holdsConstruction?(set, base, ground, exterior, type)
1003
993
  end
1004
994
  end
1005
995
 
@@ -1008,7 +998,7 @@ module OSut
1008
998
 
1009
999
  unless story.defaultConstructionSet.empty?
1010
1000
  set = story.defaultConstructionSet.get
1011
- return set if holdsConstruction?(set, base, ground, exterior, type)
1001
+ return set if holdsConstruction?(set, base, ground, exterior, type)
1012
1002
  end
1013
1003
  end
1014
1004
 
@@ -1016,7 +1006,7 @@ module OSut
1016
1006
 
1017
1007
  unless building.defaultConstructionSet.empty?
1018
1008
  set = building.defaultConstructionSet.get
1019
- return set if holdsConstruction?(set, base, ground, exterior, type)
1009
+ return set if holdsConstruction?(set, base, ground, exterior, type)
1020
1010
  end
1021
1011
 
1022
1012
  nil
@@ -1046,15 +1036,15 @@ module OSut
1046
1036
  #
1047
1037
  # @param lc [OpenStudio::LayeredConstruction] a layered construction
1048
1038
  #
1049
- # @return [Double] total layered construction thickness
1050
- # @return [Double] 0 if invalid input
1039
+ # @return [Float] total layered construction thickness
1040
+ # @return [Float] 0 if invalid input
1051
1041
  def thickness(lc = nil)
1052
1042
  mth = "OSut::#{__callee__}"
1053
1043
  cl = OpenStudio::Model::LayeredConstruction
1054
1044
 
1055
- return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
1045
+ return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
1056
1046
  id = lc.nameString
1057
- return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl)
1047
+ return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl)
1058
1048
 
1059
1049
  ok = standardOpaqueLayers?(lc)
1060
1050
  log(ERR, "'#{id}' holds non-StandardOpaqueMaterial(s) (#{mth})") unless ok
@@ -1176,9 +1166,9 @@ module OSut
1176
1166
  res = { index: nil, type: nil, r: 0.0 }
1177
1167
  i = 0 # iterator
1178
1168
 
1179
- return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
1180
- id = lc.nameString
1181
- return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl)
1169
+ return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
1170
+ id = lc.nameString
1171
+ return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl)
1182
1172
 
1183
1173
  lc.layers.each do |m|
1184
1174
  unless m.to_MasslessOpaqueMaterial.empty?
@@ -1188,9 +1178,9 @@ module OSut
1188
1178
  i += 1
1189
1179
  next
1190
1180
  else
1191
- res[:r] = m.thermalResistance
1181
+ res[:r ] = m.thermalResistance
1192
1182
  res[:index] = i
1193
- res[:type] = :massless
1183
+ res[:type ] = :massless
1194
1184
  end
1195
1185
  end
1196
1186
 
@@ -1203,9 +1193,9 @@ module OSut
1203
1193
  i += 1
1204
1194
  next
1205
1195
  else
1206
- res[:r] = d / k
1196
+ res[:r ] = d / k
1207
1197
  res[:index] = i
1208
- res[:type] = :standard
1198
+ res[:type ] = :standard
1209
1199
  end
1210
1200
  end
1211
1201
 
@@ -1240,6 +1230,28 @@ module OSut
1240
1230
  res
1241
1231
  end
1242
1232
 
1233
+ ##
1234
+ # Return a scalar product of an OpenStudio Vector3d.
1235
+ #
1236
+ # @param v [OpenStudio::Vector3d] a vector
1237
+ # @param m [Float] a scalar
1238
+ #
1239
+ # @return [OpenStudio::Vector3d] modified vector
1240
+ # @return [OpenStudio::Vector3d] provided (or empty) vector if invalid input
1241
+ def scalar(v = OpenStudio::Vector3d.new(0,0,0), m = 0)
1242
+ mth = "OSut::#{__callee__}"
1243
+ cl1 = OpenStudio::Vector3d
1244
+ cl2 = Numeric
1245
+
1246
+ return mismatch("vector", v, cl1, mth, DBG, v) unless v.is_a?(cl1)
1247
+ return mismatch("x", v.x, cl2, mth, DBG, v) unless v.x.respond_to?(:to_f)
1248
+ return mismatch("y", v.y, cl2, mth, DBG, v) unless v.y.respond_to?(:to_f)
1249
+ return mismatch("z", v.z, cl2, mth, DBG, v) unless v.z.respond_to?(:to_f)
1250
+ return mismatch("m", m, cl2, mth, DBG, v) unless m.respond_to?(:to_f)
1251
+
1252
+ OpenStudio::Vector3d.new(m * v.x, m * v.y, m * v.z)
1253
+ end
1254
+
1243
1255
  ##
1244
1256
  # Flatten OpenStudio 3D points vs Z-axis (Z=0).
1245
1257
  #
@@ -1253,8 +1265,8 @@ module OSut
1253
1265
  v = OpenStudio::Point3dVector.new
1254
1266
 
1255
1267
  valid = pts.is_a?(cl1) || pts.is_a?(Array)
1256
- return mismatch("points", pts, cl1, mth, DBG, v) unless valid
1257
- pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) }
1268
+ return mismatch("points", pts, cl1, mth, DBG, v) unless valid
1269
+ pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) }
1258
1270
  pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, 0) }
1259
1271
 
1260
1272
  v
@@ -1278,40 +1290,47 @@ module OSut
1278
1290
 
1279
1291
  return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s)
1280
1292
  return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s)
1293
+
1281
1294
  i1 = id1.to_s
1282
1295
  i2 = id2.to_s
1283
1296
  i1 = "poly1" if i1.empty?
1284
1297
  i2 = "poly2" if i2.empty?
1298
+
1285
1299
  valid1 = p1.is_a?(cl1) || p1.is_a?(Array)
1286
1300
  valid2 = p2.is_a?(cl1) || p2.is_a?(Array)
1301
+
1287
1302
  return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1
1288
1303
  return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2
1289
- return empty(i1, mth, ERR, a) if p1.empty?
1290
- return empty(i2, mth, ERR, a) if p2.empty?
1304
+ return empty(i1, mth, ERR, a) if p1.empty?
1305
+ return empty(i2, mth, ERR, a) if p2.empty?
1306
+
1291
1307
  p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
1292
1308
  p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
1293
1309
 
1294
- ft = OpenStudio::Transformation::alignFace(p1).inverse
1295
- ft_p1 = flatZ( (ft * p1).reverse )
1296
- return false if ft_p1.empty?
1297
- area1 = OpenStudio::getArea(ft_p1)
1298
- return empty("#{i1} area", mth, ERR, a) if area1.empty?
1310
+ # XY-plane transformation matrix ... needs to be clockwise for boost.
1311
+ ft = OpenStudio::Transformation.alignFace(p1)
1312
+ ft_p1 = flatZ( (ft.inverse * p1) )
1313
+ return false if ft_p1.empty?
1314
+ cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL)
1315
+ ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw
1316
+ ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw
1317
+ ft_p2 = flatZ( (ft.inverse * p2) ) if cw
1318
+ return false if ft_p2.empty?
1319
+ area1 = OpenStudio.getArea(ft_p1)
1320
+ area2 = OpenStudio.getArea(ft_p2)
1321
+ return empty("#{i1} area", mth, ERR, a) if area1.empty?
1322
+ return empty("#{i2} area", mth, ERR, a) if area2.empty?
1299
1323
  area1 = area1.get
1300
- ft_p2 = flatZ( (ft * p2).reverse )
1301
- return false if ft_p2.empty?
1302
- area2 = OpenStudio::getArea(ft_p2)
1303
- return empty("#{i2} area", mth, ERR, a) if area2.empty?
1304
1324
  area2 = area2.get
1305
- union = OpenStudio::join(ft_p1, ft_p2, TOL2)
1306
- return false if union.empty?
1325
+ union = OpenStudio.join(ft_p1, ft_p2, TOL2)
1326
+ return false if union.empty?
1307
1327
  union = union.get
1308
- area = OpenStudio::getArea(union)
1309
- return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
1328
+ area = OpenStudio.getArea(union)
1329
+ return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
1310
1330
  area = area.get
1311
-
1312
- return false if area < TOL
1313
- return true if (area - area2).abs < TOL
1314
- return false if (area - area2).abs > TOL
1331
+ return false if area < TOL
1332
+ return true if (area - area2).abs < TOL
1333
+ return false if (area - area2).abs > TOL
1315
1334
 
1316
1335
  true
1317
1336
  end
@@ -1334,42 +1353,279 @@ module OSut
1334
1353
 
1335
1354
  return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s)
1336
1355
  return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s)
1356
+
1337
1357
  i1 = id1.to_s
1338
1358
  i2 = id2.to_s
1339
1359
  i1 = "poly1" if i1.empty?
1340
1360
  i2 = "poly2" if i2.empty?
1361
+
1341
1362
  valid1 = p1.is_a?(cl1) || p1.is_a?(Array)
1342
1363
  valid2 = p2.is_a?(cl1) || p2.is_a?(Array)
1364
+
1343
1365
  return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1
1344
1366
  return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2
1345
- return empty(i1, mth, ERR, a) if p1.empty?
1346
- return empty(i2, mth, ERR, a) if p2.empty?
1367
+ return empty(i1, mth, ERR, a) if p1.empty?
1368
+ return empty(i2, mth, ERR, a) if p2.empty?
1369
+
1347
1370
  p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
1348
1371
  p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
1349
1372
 
1350
- ft = OpenStudio::Transformation::alignFace(p1).inverse
1351
- ft_p1 = flatZ( (ft * p1).reverse )
1352
- return false if ft_p1.empty?
1353
- area1 = OpenStudio::getArea(ft_p1)
1354
- return empty("#{i1} area", mth, ERR, a) if area1.empty?
1373
+ # XY-plane transformation matrix ... needs to be clockwise for boost.
1374
+ ft = OpenStudio::Transformation.alignFace(p1)
1375
+ ft_p1 = flatZ( (ft.inverse * p1) )
1376
+ ft_p2 = flatZ( (ft.inverse * p2) )
1377
+ return false if ft_p1.empty?
1378
+ return false if ft_p2.empty?
1379
+ cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL)
1380
+ ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw
1381
+ ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw
1382
+ return false if ft_p1.empty?
1383
+ return false if ft_p2.empty?
1384
+ area1 = OpenStudio.getArea(ft_p1)
1385
+ area2 = OpenStudio.getArea(ft_p2)
1386
+ return empty("#{i1} area", mth, ERR, a) if area1.empty?
1387
+ return empty("#{i2} area", mth, ERR, a) if area2.empty?
1355
1388
  area1 = area1.get
1356
- ft_p2 = flatZ( (ft * p2).reverse )
1357
- return false if ft_p2.empty?
1358
- area2 = OpenStudio::getArea(ft_p2)
1359
- return empty("#{i2} area", mth, ERR, a) if area2.empty?
1360
1389
  area2 = area2.get
1361
- union = OpenStudio::join(ft_p1, ft_p2, TOL2)
1362
- return false if union.empty?
1390
+ union = OpenStudio.join(ft_p1, ft_p2, TOL2)
1391
+ return false if union.empty?
1363
1392
  union = union.get
1364
- area = OpenStudio::getArea(union)
1365
- return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
1393
+ area = OpenStudio.getArea(union)
1394
+ return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
1366
1395
  area = area.get
1367
-
1368
- return false if area < TOL
1396
+ return false if area < TOL
1369
1397
 
1370
1398
  true
1371
1399
  end
1372
1400
 
1401
+ ##
1402
+ # Generate offset vertices (by width) for a 3- or 4-sided, convex polygon.
1403
+ #
1404
+ # @param p1 [OpenStudio::Point3dVector] OpenStudio Point3D vector/array
1405
+ # @param w [Float] offset width (min: 0.0254m)
1406
+ # @param v [Integer] OpenStudio SDK version, eg '321' for 'v3.2.1' (optional)
1407
+ #
1408
+ # @return [OpenStudio::Point3dVector] offset points if successful
1409
+ # @return [OpenStudio::Point3dVector] original points if invalid input
1410
+ def offset(p1 = [], w = 0, v = 0)
1411
+ mth = "TBD::#{__callee__}"
1412
+ cl = OpenStudio::Point3d
1413
+ vrsn = OpenStudio.openStudioVersion.split(".").map(&:to_i).join.to_i
1414
+
1415
+ valid = p1.is_a?(OpenStudio::Point3dVector) || p1.is_a?(Array)
1416
+ return mismatch("pts", p1, cl1, mth, DBG, p1) unless valid
1417
+ return empty("pts", mth, ERR, p1) if p1.empty?
1418
+ valid = p1.size == 3 || p1.size == 4
1419
+ iv = true if p1.size == 4
1420
+ return invalid("pts", mth, 1, DBG, p1) unless valid
1421
+ return invalid("width", mth, 2, DBG, p1) unless w.respond_to?(:to_f)
1422
+ w = w.to_f
1423
+ return p1 if w < 0.0254
1424
+ v = v.to_i if v.respond_to?(:to_i)
1425
+ v = 0 unless v.respond_to?(:to_i)
1426
+ v = vrsn if v.zero?
1427
+
1428
+ p1.each { |x| return mismatch("p", x, cl, mth, ERR, p1) unless x.is_a?(cl) }
1429
+
1430
+ unless v < 340
1431
+ # XY-plane transformation matrix ... needs to be clockwise for boost.
1432
+ ft = OpenStudio::Transformation::alignFace(p1)
1433
+ ft_pts = flatZ( (ft.inverse * p1) )
1434
+ return p1 if ft_pts.empty?
1435
+ cw = OpenStudio::pointInPolygon(ft_pts.first, ft_pts, TOL)
1436
+ ft_pts = flatZ( (ft.inverse * p1).reverse ) unless cw
1437
+ offset = OpenStudio.buffer(ft_pts, w, TOL)
1438
+ return p1 if offset.empty?
1439
+ offset = offset.get
1440
+ offset = ft * offset if cw
1441
+ offset = (ft * offset).reverse unless cw
1442
+
1443
+ pz = OpenStudio::Point3dVector.new
1444
+ offset.each { |o| pz << OpenStudio::Point3d.new(o.x, o.y, o.z ) }
1445
+ return pz
1446
+ else # brute force approach
1447
+ pz = {}
1448
+ pz[:A] = {}
1449
+ pz[:B] = {}
1450
+ pz[:C] = {}
1451
+ pz[:D] = {} if iv
1452
+
1453
+ pz[:A][:p] = OpenStudio::Point3d.new(p1[0].x, p1[0].y, p1[0].z)
1454
+ pz[:B][:p] = OpenStudio::Point3d.new(p1[1].x, p1[1].y, p1[1].z)
1455
+ pz[:C][:p] = OpenStudio::Point3d.new(p1[2].x, p1[2].y, p1[2].z)
1456
+ pz[:D][:p] = OpenStudio::Point3d.new(p1[3].x, p1[3].y, p1[3].z) if iv
1457
+
1458
+ pzAp = pz[:A][:p]
1459
+ pzBp = pz[:B][:p]
1460
+ pzCp = pz[:C][:p]
1461
+ pzDp = pz[:D][:p] if iv
1462
+
1463
+ # Generate vector pairs, from next point & from previous point.
1464
+ # :f_n : "from next"
1465
+ # :f_p : "from previous"
1466
+ #
1467
+ #
1468
+ #
1469
+ #
1470
+ #
1471
+ #
1472
+ # A <---------- B
1473
+ # ^
1474
+ # \
1475
+ # \
1476
+ # C (or D)
1477
+ #
1478
+ pz[:A][:f_n] = pzAp - pzBp
1479
+ pz[:A][:f_p] = pzAp - pzCp unless iv
1480
+ pz[:A][:f_p] = pzAp - pzDp if iv
1481
+
1482
+ pz[:B][:f_n] = pzBp - pzCp
1483
+ pz[:B][:f_p] = pzBp - pzAp
1484
+
1485
+ pz[:C][:f_n] = pzCp - pzAp unless iv
1486
+ pz[:C][:f_n] = pzCp - pzDp if iv
1487
+ pz[:C][:f_p] = pzCp - pzBp
1488
+
1489
+ pz[:D][:f_n] = pzDp - pzAp if iv
1490
+ pz[:D][:f_p] = pzDp - pzCp if iv
1491
+
1492
+ # Generate 3D plane from vectors.
1493
+ #
1494
+ #
1495
+ # | <<< 3D plane ... from point A, with normal B>A
1496
+ # |
1497
+ # |
1498
+ # |
1499
+ # <---------- A <---------- B
1500
+ # |\
1501
+ # | \
1502
+ # | \
1503
+ # | C (or D)
1504
+ #
1505
+ pz[:A][:pl_f_n] = OpenStudio::Plane.new(pzAp, pz[:A][:f_n])
1506
+ pz[:A][:pl_f_p] = OpenStudio::Plane.new(pzAp, pz[:A][:f_p])
1507
+
1508
+ pz[:B][:pl_f_n] = OpenStudio::Plane.new(pzBp, pz[:B][:f_n])
1509
+ pz[:B][:pl_f_p] = OpenStudio::Plane.new(pzBp, pz[:B][:f_p])
1510
+
1511
+ pz[:C][:pl_f_n] = OpenStudio::Plane.new(pzCp, pz[:C][:f_n])
1512
+ pz[:C][:pl_f_p] = OpenStudio::Plane.new(pzCp, pz[:C][:f_p])
1513
+
1514
+ pz[:D][:pl_f_n] = OpenStudio::Plane.new(pzDp, pz[:D][:f_n]) if iv
1515
+ pz[:D][:pl_f_p] = OpenStudio::Plane.new(pzDp, pz[:D][:f_p]) if iv
1516
+
1517
+ # Project an extended point (pC) unto 3D plane.
1518
+ #
1519
+ # pC <<< projected unto extended B>A 3D plane
1520
+ # eC |
1521
+ # \ |
1522
+ # \ |
1523
+ # \|
1524
+ # <---------- A <---------- B
1525
+ # |\
1526
+ # | \
1527
+ # | \
1528
+ # | C (or D)
1529
+ #
1530
+ pz[:A][:p_n_pl] = pz[:A][:pl_f_n].project(pz[:A][:p] + pz[:A][:f_p])
1531
+ pz[:A][:n_p_pl] = pz[:A][:pl_f_p].project(pz[:A][:p] + pz[:A][:f_n])
1532
+
1533
+ pz[:B][:p_n_pl] = pz[:B][:pl_f_n].project(pz[:B][:p] + pz[:B][:f_p])
1534
+ pz[:B][:n_p_pl] = pz[:B][:pl_f_p].project(pz[:B][:p] + pz[:B][:f_n])
1535
+
1536
+ pz[:C][:p_n_pl] = pz[:C][:pl_f_n].project(pz[:C][:p] + pz[:C][:f_p])
1537
+ pz[:C][:n_p_pl] = pz[:C][:pl_f_p].project(pz[:C][:p] + pz[:C][:f_n])
1538
+
1539
+ pz[:D][:p_n_pl] = pz[:D][:pl_f_n].project(pz[:D][:p] + pz[:D][:f_p]) if iv
1540
+ pz[:D][:n_p_pl] = pz[:D][:pl_f_p].project(pz[:D][:p] + pz[:D][:f_n]) if iv
1541
+
1542
+ # Generate vector from point (e.g. A) to projected extended point (pC).
1543
+ #
1544
+ # pC
1545
+ # eC ^
1546
+ # \ |
1547
+ # \ |
1548
+ # \|
1549
+ # <---------- A <---------- B
1550
+ # |\
1551
+ # | \
1552
+ # | \
1553
+ # | C (or D)
1554
+ #
1555
+ pz[:A][:n_p_n_pl] = pz[:A][:p_n_pl] - pzAp
1556
+ pz[:A][:n_n_p_pl] = pz[:A][:n_p_pl] - pzAp
1557
+
1558
+ pz[:B][:n_p_n_pl] = pz[:B][:p_n_pl] - pzBp
1559
+ pz[:B][:n_n_p_pl] = pz[:B][:n_p_pl] - pzBp
1560
+
1561
+ pz[:C][:n_p_n_pl] = pz[:C][:p_n_pl] - pzCp
1562
+ pz[:C][:n_n_p_pl] = pz[:C][:n_p_pl] - pzCp
1563
+
1564
+ pz[:D][:n_p_n_pl] = pz[:D][:p_n_pl] - pzDp if iv
1565
+ pz[:D][:n_n_p_pl] = pz[:D][:n_p_pl] - pzDp if iv
1566
+
1567
+ # Fetch angle between both extended vectors (A>pC & A>pB),
1568
+ # ... then normalize (Cn).
1569
+ #
1570
+ # pC
1571
+ # eC ^
1572
+ # \ |
1573
+ # \ Cn
1574
+ # \|
1575
+ # <---------- A <---------- B
1576
+ # |\
1577
+ # | \
1578
+ # | \
1579
+ # | C (or D)
1580
+ #
1581
+ a1 = OpenStudio.getAngle(pz[:A][:n_p_n_pl], pz[:A][:n_n_p_pl])
1582
+ a2 = OpenStudio.getAngle(pz[:B][:n_p_n_pl], pz[:B][:n_n_p_pl])
1583
+ a3 = OpenStudio.getAngle(pz[:C][:n_p_n_pl], pz[:C][:n_n_p_pl])
1584
+ a4 = OpenStudio.getAngle(pz[:D][:n_p_n_pl], pz[:D][:n_n_p_pl]) if iv
1585
+
1586
+ # Generate new 3D points A', B', C' (and D') ... zigzag.
1587
+ #
1588
+ #
1589
+ #
1590
+ #
1591
+ # A' ---------------------- B'
1592
+ # \
1593
+ # \ A <---------- B
1594
+ # \ \
1595
+ # \ \
1596
+ # \ \
1597
+ # C' C
1598
+ pz[:A][:f_n].normalize
1599
+ pz[:A][:n_p_n_pl].normalize
1600
+ pzAp = pzAp + scalar(pz[:A][:n_p_n_pl], w)
1601
+ pzAp = pzAp + scalar(pz[:A][:f_n], w * Math.tan(a1/2))
1602
+
1603
+ pz[:B][:f_n].normalize
1604
+ pz[:B][:n_p_n_pl].normalize
1605
+ pzBp = pzBp + scalar(pz[:B][:n_p_n_pl], w)
1606
+ pzBp = pzBp + scalar(pz[:B][:f_n], w * Math.tan(a2/2))
1607
+
1608
+ pz[:C][:f_n].normalize
1609
+ pz[:C][:n_p_n_pl].normalize
1610
+ pzCp = pzCp + scalar(pz[:C][:n_p_n_pl], w)
1611
+ pzCp = pzCp + scalar(pz[:C][:f_n], w * Math.tan(a3/2))
1612
+
1613
+ pz[:D][:f_n].normalize if iv
1614
+ pz[:D][:n_p_n_pl].normalize if iv
1615
+ pzDp = pzDp + scalar(pz[:D][:n_p_n_pl], w) if iv
1616
+ pzDp = pzDp + scalar(pz[:D][:f_n], w * Math.tan(a4/2)) if iv
1617
+
1618
+ # Re-convert to OpenStudio 3D points.
1619
+ vec = OpenStudio::Point3dVector.new
1620
+ vec << OpenStudio::Point3d.new(pzAp.x, pzAp.y, pzAp.z)
1621
+ vec << OpenStudio::Point3d.new(pzBp.x, pzBp.y, pzBp.z)
1622
+ vec << OpenStudio::Point3d.new(pzCp.x, pzCp.y, pzCp.z)
1623
+ vec << OpenStudio::Point3d.new(pzDp.x, pzDp.y, pzDp.z) if iv
1624
+
1625
+ return vec
1626
+ end
1627
+ end
1628
+
1373
1629
  ##
1374
1630
  # Callback when other modules extend OSlg
1375
1631
  #
data/lib/osut/version.rb CHANGED
@@ -29,5 +29,5 @@
29
29
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
30
 
31
31
  module OSut
32
- VERSION = "0.2.3".freeze
32
+ VERSION = "0.2.7".freeze
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Bourgeois
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-15 00:00:00.000000000 Z
11
+ date: 2022-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oslg
@@ -90,7 +90,7 @@ licenses:
90
90
  - BSD-3-Clause
91
91
  metadata:
92
92
  homepage_uri: https://github.com/rd2/osut
93
- source_code_uri: https://github.com/rd2/osut/tree/v0.2.3
93
+ source_code_uri: https://github.com/rd2/osut/tree/v0.2.7
94
94
  bug_tracker_uri: https://github.com/rd2/osut/issues
95
95
  post_install_message:
96
96
  rdoc_options: []