tbd 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/pull_request.yml +16 -0
- data/LICENSE.md +1 -1
- data/lib/measures/tbd/LICENSE.md +1 -1
- data/lib/measures/tbd/README.md +8 -0
- data/lib/measures/tbd/measure.rb +44 -2
- data/lib/measures/tbd/measure.xml +33 -24
- data/lib/measures/tbd/resources/geo.rb +56 -16
- data/lib/measures/tbd/resources/oslog.rb +17 -7
- data/lib/measures/tbd/resources/psi.rb +113 -7
- data/lib/measures/tbd/resources/tbd.rb +1 -1
- data/lib/measures/tbd/resources/ua.rb +36 -69
- data/lib/measures/tbd/resources/utils.rb +86 -28
- data/lib/measures/tbd/tests/tbd_tests.rb +117 -87
- data/lib/tbd/geo.rb +56 -16
- data/lib/tbd/psi.rb +113 -7
- data/lib/tbd/ua.rb +36 -69
- data/lib/tbd/version.rb +2 -2
- data/lib/tbd.rb +1 -1
- metadata +3 -3
@@ -1,6 +1,6 @@
|
|
1
1
|
# MIT License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2020-
|
3
|
+
# Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# MIT License
|
2
2
|
#
|
3
|
-
# Copyright (c) 2020-
|
3
|
+
# Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber
|
4
4
|
#
|
5
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -47,6 +47,8 @@ module TBD
|
|
47
47
|
return mismatch("film", film, cl3, mth, DBG, res) unless film.is_a?(cl3)
|
48
48
|
return mismatch("Ut", ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
|
49
49
|
|
50
|
+
loss = 0.0 # residual heatloss (not assigned) [W/K]
|
51
|
+
area = lc.getNetArea
|
50
52
|
lyr = insulatingLayer(lc)
|
51
53
|
lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
|
52
54
|
lyr[:index] = nil unless lyr[:index] >= 0
|
@@ -57,8 +59,6 @@ module TBD
|
|
57
59
|
return zero("'#{id}': films", mth, WRN, res) unless film > TOL
|
58
60
|
return zero("'#{id}': Ut", mth, WRN, res) unless ut > TOL
|
59
61
|
return invalid("'#{id}': Ut", mth, 0, WRN, res) unless ut < 5.678
|
60
|
-
|
61
|
-
area = lc.getNetArea
|
62
62
|
return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
|
63
63
|
|
64
64
|
# First, calculate initial layer RSi to initially meet Ut target.
|
@@ -74,8 +74,6 @@ module TBD
|
|
74
74
|
|
75
75
|
return zero("'#{id}': new Rsi", mth, ERR, res) unless new_r > 0.001
|
76
76
|
|
77
|
-
loss = 0.0 # residual heatloss (not assigned) [W/K]
|
78
|
-
|
79
77
|
if lyr[:type] == :massless
|
80
78
|
m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
|
81
79
|
return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
|
@@ -85,7 +83,7 @@ module TBD
|
|
85
83
|
new_r = 0.001 unless new_r > 0.001
|
86
84
|
loss = (new_u - 1 / new_r) * area unless new_r > 0.001
|
87
85
|
m.setThermalResistance(new_r)
|
88
|
-
else
|
86
|
+
else # type == :standard
|
89
87
|
m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
|
90
88
|
return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
|
91
89
|
|
@@ -595,77 +593,46 @@ module TBD
|
|
595
593
|
next unless surface[:net] > TOL
|
596
594
|
next unless surface.key?(:u)
|
597
595
|
next unless surface[:u] > TOL
|
598
|
-
heating
|
599
|
-
heating
|
600
|
-
bloc
|
601
|
-
bloc
|
602
|
-
|
596
|
+
heating = 21.0
|
597
|
+
heating = surface[:heating] if surface.key?(:heating)
|
598
|
+
bloc = b1
|
599
|
+
bloc = b2 if heating < 18
|
603
600
|
reference = surface.key?(:ref)
|
601
|
+
|
604
602
|
if type == :wall
|
605
603
|
areas[:walls][:net ] += surface[:net]
|
606
|
-
|
607
|
-
|
608
|
-
|
604
|
+
bloc[:pro][:walls ] += surface[:net] * surface[:u ]
|
605
|
+
bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
|
606
|
+
bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
|
609
607
|
elsif type == :ceiling
|
610
608
|
areas[:roofs][:net ] += surface[:net]
|
611
|
-
|
612
|
-
|
613
|
-
|
609
|
+
bloc[:pro][:roofs ] += surface[:net] * surface[:u ]
|
610
|
+
bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
|
611
|
+
bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
|
614
612
|
else
|
615
613
|
areas[:floors][:net] += surface[:net]
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
end
|
620
|
-
|
621
|
-
if surface.key?(:doors)
|
622
|
-
surface[:doors].values.each do |door|
|
623
|
-
next unless door.key?(:gross)
|
624
|
-
next unless door[:gross] > TOL
|
625
|
-
next unless door.key?(:u)
|
626
|
-
next unless door[:u] > TOL
|
627
|
-
areas[:walls][:subs ] += door[:gross] if type == :wall
|
628
|
-
areas[:roofs][:subs ] += door[:gross] if type == :ceiling
|
629
|
-
areas[:floors][:subs] += door[:gross] if type == :floor
|
630
|
-
bloc[:pro][:doors ] += door[:gross] * door[:u]
|
631
|
-
|
632
|
-
ok = door.key?(:ref)
|
633
|
-
bloc[:ref][:doors ] += door[:gross] * door[:ref] if ok
|
634
|
-
bloc[:ref][:doors ] += door[:gross] * door[:u ] unless ok
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
|
-
if surface.key?(:windows)
|
639
|
-
surface[:windows].values.each do |window|
|
640
|
-
next unless window.key?(:gross)
|
641
|
-
next unless window[:gross] > TOL
|
642
|
-
next unless window.key?(:u)
|
643
|
-
next unless window[:u] > TOL
|
644
|
-
areas[:walls][:subs ] += window[:gross] if type == :wall
|
645
|
-
areas[:roofs][:subs ] += window[:gross] if type == :ceiling
|
646
|
-
areas[:floors][:subs] += window[:gross] if type == :floor
|
647
|
-
bloc[:pro][:windows] += window[:gross] * window[:u]
|
648
|
-
|
649
|
-
ok = window.key?(:ref)
|
650
|
-
bloc[:ref][:windows ] += window[:gross] * window[:ref] if ok
|
651
|
-
bloc[:ref][:windows ] += window[:gross] * window[:u ] unless ok
|
652
|
-
end
|
614
|
+
bloc[:pro][:floors ] += surface[:net] * surface[:u ]
|
615
|
+
bloc[:ref][:floors ] += surface[:net] * surface[:ref] if reference
|
616
|
+
bloc[:ref][:floors ] += surface[:net] * surface[:u ] unless reference
|
653
617
|
end
|
654
618
|
|
655
|
-
|
656
|
-
surface
|
657
|
-
|
658
|
-
|
659
|
-
next unless
|
660
|
-
next unless
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
619
|
+
[:doors, :windows, :skylights].each do |subs|
|
620
|
+
next unless surface.key?(subs)
|
621
|
+
|
622
|
+
surface[subs].values.each do |sub|
|
623
|
+
next unless sub.key?(:gross)
|
624
|
+
next unless sub.key?(:u )
|
625
|
+
next unless sub[:gross] > TOL
|
626
|
+
next unless sub[:u ] > TOL
|
627
|
+
|
628
|
+
gross = sub[:gross]
|
629
|
+
gross *= sub[:mult ] if sub.key?(:mult)
|
630
|
+
areas[:walls ][:subs] += gross if type == :wall
|
631
|
+
areas[:roofs ][:subs] += gross if type == :ceiling
|
632
|
+
areas[:floors][:subs] += gross if type == :floor
|
633
|
+
bloc[:pro ][subs ] += gross * sub[:u ]
|
634
|
+
bloc[:ref ][subs ] += gross * sub[:ref] if sub.key?(:ref)
|
635
|
+
bloc[:ref ][subs ] += gross * sub[:u ] unless sub.key?(:ref)
|
669
636
|
end
|
670
637
|
end
|
671
638
|
|
@@ -953,7 +920,7 @@ module TBD
|
|
953
920
|
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
|
954
921
|
model += " (v#{ua[:version]})" if ua.key?(:version)
|
955
922
|
report << model unless model.empty?
|
956
|
-
report << "* TBD : v3.
|
923
|
+
report << "* TBD : v3.2.0"
|
957
924
|
report << "* date : #{ua[:date]}"
|
958
925
|
|
959
926
|
if lang == :en
|
@@ -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
|
|
@@ -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
|
@@ -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
|
|
@@ -771,6 +788,7 @@ module OSut
|
|
771
788
|
# Seasonal availability start/end dates.
|
772
789
|
year = model.yearDescription
|
773
790
|
return empty("yearDescription", mth, ERR) if year.empty?
|
791
|
+
|
774
792
|
year = year.get
|
775
793
|
may01 = year.makeDate(OpenStudio::MonthOfYear.new("May"), 1)
|
776
794
|
oct31 = year.makeDate(OpenStudio::MonthOfYear.new("Oct"), 31)
|
@@ -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
|
@@ -959,6 +988,7 @@ module OSut
|
|
959
988
|
|
960
989
|
return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
|
961
990
|
return invalid("s", mth, 2) unless s.respond_to?(NS)
|
991
|
+
|
962
992
|
id = s.nameString
|
963
993
|
return mismatch(id, s, cl2, mth) unless s.is_a?(cl2)
|
964
994
|
|
@@ -966,8 +996,10 @@ module OSut
|
|
966
996
|
log(ERR, "'#{id}' construction not defaulted (#{mth})") unless ok
|
967
997
|
return nil unless ok
|
968
998
|
return empty("'#{id}' construction", mth, ERR) if s.construction.empty?
|
999
|
+
|
969
1000
|
base = s.construction.get
|
970
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
|
@@ -1043,12 +1075,14 @@ module OSut
|
|
1043
1075
|
cl = OpenStudio::Model::LayeredConstruction
|
1044
1076
|
|
1045
1077
|
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
1078
|
+
|
1046
1079
|
id = lc.nameString
|
1047
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?
|
@@ -1167,6 +1205,7 @@ module OSut
|
|
1167
1205
|
i = 0 # iterator
|
1168
1206
|
|
1169
1207
|
return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
|
1208
|
+
|
1170
1209
|
id = lc.nameString
|
1171
1210
|
return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl)
|
1172
1211
|
|
@@ -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
|
|
@@ -1244,10 +1284,10 @@ module OSut
|
|
1244
1284
|
cl2 = Numeric
|
1245
1285
|
|
1246
1286
|
return mismatch("vector", v, cl1, mth, DBG, v) unless v.is_a?(cl1)
|
1247
|
-
return mismatch("x",
|
1248
|
-
return mismatch("y",
|
1249
|
-
return mismatch("z",
|
1250
|
-
return mismatch("m",
|
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)
|
1251
1291
|
|
1252
1292
|
OpenStudio::Vector3d.new(m * v.x, m * v.y, m * v.z)
|
1253
1293
|
end
|
@@ -1266,6 +1306,7 @@ module OSut
|
|
1266
1306
|
|
1267
1307
|
valid = pts.is_a?(cl1) || pts.is_a?(Array)
|
1268
1308
|
return mismatch("points", pts, cl1, mth, DBG, v) unless valid
|
1309
|
+
|
1269
1310
|
pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) }
|
1270
1311
|
pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, 0) }
|
1271
1312
|
|
@@ -1311,23 +1352,29 @@ module OSut
|
|
1311
1352
|
ft = OpenStudio::Transformation.alignFace(p1)
|
1312
1353
|
ft_p1 = flatZ( (ft.inverse * p1) )
|
1313
1354
|
return false if ft_p1.empty?
|
1355
|
+
|
1314
1356
|
cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL)
|
1315
1357
|
ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw
|
1316
1358
|
ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw
|
1317
1359
|
ft_p2 = flatZ( (ft.inverse * p2) ) if cw
|
1318
1360
|
return false if ft_p2.empty?
|
1361
|
+
|
1319
1362
|
area1 = OpenStudio.getArea(ft_p1)
|
1320
1363
|
area2 = OpenStudio.getArea(ft_p2)
|
1321
1364
|
return empty("#{i1} area", mth, ERR, a) if area1.empty?
|
1322
1365
|
return empty("#{i2} area", mth, ERR, a) if area2.empty?
|
1366
|
+
|
1323
1367
|
area1 = area1.get
|
1324
1368
|
area2 = area2.get
|
1325
1369
|
union = OpenStudio.join(ft_p1, ft_p2, TOL2)
|
1326
1370
|
return false if union.empty?
|
1371
|
+
|
1327
1372
|
union = union.get
|
1328
1373
|
area = OpenStudio.getArea(union)
|
1329
1374
|
return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
|
1375
|
+
|
1330
1376
|
area = area.get
|
1377
|
+
|
1331
1378
|
return false if area < TOL
|
1332
1379
|
return true if (area - area2).abs < TOL
|
1333
1380
|
return false if (area - area2).abs > TOL
|
@@ -1376,22 +1423,27 @@ module OSut
|
|
1376
1423
|
ft_p2 = flatZ( (ft.inverse * p2) )
|
1377
1424
|
return false if ft_p1.empty?
|
1378
1425
|
return false if ft_p2.empty?
|
1426
|
+
|
1379
1427
|
cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL)
|
1380
1428
|
ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw
|
1381
1429
|
ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw
|
1382
1430
|
return false if ft_p1.empty?
|
1383
1431
|
return false if ft_p2.empty?
|
1432
|
+
|
1384
1433
|
area1 = OpenStudio.getArea(ft_p1)
|
1385
1434
|
area2 = OpenStudio.getArea(ft_p2)
|
1386
1435
|
return empty("#{i1} area", mth, ERR, a) if area1.empty?
|
1387
1436
|
return empty("#{i2} area", mth, ERR, a) if area2.empty?
|
1437
|
+
|
1388
1438
|
area1 = area1.get
|
1389
1439
|
area2 = area2.get
|
1390
1440
|
union = OpenStudio.join(ft_p1, ft_p2, TOL2)
|
1391
1441
|
return false if union.empty?
|
1442
|
+
|
1392
1443
|
union = union.get
|
1393
1444
|
area = OpenStudio.getArea(union)
|
1394
1445
|
return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty?
|
1446
|
+
|
1395
1447
|
area = area.get
|
1396
1448
|
return false if area < TOL
|
1397
1449
|
|
@@ -1415,12 +1467,15 @@ module OSut
|
|
1415
1467
|
valid = p1.is_a?(OpenStudio::Point3dVector) || p1.is_a?(Array)
|
1416
1468
|
return mismatch("pts", p1, cl1, mth, DBG, p1) unless valid
|
1417
1469
|
return empty("pts", mth, ERR, p1) if p1.empty?
|
1470
|
+
|
1418
1471
|
valid = p1.size == 3 || p1.size == 4
|
1419
1472
|
iv = true if p1.size == 4
|
1420
1473
|
return invalid("pts", mth, 1, DBG, p1) unless valid
|
1421
1474
|
return invalid("width", mth, 2, DBG, p1) unless w.respond_to?(:to_f)
|
1475
|
+
|
1422
1476
|
w = w.to_f
|
1423
1477
|
return p1 if w < 0.0254
|
1478
|
+
|
1424
1479
|
v = v.to_i if v.respond_to?(:to_i)
|
1425
1480
|
v = 0 unless v.respond_to?(:to_i)
|
1426
1481
|
v = vrsn if v.zero?
|
@@ -1432,16 +1487,19 @@ module OSut
|
|
1432
1487
|
ft = OpenStudio::Transformation::alignFace(p1)
|
1433
1488
|
ft_pts = flatZ( (ft.inverse * p1) )
|
1434
1489
|
return p1 if ft_pts.empty?
|
1490
|
+
|
1435
1491
|
cw = OpenStudio::pointInPolygon(ft_pts.first, ft_pts, TOL)
|
1436
1492
|
ft_pts = flatZ( (ft.inverse * p1).reverse ) unless cw
|
1437
1493
|
offset = OpenStudio.buffer(ft_pts, w, TOL)
|
1438
1494
|
return p1 if offset.empty?
|
1495
|
+
|
1439
1496
|
offset = offset.get
|
1440
1497
|
offset = ft * offset if cw
|
1441
1498
|
offset = (ft * offset).reverse unless cw
|
1442
1499
|
|
1443
1500
|
pz = OpenStudio::Point3dVector.new
|
1444
1501
|
offset.each { |o| pz << OpenStudio::Point3d.new(o.x, o.y, o.z ) }
|
1502
|
+
|
1445
1503
|
return pz
|
1446
1504
|
else # brute force approach
|
1447
1505
|
pz = {}
|