tbd 3.4.5 → 3.5.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.
@@ -1355,103 +1355,103 @@ module TBD
1355
1355
  ##
1356
1356
  # Thermally derates insulating material within construction.
1357
1357
  #
1358
- # @param id [#to_s] surface identifier
1358
+ # @param id [#to_sym] surface identifier
1359
1359
  # @param [Hash] s TBD surface parameters
1360
- # @option s [#to_f] :heatloss heat loss from major thermal bridging, in W/K
1361
- # @option s [#to_f] :net surface net area, in m2
1360
+ # @option s [Numeric] :heatloss heat loss from major thermal bridging, in W/K
1361
+ # @option s [Numeric] :net surface net area, in m2
1362
1362
  # @option s [:massless, :standard] :ltype indexed layer type
1363
- # @option s [#to_i] :index deratable construction layer index
1364
- # @option s [#to_f] :r deratable layer Rsi-factor, in m2•K/W
1363
+ # @option s [Integer] :index deratable construction layer index
1364
+ # @option s [Numeric] :r deratable layer Rsi-factor, in m2•K/W
1365
1365
  # @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
1366
1366
  #
1367
- # @return [OpenStudio::Model::Material] derated (cloned) material
1367
+ # @return [OpenStudio::Model::OpaqueMaterial] derated (cloned) material
1368
1368
  # @return [nil] if invalid input (see logs)
1369
1369
  def derate(id = "", s = {}, lc = nil)
1370
1370
  mth = "TBD::#{__callee__}"
1371
1371
  m = nil
1372
- id = trim(id)
1373
1372
  kys = [:heatloss, :net, :ltype, :index, :r]
1374
- ck1 = s.is_a?(Hash)
1375
- ck2 = lc.is_a?(OpenStudio::Model::LayeredConstruction)
1376
- return mismatch("id" , id, cl6, mth) if id.empty?
1377
- return mismatch("#{id} surface" , s , cl1, mth) unless ck1
1378
- return mismatch("#{id} construction", lc, cl2, mth) unless ck2
1373
+ cl = OpenStudio::Model::LayeredConstruction
1374
+ return mismatch("lc", lc, cl, mth) unless lc.respond_to?(NS)
1375
+ return mismatch("id", id, String, mth) unless id.respond_to?(:to_sym)
1376
+
1377
+ id = trim(id)
1378
+ nom = lc.nameString
1379
+ return invalid("id", mth, 1) if id.empty?
1380
+ return mismatch(nom, lc, cl, mth) unless lc.is_a?(cl)
1381
+ return mismatch("#{nom} surface", s, Hash, mth) unless s.is_a?(Hash)
1382
+
1383
+ if nom.downcase.include?(" tbd")
1384
+ log(WRN, "Won't derate '#{nom}': tagged as derated (#{mth})")
1385
+ return m
1386
+ end
1379
1387
 
1380
1388
  kys.each do |k|
1381
1389
  tag = "#{id} #{k}"
1382
- return hashkey(tag, s, k, mth, ERR) unless s.key?(k)
1390
+ return hashkey(tag, s, k, mth) unless s.key?(k)
1383
1391
 
1384
1392
  case k
1385
- when :heatloss
1386
- return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_f)
1387
- return zero(tag, mth, WRN) if s[k].to_f.abs < 0.001
1388
- when :net, :r
1389
- return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_f)
1390
- return negative(tag, mth, 2, ERR) if s[k].to_f < 0
1391
- return zero(tag, mth, WRN) if s[k].to_f.abs < 0.001
1392
- when :index
1393
- return mismatch(tag, s[k], Numeric, mth) unless s[k].respond_to?(:to_i)
1394
- return negative(tag, mth, 2, ERR) if s[k].to_f < 0
1395
- else # :ltype
1393
+ when :ltype
1396
1394
  next if [:massless, :standard].include?(s[k])
1397
- return invalid(tag, mth, 2, ERR)
1398
- end
1399
- end
1395
+ return invalid(tag, mth, 2)
1396
+ when :index
1397
+ return mismatch(tag, s[k], Integer, mth) unless s[k].is_a?(Integer)
1398
+ return invalid(tag, mth, 2) unless s[k].between?(0, lc.numLayers - 1)
1399
+ else
1400
+ return mismatch(tag, s[k], Numeric, mth) unless s[k].is_a?(Numeric)
1401
+ next if k == :heatloss
1400
1402
 
1401
- if lc.nameString.downcase.include?(" tbd")
1402
- log(WRN, "Won't derate '#{id}': tagged as derated (#{mth})")
1403
- return m
1403
+ return negative(tag, mth, 2) if s[k] < 0
1404
+ return zero(tag, mth) if s[k].abs < 0.001
1405
+ end
1404
1406
  end
1405
1407
 
1406
1408
  model = lc.model
1407
- ltype = s[:ltype ]
1408
- index = s[:index ].to_i
1409
- net = s[:net ].to_f
1410
- r = s[:r ].to_f
1411
- u = s[:heatloss].to_f / net
1409
+ ltype = s[:ltype]
1410
+ index = s[:index]
1411
+ net = s[:net]
1412
+ r = s[:r]
1413
+ u = s[:heatloss] / net
1412
1414
  loss = 0
1413
- de_u = 1 / r + u # derated U
1414
- de_r = 1 / de_u # derated R
1415
+ de_u = 1 / r + u # derated insulating material U
1416
+ de_r = 1 / de_u # derated insulating material R
1415
1417
 
1416
1418
  if ltype == :massless
1417
- m = lc.getLayer(index).to_MasslessOpaqueMaterial
1419
+ m = lc.getLayer(index).to_MasslessOpaqueMaterial
1418
1420
  return invalid("#{id} massless layer?", mth, 0) if m.empty?
1419
- m = m.get
1420
- up = ""
1421
- up = "uprated " if m.nameString.downcase.include?(" uprated")
1422
- m = m.clone(model).to_MasslessOpaqueMaterial.get
1423
- m.setName("#{id} #{up}m tbd")
1424
- de_r = 0.001 unless de_r > 0.001
1425
- loss = (de_u - 1 / de_r) * net unless de_r > 0.001
1426
- m.setThermalResistance(de_r)
1421
+
1422
+ m = m.get
1423
+ up = m.nameString.downcase.include?(" uprated") ? "uprated " : ""
1424
+ m = m.clone(model).to_MasslessOpaqueMaterial.get
1425
+ m.setName("#{id} #{up}m tbd")
1426
+
1427
+ de_r = RMIN unless de_r > RMIN
1428
+ loss = (de_u - 1 / de_r) * net unless de_r > RMIN
1429
+
1430
+ unless m.setThermalResistance(de_r)
1431
+ return invalid("Can't derate #{id}: RSi#{de_r.round(2)}", mth)
1432
+ end
1427
1433
  else
1428
- m = lc.getLayer(index).to_StandardOpaqueMaterial
1434
+ m = lc.getLayer(index).to_StandardOpaqueMaterial
1429
1435
  return invalid("#{id} standard layer?", mth, 0) if m.empty?
1430
- m = m.get
1431
- up = ""
1432
- up = "uprated " if m.nameString.downcase.include?(" uprated")
1433
- m = m.clone(model).to_StandardOpaqueMaterial.get
1434
- m.setName("#{id} #{up}m tbd")
1435
- k = m.thermalConductivity
1436
-
1437
- if de_r > 0.001
1438
- d = de_r * k
1439
-
1440
- unless d > 0.003
1441
- d = 0.003
1442
- k = d / de_r
1443
- k = 3 unless k < 3
1444
- loss = (de_u - k / d) * net unless k < 3
1445
- end
1446
- else # de_r < 0.001 m2•K/W
1447
- d = 0.001 * k
1448
- d = 0.003 unless d > 0.003
1449
- k = d / 0.001 unless d > 0.003
1450
- loss = (de_u - k / d) * net
1436
+
1437
+ m = m.get
1438
+ up = m.nameString.downcase.include?(" uprated") ? "uprated " : ""
1439
+ m = m.clone(model).to_StandardOpaqueMaterial.get
1440
+ m.setName("#{id} #{up}m tbd")
1441
+
1442
+ d = m.thickness
1443
+ k = (d / de_r).clamp(KMIN, KMAX)
1444
+ d = (k * de_r).clamp(DMIN, DMAX)
1445
+
1446
+ loss = (de_u - k / d) * net unless d / k > RMIN
1447
+
1448
+ unless m.setThermalConductivity(k)
1449
+ return invalid("Can't derate #{id}: K#{k.round(3)}", mth)
1451
1450
  end
1452
1451
 
1453
- m.setThickness(d)
1454
- m.setThermalConductivity(k)
1452
+ unless m.setThickness(d)
1453
+ return invalid("Can't derate #{id}: #{(d*1000).to_i}mm", mth)
1454
+ end
1455
1455
  end
1456
1456
 
1457
1457
  if m && loss > TOL
@@ -1941,14 +1941,38 @@ module TBD
1941
1941
  ids = windows.keys + doors.keys + skylights.keys
1942
1942
  end
1943
1943
 
1944
- unless ids.include?(i)
1945
- log(ERR, "Orphaned subsurface #{i} (mth)")
1944
+ adj = nil
1945
+
1946
+ unless ids.include?(i) # adjacent sub surface?
1947
+ sb = model.getSubSurfaceByName(i)
1948
+
1949
+ if sb.empty?
1950
+ log(DBG, "Orphaned subsurface #{i} (#{mth})?")
1951
+ else
1952
+ sb = sb.get
1953
+ adj = sb.adjacentSubSurface
1954
+
1955
+ if adj.empty?
1956
+ log(DBG, "Orphaned sub #{i} (#{mth})?")
1957
+ end
1958
+ end
1959
+
1946
1960
  next
1947
1961
  end
1948
1962
 
1949
- window = windows.key?(i) ? windows[i] : {}
1950
- door = doors.key?(i) ? doors[i] : {}
1951
- skylight = skylights.key?(i) ? skylights[i] : {}
1963
+ if adj
1964
+ window = windows.key?(i) ? windows[i] : {}
1965
+ door = doors.key?(i) ? doors[i] : {}
1966
+ skylight = skylights.key?(i) ? skylights[i] : {}
1967
+ else
1968
+ window = windows.key?(i) ? windows[i] : {}
1969
+ door = doors.key?(i) ? doors[i] : {}
1970
+ skylight = skylights.key?(i) ? skylights[i] : {}
1971
+ end
1972
+
1973
+ # window = windows.key?(i) ? windows[i] : {}
1974
+ # door = doors.key?(i) ? doors[i] : {}
1975
+ # skylight = skylights.key?(i) ? skylights[i] : {}
1952
1976
 
1953
1977
  sub = window unless window.empty?
1954
1978
  sub = door unless door.empty?
@@ -2939,12 +2963,12 @@ module TBD
2939
2963
  up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
2940
2964
  uprate(model, tbd[:surfaces], argh) if up
2941
2965
 
2942
- # Derated (cloned) constructions are unique to each deratable surface.
2943
- # Unique construction names are prefixed with the surface name,
2944
- # and suffixed with " tbd", indicating that the construction is
2945
- # henceforth thermally derated. The " tbd" expression is also key in
2946
- # avoiding inadvertent derating - TBD will not derate constructions
2947
- # (or rather layered materials) having " tbd" in their OpenStudio name.
2966
+ # A derated (cloned) construction and (cloned) insulating layer are unique
2967
+ # to each deratable surface. Unique construction and material names are
2968
+ # prefixed with the surface name, and suffixed with " tbd", indicating that
2969
+ # the construction is henceforth thermally derated. The " tbd" expression
2970
+ # is also key in avoiding inadvertent sequential derating - TBD will not
2971
+ # derate a construction/material pair having " tbd" in their OpenStudio name.
2948
2972
  tbd[:surfaces].each do |id, surface|
2949
2973
  next unless surface.key?(:construction)
2950
2974
  next unless surface.key?(:index)
@@ -3138,9 +3162,9 @@ module TBD
3138
3162
  argh[:uprate_walls ] = false unless argh.key?(:uprate_walls )
3139
3163
  argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs )
3140
3164
  argh[:uprate_floors] = false unless argh.key?(:uprate_floors)
3141
- argh[:wall_ut ] = 5.678 unless argh.key?(:wall_ut )
3142
- argh[:roof_ut ] = 5.678 unless argh.key?(:roof_ut )
3143
- argh[:floor_ut ] = 5.678 unless argh.key?(:floor_ut )
3165
+ argh[:wall_ut ] = UMAX unless argh.key?(:wall_ut )
3166
+ argh[:roof_ut ] = UMAX unless argh.key?(:roof_ut )
3167
+ argh[:floor_ut ] = UMAX unless argh.key?(:floor_ut )
3144
3168
  argh[:wall_option ] = "" unless argh.key?(:wall_option )
3145
3169
  argh[:roof_option ] = "" unless argh.key?(:roof_option )
3146
3170
  argh[:floor_option ] = "" unless argh.key?(:floor_option )
@@ -1,121 +1,121 @@
1
- require 'matrix'
2
-
3
- class Matrix
4
- def []=(i, j, x)
5
- @rows[i][j] = x
6
- end
7
- end
8
-
9
- module Topolys
10
-
11
- # Transformations can be applied to Geometry. Ported from OpenStudio Transformation library.
12
-
13
- class Transformation
14
-
15
- # @return [Matrix] internal 4x4 matrix
16
- attr_reader :matrix
17
-
18
- ##
19
- # Initializes an Transformation object
20
- #
21
- # @param [Matrix] matrix A 4x4 matrix, defaults to identity
22
- def initialize(matrix=Matrix.identity(4))
23
- raise "Incorrect argument for Transformation, expected Matrix but got #{matrix.class}" unless matrix.is_a?(Matrix)
24
- @matrix = matrix
25
- end
26
-
27
- # translation along vector
28
- def Transformation.translation(translation)
29
- return nil if !translation.is_a?(Vector3D)
30
-
31
- matrix = Matrix.identity(4)
32
- matrix[0,3] = translation.x
33
- matrix[1,3] = translation.y
34
- matrix[2,3] = translation.z
35
-
36
- return Transformation.new(matrix)
37
- end
38
-
39
- ##
40
- # Initializes a rotation about origin defined by axis and angle (radians)
41
- #
42
- def Transformation.rotation(axis, radians)
43
- return nil if !axis.is_a?(Vector3D)
44
- return nil if !radians.is_a?(Numeric)
45
- return nil if (axis.magnitude < Float::EPSILON)
46
- normal = axis
47
- normal.normalize!
48
-
49
- # Rodrigues' rotation formula / Rotation matrix from Euler axis/angle
50
- # I*cos(radians) + I*(1-cos(radians))*axis*axis^T + Q*sin(radians)
51
- # Q = [0, -axis[2], axis[1]; axis[2], 0, -axis[0]; -axis[1], axis[0], 0]
52
- p = normal.outer_product(normal)
53
- i = Matrix.identity(3)
54
- q = Matrix.zero(3)
55
- q[0,1] = -normal.z
56
- q[0,2] = normal.y
57
- q[1,0] = normal.z
58
- q[1,2] = -normal.x
59
- q[2,0] = -normal.y
60
- q[2,1] = normal.x
61
-
62
- # rotation matrix
63
- r = i*Math.cos(radians) + (1-Math.cos(radians))*p + q*Math.sin(radians)
64
-
65
- matrix = Matrix.identity(4)
66
- matrix[0,0] = r[0,0]
67
- matrix[0,1] = r[0,1]
68
- matrix[0,2] = r[0,2]
69
- matrix[1,0] = r[1,0]
70
- matrix[1,1] = r[1,1]
71
- matrix[1,2] = r[1,2]
72
- matrix[2,0] = r[2,0]
73
- matrix[2,1] = r[2,1]
74
- matrix[2,2] = r[2,2]
75
-
76
- return Transformation.new(matrix)
77
- end
78
-
79
- ##
80
- # Multiplies a Transformation by geometry class
81
- #
82
- # @param [Obj] obj A geometry object
83
- #
84
- # @return [Obj] Returns a new, transformed object - nil if not a geometry object
85
- def *(obj)
86
- if obj.is_a?(Point3D)
87
- return mult_point(obj)
88
- elsif obj.is_a?(Vector3D)
89
- return mult_vector(obj)
90
- elsif obj.is_a?(Array)
91
- return mult_array(obj)
92
- elsif obj.is_a?(Transformation)
93
- return mult_transformation(obj)
94
- end
95
- return nil
96
- end
97
-
98
- private
99
-
100
- def mult_point(point)
101
- temp = Matrix.column_vector([point.x, point.y, point.z, 1])
102
- temp = @matrix*temp
103
- return Point3D.new(temp[0,0],temp[1,0],temp[2,0])
104
- end
105
-
106
- def mult_vector(vector)
107
- temp = Matrix.column_vector([vector.x, vector.y, vector.z, 1])
108
- temp = @matrix*temp
109
- return Vector3D.new(temp[0,0],temp[1,0],temp[2,0])
110
- end
111
-
112
- def mult_array(array)
113
- array.map {|obj| self*obj}
114
- end
115
-
116
- def mult_transformation(obj)
117
- Transformation.new(@matrix * obj.matrix)
118
- end
119
-
120
- end
1
+ require 'matrix'
2
+
3
+ class Matrix
4
+ def []=(i, j, x)
5
+ @rows[i][j] = x
6
+ end
7
+ end
8
+
9
+ module Topolys
10
+
11
+ # Transformations can be applied to Geometry. Ported from OpenStudio Transformation library.
12
+
13
+ class Transformation
14
+
15
+ # @return [Matrix] internal 4x4 matrix
16
+ attr_reader :matrix
17
+
18
+ ##
19
+ # Initializes an Transformation object
20
+ #
21
+ # @param [Matrix] matrix A 4x4 matrix, defaults to identity
22
+ def initialize(matrix=Matrix.identity(4))
23
+ raise "Incorrect argument for Transformation, expected Matrix but got #{matrix.class}" unless matrix.is_a?(Matrix)
24
+ @matrix = matrix
25
+ end
26
+
27
+ # translation along vector
28
+ def Transformation.translation(translation)
29
+ return nil if !translation.is_a?(Vector3D)
30
+
31
+ matrix = Matrix.identity(4)
32
+ matrix[0,3] = translation.x
33
+ matrix[1,3] = translation.y
34
+ matrix[2,3] = translation.z
35
+
36
+ return Transformation.new(matrix)
37
+ end
38
+
39
+ ##
40
+ # Initializes a rotation about origin defined by axis and angle (radians)
41
+ #
42
+ def Transformation.rotation(axis, radians)
43
+ return nil if !axis.is_a?(Vector3D)
44
+ return nil if !radians.is_a?(Numeric)
45
+ return nil if (axis.magnitude < Float::EPSILON)
46
+ normal = axis
47
+ normal.normalize!
48
+
49
+ # Rodrigues' rotation formula / Rotation matrix from Euler axis/angle
50
+ # I*cos(radians) + I*(1-cos(radians))*axis*axis^T + Q*sin(radians)
51
+ # Q = [0, -axis[2], axis[1]; axis[2], 0, -axis[0]; -axis[1], axis[0], 0]
52
+ p = normal.outer_product(normal)
53
+ i = Matrix.identity(3)
54
+ q = Matrix.zero(3)
55
+ q[0,1] = -normal.z
56
+ q[0,2] = normal.y
57
+ q[1,0] = normal.z
58
+ q[1,2] = -normal.x
59
+ q[2,0] = -normal.y
60
+ q[2,1] = normal.x
61
+
62
+ # rotation matrix
63
+ r = i*Math.cos(radians) + (1-Math.cos(radians))*p + q*Math.sin(radians)
64
+
65
+ matrix = Matrix.identity(4)
66
+ matrix[0,0] = r[0,0]
67
+ matrix[0,1] = r[0,1]
68
+ matrix[0,2] = r[0,2]
69
+ matrix[1,0] = r[1,0]
70
+ matrix[1,1] = r[1,1]
71
+ matrix[1,2] = r[1,2]
72
+ matrix[2,0] = r[2,0]
73
+ matrix[2,1] = r[2,1]
74
+ matrix[2,2] = r[2,2]
75
+
76
+ return Transformation.new(matrix)
77
+ end
78
+
79
+ ##
80
+ # Multiplies a Transformation by geometry class
81
+ #
82
+ # @param [Obj] obj A geometry object
83
+ #
84
+ # @return [Obj] Returns a new, transformed object - nil if not a geometry object
85
+ def *(obj)
86
+ if obj.is_a?(Point3D)
87
+ return mult_point(obj)
88
+ elsif obj.is_a?(Vector3D)
89
+ return mult_vector(obj)
90
+ elsif obj.is_a?(Array)
91
+ return mult_array(obj)
92
+ elsif obj.is_a?(Transformation)
93
+ return mult_transformation(obj)
94
+ end
95
+ return nil
96
+ end
97
+
98
+ private
99
+
100
+ def mult_point(point)
101
+ temp = Matrix.column_vector([point.x, point.y, point.z, 1])
102
+ temp = @matrix*temp
103
+ return Point3D.new(temp[0,0],temp[1,0],temp[2,0])
104
+ end
105
+
106
+ def mult_vector(vector)
107
+ temp = Matrix.column_vector([vector.x, vector.y, vector.z, 1])
108
+ temp = @matrix*temp
109
+ return Vector3D.new(temp[0,0],temp[1,0],temp[2,0])
110
+ end
111
+
112
+ def mult_array(array)
113
+ array.map {|obj| self*obj}
114
+ end
115
+
116
+ def mult_transformation(obj)
117
+ Transformation.new(@matrix * obj.matrix)
118
+ end
119
+
120
+ end
121
121
  end