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.
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
  #
3
- # Copyright (c) 2020-2022 Denis Bourgeois & Dan Macumber
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-2022 Denis Bourgeois & Dan Macumber
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 # type == :standard
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 = 21.0
599
- heating = surface[:heating] if surface.key?(:heating)
600
- bloc = b1
601
- bloc = b2 if heating < 18
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
- bloc[:pro][:walls ] += surface[:net] * surface[:u ]
607
- bloc[:ref][:walls ] += surface[:net] * surface[:ref] if reference
608
- bloc[:ref][:walls ] += surface[:net] * surface[:u ] unless reference
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
- bloc[:pro][:roofs ] += surface[:net] * surface[:u ]
612
- bloc[:ref][:roofs ] += surface[:net] * surface[:ref] if reference
613
- bloc[:ref][:roofs ] += surface[:net] * surface[:u ] unless reference
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
- bloc[:pro][:floors] += surface[:net] * surface[:u ]
617
- bloc[:ref][:floors] += surface[:net] * surface[:ref] if reference
618
- bloc[:ref][:floors] += surface[:net] * surface[:u ] unless reference
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
- if surface.key?(:skylights)
656
- surface[:skylights].values.each do |sky|
657
- next unless sky.key?(:gross)
658
- next unless sky[:gross] > TOL
659
- next unless sky.key?(:u)
660
- next unless sky[:u] > TOL
661
- areas[:walls][:subs ] += sky[:gross] if type == :wall
662
- areas[:roofs][:subs ] += sky[:gross] if type == :ceiling
663
- areas[:floors][:subs ] += sky[:gross] if type == :floor
664
- bloc[:pro][:skylights] += sky[:gross] * sky[:u]
665
-
666
- ok = sky.key?(:ref)
667
- bloc[:ref][:skylights] += sky[:gross] * sky[:ref] if ok
668
- bloc[:ref][:skylights] += sky[:gross] * sky[:u ] unless ok
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.1.1"
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., >= 50 W/m2 in Climate Zone CZ-7
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., heating and/or cooling based, no distinction for
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 (OSM) in an incomplete or preliminary state, e.g.
113
- # holding fully-formed ENCLOSED spaces without thermal zoning information or
114
- # setpoint temperatures (early design stage assessments of form, porosity or
115
- # envelope), all OSM spaces will be considered CONDITIONED, presuming
116
- # setpoints of ~21°C (heating) and ~24°C (cooling).
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 OSM,
119
- # spaces/zones WITHOUT valid heating or cooling setpoints are considered as
120
- # UNCONDITIONED or UNENCLOSED spaces (like attics), or INDIRECTLY CONDITIONED
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 = "OSut::#{__callee__}"
252
- cl = OpenStudio::Model::ScheduleInterval
253
- vals = []
254
- prev_str = ""
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 = vals.min.is_a?(Numeric) && vals.max.is_a?(Numeric)
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
- unless type.standardsSpaceType.empty?
719
- type = type.standardsSpaceType.get
720
- return type.downcase == "plenum" # C
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 ground construction.
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
- t += 273.0 # °C to K
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", 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)
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 = {}