tbd 3.2.0 → 3.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92789b60b438d1518f82d2596a12314ea5c6903de2bd902f1cbbea1a2379fd1d
4
- data.tar.gz: e0e17c13b6db89a38fe39a9f8a6f4e2249e09976c08049250da3d6546689064f
3
+ metadata.gz: 858b576df6d2a7c00b547c1bcb86fcae917f441c236f012610309b5df96f2d19
4
+ data.tar.gz: ee47f03ce21e7ef8d95968aa0d159a39875ca546c32b48c8f96d7779b1ef341d
5
5
  SHA512:
6
- metadata.gz: a054444e9871a6a6844c4535913b50700b94588f338434e47b8dd41ec6d73c78df8f2efd773a4bc1f86d25cd37216b7ebe48ba94e18a6f4e8547b2be987c5ecb
7
- data.tar.gz: 360c811de3c35599d73741985d80df474379a29fbad16714a0afa553b4719d50b41415f18006462176f9adc26377980507e535c616a64b77f0398b24d3fa435c
6
+ metadata.gz: 1d7d4ad54c0d8d06776b868037a717d8efc11d4f1e1290cbf972202bae04ef4e6e6ea21cf227a8405c508c1d31f1bbb9e5667a21af9186984b28eee19a5e4395
7
+ data.tar.gz: eaae502337964abffe89643e4c1e6070f3a228db3ef6d87983f0cc7c9218f124e72eae6e502bd06ff7119fd594ed784d3f4232a3bd1ddb03c1d23f7bcce1c951
data/README.md CHANGED
@@ -32,9 +32,7 @@ TBD is systematically tested against updated OpenStudio versions (since v3.0.0).
32
32
 
33
33
  ### Windows Installation
34
34
 
35
- Install Ruby using the [RubyInstaller](https://rubyinstaller.org/downloads/archives/) for [Ruby 2.7.2 (x64)](https://github.com/oneclick/rubyinstaller2/releases/tag/RubyInstaller-2.7.2-1/rubyinstaller-2.7.2-1-x64.exe).
36
-
37
- From the command line, check that the ruby installation returns the correct Ruby version:
35
+ Either install Ruby using the [RubyInstaller](https://rubyinstaller.org/downloads/archives/) for [Ruby 2.7.2 (x64)](https://github.com/oneclick/rubyinstaller2/releases/tag/RubyInstaller-2.7.2-1/rubyinstaller-2.7.2-1-x64.exe), or preferably under [WSL2](https://gist.github.com/brgix/0d968d8f32c41f13300dc6769414df79). Run the following steps if going down the _RubyInstaller_ route. From the command line, check that the ruby installation returns the correct Ruby version:
38
36
  ```
39
37
  ruby -v
40
38
  ```
@@ -57,9 +55,6 @@ Verify your OpenStudio and Ruby configuration:
57
55
  ruby -e "require 'openstudio'" -e "puts OpenStudio::Model::Model.new"
58
56
  ```
59
57
 
60
- `git clone` the TBD repo, then run basic tests to ensure the measure operates properly (see end of this README).
61
-
62
-
63
58
  ### MacOS Installation
64
59
 
65
60
  MacOS already comes with Ruby, but likely not the right Ruby version for the desired OpenStudio measure development [environment](https://github.com/NREL/OpenStudio/wiki/OpenStudio-SDK-Version-Compatibility-Matrix). Instructions here show how to install Ruby v2.7.2 alongside MacOS's own Ruby version. Although no longer officially supported, instructions for an OpenStudio v2.9.1 setup is described [here](https://github.com/rd2/tbd/blob/master/v291_MacOS.md).
@@ -117,8 +112,9 @@ cd ~/Documents/sandbox340
117
112
  ruby -e "require 'openstudio'" -e "puts OpenStudio::Model::Model.new"
118
113
  ```
119
114
 
120
- Install the latest version of _git_ (e.g. through Homebrew), then ```git clone``` the TBD repo, e.g. under "sandbox340". Run the basic tests below to ensure the measure operates as expected.
115
+ ## Clone TBD
121
116
 
117
+ Once done with either the Windows or MacOS setup, install the latest version of _git_ (e.g. through Homebrew), then ```git clone``` the TBD repo, e.g. under "sandbox340". Run the basic tests below to ensure the measure operates as expected.
122
118
 
123
119
  ## Complete list of test commands
124
120
 
data/Rakefile CHANGED
@@ -2,28 +2,29 @@ require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec) do |t|
5
- t.rspec_opts = '--exclude-pattern \'spec/**/*suite_spec.rb\''
5
+ t.rspec_opts = "--exclude-pattern \'spec/**/*suite_spec.rb\'"
6
6
  end
7
7
 
8
8
  task default: :spec
9
9
 
10
10
  desc "Update Library Files"
11
11
  task :libraries do
12
- puts "Updating Library Files"
12
+ # puts "Updating Library Files"
13
13
 
14
14
  require "fileutils"
15
15
 
16
16
  libs = ["topolys", "osut", "oslg", "tbd"]
17
17
  files = {}
18
-
18
+
19
19
  $:.each do |path|
20
20
  libs.each do |l|
21
21
  next unless path.include?(l)
22
+
22
23
  files[l] = Dir.glob(File.join(path, "#{l}/*.rb"))
23
24
  files[l].delete_if { |f| f.include?("version.rb") } unless l == "topolys"
24
- puts "#{l} lib files:"
25
- files[l].each { |lf| puts "... #{lf}" }
26
- puts
25
+ # puts "#{l} lib files:"
26
+ # files[l].each { |lf| puts "... #{lf}" }
27
+ # puts
27
28
  end
28
29
  end
29
30
 
@@ -38,7 +39,7 @@ end
38
39
 
39
40
  desc "Update Measure"
40
41
  task measure: [:libraries] do
41
- puts "Updating Measure"
42
+ # puts "Updating Measure"
42
43
 
43
44
  require "openstudio"
44
45
  require "open3"
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.0</schema_version>
4
4
  <name>tbd_measure</name>
5
5
  <uid>8890787b-8c25-4dc8-8641-b6be1b6c2357</uid>
6
- <version_id>216d4e2e-fab7-49c6-95e9-7262edbf3277</version_id>
7
- <version_modified>20230111T223650Z</version_modified>
6
+ <version_id>ca5db4f4-8624-4699-a544-609690a03d14</version_id>
7
+ <version_modified>20230322T224647Z</version_modified>
8
8
  <xml_checksum>99772807</xml_checksum>
9
9
  <class_name>TBDMeasure</class_name>
10
10
  <display_name>Thermal Bridging and Derating - TBD</display_name>
@@ -388,18 +388,6 @@
388
388
  <usage_type>resource</usage_type>
389
389
  <checksum>D80E9AE6</checksum>
390
390
  </file>
391
- <file>
392
- <filename>model.rb</filename>
393
- <filetype>rb</filetype>
394
- <usage_type>resource</usage_type>
395
- <checksum>57ED37BF</checksum>
396
- </file>
397
- <file>
398
- <filename>version.rb</filename>
399
- <filetype>rb</filetype>
400
- <usage_type>resource</usage_type>
401
- <checksum>05CC939E</checksum>
402
- </file>
403
391
  <file>
404
392
  <filename>LICENSE.md</filename>
405
393
  <filetype>md</filetype>
@@ -442,28 +430,40 @@
442
430
  <checksum>58ED6635</checksum>
443
431
  </file>
444
432
  <file>
445
- <filename>ua.rb</filename>
433
+ <filename>README.md</filename>
434
+ <filetype>md</filetype>
435
+ <usage_type>readme</usage_type>
436
+ <checksum>B836C43A</checksum>
437
+ </file>
438
+ <file>
439
+ <filename>psi.rb</filename>
446
440
  <filetype>rb</filetype>
447
441
  <usage_type>resource</usage_type>
448
- <checksum>9EBACA60</checksum>
442
+ <checksum>1045EF38</checksum>
449
443
  </file>
450
444
  <file>
451
- <filename>geo.rb</filename>
445
+ <filename>model.rb</filename>
452
446
  <filetype>rb</filetype>
453
447
  <usage_type>resource</usage_type>
454
- <checksum>F447D8CE</checksum>
448
+ <checksum>8E9A76C7</checksum>
455
449
  </file>
456
450
  <file>
457
- <filename>README.md</filename>
458
- <filetype>md</filetype>
459
- <usage_type>readme</usage_type>
460
- <checksum>B836C43A</checksum>
451
+ <filename>version.rb</filename>
452
+ <filetype>rb</filetype>
453
+ <usage_type>resource</usage_type>
454
+ <checksum>A3BB982A</checksum>
461
455
  </file>
462
456
  <file>
463
- <filename>psi.rb</filename>
457
+ <filename>geo.rb</filename>
458
+ <filetype>rb</filetype>
459
+ <usage_type>resource</usage_type>
460
+ <checksum>8242FCEF</checksum>
461
+ </file>
462
+ <file>
463
+ <filename>ua.rb</filename>
464
464
  <filetype>rb</filetype>
465
465
  <usage_type>resource</usage_type>
466
- <checksum>E6ED8157</checksum>
466
+ <checksum>695F5AC2</checksum>
467
467
  </file>
468
468
  </files>
469
469
  </measure>
@@ -292,28 +292,31 @@ module TBD
292
292
 
293
293
  return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
294
294
  return mismatch("surface", surface, cl2, mth) unless surface.is_a?(cl2)
295
-
296
- return nil unless validate(surface)
295
+ return nil unless validate(surface)
297
296
 
298
297
  nom = surface.nameString
299
298
  surf = {}
300
299
  subs = {}
301
300
  fd = false
302
301
  return empty("'#{nom}' space", mth, ERR) if surface.space.empty?
302
+
303
303
  space = surface.space.get
304
304
  stype = space.spaceType
305
305
  story = space.buildingStory
306
306
  tr = transforms(model, space)
307
307
  return invalid("'#{nom}' transform", mth, 0, FTL) unless tr[:t] && tr[:r]
308
+
308
309
  t = tr[:t]
309
310
  n = trueNormal(surface, tr[:r])
310
311
  return invalid("'#{nom}' normal", mth, 0, FTL) unless n
312
+
311
313
  type = surface.surfaceType.downcase
312
314
  facing = surface.outsideBoundaryCondition
313
315
 
314
316
  if facing.downcase == "surface"
315
317
  empty = surface.adjacentSurface.empty?
316
- return invalid("'#{nom}': adjacent surface", mth, 0, ERR) if empty
318
+ return invalid("'#{nom}': adjacent surface", mth, 0, ERR) if empty
319
+
317
320
  facing = surface.adjacentSurface.get.nameString
318
321
  end
319
322
 
@@ -350,6 +353,7 @@ module TBD
350
353
  surf[:story ] = story.get unless story.empty?
351
354
  surf[:n ] = n
352
355
  surf[:gross ] = surface.grossArea
356
+ surf[:filmRSI ] = surface.filmResistance
353
357
 
354
358
  surface.subSurfaces.sort_by { |s| s.nameString }.each do |s|
355
359
  next unless validate(s)
@@ -1,9 +1,3 @@
1
- begin
2
- require "topolys/version"
3
- rescue LoadError
4
- require File.join(File.dirname(__FILE__), 'version.rb')
5
- end
6
-
7
1
  require 'json'
8
2
  require 'securerandom'
9
3
  require 'set'
@@ -975,8 +975,9 @@ module TBD
975
975
  end
976
976
  end
977
977
 
978
- surface[:heating] = heat[:spt] if heat[:spt] # if valid heating setpoints
979
- surface[:cooling] = cool[:spt] if cool[:spt] # if valid cooling setpoints
978
+ # Recover if valid setpoints.
979
+ surface[:heating] = heat[:spt] if heat && heat[:spt]
980
+ surface[:cooling] = cool[:spt] if cool && cool[:spt]
980
981
 
981
982
  tbd[:surfaces][s.nameString] = surface
982
983
  end # (opaque) surfaces populated
@@ -1486,7 +1487,7 @@ module TBD
1486
1487
 
1487
1488
  edge[:surfaces].keys.each do |i|
1488
1489
  break if is[:rimjoist] || is[:balcony]
1489
- break unless deratables.size == 2
1490
+ break unless deratables.size > 0
1490
1491
  break if floors.key?(id)
1491
1492
  next if i == id
1492
1493
  next unless floors.key?(i)
@@ -39,13 +39,14 @@ module TBD
39
39
  cl1 = OpenStudio::Model::Model
40
40
  cl2 = OpenStudio::Model::LayeredConstruction
41
41
  cl3 = Numeric
42
+ cl4 = String
42
43
 
43
- return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
44
- return mismatch("id", id, String, mth, DBG, res) unless id.is_a?(String)
45
- return mismatch("lc", lc, cl2, mth, DBG, res) unless lc.is_a?(cl2)
46
- return mismatch("hloss", hloss, cl3, mth, DBG, res) unless hloss.is_a?(cl3)
47
- return mismatch("film", film, cl3, mth, DBG, res) unless film.is_a?(cl3)
48
- return mismatch("Ut", ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
44
+ return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
45
+ return mismatch("id" , id, cl4, mth, DBG, res) unless id.is_a?(cl4)
46
+ return mismatch("lc" , lc, cl2, mth, DBG, res) unless lc.is_a?(cl2)
47
+ return mismatch("hloss", hloss, cl3, mth, DBG, res) unless hloss.is_a?(cl3)
48
+ return mismatch("film" , film, cl3, mth, DBG, res) unless film.is_a?(cl3)
49
+ return mismatch("Ut" , ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
49
50
 
50
51
  loss = 0.0 # residual heatloss (not assigned) [W/K]
51
52
  area = lc.getNetArea
@@ -54,12 +55,12 @@ module TBD
54
55
  lyr[:index] = nil unless lyr[:index] >= 0
55
56
  lyr[:index] = nil unless lyr[:index] < lc.layers.size
56
57
 
57
- return invalid("'#{id}' layer index", mth, 0, ERR, res) unless lyr[:index]
58
- return zero("'#{id}': heatloss", mth, WRN, res) unless hloss > TOL
59
- return zero("'#{id}': films", mth, WRN, res) unless film > TOL
60
- return zero("'#{id}': Ut", mth, WRN, res) unless ut > TOL
61
- return invalid("'#{id}': Ut", mth, 0, WRN, res) unless ut < 5.678
62
- return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
58
+ return invalid("'#{id}' layer index", mth, 0, ERR, res) unless lyr[:index]
59
+ return zero("'#{id}': heatloss" , mth, WRN, res) unless hloss > TOL
60
+ return zero("'#{id}': films" , mth, WRN, res) unless film > TOL
61
+ return zero("'#{id}': Ut" , mth, WRN, res) unless ut > TOL
62
+ return invalid("'#{id}': Ut" , mth, 0, WRN, res) unless ut < 5.678
63
+ return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
63
64
 
64
65
  # First, calculate initial layer RSi to initially meet Ut target.
65
66
  rt = 1 / ut # target construction Rt
@@ -76,7 +77,7 @@ module TBD
76
77
 
77
78
  if lyr[:type] == :massless
78
79
  m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
79
- return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
80
+ return invalid("'#{id}' massless layer?", mth, 0, DBG, res) if m.empty?
80
81
 
81
82
  m = m.get.clone(model).to_MasslessOpaqueMaterial.get
82
83
  m.setName("#{id} uprated")
@@ -85,7 +86,7 @@ module TBD
85
86
  m.setThermalResistance(new_r)
86
87
  else # type == :standard
87
88
  m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
88
- return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
89
+ return invalid("'#{id}' standard layer?", mth, 0, DBG, res) if m.empty?
89
90
 
90
91
  m = m.get.clone(model).to_StandardOpaqueMaterial.get
91
92
  m.setName("#{id} uprated")
@@ -141,11 +142,12 @@ module TBD
141
142
  mth = "TBD::#{__callee__}"
142
143
  cl1 = OpenStudio::Model::Model
143
144
  cl2 = Hash
145
+ cl3 = OpenStudio::Model::LayeredConstruction
144
146
  a = false
145
147
 
146
- return mismatch("model", model, cl1, mth, DBG, a) unless model.is_a?(cl1)
147
- return mismatch("surfaces", s, cl2, mth, DBG, a) unless s.is_a?(cl2)
148
- return mismatch("argh", model, cl1, mth, DBG, a) unless argh.is_a?(cl2)
148
+ return mismatch("model" , model, cl1, mth, DBG, a) unless model.is_a?(cl1)
149
+ return mismatch("surfaces", s, cl2, mth, DBG, a) unless s.is_a?(cl2)
150
+ return mismatch("argh" , model, cl1, mth, DBG, a) unless argh.is_a?(cl2)
149
151
 
150
152
  argh[:uprate_walls ] = false unless argh.key?(:uprate_walls )
151
153
  argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs )
@@ -168,11 +170,13 @@ module TBD
168
170
  groups[:roof ][:op] = argh[:roof_option ]
169
171
  groups[:floor][:op] = argh[:floor_option ]
170
172
 
171
- groups.each do |label, g|
173
+ groups.each do |type, g|
172
174
  next unless g[:up]
173
175
  next unless g[:ut].is_a?(Numeric)
174
176
  next unless g[:ut] < 5.678
175
177
 
178
+ typ = type
179
+ typ = :ceiling if typ == :roof # fix in future revision. TO-DO.
176
180
  coll = {}
177
181
  area = 0
178
182
  film = 100000000000000
@@ -183,86 +187,81 @@ module TBD
183
187
  g[:op].downcase == "all floor constructions"
184
188
 
185
189
  if g[:op].empty?
186
- log(ERR, "Construction to uprate? (#{mth})")
190
+ log(ERR, "Construction (#{type}) to uprate? (#{mth})")
187
191
  elsif all
188
- model.getSurfaces.each do |sss|
189
- next unless sss.surfaceType.downcase.include?(label.to_s)
190
- next unless sss.outsideBoundaryCondition.downcase == "outdoors"
191
- next if sss.construction.empty?
192
- next if sss.construction.get.to_LayeredConstruction.empty?
193
-
194
- c = sss.construction.get.to_LayeredConstruction.get
195
- i = c.nameString
196
-
197
- # Reliable unless referenced by other surface types e.g. floor vs wall.
198
- if c.getNetArea > area
199
- area = c.getNetArea
192
+ s.each do |nom, surface|
193
+ next unless surface.key?(:deratable )
194
+ next unless surface.key?(:type )
195
+ next unless surface.key?(:construction)
196
+ next unless surface.key?(:filmRSI )
197
+ next unless surface.key?(:index )
198
+ next unless surface.key?(:ltype )
199
+ next unless surface.key?(:r )
200
+ next unless surface[:deratable ]
201
+ next unless surface[:type ] == typ
202
+ next unless surface[:construction].is_a?(cl3)
203
+ next if surface[:index ].nil?
204
+
205
+ # Retain lowest surface film resistance (e.g. tilted surfaces).
206
+ c = surface[:construction]
207
+ i = c.nameString
208
+ aire = c.getNetArea
209
+ film = surface[:filmRSI] if surface[:filmRSI] < film
210
+
211
+ # Retain construction covering largest area. The following conditional
212
+ # is reliable UNLESS linked to other deratable surface types e.g. both
213
+ # floors AND walls (see "elsif lc" corrections below).
214
+ if aire > area
200
215
  lc = c
216
+ area = aire
201
217
  id = i
202
218
  end
203
219
 
204
- film = sss.filmResistance if sss.filmResistance < film
205
- nom = sss.nameString
206
- coll[i] = { area: c.getNetArea, lc: c, s: {} } unless coll.key?(i)
207
- coll[i][:s][nom] = { a: sss.netArea } unless coll[i][:s].key?(nom)
220
+ coll[i] = { area: aire, lc: c, s: {} } unless coll.key?(i)
221
+ coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
208
222
  end
209
223
  else
210
224
  id = g[:op]
211
- c = model.getConstructionByName(id)
212
-
213
- if c.empty?
214
- log(ERR, "Construction '#{id}'? (#{mth})")
215
- else
216
- c = c.get.to_LayeredConstruction
217
-
218
- if c.empty?
219
- log(ERR, "'#{id}' layered construction? (#{mth})")
220
- else
221
- lc = c.get
222
- area = lc.getNetArea
223
- coll[id] = { area: area, lc: lc, s: {} }
224
-
225
- model.getSurfaces.each do |sss|
226
- next unless sss.surfaceType.downcase.include?(label.to_s)
227
- next unless sss.outsideBoundaryCondition.downcase == "outdoors"
228
- next if sss.construction.empty?
229
- next if sss.construction.get.to_LayeredConstruction.empty?
230
- lc = sss.construction.get.to_LayeredConstruction.get
231
- next unless id == lc.nameString
232
- nom = sss.nameString
233
- film = sss.filmResistance if sss.filmResistance < film
234
- ok = coll[id][:s].key?(nom)
235
- coll[id][:s][nom] = { a: sss.netArea } unless ok
236
- end
237
- end
225
+ lc = model.getConstructionByName(id)
226
+ log(ERR, "Construction '#{id}'? (#{mth})") if lc.empty?
227
+ next if lc.empty?
228
+
229
+ lc = lc.get.to_LayeredConstruction
230
+ log(ERR, "'#{id}' layered construction? (#{mth})") if lc.empty?
231
+ next if lc.empty?
232
+
233
+ lc = lc.get
234
+ area = lc.getNetArea
235
+ coll[id] = { area: area, lc: lc, s: {} }
236
+
237
+ s.each do |nom, surface|
238
+ next unless surface.key?(:deratable )
239
+ next unless surface.key?(:type )
240
+ next unless surface.key?(:construction)
241
+ next unless surface.key?(:filmRSI )
242
+ next unless surface.key?(:index )
243
+ next unless surface.key?(:ltype )
244
+ next unless surface.key?(:r )
245
+ next unless surface[:deratable ]
246
+ next unless surface[:type ] == typ
247
+ next unless surface[:construction].is_a?(cl3)
248
+ next if surface[:index ].nil?
249
+
250
+ i = surface[:construction].nameString
251
+ next unless i == id
252
+
253
+ # Retain lowest surface film resistance (e.g. tilted surfaces).
254
+ film = surface[:filmRSI] if surface[:filmRSI] < film
255
+
256
+ coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
238
257
  end
239
258
  end
240
259
 
241
260
  if coll.empty?
242
- log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
261
+ log(ERR, "No #{type} construction to uprate - skipping (#{mth})")
243
262
  next
244
- elsif lc # valid layered construction - good to uprate!
245
- # Ensure lc is referenced by surface types == label.
246
- model.getSurfaces.each do |sss|
247
- next if sss.construction.empty?
248
- next if sss.construction.get.to_LayeredConstruction.empty?
249
- c = sss.construction.get.to_LayeredConstruction.get
250
- i = c.nameString
251
- next unless coll.key?(i)
252
-
253
- unless sss.surfaceType.downcase.include?(label.to_s)
254
- log(ERR, "Uprating #{label.to_s}, not '#{sss.nameString}' (#{mth})")
255
- cloned = c.clone(model).to_LayeredConstruction.get
256
- cloned.setName("'#{i}' cloned")
257
- sss.setConstruction(cloned)
258
- ok = s.key?(sss.nameString)
259
- s[sss.nameString][:construction] = cloned if ok
260
- coll[i][:s].delete(sss.nameString)
261
- coll[i][:area] = c.getNetArea
262
- next
263
- end
264
- end
265
-
263
+ elsif lc
264
+ # Valid layered construction - good to uprate!
266
265
  lyr = insulatingLayer(lc)
267
266
  lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
268
267
  lyr[:index] = nil unless lyr[:index] >= 0
@@ -270,64 +269,81 @@ module TBD
270
269
 
271
270
  log(ERR, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
272
271
  next unless lyr[:index]
273
- hloss = 0 # sum of applicable psi & khi effects [W/K]
274
272
 
275
- coll.each do |i, col|
276
- next unless col.key?(:s)
277
- next unless col.is_a?(Hash)
273
+ # Ensure lc is exclusively linked to deratable surfaces of right type.
274
+ # If not, assign new lc clone to non-targeted surfaces.
275
+ s.each do |nom, surface|
276
+ next unless surface.key?(:type )
277
+ next unless surface.key?(:deratable )
278
+ next unless surface.key?(:construction)
279
+ next unless surface[:construction].is_a?(cl3)
280
+ next unless surface[:construction] == lc
281
+
282
+ ok = true
283
+ ok = false unless surface[:type ] == typ
284
+ ok = false unless surface[:deratable]
285
+ ok = false unless coll.key?(id)
286
+ ok = false unless coll[id][:s].key?(nom)
287
+
288
+ unless ok
289
+ log(WRN, "Cloning '#{nom}' construction - not '#{id}' (#{mth})")
290
+ sss = model.getSurfaceByName(nom)
291
+ next if sss.empty?
292
+
293
+ sss = sss.get
294
+ cloned = lc.clone(model).to_LayeredConstruction.get
295
+ cloned.setName("#{nom} - cloned")
296
+ sss.setConstruction(cloned)
297
+ surface[:construction] = cloned
298
+ coll[id][:s].delete(nom)
299
+ end
300
+ end
301
+
302
+ hloss = 0 # sum of applicable psi & khi effects [W/K]
278
303
 
304
+ # Tally applicable psi + khi losses. Possible construction reassignment.
305
+ coll.each do |i, col|
279
306
  col[:s].keys.each do |nom|
280
307
  next unless s.key?(nom)
281
- next unless s[nom].key?(:deratable )
282
308
  next unless s[nom].key?(:construction)
283
309
  next unless s[nom].key?(:index )
284
310
  next unless s[nom].key?(:ltype )
285
311
  next unless s[nom].key?(:r )
286
- next unless s[nom].key?(:type )
287
-
288
- next unless s[nom][:deratable]
289
- type = s[nom][:type].to_s.downcase
290
- type = "roof" if type == "ceiling"
291
- next unless type.include?(label.to_s)
292
312
 
293
313
  # Tally applicable psi + khi.
294
- hloss += s[nom][:heatloss] if s[nom].key?(:heatloss)
295
-
296
- # Skip construction reassignment if already referencing right one.
297
- unless s[nom][:construction] == lc
298
- sss = model.getSurfaceByName(nom)
299
- next if sss.empty?
300
- sss = sss.get
301
-
302
- if sss.isConstructionDefaulted
303
- set = defaultConstructionSet(model, sss)
304
- constructions = set.defaultExteriorSurfaceConstructions.get
305
-
306
- case sss.surfaceType.downcase
307
- when "roofceiling"
308
- constructions.setRoofCeilingConstruction(lc)
309
- when "floor"
310
- constructions.setFloorConstruction(lc)
311
- else
312
- constructions.setWallConstruction(lc)
313
- end
314
- else
315
- sss.setConstruction(lc)
316
- end
314
+ hloss += s[nom][:heatloss ] if s[nom].key?(:heatloss)
315
+ next if s[nom][:construction] == lc
316
+
317
+ # Reassign construction unless referencing lc.
318
+ sss = model.getSurfaceByName(nom)
319
+ next if sss.empty?
317
320
 
318
- s[nom][:construction] = lc # reset TBD attributes
319
- s[nom][:index ] = lyr[:index]
320
- s[nom][:ltype ] = lyr[:type ]
321
- s[nom][:r ] = lyr[:r ] # temporary
321
+ sss = sss.get
322
+
323
+ if sss.isConstructionDefaulted
324
+ set = defaultConstructionSet(model, sss) # building? story?
325
+ constructions = set.defaultExteriorSurfaceConstructions
326
+
327
+ unless constructions.empty?
328
+ constructions = constructions.get
329
+ constructions.setWallConstruction(lc) if typ == :wall
330
+ constructions.setFloorConstruction(lc) if typ == :floor
331
+ constructions.setRoofCeilingConstruction(lc) if typ == :ceiling
332
+ end
333
+ else
334
+ sss.setConstruction(lc)
322
335
  end
336
+
337
+ s[nom][:construction] = lc # reset TBD attributes
338
+ s[nom][:index ] = lyr[:index]
339
+ s[nom][:ltype ] = lyr[:type ]
340
+ s[nom][:r ] = lyr[:r ] # temporary
323
341
  end
324
342
  end
325
343
 
326
344
  # Merge to ensure a single entry for coll Hash.
327
345
  coll.each do |i, col|
328
346
  next if i == id
329
- next unless coll.key?(id)
330
- coll[id][:area] += col[:area]
331
347
 
332
348
  col[:s].each do |nom, sss|
333
349
  coll[id][:s][nom] = sss unless coll[id][:s].key?(nom)
@@ -338,6 +354,8 @@ module TBD
338
354
  log(DBG, "Collection == 1? for '#{id}' (#{mth})") unless coll.size == 1
339
355
  next unless coll.size == 1
340
356
 
357
+ area = lc.getNetArea
358
+ coll[id][:area] = area
341
359
  res = uo(model, lc, id, hloss, film, g[:ut])
342
360
  log(ERR, "Unable to uprate '#{id}' (#{mth})") unless res[:uo] && res[:m]
343
361
  next unless res[:uo] && res[:m]
@@ -347,27 +365,18 @@ module TBD
347
365
  # Loop through coll :s, and reset :r - likely modified by uo().
348
366
  coll.values.first[:s].keys.each do |nom|
349
367
  next unless s.key?(nom)
350
- next unless s[nom].key?(:deratable )
351
- next unless s[nom].key?(:construction)
352
- next unless s[nom].key?(:index )
353
- next unless s[nom].key?(:ltype )
354
- next unless s[nom].key?(:type )
355
-
356
- next unless s[nom][:deratable ]
357
- next unless s[nom][:construction] == lc
358
- next unless s[nom][:index ] == lyr[:index]
359
- next unless s[nom][:ltype ] == lyr[:type]
360
-
361
- type = s[nom][:type].to_s.downcase
362
- type = "roof" if type == "ceiling"
363
- next unless type.include?(label.to_s)
364
- next unless s[nom].key?(:r)
365
- s[nom][:r] = lyr[:r] # final
368
+ next unless s[nom].key?(:index)
369
+ next unless s[nom].key?(:ltype)
370
+ next unless s[nom].key?(:r )
371
+ next unless s[nom][:index] == lyr[:index]
372
+ next unless s[nom][:ltype] == lyr[:type ]
373
+
374
+ s[nom][:r] = lyr[:r] # uprated insulating RSi factor, before derating
366
375
  end
367
376
 
368
- argh[:wall_uo ] = res[:uo] if label == :wall
369
- argh[:roof_uo ] = res[:uo] if label == :roof
370
- argh[:floor_uo] = res[:uo] if label == :floor
377
+ argh[:wall_uo ] = res[:uo] if typ == :wall
378
+ argh[:roof_uo ] = res[:uo] if typ == :ceiling
379
+ argh[:floor_uo] = res[:uo] if typ == :floor
371
380
  else
372
381
  log(ERR, "Nilled construction to uprate - (#{mth})")
373
382
  return false
@@ -920,7 +929,7 @@ module TBD
920
929
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
921
930
  model += " (v#{ua[:version]})" if ua.key?(:version)
922
931
  report << model unless model.empty?
923
- report << "* TBD : v3.2.0"
932
+ report << "* TBD : v3.2.2"
924
933
  report << "* date : #{ua[:date]}"
925
934
 
926
935
  if lang == :en
@@ -1,3 +1,3 @@
1
1
  module Topolys
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.1"
3
3
  end
data/lib/tbd/geo.rb CHANGED
@@ -292,28 +292,31 @@ module TBD
292
292
 
293
293
  return mismatch("model", model, cl1, mth) unless model.is_a?(cl1)
294
294
  return mismatch("surface", surface, cl2, mth) unless surface.is_a?(cl2)
295
-
296
- return nil unless validate(surface)
295
+ return nil unless validate(surface)
297
296
 
298
297
  nom = surface.nameString
299
298
  surf = {}
300
299
  subs = {}
301
300
  fd = false
302
301
  return empty("'#{nom}' space", mth, ERR) if surface.space.empty?
302
+
303
303
  space = surface.space.get
304
304
  stype = space.spaceType
305
305
  story = space.buildingStory
306
306
  tr = transforms(model, space)
307
307
  return invalid("'#{nom}' transform", mth, 0, FTL) unless tr[:t] && tr[:r]
308
+
308
309
  t = tr[:t]
309
310
  n = trueNormal(surface, tr[:r])
310
311
  return invalid("'#{nom}' normal", mth, 0, FTL) unless n
312
+
311
313
  type = surface.surfaceType.downcase
312
314
  facing = surface.outsideBoundaryCondition
313
315
 
314
316
  if facing.downcase == "surface"
315
317
  empty = surface.adjacentSurface.empty?
316
- return invalid("'#{nom}': adjacent surface", mth, 0, ERR) if empty
318
+ return invalid("'#{nom}': adjacent surface", mth, 0, ERR) if empty
319
+
317
320
  facing = surface.adjacentSurface.get.nameString
318
321
  end
319
322
 
@@ -350,6 +353,7 @@ module TBD
350
353
  surf[:story ] = story.get unless story.empty?
351
354
  surf[:n ] = n
352
355
  surf[:gross ] = surface.grossArea
356
+ surf[:filmRSI ] = surface.filmResistance
353
357
 
354
358
  surface.subSurfaces.sort_by { |s| s.nameString }.each do |s|
355
359
  next unless validate(s)
data/lib/tbd/psi.rb CHANGED
@@ -975,8 +975,9 @@ module TBD
975
975
  end
976
976
  end
977
977
 
978
- surface[:heating] = heat[:spt] if heat[:spt] # if valid heating setpoints
979
- surface[:cooling] = cool[:spt] if cool[:spt] # if valid cooling setpoints
978
+ # Recover if valid setpoints.
979
+ surface[:heating] = heat[:spt] if heat && heat[:spt]
980
+ surface[:cooling] = cool[:spt] if cool && cool[:spt]
980
981
 
981
982
  tbd[:surfaces][s.nameString] = surface
982
983
  end # (opaque) surfaces populated
@@ -1486,7 +1487,7 @@ module TBD
1486
1487
 
1487
1488
  edge[:surfaces].keys.each do |i|
1488
1489
  break if is[:rimjoist] || is[:balcony]
1489
- break unless deratables.size == 2
1490
+ break unless deratables.size > 0
1490
1491
  break if floors.key?(id)
1491
1492
  next if i == id
1492
1493
  next unless floors.key?(i)
data/lib/tbd/ua.rb CHANGED
@@ -39,13 +39,14 @@ module TBD
39
39
  cl1 = OpenStudio::Model::Model
40
40
  cl2 = OpenStudio::Model::LayeredConstruction
41
41
  cl3 = Numeric
42
+ cl4 = String
42
43
 
43
- return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
44
- return mismatch("id", id, String, mth, DBG, res) unless id.is_a?(String)
45
- return mismatch("lc", lc, cl2, mth, DBG, res) unless lc.is_a?(cl2)
46
- return mismatch("hloss", hloss, cl3, mth, DBG, res) unless hloss.is_a?(cl3)
47
- return mismatch("film", film, cl3, mth, DBG, res) unless film.is_a?(cl3)
48
- return mismatch("Ut", ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
44
+ return mismatch("model", model, cl1, mth, DBG, res) unless model.is_a?(cl1)
45
+ return mismatch("id" , id, cl4, mth, DBG, res) unless id.is_a?(cl4)
46
+ return mismatch("lc" , lc, cl2, mth, DBG, res) unless lc.is_a?(cl2)
47
+ return mismatch("hloss", hloss, cl3, mth, DBG, res) unless hloss.is_a?(cl3)
48
+ return mismatch("film" , film, cl3, mth, DBG, res) unless film.is_a?(cl3)
49
+ return mismatch("Ut" , ut, cl3, mth, DBG, res) unless ut.is_a?(cl3)
49
50
 
50
51
  loss = 0.0 # residual heatloss (not assigned) [W/K]
51
52
  area = lc.getNetArea
@@ -54,12 +55,12 @@ module TBD
54
55
  lyr[:index] = nil unless lyr[:index] >= 0
55
56
  lyr[:index] = nil unless lyr[:index] < lc.layers.size
56
57
 
57
- return invalid("'#{id}' layer index", mth, 0, ERR, res) unless lyr[:index]
58
- return zero("'#{id}': heatloss", mth, WRN, res) unless hloss > TOL
59
- return zero("'#{id}': films", mth, WRN, res) unless film > TOL
60
- return zero("'#{id}': Ut", mth, WRN, res) unless ut > TOL
61
- return invalid("'#{id}': Ut", mth, 0, WRN, res) unless ut < 5.678
62
- return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
58
+ return invalid("'#{id}' layer index", mth, 0, ERR, res) unless lyr[:index]
59
+ return zero("'#{id}': heatloss" , mth, WRN, res) unless hloss > TOL
60
+ return zero("'#{id}': films" , mth, WRN, res) unless film > TOL
61
+ return zero("'#{id}': Ut" , mth, WRN, res) unless ut > TOL
62
+ return invalid("'#{id}': Ut" , mth, 0, WRN, res) unless ut < 5.678
63
+ return zero("'#{id}': net area (m2)", mth, ERR, res) unless area > TOL
63
64
 
64
65
  # First, calculate initial layer RSi to initially meet Ut target.
65
66
  rt = 1 / ut # target construction Rt
@@ -76,7 +77,7 @@ module TBD
76
77
 
77
78
  if lyr[:type] == :massless
78
79
  m = lc.getLayer(lyr[:index]).to_MasslessOpaqueMaterial
79
- return invalid("'#{id}' massless layer?", mth, 0) if m.empty?
80
+ return invalid("'#{id}' massless layer?", mth, 0, DBG, res) if m.empty?
80
81
 
81
82
  m = m.get.clone(model).to_MasslessOpaqueMaterial.get
82
83
  m.setName("#{id} uprated")
@@ -85,7 +86,7 @@ module TBD
85
86
  m.setThermalResistance(new_r)
86
87
  else # type == :standard
87
88
  m = lc.getLayer(lyr[:index]).to_StandardOpaqueMaterial
88
- return invalid("'#{id}' standard layer?", mth, 0) if m.empty?
89
+ return invalid("'#{id}' standard layer?", mth, 0, DBG, res) if m.empty?
89
90
 
90
91
  m = m.get.clone(model).to_StandardOpaqueMaterial.get
91
92
  m.setName("#{id} uprated")
@@ -141,11 +142,12 @@ module TBD
141
142
  mth = "TBD::#{__callee__}"
142
143
  cl1 = OpenStudio::Model::Model
143
144
  cl2 = Hash
145
+ cl3 = OpenStudio::Model::LayeredConstruction
144
146
  a = false
145
147
 
146
- return mismatch("model", model, cl1, mth, DBG, a) unless model.is_a?(cl1)
147
- return mismatch("surfaces", s, cl2, mth, DBG, a) unless s.is_a?(cl2)
148
- return mismatch("argh", model, cl1, mth, DBG, a) unless argh.is_a?(cl2)
148
+ return mismatch("model" , model, cl1, mth, DBG, a) unless model.is_a?(cl1)
149
+ return mismatch("surfaces", s, cl2, mth, DBG, a) unless s.is_a?(cl2)
150
+ return mismatch("argh" , model, cl1, mth, DBG, a) unless argh.is_a?(cl2)
149
151
 
150
152
  argh[:uprate_walls ] = false unless argh.key?(:uprate_walls )
151
153
  argh[:uprate_roofs ] = false unless argh.key?(:uprate_roofs )
@@ -168,11 +170,13 @@ module TBD
168
170
  groups[:roof ][:op] = argh[:roof_option ]
169
171
  groups[:floor][:op] = argh[:floor_option ]
170
172
 
171
- groups.each do |label, g|
173
+ groups.each do |type, g|
172
174
  next unless g[:up]
173
175
  next unless g[:ut].is_a?(Numeric)
174
176
  next unless g[:ut] < 5.678
175
177
 
178
+ typ = type
179
+ typ = :ceiling if typ == :roof # fix in future revision. TO-DO.
176
180
  coll = {}
177
181
  area = 0
178
182
  film = 100000000000000
@@ -183,86 +187,81 @@ module TBD
183
187
  g[:op].downcase == "all floor constructions"
184
188
 
185
189
  if g[:op].empty?
186
- log(ERR, "Construction to uprate? (#{mth})")
190
+ log(ERR, "Construction (#{type}) to uprate? (#{mth})")
187
191
  elsif all
188
- model.getSurfaces.each do |sss|
189
- next unless sss.surfaceType.downcase.include?(label.to_s)
190
- next unless sss.outsideBoundaryCondition.downcase == "outdoors"
191
- next if sss.construction.empty?
192
- next if sss.construction.get.to_LayeredConstruction.empty?
193
-
194
- c = sss.construction.get.to_LayeredConstruction.get
195
- i = c.nameString
196
-
197
- # Reliable unless referenced by other surface types e.g. floor vs wall.
198
- if c.getNetArea > area
199
- area = c.getNetArea
192
+ s.each do |nom, surface|
193
+ next unless surface.key?(:deratable )
194
+ next unless surface.key?(:type )
195
+ next unless surface.key?(:construction)
196
+ next unless surface.key?(:filmRSI )
197
+ next unless surface.key?(:index )
198
+ next unless surface.key?(:ltype )
199
+ next unless surface.key?(:r )
200
+ next unless surface[:deratable ]
201
+ next unless surface[:type ] == typ
202
+ next unless surface[:construction].is_a?(cl3)
203
+ next if surface[:index ].nil?
204
+
205
+ # Retain lowest surface film resistance (e.g. tilted surfaces).
206
+ c = surface[:construction]
207
+ i = c.nameString
208
+ aire = c.getNetArea
209
+ film = surface[:filmRSI] if surface[:filmRSI] < film
210
+
211
+ # Retain construction covering largest area. The following conditional
212
+ # is reliable UNLESS linked to other deratable surface types e.g. both
213
+ # floors AND walls (see "elsif lc" corrections below).
214
+ if aire > area
200
215
  lc = c
216
+ area = aire
201
217
  id = i
202
218
  end
203
219
 
204
- film = sss.filmResistance if sss.filmResistance < film
205
- nom = sss.nameString
206
- coll[i] = { area: c.getNetArea, lc: c, s: {} } unless coll.key?(i)
207
- coll[i][:s][nom] = { a: sss.netArea } unless coll[i][:s].key?(nom)
220
+ coll[i] = { area: aire, lc: c, s: {} } unless coll.key?(i)
221
+ coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
208
222
  end
209
223
  else
210
224
  id = g[:op]
211
- c = model.getConstructionByName(id)
212
-
213
- if c.empty?
214
- log(ERR, "Construction '#{id}'? (#{mth})")
215
- else
216
- c = c.get.to_LayeredConstruction
217
-
218
- if c.empty?
219
- log(ERR, "'#{id}' layered construction? (#{mth})")
220
- else
221
- lc = c.get
222
- area = lc.getNetArea
223
- coll[id] = { area: area, lc: lc, s: {} }
224
-
225
- model.getSurfaces.each do |sss|
226
- next unless sss.surfaceType.downcase.include?(label.to_s)
227
- next unless sss.outsideBoundaryCondition.downcase == "outdoors"
228
- next if sss.construction.empty?
229
- next if sss.construction.get.to_LayeredConstruction.empty?
230
- lc = sss.construction.get.to_LayeredConstruction.get
231
- next unless id == lc.nameString
232
- nom = sss.nameString
233
- film = sss.filmResistance if sss.filmResistance < film
234
- ok = coll[id][:s].key?(nom)
235
- coll[id][:s][nom] = { a: sss.netArea } unless ok
236
- end
237
- end
225
+ lc = model.getConstructionByName(id)
226
+ log(ERR, "Construction '#{id}'? (#{mth})") if lc.empty?
227
+ next if lc.empty?
228
+
229
+ lc = lc.get.to_LayeredConstruction
230
+ log(ERR, "'#{id}' layered construction? (#{mth})") if lc.empty?
231
+ next if lc.empty?
232
+
233
+ lc = lc.get
234
+ area = lc.getNetArea
235
+ coll[id] = { area: area, lc: lc, s: {} }
236
+
237
+ s.each do |nom, surface|
238
+ next unless surface.key?(:deratable )
239
+ next unless surface.key?(:type )
240
+ next unless surface.key?(:construction)
241
+ next unless surface.key?(:filmRSI )
242
+ next unless surface.key?(:index )
243
+ next unless surface.key?(:ltype )
244
+ next unless surface.key?(:r )
245
+ next unless surface[:deratable ]
246
+ next unless surface[:type ] == typ
247
+ next unless surface[:construction].is_a?(cl3)
248
+ next if surface[:index ].nil?
249
+
250
+ i = surface[:construction].nameString
251
+ next unless i == id
252
+
253
+ # Retain lowest surface film resistance (e.g. tilted surfaces).
254
+ film = surface[:filmRSI] if surface[:filmRSI] < film
255
+
256
+ coll[i][:s][nom] = { a: surface[:net] } unless coll[i][:s].key?(nom)
238
257
  end
239
258
  end
240
259
 
241
260
  if coll.empty?
242
- log(ERR, "No #{label} construction to uprate - skipping (#{mth})")
261
+ log(ERR, "No #{type} construction to uprate - skipping (#{mth})")
243
262
  next
244
- elsif lc # valid layered construction - good to uprate!
245
- # Ensure lc is referenced by surface types == label.
246
- model.getSurfaces.each do |sss|
247
- next if sss.construction.empty?
248
- next if sss.construction.get.to_LayeredConstruction.empty?
249
- c = sss.construction.get.to_LayeredConstruction.get
250
- i = c.nameString
251
- next unless coll.key?(i)
252
-
253
- unless sss.surfaceType.downcase.include?(label.to_s)
254
- log(ERR, "Uprating #{label.to_s}, not '#{sss.nameString}' (#{mth})")
255
- cloned = c.clone(model).to_LayeredConstruction.get
256
- cloned.setName("'#{i}' cloned")
257
- sss.setConstruction(cloned)
258
- ok = s.key?(sss.nameString)
259
- s[sss.nameString][:construction] = cloned if ok
260
- coll[i][:s].delete(sss.nameString)
261
- coll[i][:area] = c.getNetArea
262
- next
263
- end
264
- end
265
-
263
+ elsif lc
264
+ # Valid layered construction - good to uprate!
266
265
  lyr = insulatingLayer(lc)
267
266
  lyr[:index] = nil unless lyr[:index].is_a?(Numeric)
268
267
  lyr[:index] = nil unless lyr[:index] >= 0
@@ -270,64 +269,81 @@ module TBD
270
269
 
271
270
  log(ERR, "Insulation index for '#{id}'? (#{mth})") unless lyr[:index]
272
271
  next unless lyr[:index]
273
- hloss = 0 # sum of applicable psi & khi effects [W/K]
274
272
 
275
- coll.each do |i, col|
276
- next unless col.key?(:s)
277
- next unless col.is_a?(Hash)
273
+ # Ensure lc is exclusively linked to deratable surfaces of right type.
274
+ # If not, assign new lc clone to non-targeted surfaces.
275
+ s.each do |nom, surface|
276
+ next unless surface.key?(:type )
277
+ next unless surface.key?(:deratable )
278
+ next unless surface.key?(:construction)
279
+ next unless surface[:construction].is_a?(cl3)
280
+ next unless surface[:construction] == lc
281
+
282
+ ok = true
283
+ ok = false unless surface[:type ] == typ
284
+ ok = false unless surface[:deratable]
285
+ ok = false unless coll.key?(id)
286
+ ok = false unless coll[id][:s].key?(nom)
287
+
288
+ unless ok
289
+ log(WRN, "Cloning '#{nom}' construction - not '#{id}' (#{mth})")
290
+ sss = model.getSurfaceByName(nom)
291
+ next if sss.empty?
292
+
293
+ sss = sss.get
294
+ cloned = lc.clone(model).to_LayeredConstruction.get
295
+ cloned.setName("#{nom} - cloned")
296
+ sss.setConstruction(cloned)
297
+ surface[:construction] = cloned
298
+ coll[id][:s].delete(nom)
299
+ end
300
+ end
301
+
302
+ hloss = 0 # sum of applicable psi & khi effects [W/K]
278
303
 
304
+ # Tally applicable psi + khi losses. Possible construction reassignment.
305
+ coll.each do |i, col|
279
306
  col[:s].keys.each do |nom|
280
307
  next unless s.key?(nom)
281
- next unless s[nom].key?(:deratable )
282
308
  next unless s[nom].key?(:construction)
283
309
  next unless s[nom].key?(:index )
284
310
  next unless s[nom].key?(:ltype )
285
311
  next unless s[nom].key?(:r )
286
- next unless s[nom].key?(:type )
287
-
288
- next unless s[nom][:deratable]
289
- type = s[nom][:type].to_s.downcase
290
- type = "roof" if type == "ceiling"
291
- next unless type.include?(label.to_s)
292
312
 
293
313
  # Tally applicable psi + khi.
294
- hloss += s[nom][:heatloss] if s[nom].key?(:heatloss)
295
-
296
- # Skip construction reassignment if already referencing right one.
297
- unless s[nom][:construction] == lc
298
- sss = model.getSurfaceByName(nom)
299
- next if sss.empty?
300
- sss = sss.get
301
-
302
- if sss.isConstructionDefaulted
303
- set = defaultConstructionSet(model, sss)
304
- constructions = set.defaultExteriorSurfaceConstructions.get
305
-
306
- case sss.surfaceType.downcase
307
- when "roofceiling"
308
- constructions.setRoofCeilingConstruction(lc)
309
- when "floor"
310
- constructions.setFloorConstruction(lc)
311
- else
312
- constructions.setWallConstruction(lc)
313
- end
314
- else
315
- sss.setConstruction(lc)
316
- end
314
+ hloss += s[nom][:heatloss ] if s[nom].key?(:heatloss)
315
+ next if s[nom][:construction] == lc
316
+
317
+ # Reassign construction unless referencing lc.
318
+ sss = model.getSurfaceByName(nom)
319
+ next if sss.empty?
317
320
 
318
- s[nom][:construction] = lc # reset TBD attributes
319
- s[nom][:index ] = lyr[:index]
320
- s[nom][:ltype ] = lyr[:type ]
321
- s[nom][:r ] = lyr[:r ] # temporary
321
+ sss = sss.get
322
+
323
+ if sss.isConstructionDefaulted
324
+ set = defaultConstructionSet(model, sss) # building? story?
325
+ constructions = set.defaultExteriorSurfaceConstructions
326
+
327
+ unless constructions.empty?
328
+ constructions = constructions.get
329
+ constructions.setWallConstruction(lc) if typ == :wall
330
+ constructions.setFloorConstruction(lc) if typ == :floor
331
+ constructions.setRoofCeilingConstruction(lc) if typ == :ceiling
332
+ end
333
+ else
334
+ sss.setConstruction(lc)
322
335
  end
336
+
337
+ s[nom][:construction] = lc # reset TBD attributes
338
+ s[nom][:index ] = lyr[:index]
339
+ s[nom][:ltype ] = lyr[:type ]
340
+ s[nom][:r ] = lyr[:r ] # temporary
323
341
  end
324
342
  end
325
343
 
326
344
  # Merge to ensure a single entry for coll Hash.
327
345
  coll.each do |i, col|
328
346
  next if i == id
329
- next unless coll.key?(id)
330
- coll[id][:area] += col[:area]
331
347
 
332
348
  col[:s].each do |nom, sss|
333
349
  coll[id][:s][nom] = sss unless coll[id][:s].key?(nom)
@@ -338,6 +354,8 @@ module TBD
338
354
  log(DBG, "Collection == 1? for '#{id}' (#{mth})") unless coll.size == 1
339
355
  next unless coll.size == 1
340
356
 
357
+ area = lc.getNetArea
358
+ coll[id][:area] = area
341
359
  res = uo(model, lc, id, hloss, film, g[:ut])
342
360
  log(ERR, "Unable to uprate '#{id}' (#{mth})") unless res[:uo] && res[:m]
343
361
  next unless res[:uo] && res[:m]
@@ -347,27 +365,18 @@ module TBD
347
365
  # Loop through coll :s, and reset :r - likely modified by uo().
348
366
  coll.values.first[:s].keys.each do |nom|
349
367
  next unless s.key?(nom)
350
- next unless s[nom].key?(:deratable )
351
- next unless s[nom].key?(:construction)
352
- next unless s[nom].key?(:index )
353
- next unless s[nom].key?(:ltype )
354
- next unless s[nom].key?(:type )
355
-
356
- next unless s[nom][:deratable ]
357
- next unless s[nom][:construction] == lc
358
- next unless s[nom][:index ] == lyr[:index]
359
- next unless s[nom][:ltype ] == lyr[:type]
360
-
361
- type = s[nom][:type].to_s.downcase
362
- type = "roof" if type == "ceiling"
363
- next unless type.include?(label.to_s)
364
- next unless s[nom].key?(:r)
365
- s[nom][:r] = lyr[:r] # final
368
+ next unless s[nom].key?(:index)
369
+ next unless s[nom].key?(:ltype)
370
+ next unless s[nom].key?(:r )
371
+ next unless s[nom][:index] == lyr[:index]
372
+ next unless s[nom][:ltype] == lyr[:type ]
373
+
374
+ s[nom][:r] = lyr[:r] # uprated insulating RSi factor, before derating
366
375
  end
367
376
 
368
- argh[:wall_uo ] = res[:uo] if label == :wall
369
- argh[:roof_uo ] = res[:uo] if label == :roof
370
- argh[:floor_uo] = res[:uo] if label == :floor
377
+ argh[:wall_uo ] = res[:uo] if typ == :wall
378
+ argh[:roof_uo ] = res[:uo] if typ == :ceiling
379
+ argh[:floor_uo] = res[:uo] if typ == :floor
371
380
  else
372
381
  log(ERR, "Nilled construction to uprate - (#{mth})")
373
382
  return false
@@ -920,7 +929,7 @@ module TBD
920
929
  model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
921
930
  model += " (v#{ua[:version]})" if ua.key?(:version)
922
931
  report << model unless model.empty?
923
- report << "* TBD : v3.2.0"
932
+ report << "* TBD : v3.2.2"
924
933
  report << "* date : #{ua[:date]}"
925
934
 
926
935
  if lang == :en
data/lib/tbd/version.rb CHANGED
@@ -21,5 +21,5 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  module TBD
24
- VERSION = "3.2.0".freeze
24
+ VERSION = "3.2.2".freeze
25
25
  end
data/lib/tbd.rb CHANGED
@@ -26,38 +26,38 @@ begin
26
26
  # Try to load from the Topolys gem.
27
27
  require "topolys"
28
28
 
29
- puts "... relying on the Topolys gem"
29
+ # puts "... relying on the Topolys gem"
30
30
  rescue LoadError
31
31
  require_relative "topolys/model"
32
32
  require_relative "topolys/geometry"
33
33
  require_relative "topolys/transformation"
34
34
  require_relative "topolys/version"
35
35
 
36
- puts "... fallback to local Topolys files"
36
+ # puts "... fallback to local Topolys files"
37
37
  end
38
38
 
39
39
  begin
40
40
  # Try to load from the OSlg gem.
41
41
  require "oslg"
42
42
 
43
- puts "... relying on the OSlg gem"
43
+ # puts "... relying on the OSlg gem"
44
44
  rescue LoadError
45
45
  require_relative "oslg/oslog"
46
46
  require_relative "osut/version"
47
47
 
48
- puts "... fallback to local OSlg files"
48
+ # puts "... fallback to local OSlg files"
49
49
  end
50
50
 
51
51
  begin
52
52
  # Try to load from the OSut gem.
53
53
  require "osut"
54
54
 
55
- puts "... relying on the OSut gem"
55
+ # puts "... relying on the OSut gem"
56
56
  rescue LoadError
57
57
  require_relative "osut/utils"
58
58
  require_relative "osut/version"
59
59
 
60
- puts "... fallback to local OSut files"
60
+ # puts "... fallback to local OSut files"
61
61
  end
62
62
 
63
63
  begin
@@ -67,14 +67,14 @@ begin
67
67
  require "tbd/ua"
68
68
  require "tbd/version"
69
69
 
70
- puts "... relying on the TBD gem"
70
+ # puts "... relying on the TBD gem"
71
71
  rescue LoadError
72
72
  require_relative "tbd/psi"
73
73
  require_relative "tbd/geo"
74
74
  require_relative "tbd/ua"
75
75
  require_relative "tbd/version"
76
76
 
77
- puts "... fallback to local TBD files"
77
+ # puts "... fallback to local TBD files"
78
78
  end
79
79
 
80
80
  module TBD
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tbd
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Bourgeois & Dan Macumber
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-17 00:00:00.000000000 Z
11
+ date: 2023-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: topolys
@@ -161,7 +161,7 @@ licenses:
161
161
  - MIT
162
162
  metadata:
163
163
  homepage_uri: https://github.com/rd2/tbd
164
- source_code_uri: https://github.com/rd2/tbd/tree/v3.2.0
164
+ source_code_uri: https://github.com/rd2/tbd/tree/v3.2.2
165
165
  bug_tracker_uri: https://github.com/rd2/tbd/issues
166
166
  post_install_message:
167
167
  rdoc_options: []