tbd 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = {}