osut 0.2.6 → 0.2.8
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 +4 -4
- data/.github/workflows/pull_request.yml +20 -4
- data/LICENSE +1 -1
- data/lib/osut/utils.rb +417 -93
- data/lib/osut/version.rb +2 -2
- data/lib/osut.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bec97227aeda04a933080c621650e72003d97816d01d2a4716f146e6501b7571
|
4
|
+
data.tar.gz: 82c0d1c211238120894a1bfaffed802b09461f22713957ed5c4a6b9bcb0ef757
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3897a390ef6f90524503ce63ff70a3e5275b1d3abf6148ac7752eb82ec64d5bfa2eedbf56898accdd87c7f394adf435e20b30dec457d3ebe4b3fc92e9130be5
|
7
|
+
data.tar.gz: 51529559490ab5f48e16a696ce657b867fe538ade32d07237b9fffc28e48b6da98905998c7819e019cdf8232fc2e16d8bc9cdb673280c2cc64861289f24370a5
|
@@ -7,7 +7,7 @@ on:
|
|
7
7
|
|
8
8
|
jobs:
|
9
9
|
test_300x:
|
10
|
-
runs-on: ubuntu-
|
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-
|
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-
|
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-
|
58
|
+
runs-on: ubuntu-22.04
|
59
59
|
steps:
|
60
60
|
- name: Check out repository
|
61
61
|
uses: actions/checkout@v2
|
@@ -70,3 +70,19 @@ jobs:
|
|
70
70
|
docker exec -t test bundle update
|
71
71
|
docker exec -t test bundle exec rake
|
72
72
|
docker kill test
|
73
|
+
test_351x:
|
74
|
+
runs-on: ubuntu-22.04
|
75
|
+
steps:
|
76
|
+
- name: Check out repository
|
77
|
+
uses: actions/checkout@v2
|
78
|
+
- name: Run Tests
|
79
|
+
run: |
|
80
|
+
echo $(pwd)
|
81
|
+
echo $(ls)
|
82
|
+
docker pull nrel/openstudio:3.5.1
|
83
|
+
docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.5.1
|
84
|
+
docker exec -t test pwd
|
85
|
+
docker exec -t test ls
|
86
|
+
docker exec -t test bundle update
|
87
|
+
docker exec -t test bundle exec rake
|
88
|
+
docker kill test
|
data/LICENSE
CHANGED
data/lib/osut/utils.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# BSD 3-Clause License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2022, Denis Bourgeois
|
3
|
+
# Copyright (c) 2022-2023, Denis Bourgeois
|
4
4
|
# All rights reserved.
|
5
5
|
#
|
6
6
|
# Redistribution and use in source and binary forms, with or without
|
@@ -59,7 +59,7 @@ module OSut
|
|
59
59
|
# cooling system of sufficient size to maintain temperatures suitable
|
60
60
|
# for HUMAN COMFORT:
|
61
61
|
# - COOLED: cooled by a system >= 10 W/m2
|
62
|
-
# - HEATED: heated by a system e.g
|
62
|
+
# - HEATED: heated by a system, e.g. >= 50 W/m2 in Climate Zone CZ-7
|
63
63
|
# - INDIRECTLY: heated or cooled via adjacent space(s) provided:
|
64
64
|
# - UA of adjacent surfaces > UA of other surfaces
|
65
65
|
# or
|
@@ -89,7 +89,7 @@ module OSut
|
|
89
89
|
# response to the exterior ambient temperature by the provision, either
|
90
90
|
# DIRECTLY or INDIRECTLY, of heating or cooling [...]". Although criteria
|
91
91
|
# differ (e.g., not sizing-based), the general idea is sufficiently similar
|
92
|
-
# to ASHRAE 90.1 (e.g
|
92
|
+
# to ASHRAE 90.1 (e.g. heating and/or cooling based, no distinction for
|
93
93
|
# INDIRECTLY conditioned spaces like plenums).
|
94
94
|
#
|
95
95
|
# SEMI-HEATED spaces are also a defined NECB term, but again the distinction
|
@@ -109,16 +109,16 @@ module OSut
|
|
109
109
|
# processes. As discussed in greater detail elswhere, methods are developed to
|
110
110
|
# rely on zoning info and/or "intended" temperature setpoints.
|
111
111
|
#
|
112
|
-
# For an OpenStudio model
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
112
|
+
# For an OpenStudio model in an incomplete or preliminary state, e.g. holding
|
113
|
+
# fully-formed ENCLOSED spaces without thermal zoning information or setpoint
|
114
|
+
# temperatures (early design stage assessments of form, porosity or envelope),
|
115
|
+
# all OpenStudio spaces will be considered CONDITIONED, presuming setpoints of
|
116
|
+
# ~21°C (heating) and ~24°C (cooling).
|
117
117
|
#
|
118
|
-
# If ANY valid space/zone-specific temperature setpoints are found in the
|
119
|
-
# spaces/zones WITHOUT valid heating or cooling setpoints
|
120
|
-
# UNCONDITIONED or UNENCLOSED spaces (like attics), or
|
121
|
-
# spaces (like plenums), see "plenum?" method.
|
118
|
+
# If ANY valid space/zone-specific temperature setpoints are found in the
|
119
|
+
# OpenStudio model, spaces/zones WITHOUT valid heating or cooling setpoints
|
120
|
+
# are considered as UNCONDITIONED or UNENCLOSED spaces (like attics), or
|
121
|
+
# INDIRECTLY CONDITIONED spaces (like plenums), see "plenum?" method.
|
122
122
|
|
123
123
|
##
|
124
124
|
# Return min & max values of a schedule (ruleset).
|
@@ -139,6 +139,7 @@ module OSut
|
|
139
139
|
res = { min: nil, max: nil }
|
140
140
|
|
141
141
|
return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
|
142
|
+
|
142
143
|
id = sched.nameString
|
143
144
|
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
|
144
145
|
|
@@ -186,6 +187,7 @@ module OSut
|
|
186
187
|
res = { min: nil, max: nil }
|
187
188
|
|
188
189
|
return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
|
190
|
+
|
189
191
|
id = sched.nameString
|
190
192
|
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
|
191
193
|
|
@@ -218,6 +220,7 @@ module OSut
|
|
218
220
|
res = { min: nil, max: nil }
|
219
221
|
|
220
222
|
return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
|
223
|
+
|
221
224
|
id = sched.nameString
|
222
225
|
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
|
223
226
|
|
@@ -231,9 +234,11 @@ module OSut
|
|
231
234
|
end
|
232
235
|
|
233
236
|
return empty("'#{id}' values", mth, ERR, res) if vals.empty?
|
237
|
+
|
234
238
|
ok = vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
|
235
239
|
log(ERR, "Non-numeric values in '#{id}' (#{mth})") unless ok
|
236
240
|
return res unless ok
|
241
|
+
|
237
242
|
res[:min] = vals.min
|
238
243
|
res[:max] = vals.max
|
239
244
|
|
@@ -248,19 +253,21 @@ module OSut
|
|
248
253
|
# @return [Hash] min: (Float), max: (Float)
|
249
254
|
# @return [Hash] min: nil, max: nil (if invalid input)
|
250
255
|
def scheduleIntervalMinMax(sched = nil)
|
251
|
-
mth
|
252
|
-
cl
|
253
|
-
vals
|
254
|
-
|
255
|
-
res = { min: nil, max: nil }
|
256
|
+
mth = "OSut::#{__callee__}"
|
257
|
+
cl = OpenStudio::Model::ScheduleInterval
|
258
|
+
vals = []
|
259
|
+
res = { min: nil, max: nil }
|
256
260
|
|
257
261
|
return invalid("sched", mth, 1, DBG, res) unless sched.respond_to?(NS)
|
262
|
+
|
258
263
|
id = sched.nameString
|
259
264
|
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
|
265
|
+
|
260
266
|
vals = sched.timeSeries.values
|
261
|
-
ok
|
267
|
+
ok = vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
|
262
268
|
log(ERR, "Non-numeric values in '#{id}' (#{mth})") unless ok
|
263
269
|
return res unless ok
|
270
|
+
|
264
271
|
res[:min] = vals.min
|
265
272
|
res[:max] = vals.max
|
266
273
|
|
@@ -289,6 +296,7 @@ module OSut
|
|
289
296
|
res = { spt: nil, dual: false }
|
290
297
|
|
291
298
|
return invalid("zone", mth, 1, DBG, res) unless zone.respond_to?(NS)
|
299
|
+
|
292
300
|
id = zone.nameString
|
293
301
|
return mismatch(id, zone, cl, mth, DBG, res) unless zone.is_a?(cl)
|
294
302
|
|
@@ -369,6 +377,7 @@ module OSut
|
|
369
377
|
end
|
370
378
|
|
371
379
|
return res if zone.thermostat.empty?
|
380
|
+
|
372
381
|
tstat = zone.thermostat.get
|
373
382
|
res[:spt] = nil
|
374
383
|
|
@@ -427,6 +436,7 @@ module OSut
|
|
427
436
|
|
428
437
|
sched.getScheduleWeeks.each do |week|
|
429
438
|
next if week.winterDesignDaySchedule.empty?
|
439
|
+
|
430
440
|
dd = week.winterDesignDaySchedule.get
|
431
441
|
next unless dd.values.empty?
|
432
442
|
|
@@ -479,6 +489,7 @@ module OSut
|
|
479
489
|
res = { spt: nil, dual: false }
|
480
490
|
|
481
491
|
return invalid("zone", mth, 1, DBG, res) unless zone.respond_to?(NS)
|
492
|
+
|
482
493
|
id = zone.nameString
|
483
494
|
return mismatch(id, zone, cl, mth, DBG, res) unless zone.is_a?(cl)
|
484
495
|
|
@@ -546,6 +557,7 @@ module OSut
|
|
546
557
|
end
|
547
558
|
|
548
559
|
return res if zone.thermostat.empty?
|
560
|
+
|
549
561
|
tstat = zone.thermostat.get
|
550
562
|
res[:spt] = nil
|
551
563
|
|
@@ -604,6 +616,7 @@ module OSut
|
|
604
616
|
|
605
617
|
sched.getScheduleWeeks.each do |week|
|
606
618
|
next if week.summerDesignDaySchedule.empty?
|
619
|
+
|
607
620
|
dd = week.summerDesignDaySchedule.get
|
608
621
|
next unless dd.values.empty?
|
609
622
|
|
@@ -628,7 +641,7 @@ module OSut
|
|
628
641
|
mth = "OSut::#{__callee__}"
|
629
642
|
cl = OpenStudio::Model::Model
|
630
643
|
|
631
|
-
return mismatch("model", model, cl, mth, DBG, false)
|
644
|
+
return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
|
632
645
|
|
633
646
|
model.getThermalZones.each do |zone|
|
634
647
|
return true if minCoolScheduledSetpoint(zone)[:spt]
|
@@ -648,12 +661,12 @@ module OSut
|
|
648
661
|
mth = "OSut::#{__callee__}"
|
649
662
|
cl = OpenStudio::Model::Model
|
650
663
|
|
651
|
-
return mismatch("model", model, cl, mth, DBG, false)
|
664
|
+
return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl)
|
652
665
|
|
653
666
|
model.getThermalZones.each do |zone|
|
654
|
-
next
|
655
|
-
return true
|
656
|
-
return true
|
667
|
+
next if zone.canBePlenum
|
668
|
+
return true unless zone.airLoopHVACs.empty?
|
669
|
+
return true if zone.isPlenum
|
657
670
|
end
|
658
671
|
|
659
672
|
false
|
@@ -678,7 +691,7 @@ module OSut
|
|
678
691
|
# A space may be tagged as a plenum if:
|
679
692
|
#
|
680
693
|
# CASE A: its zone's "isPlenum" == true (SDK method) for a fully-developed
|
681
|
-
# OpenStudio model (complete with HVAC air loops);
|
694
|
+
# OpenStudio model (complete with HVAC air loops); OR
|
682
695
|
#
|
683
696
|
# CASE B: (IN ABSENCE OF HVAC AIRLOOPS) if it's excluded from a building's
|
684
697
|
# total floor area yet linked to a zone holding an 'inactive'
|
@@ -691,10 +704,13 @@ module OSut
|
|
691
704
|
cl = OpenStudio::Model::Space
|
692
705
|
|
693
706
|
return invalid("space", mth, 1, DBG, false) unless space.respond_to?(NS)
|
707
|
+
|
694
708
|
id = space.nameString
|
695
709
|
return mismatch(id, space, cl, mth, DBG, false) unless space.is_a?(cl)
|
710
|
+
|
696
711
|
valid = loops == true || loops == false
|
697
712
|
return invalid("loops", mth, 2, DBG, false) unless valid
|
713
|
+
|
698
714
|
valid = setpoints == true || setpoints == false
|
699
715
|
return invalid("setpoints", mth, 3, DBG, false) unless valid
|
700
716
|
|
@@ -714,11 +730,11 @@ module OSut
|
|
714
730
|
unless space.spaceType.empty?
|
715
731
|
type = space.spaceType.get
|
716
732
|
return type.nameString.downcase == "plenum" # C
|
733
|
+
end
|
717
734
|
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
end
|
735
|
+
unless type.standardsSpaceType.empty?
|
736
|
+
type = type.standardsSpaceType.get
|
737
|
+
return type.downcase == "plenum" # C
|
722
738
|
end
|
723
739
|
|
724
740
|
false
|
@@ -737,8 +753,8 @@ module OSut
|
|
737
753
|
cl = OpenStudio::Model::Model
|
738
754
|
limits = nil
|
739
755
|
|
740
|
-
return mismatch("model", model, cl, mth)
|
741
|
-
return invalid("availability", avl, 2, mth)
|
756
|
+
return mismatch("model", model, cl, mth) unless model.is_a?(cl)
|
757
|
+
return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_s)
|
742
758
|
|
743
759
|
# Either fetch availability ScheduleTypeLimits object, or create one.
|
744
760
|
model.getScheduleTypeLimitss.each do |l|
|
@@ -751,6 +767,7 @@ module OSut
|
|
751
767
|
next unless l.numericType.get.downcase == "discrete"
|
752
768
|
next unless l.unitType.downcase == "availability"
|
753
769
|
next unless l.nameString.downcase == "hvac operation scheduletypelimits"
|
770
|
+
|
754
771
|
limits = l
|
755
772
|
end
|
756
773
|
|
@@ -769,9 +786,10 @@ module OSut
|
|
769
786
|
off = OpenStudio::Model::ScheduleDay.new(model, 0)
|
770
787
|
|
771
788
|
# Seasonal availability start/end dates.
|
772
|
-
year
|
773
|
-
return
|
774
|
-
|
789
|
+
year = model.yearDescription
|
790
|
+
return empty("yearDescription", mth, ERR) if year.empty?
|
791
|
+
|
792
|
+
year = year.get
|
775
793
|
may01 = year.makeDate(OpenStudio::MonthOfYear.new("May"), 1)
|
776
794
|
oct31 = year.makeDate(OpenStudio::MonthOfYear.new("Oct"), 31)
|
777
795
|
|
@@ -850,9 +868,11 @@ module OSut
|
|
850
868
|
ok = schedule.setScheduleTypeLimits(limits)
|
851
869
|
log(ERR, "'#{nom}': Can't set schedule type limits (#{mth})") unless ok
|
852
870
|
return nil unless ok
|
871
|
+
|
853
872
|
ok = schedule.defaultDaySchedule.addValue(time, val)
|
854
873
|
log(ERR, "'#{nom}': Can't set default day schedule (#{mth})") unless ok
|
855
874
|
return nil unless ok
|
875
|
+
|
856
876
|
schedule.defaultDaySchedule.setName(dft)
|
857
877
|
|
858
878
|
unless tag.empty?
|
@@ -861,12 +881,15 @@ module OSut
|
|
861
881
|
ok = rule.setStartDate(may01)
|
862
882
|
log(ERR, "'#{tag}': Can't set start date (#{mth})") unless ok
|
863
883
|
return nil unless ok
|
884
|
+
|
864
885
|
ok = rule.setEndDate(oct31)
|
865
886
|
log(ERR, "'#{tag}': Can't set end date (#{mth})") unless ok
|
866
887
|
return nil unless ok
|
888
|
+
|
867
889
|
ok = rule.setApplyAllDays(true)
|
868
890
|
log(ERR, "'#{tag}': Can't apply to all days (#{mth})") unless ok
|
869
891
|
return nil unless ok
|
892
|
+
|
870
893
|
rule.daySchedule.setName(day)
|
871
894
|
end
|
872
895
|
|
@@ -874,7 +897,7 @@ module OSut
|
|
874
897
|
end
|
875
898
|
|
876
899
|
##
|
877
|
-
# Validate if default construction set holds a base
|
900
|
+
# Validate if default construction set holds a base construction.
|
878
901
|
#
|
879
902
|
# @param set [OpenStudio::Model::DefaultConstructionSet] a default set
|
880
903
|
# @param bse [OpensStudio::Model::ConstructionBase] a construction base
|
@@ -890,17 +913,23 @@ module OSut
|
|
890
913
|
cl2 = OpenStudio::Model::ConstructionBase
|
891
914
|
|
892
915
|
return invalid("set", mth, 1, DBG, false) unless set.respond_to?(NS)
|
916
|
+
|
893
917
|
id = set.nameString
|
894
918
|
return mismatch(id, set, cl1, mth, DBG, false) unless set.is_a?(cl1)
|
895
919
|
return invalid("base", mth, 2, DBG, false) unless bse.respond_to?(NS)
|
920
|
+
|
896
921
|
id = bse.nameString
|
897
922
|
return mismatch(id, bse, cl2, mth, DBG, false) unless bse.is_a?(cl2)
|
923
|
+
|
898
924
|
valid = gr == true || gr == false
|
899
925
|
return invalid("ground", mth, 3, DBG, false) unless valid
|
926
|
+
|
900
927
|
valid = ex == true || ex == false
|
901
928
|
return invalid("exterior", mth, 4, DBG, false) unless valid
|
929
|
+
|
902
930
|
valid = typ.respond_to?(:to_s)
|
903
931
|
return invalid("surface typ", mth, 4, DBG, false) unless valid
|
932
|
+
|
904
933
|
type = typ.to_s.downcase
|
905
934
|
valid = type == "floor" || type == "wall" || type == "roofceiling"
|
906
935
|
return invalid("surface type", mth, 5, DBG, false) unless valid
|
@@ -957,17 +986,20 @@ module OSut
|
|
957
986
|
cl1 = OpenStudio::Model::Model
|
958
987
|
cl2 = OpenStudio::Model::Surface
|
959
988
|
|
960
|
-
return mismatch("model", model, cl1, mth)
|
961
|
-
return invalid("s", mth, 2)
|
962
|
-
|
963
|
-
|
989
|
+
return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
|
990
|
+
return invalid("s", mth, 2) unless s.respond_to?(NS)
|
991
|
+
|
992
|
+
id = s.nameString
|
993
|
+
return mismatch(id, s, cl2, mth) unless s.is_a?(cl2)
|
964
994
|
|
965
995
|
ok = s.isConstructionDefaulted
|
966
996
|
log(ERR, "'#{id}' construction not defaulted (#{mth})") unless ok
|
967
997
|
return nil unless ok
|
968
|
-
return empty("'#{id}' construction", mth, ERR)
|
998
|
+
return empty("'#{id}' construction", mth, ERR) if s.construction.empty?
|
999
|
+
|
969
1000
|
base = s.construction.get
|
970
|
-
return empty("'#{id}' space", mth, ERR)
|
1001
|
+
return empty("'#{id}' space", mth, ERR) if s.space.empty?
|
1002
|
+
|
971
1003
|
space = s.space.get
|
972
1004
|
type = s.surfaceType
|
973
1005
|
ground = false
|
@@ -981,7 +1013,7 @@ module OSut
|
|
981
1013
|
|
982
1014
|
unless space.defaultConstructionSet.empty?
|
983
1015
|
set = space.defaultConstructionSet.get
|
984
|
-
return set
|
1016
|
+
return set if holdsConstruction?(set, base, ground, exterior, type)
|
985
1017
|
end
|
986
1018
|
|
987
1019
|
unless space.spaceType.empty?
|
@@ -989,7 +1021,7 @@ module OSut
|
|
989
1021
|
|
990
1022
|
unless spacetype.defaultConstructionSet.empty?
|
991
1023
|
set = spacetype.defaultConstructionSet.get
|
992
|
-
return set
|
1024
|
+
return set if holdsConstruction?(set, base, ground, exterior, type)
|
993
1025
|
end
|
994
1026
|
end
|
995
1027
|
|
@@ -998,7 +1030,7 @@ module OSut
|
|
998
1030
|
|
999
1031
|
unless story.defaultConstructionSet.empty?
|
1000
1032
|
set = story.defaultConstructionSet.get
|
1001
|
-
return set
|
1033
|
+
return set if holdsConstruction?(set, base, ground, exterior, type)
|
1002
1034
|
end
|
1003
1035
|
end
|
1004
1036
|
|
@@ -1006,7 +1038,7 @@ module OSut
|
|
1006
1038
|
|
1007
1039
|
unless building.defaultConstructionSet.empty?
|
1008
1040
|
set = building.defaultConstructionSet.get
|
1009
|
-
return set
|
1041
|
+
return set if holdsConstruction?(set, base, ground, exterior, type)
|
1010
1042
|
end
|
1011
1043
|
|
1012
1044
|
nil
|
@@ -1036,19 +1068,21 @@ module OSut
|
|
1036
1068
|
#
|
1037
1069
|
# @param lc [OpenStudio::LayeredConstruction] a layered construction
|
1038
1070
|
#
|
1039
|
-
# @return [
|
1040
|
-
# @return [
|
1071
|
+
# @return [Float] total layered construction thickness
|
1072
|
+
# @return [Float] 0 if invalid input
|
1041
1073
|
def thickness(lc = nil)
|
1042
1074
|
mth = "OSut::#{__callee__}"
|
1043
1075
|
cl = OpenStudio::Model::LayeredConstruction
|
1044
1076
|
|
1045
|
-
return invalid("lc", mth, 1, DBG, 0.0)
|
1077
|
+
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
1078
|
+
|
1046
1079
|
id = lc.nameString
|
1047
|
-
return mismatch(id, lc, cl, mth, DBG, 0.0)
|
1080
|
+
return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl)
|
1048
1081
|
|
1049
1082
|
ok = standardOpaqueLayers?(lc)
|
1050
1083
|
log(ERR, "'#{id}' holds non-StandardOpaqueMaterial(s) (#{mth})") unless ok
|
1051
1084
|
return 0.0 unless ok
|
1085
|
+
|
1052
1086
|
thickness = 0.0
|
1053
1087
|
lc.layers.each { |m| thickness += m.thickness }
|
1054
1088
|
|
@@ -1113,11 +1147,14 @@ module OSut
|
|
1113
1147
|
cl2 = Numeric
|
1114
1148
|
|
1115
1149
|
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
1150
|
+
|
1116
1151
|
id = lc.nameString
|
1152
|
+
|
1117
1153
|
return mismatch(id, lc, cl1, mth, DBG, 0.0) unless lc.is_a?(cl1)
|
1118
1154
|
return mismatch("film", film, cl2, mth, DBG, 0.0) unless film.is_a?(cl2)
|
1119
1155
|
return mismatch("temp K", t, cl2, mth, DBG, 0.0) unless t.is_a?(cl2)
|
1120
|
-
|
1156
|
+
|
1157
|
+
t += 273.0 # °C to K
|
1121
1158
|
return negative("temp K", mth, DBG, 0.0) if t < 0
|
1122
1159
|
return negative("film", mth, DBG, 0.0) if film < 0
|
1123
1160
|
|
@@ -1127,6 +1164,7 @@ module OSut
|
|
1127
1164
|
# Fenestration materials first (ignoring shades, screens, etc.)
|
1128
1165
|
empty = m.to_SimpleGlazing.empty?
|
1129
1166
|
return 1 / m.to_SimpleGlazing.get.uFactor unless empty
|
1167
|
+
|
1130
1168
|
empty = m.to_StandardGlazing.empty?
|
1131
1169
|
rsi += m.to_StandardGlazing.get.thermalResistance unless empty
|
1132
1170
|
empty = m.to_RefractionExtinctionGlazing.empty?
|
@@ -1166,9 +1204,10 @@ module OSut
|
|
1166
1204
|
res = { index: nil, type: nil, r: 0.0 }
|
1167
1205
|
i = 0 # iterator
|
1168
1206
|
|
1169
|
-
return invalid("lc", mth, 1, DBG, res)
|
1170
|
-
|
1171
|
-
|
1207
|
+
return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
|
1208
|
+
|
1209
|
+
id = lc.nameString
|
1210
|
+
return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl)
|
1172
1211
|
|
1173
1212
|
lc.layers.each do |m|
|
1174
1213
|
unless m.to_MasslessOpaqueMaterial.empty?
|
@@ -1178,9 +1217,9 @@ module OSut
|
|
1178
1217
|
i += 1
|
1179
1218
|
next
|
1180
1219
|
else
|
1181
|
-
res[:r]
|
1220
|
+
res[:r ] = m.thermalResistance
|
1182
1221
|
res[:index] = i
|
1183
|
-
res[:type]
|
1222
|
+
res[:type ] = :massless
|
1184
1223
|
end
|
1185
1224
|
end
|
1186
1225
|
|
@@ -1193,9 +1232,9 @@ module OSut
|
|
1193
1232
|
i += 1
|
1194
1233
|
next
|
1195
1234
|
else
|
1196
|
-
res[:r]
|
1235
|
+
res[:r ] = d / k
|
1197
1236
|
res[:index] = i
|
1198
|
-
res[:type]
|
1237
|
+
res[:type ] = :standard
|
1199
1238
|
end
|
1200
1239
|
end
|
1201
1240
|
|
@@ -1221,6 +1260,7 @@ module OSut
|
|
1221
1260
|
|
1222
1261
|
return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
|
1223
1262
|
return invalid("group", mth, 2, DBG, res) unless group.respond_to?(NS)
|
1263
|
+
|
1224
1264
|
id = group.nameString
|
1225
1265
|
return mismatch(id, group, cl2, mth, DBG, res) unless group.is_a?(cl2)
|
1226
1266
|
|
@@ -1230,6 +1270,28 @@ module OSut
|
|
1230
1270
|
res
|
1231
1271
|
end
|
1232
1272
|
|
1273
|
+
##
|
1274
|
+
# Return a scalar product of an OpenStudio Vector3d.
|
1275
|
+
#
|
1276
|
+
# @param v [OpenStudio::Vector3d] a vector
|
1277
|
+
# @param m [Float] a scalar
|
1278
|
+
#
|
1279
|
+
# @return [OpenStudio::Vector3d] modified vector
|
1280
|
+
# @return [OpenStudio::Vector3d] provided (or empty) vector if invalid input
|
1281
|
+
def scalar(v = OpenStudio::Vector3d.new(0,0,0), m = 0)
|
1282
|
+
mth = "OSut::#{__callee__}"
|
1283
|
+
cl1 = OpenStudio::Vector3d
|
1284
|
+
cl2 = Numeric
|
1285
|
+
|
1286
|
+
return mismatch("vector", v, cl1, mth, DBG, v) unless v.is_a?(cl1)
|
1287
|
+
return mismatch("x", v.x, cl2, mth, DBG, v) unless v.x.respond_to?(:to_f)
|
1288
|
+
return mismatch("y", v.y, cl2, mth, DBG, v) unless v.y.respond_to?(:to_f)
|
1289
|
+
return mismatch("z", v.z, cl2, mth, DBG, v) unless v.z.respond_to?(:to_f)
|
1290
|
+
return mismatch("m", m, cl2, mth, DBG, v) unless m.respond_to?(:to_f)
|
1291
|
+
|
1292
|
+
OpenStudio::Vector3d.new(m * v.x, m * v.y, m * v.z)
|
1293
|
+
end
|
1294
|
+
|
1233
1295
|
##
|
1234
1296
|
# Flatten OpenStudio 3D points vs Z-axis (Z=0).
|
1235
1297
|
#
|
@@ -1243,8 +1305,9 @@ module OSut
|
|
1243
1305
|
v = OpenStudio::Point3dVector.new
|
1244
1306
|
|
1245
1307
|
valid = pts.is_a?(cl1) || pts.is_a?(Array)
|
1246
|
-
return mismatch("points", pts, cl1, mth, DBG, v)
|
1247
|
-
|
1308
|
+
return mismatch("points", pts, cl1, mth, DBG, v) unless valid
|
1309
|
+
|
1310
|
+
pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) }
|
1248
1311
|
pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, 0) }
|
1249
1312
|
|
1250
1313
|
v
|
@@ -1268,40 +1331,53 @@ module OSut
|
|
1268
1331
|
|
1269
1332
|
return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s)
|
1270
1333
|
return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s)
|
1334
|
+
|
1271
1335
|
i1 = id1.to_s
|
1272
1336
|
i2 = id2.to_s
|
1273
1337
|
i1 = "poly1" if i1.empty?
|
1274
1338
|
i2 = "poly2" if i2.empty?
|
1339
|
+
|
1275
1340
|
valid1 = p1.is_a?(cl1) || p1.is_a?(Array)
|
1276
1341
|
valid2 = p2.is_a?(cl1) || p2.is_a?(Array)
|
1342
|
+
|
1277
1343
|
return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1
|
1278
1344
|
return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2
|
1279
|
-
return empty(i1, mth, ERR, a)
|
1280
|
-
return empty(i2, mth, ERR, a)
|
1345
|
+
return empty(i1, mth, ERR, a) if p1.empty?
|
1346
|
+
return empty(i2, mth, ERR, a) if p2.empty?
|
1347
|
+
|
1281
1348
|
p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
|
1282
1349
|
p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
|
1283
1350
|
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1351
|
+
# XY-plane transformation matrix ... needs to be clockwise for boost.
|
1352
|
+
ft = OpenStudio::Transformation.alignFace(p1)
|
1353
|
+
ft_p1 = flatZ( (ft.inverse * p1) )
|
1354
|
+
return false if ft_p1.empty?
|
1355
|
+
|
1356
|
+
cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL)
|
1357
|
+
ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw
|
1358
|
+
ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw
|
1359
|
+
ft_p2 = flatZ( (ft.inverse * p2) ) if cw
|
1360
|
+
return false if ft_p2.empty?
|
1361
|
+
|
1362
|
+
area1 = OpenStudio.getArea(ft_p1)
|
1363
|
+
area2 = OpenStudio.getArea(ft_p2)
|
1364
|
+
return empty("#{i1} area", mth, ERR, a) if area1.empty?
|
1365
|
+
return empty("#{i2} area", mth, ERR, a) if area2.empty?
|
1366
|
+
|
1289
1367
|
area1 = area1.get
|
1290
|
-
ft_p2 = flatZ( (ft * p2).reverse )
|
1291
|
-
return false if ft_p2.empty?
|
1292
|
-
area2 = OpenStudio::getArea(ft_p2)
|
1293
|
-
return empty("#{i2} area", mth, ERR, a) if area2.empty?
|
1294
1368
|
area2 = area2.get
|
1295
|
-
union = OpenStudio
|
1296
|
-
return
|
1369
|
+
union = OpenStudio.join(ft_p1, ft_p2, TOL2)
|
1370
|
+
return false if union.empty?
|
1371
|
+
|
1297
1372
|
union = union.get
|
1298
|
-
area
|
1299
|
-
return
|
1373
|
+
area = OpenStudio.getArea(union)
|
1374
|
+
return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
|
1375
|
+
|
1300
1376
|
area = area.get
|
1301
1377
|
|
1302
|
-
return false
|
1303
|
-
return true
|
1304
|
-
return false
|
1378
|
+
return false if area < TOL
|
1379
|
+
return true if (area - area2).abs < TOL
|
1380
|
+
return false if (area - area2).abs > TOL
|
1305
1381
|
|
1306
1382
|
true
|
1307
1383
|
end
|
@@ -1324,42 +1400,290 @@ module OSut
|
|
1324
1400
|
|
1325
1401
|
return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s)
|
1326
1402
|
return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s)
|
1403
|
+
|
1327
1404
|
i1 = id1.to_s
|
1328
1405
|
i2 = id2.to_s
|
1329
1406
|
i1 = "poly1" if i1.empty?
|
1330
1407
|
i2 = "poly2" if i2.empty?
|
1408
|
+
|
1331
1409
|
valid1 = p1.is_a?(cl1) || p1.is_a?(Array)
|
1332
1410
|
valid2 = p2.is_a?(cl1) || p2.is_a?(Array)
|
1411
|
+
|
1333
1412
|
return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1
|
1334
1413
|
return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2
|
1335
|
-
return empty(i1, mth, ERR, a)
|
1336
|
-
return empty(i2, mth, ERR, a)
|
1414
|
+
return empty(i1, mth, ERR, a) if p1.empty?
|
1415
|
+
return empty(i2, mth, ERR, a) if p2.empty?
|
1416
|
+
|
1337
1417
|
p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
|
1338
1418
|
p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) }
|
1339
1419
|
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
return
|
1420
|
+
# XY-plane transformation matrix ... needs to be clockwise for boost.
|
1421
|
+
ft = OpenStudio::Transformation.alignFace(p1)
|
1422
|
+
ft_p1 = flatZ( (ft.inverse * p1) )
|
1423
|
+
ft_p2 = flatZ( (ft.inverse * p2) )
|
1424
|
+
return false if ft_p1.empty?
|
1425
|
+
return false if ft_p2.empty?
|
1426
|
+
|
1427
|
+
cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL)
|
1428
|
+
ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw
|
1429
|
+
ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw
|
1430
|
+
return false if ft_p1.empty?
|
1431
|
+
return false if ft_p2.empty?
|
1432
|
+
|
1433
|
+
area1 = OpenStudio.getArea(ft_p1)
|
1434
|
+
area2 = OpenStudio.getArea(ft_p2)
|
1435
|
+
return empty("#{i1} area", mth, ERR, a) if area1.empty?
|
1436
|
+
return empty("#{i2} area", mth, ERR, a) if area2.empty?
|
1437
|
+
|
1345
1438
|
area1 = area1.get
|
1346
|
-
ft_p2 = flatZ( (ft * p2).reverse )
|
1347
|
-
return false if ft_p2.empty?
|
1348
|
-
area2 = OpenStudio::getArea(ft_p2)
|
1349
|
-
return empty("#{i2} area", mth, ERR, a) if area2.empty?
|
1350
1439
|
area2 = area2.get
|
1351
|
-
union = OpenStudio
|
1352
|
-
return
|
1440
|
+
union = OpenStudio.join(ft_p1, ft_p2, TOL2)
|
1441
|
+
return false if union.empty?
|
1442
|
+
|
1353
1443
|
union = union.get
|
1354
|
-
area
|
1355
|
-
return empty("#{i1}:#{i2} union area", mth, ERR, a)
|
1356
|
-
area = area.get
|
1444
|
+
area = OpenStudio.getArea(union)
|
1445
|
+
return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
|
1357
1446
|
|
1358
|
-
|
1447
|
+
area = area.get
|
1448
|
+
return false if area < TOL
|
1359
1449
|
|
1360
1450
|
true
|
1361
1451
|
end
|
1362
1452
|
|
1453
|
+
##
|
1454
|
+
# Generate offset vertices (by width) for a 3- or 4-sided, convex polygon.
|
1455
|
+
#
|
1456
|
+
# @param p1 [OpenStudio::Point3dVector] OpenStudio Point3D vector/array
|
1457
|
+
# @param w [Float] offset width (min: 0.0254m)
|
1458
|
+
# @param v [Integer] OpenStudio SDK version, eg '321' for 'v3.2.1' (optional)
|
1459
|
+
#
|
1460
|
+
# @return [OpenStudio::Point3dVector] offset points if successful
|
1461
|
+
# @return [OpenStudio::Point3dVector] original points if invalid input
|
1462
|
+
def offset(p1 = [], w = 0, v = 0)
|
1463
|
+
mth = "TBD::#{__callee__}"
|
1464
|
+
cl = OpenStudio::Point3d
|
1465
|
+
vrsn = OpenStudio.openStudioVersion.split(".").map(&:to_i).join.to_i
|
1466
|
+
|
1467
|
+
valid = p1.is_a?(OpenStudio::Point3dVector) || p1.is_a?(Array)
|
1468
|
+
return mismatch("pts", p1, cl1, mth, DBG, p1) unless valid
|
1469
|
+
return empty("pts", mth, ERR, p1) if p1.empty?
|
1470
|
+
|
1471
|
+
valid = p1.size == 3 || p1.size == 4
|
1472
|
+
iv = true if p1.size == 4
|
1473
|
+
return invalid("pts", mth, 1, DBG, p1) unless valid
|
1474
|
+
return invalid("width", mth, 2, DBG, p1) unless w.respond_to?(:to_f)
|
1475
|
+
|
1476
|
+
w = w.to_f
|
1477
|
+
return p1 if w < 0.0254
|
1478
|
+
|
1479
|
+
v = v.to_i if v.respond_to?(:to_i)
|
1480
|
+
v = 0 unless v.respond_to?(:to_i)
|
1481
|
+
v = vrsn if v.zero?
|
1482
|
+
|
1483
|
+
p1.each { |x| return mismatch("p", x, cl, mth, ERR, p1) unless x.is_a?(cl) }
|
1484
|
+
|
1485
|
+
unless v < 340
|
1486
|
+
# XY-plane transformation matrix ... needs to be clockwise for boost.
|
1487
|
+
ft = OpenStudio::Transformation::alignFace(p1)
|
1488
|
+
ft_pts = flatZ( (ft.inverse * p1) )
|
1489
|
+
return p1 if ft_pts.empty?
|
1490
|
+
|
1491
|
+
cw = OpenStudio::pointInPolygon(ft_pts.first, ft_pts, TOL)
|
1492
|
+
ft_pts = flatZ( (ft.inverse * p1).reverse ) unless cw
|
1493
|
+
offset = OpenStudio.buffer(ft_pts, w, TOL)
|
1494
|
+
return p1 if offset.empty?
|
1495
|
+
|
1496
|
+
offset = offset.get
|
1497
|
+
offset = ft * offset if cw
|
1498
|
+
offset = (ft * offset).reverse unless cw
|
1499
|
+
|
1500
|
+
pz = OpenStudio::Point3dVector.new
|
1501
|
+
offset.each { |o| pz << OpenStudio::Point3d.new(o.x, o.y, o.z ) }
|
1502
|
+
|
1503
|
+
return pz
|
1504
|
+
else # brute force approach
|
1505
|
+
pz = {}
|
1506
|
+
pz[:A] = {}
|
1507
|
+
pz[:B] = {}
|
1508
|
+
pz[:C] = {}
|
1509
|
+
pz[:D] = {} if iv
|
1510
|
+
|
1511
|
+
pz[:A][:p] = OpenStudio::Point3d.new(p1[0].x, p1[0].y, p1[0].z)
|
1512
|
+
pz[:B][:p] = OpenStudio::Point3d.new(p1[1].x, p1[1].y, p1[1].z)
|
1513
|
+
pz[:C][:p] = OpenStudio::Point3d.new(p1[2].x, p1[2].y, p1[2].z)
|
1514
|
+
pz[:D][:p] = OpenStudio::Point3d.new(p1[3].x, p1[3].y, p1[3].z) if iv
|
1515
|
+
|
1516
|
+
pzAp = pz[:A][:p]
|
1517
|
+
pzBp = pz[:B][:p]
|
1518
|
+
pzCp = pz[:C][:p]
|
1519
|
+
pzDp = pz[:D][:p] if iv
|
1520
|
+
|
1521
|
+
# Generate vector pairs, from next point & from previous point.
|
1522
|
+
# :f_n : "from next"
|
1523
|
+
# :f_p : "from previous"
|
1524
|
+
#
|
1525
|
+
#
|
1526
|
+
#
|
1527
|
+
#
|
1528
|
+
#
|
1529
|
+
#
|
1530
|
+
# A <---------- B
|
1531
|
+
# ^
|
1532
|
+
# \
|
1533
|
+
# \
|
1534
|
+
# C (or D)
|
1535
|
+
#
|
1536
|
+
pz[:A][:f_n] = pzAp - pzBp
|
1537
|
+
pz[:A][:f_p] = pzAp - pzCp unless iv
|
1538
|
+
pz[:A][:f_p] = pzAp - pzDp if iv
|
1539
|
+
|
1540
|
+
pz[:B][:f_n] = pzBp - pzCp
|
1541
|
+
pz[:B][:f_p] = pzBp - pzAp
|
1542
|
+
|
1543
|
+
pz[:C][:f_n] = pzCp - pzAp unless iv
|
1544
|
+
pz[:C][:f_n] = pzCp - pzDp if iv
|
1545
|
+
pz[:C][:f_p] = pzCp - pzBp
|
1546
|
+
|
1547
|
+
pz[:D][:f_n] = pzDp - pzAp if iv
|
1548
|
+
pz[:D][:f_p] = pzDp - pzCp if iv
|
1549
|
+
|
1550
|
+
# Generate 3D plane from vectors.
|
1551
|
+
#
|
1552
|
+
#
|
1553
|
+
# | <<< 3D plane ... from point A, with normal B>A
|
1554
|
+
# |
|
1555
|
+
# |
|
1556
|
+
# |
|
1557
|
+
# <---------- A <---------- B
|
1558
|
+
# |\
|
1559
|
+
# | \
|
1560
|
+
# | \
|
1561
|
+
# | C (or D)
|
1562
|
+
#
|
1563
|
+
pz[:A][:pl_f_n] = OpenStudio::Plane.new(pzAp, pz[:A][:f_n])
|
1564
|
+
pz[:A][:pl_f_p] = OpenStudio::Plane.new(pzAp, pz[:A][:f_p])
|
1565
|
+
|
1566
|
+
pz[:B][:pl_f_n] = OpenStudio::Plane.new(pzBp, pz[:B][:f_n])
|
1567
|
+
pz[:B][:pl_f_p] = OpenStudio::Plane.new(pzBp, pz[:B][:f_p])
|
1568
|
+
|
1569
|
+
pz[:C][:pl_f_n] = OpenStudio::Plane.new(pzCp, pz[:C][:f_n])
|
1570
|
+
pz[:C][:pl_f_p] = OpenStudio::Plane.new(pzCp, pz[:C][:f_p])
|
1571
|
+
|
1572
|
+
pz[:D][:pl_f_n] = OpenStudio::Plane.new(pzDp, pz[:D][:f_n]) if iv
|
1573
|
+
pz[:D][:pl_f_p] = OpenStudio::Plane.new(pzDp, pz[:D][:f_p]) if iv
|
1574
|
+
|
1575
|
+
# Project an extended point (pC) unto 3D plane.
|
1576
|
+
#
|
1577
|
+
# pC <<< projected unto extended B>A 3D plane
|
1578
|
+
# eC |
|
1579
|
+
# \ |
|
1580
|
+
# \ |
|
1581
|
+
# \|
|
1582
|
+
# <---------- A <---------- B
|
1583
|
+
# |\
|
1584
|
+
# | \
|
1585
|
+
# | \
|
1586
|
+
# | C (or D)
|
1587
|
+
#
|
1588
|
+
pz[:A][:p_n_pl] = pz[:A][:pl_f_n].project(pz[:A][:p] + pz[:A][:f_p])
|
1589
|
+
pz[:A][:n_p_pl] = pz[:A][:pl_f_p].project(pz[:A][:p] + pz[:A][:f_n])
|
1590
|
+
|
1591
|
+
pz[:B][:p_n_pl] = pz[:B][:pl_f_n].project(pz[:B][:p] + pz[:B][:f_p])
|
1592
|
+
pz[:B][:n_p_pl] = pz[:B][:pl_f_p].project(pz[:B][:p] + pz[:B][:f_n])
|
1593
|
+
|
1594
|
+
pz[:C][:p_n_pl] = pz[:C][:pl_f_n].project(pz[:C][:p] + pz[:C][:f_p])
|
1595
|
+
pz[:C][:n_p_pl] = pz[:C][:pl_f_p].project(pz[:C][:p] + pz[:C][:f_n])
|
1596
|
+
|
1597
|
+
pz[:D][:p_n_pl] = pz[:D][:pl_f_n].project(pz[:D][:p] + pz[:D][:f_p]) if iv
|
1598
|
+
pz[:D][:n_p_pl] = pz[:D][:pl_f_p].project(pz[:D][:p] + pz[:D][:f_n]) if iv
|
1599
|
+
|
1600
|
+
# Generate vector from point (e.g. A) to projected extended point (pC).
|
1601
|
+
#
|
1602
|
+
# pC
|
1603
|
+
# eC ^
|
1604
|
+
# \ |
|
1605
|
+
# \ |
|
1606
|
+
# \|
|
1607
|
+
# <---------- A <---------- B
|
1608
|
+
# |\
|
1609
|
+
# | \
|
1610
|
+
# | \
|
1611
|
+
# | C (or D)
|
1612
|
+
#
|
1613
|
+
pz[:A][:n_p_n_pl] = pz[:A][:p_n_pl] - pzAp
|
1614
|
+
pz[:A][:n_n_p_pl] = pz[:A][:n_p_pl] - pzAp
|
1615
|
+
|
1616
|
+
pz[:B][:n_p_n_pl] = pz[:B][:p_n_pl] - pzBp
|
1617
|
+
pz[:B][:n_n_p_pl] = pz[:B][:n_p_pl] - pzBp
|
1618
|
+
|
1619
|
+
pz[:C][:n_p_n_pl] = pz[:C][:p_n_pl] - pzCp
|
1620
|
+
pz[:C][:n_n_p_pl] = pz[:C][:n_p_pl] - pzCp
|
1621
|
+
|
1622
|
+
pz[:D][:n_p_n_pl] = pz[:D][:p_n_pl] - pzDp if iv
|
1623
|
+
pz[:D][:n_n_p_pl] = pz[:D][:n_p_pl] - pzDp if iv
|
1624
|
+
|
1625
|
+
# Fetch angle between both extended vectors (A>pC & A>pB),
|
1626
|
+
# ... then normalize (Cn).
|
1627
|
+
#
|
1628
|
+
# pC
|
1629
|
+
# eC ^
|
1630
|
+
# \ |
|
1631
|
+
# \ Cn
|
1632
|
+
# \|
|
1633
|
+
# <---------- A <---------- B
|
1634
|
+
# |\
|
1635
|
+
# | \
|
1636
|
+
# | \
|
1637
|
+
# | C (or D)
|
1638
|
+
#
|
1639
|
+
a1 = OpenStudio.getAngle(pz[:A][:n_p_n_pl], pz[:A][:n_n_p_pl])
|
1640
|
+
a2 = OpenStudio.getAngle(pz[:B][:n_p_n_pl], pz[:B][:n_n_p_pl])
|
1641
|
+
a3 = OpenStudio.getAngle(pz[:C][:n_p_n_pl], pz[:C][:n_n_p_pl])
|
1642
|
+
a4 = OpenStudio.getAngle(pz[:D][:n_p_n_pl], pz[:D][:n_n_p_pl]) if iv
|
1643
|
+
|
1644
|
+
# Generate new 3D points A', B', C' (and D') ... zigzag.
|
1645
|
+
#
|
1646
|
+
#
|
1647
|
+
#
|
1648
|
+
#
|
1649
|
+
# A' ---------------------- B'
|
1650
|
+
# \
|
1651
|
+
# \ A <---------- B
|
1652
|
+
# \ \
|
1653
|
+
# \ \
|
1654
|
+
# \ \
|
1655
|
+
# C' C
|
1656
|
+
pz[:A][:f_n].normalize
|
1657
|
+
pz[:A][:n_p_n_pl].normalize
|
1658
|
+
pzAp = pzAp + scalar(pz[:A][:n_p_n_pl], w)
|
1659
|
+
pzAp = pzAp + scalar(pz[:A][:f_n], w * Math.tan(a1/2))
|
1660
|
+
|
1661
|
+
pz[:B][:f_n].normalize
|
1662
|
+
pz[:B][:n_p_n_pl].normalize
|
1663
|
+
pzBp = pzBp + scalar(pz[:B][:n_p_n_pl], w)
|
1664
|
+
pzBp = pzBp + scalar(pz[:B][:f_n], w * Math.tan(a2/2))
|
1665
|
+
|
1666
|
+
pz[:C][:f_n].normalize
|
1667
|
+
pz[:C][:n_p_n_pl].normalize
|
1668
|
+
pzCp = pzCp + scalar(pz[:C][:n_p_n_pl], w)
|
1669
|
+
pzCp = pzCp + scalar(pz[:C][:f_n], w * Math.tan(a3/2))
|
1670
|
+
|
1671
|
+
pz[:D][:f_n].normalize if iv
|
1672
|
+
pz[:D][:n_p_n_pl].normalize if iv
|
1673
|
+
pzDp = pzDp + scalar(pz[:D][:n_p_n_pl], w) if iv
|
1674
|
+
pzDp = pzDp + scalar(pz[:D][:f_n], w * Math.tan(a4/2)) if iv
|
1675
|
+
|
1676
|
+
# Re-convert to OpenStudio 3D points.
|
1677
|
+
vec = OpenStudio::Point3dVector.new
|
1678
|
+
vec << OpenStudio::Point3d.new(pzAp.x, pzAp.y, pzAp.z)
|
1679
|
+
vec << OpenStudio::Point3d.new(pzBp.x, pzBp.y, pzBp.z)
|
1680
|
+
vec << OpenStudio::Point3d.new(pzCp.x, pzCp.y, pzCp.z)
|
1681
|
+
vec << OpenStudio::Point3d.new(pzDp.x, pzDp.y, pzDp.z) if iv
|
1682
|
+
|
1683
|
+
return vec
|
1684
|
+
end
|
1685
|
+
end
|
1686
|
+
|
1363
1687
|
##
|
1364
1688
|
# Callback when other modules extend OSlg
|
1365
1689
|
#
|
data/lib/osut/version.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# BSD 3-Clause License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2022, Denis Bourgeois
|
3
|
+
# Copyright (c) 2022-2023, Denis Bourgeois
|
4
4
|
# All rights reserved.
|
5
5
|
#
|
6
6
|
# Redistribution and use in source and binary forms, with or without
|
@@ -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.
|
32
|
+
VERSION = "0.2.8".freeze
|
33
33
|
end
|
data/lib/osut.rb
CHANGED
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.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Bourgeois
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-01 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.
|
93
|
+
source_code_uri: https://github.com/rd2/osut/tree/v0.2.8
|
94
94
|
bug_tracker_uri: https://github.com/rd2/osut/issues
|
95
95
|
post_install_message:
|
96
96
|
rdoc_options: []
|