osut 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/osut/utils.rb +532 -301
- data/lib/osut/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 611db1774e215ed6c7d0647b8790d01fc60ab91dde3b6bc8827641f632f5ba86
|
4
|
+
data.tar.gz: 670a488f2e20af052f34ff50cbeebab1f98e39422af24d1dc3d07c9a773f636b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38dc05428a3e629209baeb62dc297749ca9aba00e9af2f77fe3e0f2416e232c6d25f92877116afa3b6414515ac246f4a1f99c31600723dd9f2bcf0077e97ba4d
|
7
|
+
data.tar.gz: b30f0e97c2df6d207395830e61075ba4993d2a82f9accac5f664a16a0cfbf420ec67e814dd925a7ef534a796cf6a550f3b65f7d45cc2caef271bb4e55e5f0f5d
|
data/lib/osut/utils.rb
CHANGED
@@ -31,20 +31,26 @@
|
|
31
31
|
require "openstudio"
|
32
32
|
|
33
33
|
module OSut
|
34
|
-
# DEBUG for devs; WARN/ERROR for users (bad OS input), see OSlg
|
35
34
|
extend OSlg
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
DBG = OSlg::DEBUG.dup # see github.com/rd2/oslg
|
37
|
+
INF = OSlg::INFO.dup # see github.com/rd2/oslg
|
38
|
+
WRN = OSlg::WARN.dup # see github.com/rd2/oslg
|
39
|
+
ERR = OSlg::ERROR.dup # see github.com/rd2/oslg
|
40
|
+
FTL = OSlg::FATAL.dup # see github.com/rd2/oslg
|
41
|
+
NS = "nameString" # OpenStudio object identifier method
|
42
|
+
TOL = 0.01 # default distance tolerance (m)
|
43
|
+
TOL2 = TOL * TOL # default area tolerance (m2)
|
44
|
+
HEAD = 2.032 # standard 80" door
|
45
|
+
SILL = 0.762 # standard 30" window sill
|
46
|
+
DMIN = 0.010 # min. insulating material thickness
|
47
|
+
DMAX = 1.000 # max. insulating material thickness
|
48
|
+
KMIN = 0.010 # min. insulating material thermal conductivity
|
49
|
+
KMAX = 2.000 # max. insulating material thermal conductivity
|
50
|
+
UMAX = KMAX / DMIN # material USi upper limit, 200.000
|
51
|
+
UMIN = KMIN / DMAX # material USi lower limit, 0.010
|
52
|
+
RMIN = 1.0 / UMAX # material RSi lower limit, 0.005 (or R-IP 0.03)
|
53
|
+
RMAX = 1.0 / UMIN # material RSi upper limit, 100.000 (or R-IP 567.80)
|
48
54
|
|
49
55
|
# General surface orientations (see facets method)
|
50
56
|
SIDZ = [:bottom, # e.g. ground-facing, exposed floors
|
@@ -191,6 +197,388 @@ module OSut
|
|
191
197
|
@@mats[:door ][:rho] = 600.000
|
192
198
|
@@mats[:door ][:cp ] = 1000.000
|
193
199
|
|
200
|
+
##
|
201
|
+
# Validates if every material in a layered construction is standard & opaque.
|
202
|
+
#
|
203
|
+
# @param lc [OpenStudio::LayeredConstruction] a layered construction
|
204
|
+
#
|
205
|
+
# @return [Bool] whether all layers are valid
|
206
|
+
# @return [false] if invalid input (see logs)
|
207
|
+
def standardOpaqueLayers?(lc = nil)
|
208
|
+
mth = "OSut::#{__callee__}"
|
209
|
+
cl = OpenStudio::Model::LayeredConstruction
|
210
|
+
return invalid("lc", mth, 1, DBG, false) unless lc.respond_to?(NS)
|
211
|
+
return mismatch(lc.nameString, lc, cl, mth, DBG, false) unless lc.is_a?(cl)
|
212
|
+
|
213
|
+
lc.layers.each { |m| return false if m.to_StandardOpaqueMaterial.empty? }
|
214
|
+
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Returns total (standard opaque) layered construction thickness (m).
|
220
|
+
#
|
221
|
+
# @param lc [OpenStudio::LayeredConstruction] a layered construction
|
222
|
+
#
|
223
|
+
# @return [Float] construction thickness
|
224
|
+
# @return [0.0] if invalid input (see logs)
|
225
|
+
def thickness(lc = nil)
|
226
|
+
mth = "OSut::#{__callee__}"
|
227
|
+
cl = OpenStudio::Model::LayeredConstruction
|
228
|
+
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
229
|
+
return mismatch(lc.nameString, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl)
|
230
|
+
|
231
|
+
unless standardOpaqueLayers?(lc)
|
232
|
+
log(ERR, "#{lc.nameString} holds non-StandardOpaqueMaterial(s) (#{mth})")
|
233
|
+
return 0.0
|
234
|
+
end
|
235
|
+
|
236
|
+
thickness = 0.0
|
237
|
+
|
238
|
+
lc.layers.each { |m| thickness += m.thickness }
|
239
|
+
|
240
|
+
thickness
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# Returns total air film resistance of a fenestrated construction (m2•K/W)
|
245
|
+
#
|
246
|
+
# @param usi [Numeric] a fenestrated construction's U-factor (W/m2•K)
|
247
|
+
#
|
248
|
+
# @return [Float] total air film resistances
|
249
|
+
# @return [0.1216] if invalid input (see logs)
|
250
|
+
def glazingAirFilmRSi(usi = 5.85)
|
251
|
+
# The sum of thermal resistances of calculated exterior and interior film
|
252
|
+
# coefficients under standard winter conditions are taken from:
|
253
|
+
#
|
254
|
+
# https://bigladdersoftware.com/epx/docs/9-6/engineering-reference/
|
255
|
+
# window-calculation-module.html#simple-window-model
|
256
|
+
#
|
257
|
+
# These remain acceptable approximations for flat windows, yet likely
|
258
|
+
# unsuitable for subsurfaces with curved or projecting shapes like domed
|
259
|
+
# skylights. The solution here is considered an adequate fix for reporting.
|
260
|
+
#
|
261
|
+
# For U-factors above 8.0 W/m2•K (or invalid input), the function returns
|
262
|
+
# 0.1216 m2•K/W, which corresponds to a construction with a single glass
|
263
|
+
# layer thickness of 2mm & k = ~0.6 W/m.K.
|
264
|
+
#
|
265
|
+
# The EnergyPlus Engineering calculations were designed for vertical
|
266
|
+
# windows - not horizontal, slanted or domed surfaces - use with caution.
|
267
|
+
mth = "OSut::#{__callee__}"
|
268
|
+
cl = Numeric
|
269
|
+
return mismatch("usi", usi, cl, mth, DBG, 0.1216) unless usi.is_a?(cl)
|
270
|
+
return invalid("usi", mth, 1, WRN, 0.1216) if usi > 8.0
|
271
|
+
return negative("usi", mth, WRN, 0.1216) if usi < 0
|
272
|
+
return zero("usi", mth, WRN, 0.1216) if usi.abs < TOL
|
273
|
+
|
274
|
+
rsi = 1 / (0.025342 * usi + 29.163853) # exterior film, next interior film
|
275
|
+
return rsi + 1 / (0.359073 * Math.log(usi) + 6.949915) if usi < 5.85
|
276
|
+
return rsi + 1 / (1.788041 * usi - 2.886625)
|
277
|
+
end
|
278
|
+
|
279
|
+
##
|
280
|
+
# Returns a construction's 'standard calc' thermal resistance (m2•K/W), which
|
281
|
+
# includes air film resistances. It excludes insulating effects of shades,
|
282
|
+
# screens, etc. in the case of fenestrated constructions.
|
283
|
+
#
|
284
|
+
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
|
285
|
+
# @param film [Numeric] thermal resistance of surface air films (m2•K/W)
|
286
|
+
# @param t [Numeric] gas temperature (°C) (optional)
|
287
|
+
#
|
288
|
+
# @return [Float] layered construction's thermal resistance
|
289
|
+
# @return [0.0] if invalid input (see logs)
|
290
|
+
def rsi(lc = nil, film = 0.0, t = 0.0)
|
291
|
+
# This is adapted from BTAP's Material Module "get_conductance" (P. Lopez)
|
292
|
+
#
|
293
|
+
# https://github.com/NREL/OpenStudio-Prototype-Buildings/blob/
|
294
|
+
# c3d5021d8b7aef43e560544699fb5c559e6b721d/lib/btap/measures/
|
295
|
+
# btap_equest_converter/envelope.rb#L122
|
296
|
+
mth = "OSut::#{__callee__}"
|
297
|
+
cl1 = OpenStudio::Model::LayeredConstruction
|
298
|
+
cl2 = Numeric
|
299
|
+
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
300
|
+
return mismatch(lc.nameString, lc, cl1, mth, DBG, 0.0) unless lc.is_a?(cl1)
|
301
|
+
return mismatch("film", film, cl2, mth, DBG, 0.0) unless film.is_a?(cl2)
|
302
|
+
return mismatch("temp K", t, cl2, mth, DBG, 0.0) unless t.is_a?(cl2)
|
303
|
+
|
304
|
+
t += 273.0 # °C to K
|
305
|
+
return negative("temp K", mth, ERR, 0.0) if t < 0
|
306
|
+
return negative("film", mth, ERR, 0.0) if film < 0
|
307
|
+
|
308
|
+
rsi = film
|
309
|
+
|
310
|
+
lc.layers.each do |m|
|
311
|
+
# Fenestration materials first.
|
312
|
+
empty = m.to_SimpleGlazing.empty?
|
313
|
+
return 1 / m.to_SimpleGlazing.get.uFactor unless empty
|
314
|
+
|
315
|
+
empty = m.to_StandardGlazing.empty?
|
316
|
+
rsi += m.to_StandardGlazing.get.thermalResistance unless empty
|
317
|
+
empty = m.to_RefractionExtinctionGlazing.empty?
|
318
|
+
rsi += m.to_RefractionExtinctionGlazing.get.thermalResistance unless empty
|
319
|
+
empty = m.to_Gas.empty?
|
320
|
+
rsi += m.to_Gas.get.getThermalResistance(t) unless empty
|
321
|
+
empty = m.to_GasMixture.empty?
|
322
|
+
rsi += m.to_GasMixture.get.getThermalResistance(t) unless empty
|
323
|
+
|
324
|
+
# Opaque materials next.
|
325
|
+
empty = m.to_StandardOpaqueMaterial.empty?
|
326
|
+
rsi += m.to_StandardOpaqueMaterial.get.thermalResistance unless empty
|
327
|
+
empty = m.to_MasslessOpaqueMaterial.empty?
|
328
|
+
rsi += m.to_MasslessOpaqueMaterial.get.thermalResistance unless empty
|
329
|
+
empty = m.to_RoofVegetation.empty?
|
330
|
+
rsi += m.to_RoofVegetation.get.thermalResistance unless empty
|
331
|
+
empty = m.to_AirGap.empty?
|
332
|
+
rsi += m.to_AirGap.get.thermalResistance unless empty
|
333
|
+
end
|
334
|
+
|
335
|
+
rsi
|
336
|
+
end
|
337
|
+
|
338
|
+
##
|
339
|
+
# Identifies a layered construction's (opaque) insulating layer. The method
|
340
|
+
# returns a 3-keyed hash :index, the insulating layer index [0, n layers)
|
341
|
+
# within the layered construction; :type, either :standard or :massless; and
|
342
|
+
# :r, material thermal resistance in m2•K/W.
|
343
|
+
#
|
344
|
+
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
|
345
|
+
#
|
346
|
+
# @return [Hash] index: (Integer), type: (Symbol), r: (Float)
|
347
|
+
# @return [Hash] index: nil, type: nil, r: 0.0 if invalid input (see logs)
|
348
|
+
def insulatingLayer(lc = nil)
|
349
|
+
mth = "OSut::#{__callee__}"
|
350
|
+
cl = OpenStudio::Model::LayeredConstruction
|
351
|
+
res = { index: nil, type: nil, r: 0.0 }
|
352
|
+
i = 0 # iterator
|
353
|
+
return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
|
354
|
+
return mismatch(lc.nameString, lc, cl, mth, DBG, res) unless lc.is_a?(cl)
|
355
|
+
|
356
|
+
lc.layers.each do |m|
|
357
|
+
unless m.to_MasslessOpaqueMaterial.empty?
|
358
|
+
m = m.to_MasslessOpaqueMaterial.get
|
359
|
+
|
360
|
+
if m.thermalResistance < RMIN || m.thermalResistance < res[:r]
|
361
|
+
i += 1
|
362
|
+
next
|
363
|
+
else
|
364
|
+
res[:r ] = m.thermalResistance
|
365
|
+
res[:index] = i
|
366
|
+
res[:type ] = :massless
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
unless m.to_StandardOpaqueMaterial.empty?
|
371
|
+
m = m.to_StandardOpaqueMaterial.get
|
372
|
+
k = m.thermalConductivity
|
373
|
+
d = m.thickness
|
374
|
+
|
375
|
+
if d < DMIN || k > KMAX || d / k < res[:r]
|
376
|
+
i += 1
|
377
|
+
next
|
378
|
+
else
|
379
|
+
res[:r ] = d / k
|
380
|
+
res[:index] = i
|
381
|
+
res[:type ] = :standard
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
i += 1
|
386
|
+
end
|
387
|
+
|
388
|
+
res
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
# Validates whether a material is both uniquely reserved to a single layered
|
393
|
+
# construction in a model, and referenced only once in the construction.
|
394
|
+
# Limited to 'standard' or 'massless' materials.
|
395
|
+
#
|
396
|
+
# @param m [OpenStudio::Model::OpaqueMaterial] a material
|
397
|
+
#
|
398
|
+
# @return [Boolean] whether material is unique
|
399
|
+
# @return [false] if missing)
|
400
|
+
def uniqueMaterial?(m = nil)
|
401
|
+
mth = "OSut::#{__callee__}"
|
402
|
+
cl1 = OpenStudio::Model::OpaqueMaterial
|
403
|
+
return invalid("mat", mth, 1, DBG, false) unless m.respond_to?(NS)
|
404
|
+
return mismatch(m.nameString, m, cl1, mth, DBG, false) unless m.is_a?(cl1)
|
405
|
+
|
406
|
+
num = 0
|
407
|
+
lcs = m.model.getLayeredConstructions
|
408
|
+
|
409
|
+
unless m.to_MasslessOpaqueMaterial.empty?
|
410
|
+
m = m.to_MasslessOpaqueMaterial.get
|
411
|
+
|
412
|
+
lcs.each { |lc| num += lc.getLayerIndices(m).size }
|
413
|
+
|
414
|
+
return true if num == 1
|
415
|
+
end
|
416
|
+
|
417
|
+
unless m.to_StandardOpaqueMaterial.empty?
|
418
|
+
m = m.to_StandardOpaqueMaterial.get
|
419
|
+
|
420
|
+
lcs.each { |lc| num += lc.getLayerIndices(m).size }
|
421
|
+
|
422
|
+
return true if num == 1
|
423
|
+
end
|
424
|
+
|
425
|
+
false
|
426
|
+
end
|
427
|
+
|
428
|
+
##
|
429
|
+
# Sets a layered construction material as unique. Solution similar to
|
430
|
+
# OpenStudio::Model::LayeredConstruction's 'ensureUniqueLayers', yet limited
|
431
|
+
# here to a single indexed OpenStudio material, typically the principal
|
432
|
+
# insulating material. Returns true if the indexed material is already unique.
|
433
|
+
# Limited to 'standard' or 'massless' materials.
|
434
|
+
#
|
435
|
+
# @param lc [OpenStudio::Model::LayeredConstruction] a construction
|
436
|
+
# @param index [Integer] the construction layer index of the material
|
437
|
+
#
|
438
|
+
# @return [Boolean] if assigned as unique
|
439
|
+
# @return [false] if invalid inputs
|
440
|
+
def assignUniqueMaterial(lc = nil, index = nil)
|
441
|
+
mth = "OSut::#{__callee__}"
|
442
|
+
cl1 = OpenStudio::Model::LayeredConstruction
|
443
|
+
cl2 = Integer
|
444
|
+
return invalid("lc", mth, 1, DBG, false) unless lc.respond_to?(NS)
|
445
|
+
return mismatch(lc.nameString, lc, cl1, mth, DBG, false) unless lc.is_a?(cl1)
|
446
|
+
return mismatch("index", index, cl2, mth, DBG, false) unless index.is_a?(cl2)
|
447
|
+
return invalid("index", mth, 0, DBG, false) unless index.between?(0, lc.numLayers - 1)
|
448
|
+
|
449
|
+
m = lc.getLayer(index)
|
450
|
+
|
451
|
+
unless m.to_MasslessOpaqueMaterial.empty?
|
452
|
+
m = m.to_MasslessOpaqueMaterial.get
|
453
|
+
return true if uniqueMaterial?(m)
|
454
|
+
|
455
|
+
mat = m.clone(m.model).to_MasslessOpaqueMaterial.get
|
456
|
+
return lc.setLayer(index, mat)
|
457
|
+
end
|
458
|
+
|
459
|
+
unless m.to_StandardOpaqueMaterial.empty?
|
460
|
+
m = m.to_StandardOpaqueMaterial.get
|
461
|
+
return true if uniqueMaterial?(m)
|
462
|
+
|
463
|
+
mat = m.clone(m.model).to_StandardOpaqueMaterial.get
|
464
|
+
return lc.setLayer(index, mat)
|
465
|
+
end
|
466
|
+
|
467
|
+
false
|
468
|
+
end
|
469
|
+
|
470
|
+
##
|
471
|
+
# Resets a construction's Uo factor by adjusting its insulating layer
|
472
|
+
# thermal conductivity, then if needed its thickness (or its RSi value if
|
473
|
+
# massless). Unless material uniquness is requested, a matching material is
|
474
|
+
# recovered instead of instantiating a new one. The latter is renamed
|
475
|
+
# according to its adjusted conductivity/thickness (or RSi value).
|
476
|
+
#
|
477
|
+
# @param lc [OpenStudio::Model::LayeredConstruction] a construction
|
478
|
+
# @param film [Float] construction air film resistance
|
479
|
+
# @param index [Integer] the insulating layer's array index
|
480
|
+
# @param uo [Float] desired Uo factor (with air film resistance)
|
481
|
+
# @param uniq [Boolean] whether to enforce material uniqueness
|
482
|
+
#
|
483
|
+
# @return [Float] new layer RSi [RMIN, RMAX]
|
484
|
+
# @return [0.0] if invalid input
|
485
|
+
def resetUo(lc = nil, film = nil, index = nil, uo = nil, uniq = false)
|
486
|
+
mth = "OSut::#{__callee__}"
|
487
|
+
r = 0.0 # thermal resistance of new material
|
488
|
+
cl1 = OpenStudio::Model::LayeredConstruction
|
489
|
+
cl2 = Numeric
|
490
|
+
cl3 = Integer
|
491
|
+
return invalid("lc", mth, 1, DBG, r) unless lc.respond_to?(NS)
|
492
|
+
return mismatch(lc.nameString, lc, cl1, mth, DBG, r) unless lc.is_a?(cl1)
|
493
|
+
return mismatch("film", film, cl2, mth, DBG, r) unless film.is_a?(cl2)
|
494
|
+
return negative("film", mth, DBG, r) if film.negative?
|
495
|
+
return mismatch("index", index, cl3, mth, DBG, r) unless index.is_a?(cl3)
|
496
|
+
return invalid("index", mth, 3, DBG, r) unless index.between?(0, lc.numLayers - 1)
|
497
|
+
return mismatch("uo", uo, cl2, mth, DBG, r) unless uo.is_a?(cl2)
|
498
|
+
|
499
|
+
unless uo.between?(UMIN, UMAX)
|
500
|
+
uo = clamp(UMIN, UMAX)
|
501
|
+
log(WRN, "Resetting Uo (#{lc.nameString}) to #{uo.round(3)} (#{mth})")
|
502
|
+
end
|
503
|
+
|
504
|
+
uniq = false unless [true, false].include?(uniq)
|
505
|
+
r0 = rsi(lc, film) # current construction RSi value
|
506
|
+
ro = 1 / uo # desired construction RSi value
|
507
|
+
dR = ro - r0 # desired increase in construction RSi
|
508
|
+
m = lc.getLayer(index)
|
509
|
+
|
510
|
+
unless m.to_MasslessOpaqueMaterial.empty?
|
511
|
+
m = m.to_MasslessOpaqueMaterial.get
|
512
|
+
r = m.thermalResistance
|
513
|
+
return r if dR.abs.round(2) == 0.00
|
514
|
+
|
515
|
+
r = (r + dR).clamp(RMIN, RMAX)
|
516
|
+
id = "OSut:RSi#{r.round(2)}"
|
517
|
+
mt = lc.model.getMasslessOpaqueMaterialByName(id)
|
518
|
+
|
519
|
+
# Existing material?
|
520
|
+
unless mt.empty?
|
521
|
+
mt = mt.get
|
522
|
+
|
523
|
+
if r.round(2) == mt.thermalResistance.round(2) && uniq == false
|
524
|
+
lc.setLayer(index, mt)
|
525
|
+
return r
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
mt = m.clone(m.model).to_MasslessOpaqueMaterial.get
|
530
|
+
mt.setName(id)
|
531
|
+
|
532
|
+
unless mt.setThermalResistance(r)
|
533
|
+
return invalid("Failed #{id}: RSi#{de_r.round(2)}", mth)
|
534
|
+
end
|
535
|
+
|
536
|
+
lc.setLayer(index, mt)
|
537
|
+
|
538
|
+
return r
|
539
|
+
end
|
540
|
+
|
541
|
+
unless m.to_StandardOpaqueMaterial.empty?
|
542
|
+
m = m.to_StandardOpaqueMaterial.get
|
543
|
+
r = m.thickness / m.conductivity
|
544
|
+
return r if dR.abs.round(2) == 0.00
|
545
|
+
|
546
|
+
k = (m.thickness / (r + dR)).clamp(KMIN, KMAX)
|
547
|
+
d = (k * (r + dR)).clamp(DMIN, DMAX)
|
548
|
+
r = d / k
|
549
|
+
id = "OSUT:K#{format('%4.3f', k)}:#{format('%03d', d*1000)[-3..-1]}"
|
550
|
+
mt = lc.model.getStandardOpaqueMaterialByName(id)
|
551
|
+
|
552
|
+
# Existing material?
|
553
|
+
unless mt.empty?
|
554
|
+
mt = mt.get
|
555
|
+
rt = mt.thickness / mt.conductivity
|
556
|
+
|
557
|
+
if r.round(2) == rt.round(2) && uniq == false
|
558
|
+
lc.setLayer(index, mt)
|
559
|
+
return r
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
mt = m.clone(m.model).to_StandardOpaqueMaterial.get
|
564
|
+
mt.setName(id)
|
565
|
+
|
566
|
+
unless mt.setThermalConductivity(k)
|
567
|
+
return invalid("Failed #{id}: K#{k.round(3)}", mth)
|
568
|
+
end
|
569
|
+
|
570
|
+
unless mt.setThickness(d)
|
571
|
+
return invalid("Failed #{id}: #{(d*1000).to_i}mm", mth)
|
572
|
+
end
|
573
|
+
|
574
|
+
lc.setLayer(index, mt)
|
575
|
+
|
576
|
+
return r
|
577
|
+
end
|
578
|
+
|
579
|
+
0
|
580
|
+
end
|
581
|
+
|
194
582
|
##
|
195
583
|
# Generates an OpenStudio multilayered construction, + materials if needed.
|
196
584
|
#
|
@@ -214,20 +602,27 @@ module OSut
|
|
214
602
|
|
215
603
|
specs[:id] = "" unless specs.key?(:id)
|
216
604
|
id = trim(specs[:id])
|
217
|
-
id = "OSut
|
605
|
+
id = "OSut:CON:#{specs[:type]}" if id.empty?
|
218
606
|
|
219
|
-
|
220
|
-
|
221
|
-
|
607
|
+
if specs.key?(:type)
|
608
|
+
unless @@uo.keys.include?(specs[:type])
|
609
|
+
return invalid("surface type", mth, 2, ERR)
|
610
|
+
end
|
611
|
+
else
|
612
|
+
specs[:type] = :wall
|
613
|
+
end
|
222
614
|
|
223
615
|
specs[:uo] = @@uo[ specs[:type] ] unless specs.key?(:uo) # can be nil
|
224
616
|
u = specs[:uo]
|
225
617
|
|
226
|
-
|
227
|
-
return mismatch("#{id} Uo", u, Numeric, mth)
|
228
|
-
|
229
|
-
|
230
|
-
|
618
|
+
unless u.nil?
|
619
|
+
return mismatch("#{id} Uo", u, Numeric, mth) unless u.is_a?(Numeric)
|
620
|
+
|
621
|
+
unless u.between?(UMIN, 5.678)
|
622
|
+
uO = u
|
623
|
+
u = uO.clamp(UMIN, 5.678)
|
624
|
+
log(ERR, "Resetting Uo #{uO.round(3)} to #{u.round(3)} (#{mth})")
|
625
|
+
end
|
231
626
|
end
|
232
627
|
|
233
628
|
# Optional specs. Log/reset if invalid.
|
@@ -256,14 +651,14 @@ module OSut
|
|
256
651
|
d = 0.015
|
257
652
|
a[:compo][:mat] = @@mats[mt]
|
258
653
|
a[:compo][:d ] = d
|
259
|
-
a[:compo][:id ] = "OSut
|
654
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
260
655
|
when :partition
|
261
656
|
unless specs[:clad] == :none
|
262
657
|
d = 0.015
|
263
658
|
mt = :drywall
|
264
659
|
a[:clad][:mat] = @@mats[mt]
|
265
660
|
a[:clad][:d ] = d
|
266
|
-
a[:clad][:id ] = "OSut
|
661
|
+
a[:clad][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
267
662
|
end
|
268
663
|
|
269
664
|
d = 0.015
|
@@ -275,14 +670,14 @@ module OSut
|
|
275
670
|
mt = :mineral if u
|
276
671
|
a[:compo][:mat] = @@mats[mt]
|
277
672
|
a[:compo][:d ] = d
|
278
|
-
a[:compo][:id ] = "OSut
|
673
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
279
674
|
|
280
675
|
unless specs[:finish] == :none
|
281
676
|
d = 0.015
|
282
677
|
mt = :drywall
|
283
678
|
a[:finish][:mat] = @@mats[mt]
|
284
679
|
a[:finish][:d ] = d
|
285
|
-
a[:finish][:id ] = "OSut
|
680
|
+
a[:finish][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
286
681
|
end
|
287
682
|
when :wall
|
288
683
|
unless specs[:clad] == :none
|
@@ -293,7 +688,7 @@ module OSut
|
|
293
688
|
d = 0.015 if specs[:clad] == :light
|
294
689
|
a[:clad][:mat] = @@mats[mt]
|
295
690
|
a[:clad][:d ] = d
|
296
|
-
a[:clad][:id ] = "OSut
|
691
|
+
a[:clad][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
297
692
|
end
|
298
693
|
|
299
694
|
mt = :drywall
|
@@ -303,7 +698,7 @@ module OSut
|
|
303
698
|
d = 0.015 if specs[:frame] == :light
|
304
699
|
a[:sheath][:mat] = @@mats[mt]
|
305
700
|
a[:sheath][:d ] = d
|
306
|
-
a[:sheath][:id ] = "OSut
|
701
|
+
a[:sheath][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
307
702
|
|
308
703
|
mt = :mineral
|
309
704
|
mt = :cellulose if specs[:frame] == :medium
|
@@ -315,7 +710,7 @@ module OSut
|
|
315
710
|
|
316
711
|
a[:compo][:mat] = @@mats[mt]
|
317
712
|
a[:compo][:d ] = d
|
318
|
-
a[:compo][:id ] = "OSut
|
713
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
319
714
|
|
320
715
|
unless specs[:finish] == :none
|
321
716
|
mt = :concrete
|
@@ -325,7 +720,7 @@ module OSut
|
|
325
720
|
d = 0.200 if specs[:finish] == :heavy
|
326
721
|
a[:finish][:mat] = @@mats[mt]
|
327
722
|
a[:finish][:d ] = d
|
328
|
-
a[:finish][:id ] = "OSut
|
723
|
+
a[:finish][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
329
724
|
end
|
330
725
|
when :roof
|
331
726
|
unless specs[:clad] == :none
|
@@ -336,7 +731,7 @@ module OSut
|
|
336
731
|
d = 0.200 if specs[:clad] == :heavy # e.g. parking garage
|
337
732
|
a[:clad][:mat] = @@mats[mt]
|
338
733
|
a[:clad][:d ] = d
|
339
|
-
a[:clad][:id ] = "OSut
|
734
|
+
a[:clad][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
340
735
|
end
|
341
736
|
|
342
737
|
mt = :mineral
|
@@ -347,7 +742,7 @@ module OSut
|
|
347
742
|
d = 0.015 unless u
|
348
743
|
a[:compo][:mat] = @@mats[mt]
|
349
744
|
a[:compo][:d ] = d
|
350
|
-
a[:compo][:id ] = "OSut
|
745
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
351
746
|
|
352
747
|
unless specs[:finish] == :none
|
353
748
|
mt = :concrete
|
@@ -357,7 +752,7 @@ module OSut
|
|
357
752
|
d = 0.200 if specs[:finish] == :heavy
|
358
753
|
a[:finish][:mat] = @@mats[mt]
|
359
754
|
a[:finish][:d ] = d
|
360
|
-
a[:finish][:id ] = "OSut
|
755
|
+
a[:finish][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
361
756
|
end
|
362
757
|
when :floor
|
363
758
|
unless specs[:clad] == :none
|
@@ -365,7 +760,7 @@ module OSut
|
|
365
760
|
d = 0.015
|
366
761
|
a[:clad][:mat] = @@mats[mt]
|
367
762
|
a[:clad][:d ] = d
|
368
|
-
a[:clad][:id ] = "OSut
|
763
|
+
a[:clad][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
369
764
|
end
|
370
765
|
|
371
766
|
mt = :mineral
|
@@ -376,7 +771,7 @@ module OSut
|
|
376
771
|
d = 0.015 unless u
|
377
772
|
a[:compo][:mat] = @@mats[mt]
|
378
773
|
a[:compo][:d ] = d
|
379
|
-
a[:compo][:id ] = "OSut
|
774
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
380
775
|
|
381
776
|
unless specs[:finish] == :none
|
382
777
|
mt = :concrete
|
@@ -386,21 +781,21 @@ module OSut
|
|
386
781
|
d = 0.200 if specs[:finish] == :heavy
|
387
782
|
a[:finish][:mat] = @@mats[mt]
|
388
783
|
a[:finish][:d ] = d
|
389
|
-
a[:finish][:id ] = "OSut
|
784
|
+
a[:finish][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
390
785
|
end
|
391
786
|
when :slab
|
392
787
|
mt = :sand
|
393
788
|
d = 0.100
|
394
789
|
a[:clad][:mat] = @@mats[mt]
|
395
790
|
a[:clad][:d ] = d
|
396
|
-
a[:clad][:id ] = "OSut
|
791
|
+
a[:clad][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
397
792
|
|
398
793
|
unless specs[:frame] == :none
|
399
794
|
mt = :polyiso
|
400
795
|
d = 0.025
|
401
796
|
a[:sheath][:mat] = @@mats[mt]
|
402
797
|
a[:sheath][:d ] = d
|
403
|
-
a[:sheath][:id ] = "OSut
|
798
|
+
a[:sheath][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
404
799
|
end
|
405
800
|
|
406
801
|
mt = :concrete
|
@@ -408,14 +803,14 @@ module OSut
|
|
408
803
|
d = 0.200 if specs[:frame] == :heavy
|
409
804
|
a[:compo][:mat] = @@mats[mt]
|
410
805
|
a[:compo][:d ] = d
|
411
|
-
a[:compo][:id ] = "OSut
|
806
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
412
807
|
|
413
808
|
unless specs[:finish] == :none
|
414
809
|
mt = :material
|
415
810
|
d = 0.015
|
416
811
|
a[:finish][:mat] = @@mats[mt]
|
417
812
|
a[:finish][:d ] = d
|
418
|
-
a[:finish][:id ] = "OSut
|
813
|
+
a[:finish][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
419
814
|
end
|
420
815
|
when :basement
|
421
816
|
unless specs[:clad] == :none
|
@@ -425,38 +820,38 @@ module OSut
|
|
425
820
|
d = 0.015 if specs[:clad] == :light
|
426
821
|
a[:clad][:mat] = @@mats[mt]
|
427
822
|
a[:clad][:d ] = d
|
428
|
-
a[:clad][:id ] = "OSut
|
823
|
+
a[:clad][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
429
824
|
|
430
825
|
mt = :polyiso
|
431
826
|
d = 0.025
|
432
827
|
a[:sheath][:mat] = @@mats[mt]
|
433
828
|
a[:sheath][:d ] = d
|
434
|
-
a[:sheath][:id ] = "OSut
|
829
|
+
a[:sheath][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
435
830
|
|
436
831
|
mt = :concrete
|
437
832
|
d = 0.200
|
438
833
|
a[:compo][:mat] = @@mats[mt]
|
439
834
|
a[:compo][:d ] = d
|
440
|
-
a[:compo][:id ] = "OSut
|
835
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
441
836
|
else
|
442
837
|
mt = :concrete
|
443
838
|
d = 0.200
|
444
839
|
a[:sheath][:mat] = @@mats[mt]
|
445
840
|
a[:sheath][:d ] = d
|
446
|
-
a[:sheath][:id ] = "OSut
|
841
|
+
a[:sheath][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
447
842
|
|
448
843
|
unless specs[:finish] == :none
|
449
844
|
mt = :mineral
|
450
845
|
d = 0.075
|
451
846
|
a[:compo][:mat] = @@mats[mt]
|
452
847
|
a[:compo][:d ] = d
|
453
|
-
a[:compo][:id ] = "OSut
|
848
|
+
a[:compo][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
454
849
|
|
455
850
|
mt = :drywall
|
456
851
|
d = 0.015
|
457
852
|
a[:finish][:mat] = @@mats[mt]
|
458
853
|
a[:finish][:d ] = d
|
459
|
-
a[:finish][:id ] = "OSut
|
854
|
+
a[:finish][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
460
855
|
end
|
461
856
|
end
|
462
857
|
when :door
|
@@ -465,27 +860,25 @@ module OSut
|
|
465
860
|
|
466
861
|
a[:compo ][:mat ] = @@mats[mt]
|
467
862
|
a[:compo ][:d ] = d
|
468
|
-
a[:compo ][:id ] = "OSut
|
863
|
+
a[:compo ][:id ] = "OSut:#{mt}:#{format('%03d', d*1000)[-3..-1]}"
|
469
864
|
when :window
|
470
865
|
a[:glazing][:u ] = u ? u : @@uo[:window]
|
471
866
|
a[:glazing][:shgc] = 0.450
|
472
867
|
a[:glazing][:shgc] = specs[:shgc] if specs.key?(:shgc)
|
473
|
-
a[:glazing][:id ] = "OSut
|
474
|
-
a[:glazing][:id ] += "
|
475
|
-
a[:glazing][:id ] += "
|
868
|
+
a[:glazing][:id ] = "OSut:window"
|
869
|
+
a[:glazing][:id ] += ":U#{format('%.1f', a[:glazing][:u])}"
|
870
|
+
a[:glazing][:id ] += ":SHGC#{format('%d', a[:glazing][:shgc]*100)}"
|
476
871
|
when :skylight
|
477
872
|
a[:glazing][:u ] = u ? u : @@uo[:skylight]
|
478
873
|
a[:glazing][:shgc] = 0.450
|
479
874
|
a[:glazing][:shgc] = specs[:shgc] if specs.key?(:shgc)
|
480
|
-
a[:glazing][:id ] = "OSut
|
481
|
-
a[:glazing][:id ] += "
|
482
|
-
a[:glazing][:id ] += "
|
875
|
+
a[:glazing][:id ] = "OSut:skylight"
|
876
|
+
a[:glazing][:id ] += ":U#{format('%.1f', a[:glazing][:u])}"
|
877
|
+
a[:glazing][:id ] += ":SHGC#{format('%d', a[:glazing][:shgc]*100)}"
|
483
878
|
end
|
484
879
|
|
485
880
|
# Initiate layers.
|
486
|
-
|
487
|
-
|
488
|
-
if unglazed
|
881
|
+
if a[:glazing].empty?
|
489
882
|
layers = OpenStudio::Model::OpaqueMaterialVector.new
|
490
883
|
|
491
884
|
# Loop through each layer spec, and generate construction.
|
@@ -528,14 +921,15 @@ module OSut
|
|
528
921
|
layers << lyr
|
529
922
|
end
|
530
923
|
|
531
|
-
c
|
924
|
+
c = OpenStudio::Model::Construction.new(layers)
|
532
925
|
c.setName(id)
|
533
926
|
|
534
|
-
# Adjust insulating layer
|
535
|
-
if u and
|
927
|
+
# Adjust insulating layer conductivity (maybe thickness) to match Uo.
|
928
|
+
if u and a[:glazing].empty?
|
536
929
|
ro = 1 / u - film
|
537
930
|
|
538
|
-
|
931
|
+
|
932
|
+
if ro > RMIN
|
539
933
|
if specs[:type] == :door # 1x layer, adjust conductivity
|
540
934
|
layer = c.getLayer(0).to_StandardOpaqueMaterial
|
541
935
|
return invalid("#{id} standard material?", mth, 0) if layer.empty?
|
@@ -543,34 +937,33 @@ module OSut
|
|
543
937
|
layer = layer.get
|
544
938
|
k = layer.thickness / ro
|
545
939
|
layer.setConductivity(k)
|
546
|
-
else # multiple layers, adjust
|
940
|
+
else # multiple layers, adjust layer conductivity, then thickness
|
547
941
|
lyr = insulatingLayer(c)
|
548
942
|
return invalid("#{id} construction", mth, 0) if lyr[:index].nil?
|
549
943
|
return invalid("#{id} construction", mth, 0) if lyr[:type ].nil?
|
550
|
-
return invalid("#{id} construction", mth, 0) if lyr[:r ].zero?
|
944
|
+
return invalid("#{id} construction", mth, 0) if lyr[:r ].to_i.zero?
|
551
945
|
|
552
946
|
index = lyr[:index]
|
553
947
|
layer = c.getLayer(index).to_StandardOpaqueMaterial
|
554
948
|
return invalid("#{id} material @#{index}", mth, 0) if layer.empty?
|
555
949
|
|
556
950
|
layer = layer.get
|
557
|
-
k = layer.conductivity
|
558
|
-
d = (ro - rsi(c) + lyr[:r]) * k
|
559
|
-
return invalid("#{id} adjusted m", mth, 0) if d < 0.03
|
560
951
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
nom
|
952
|
+
k = (layer.thickness / (ro - rsi(c) + lyr[:r])).clamp(KMIN, KMAX)
|
953
|
+
d = (k * (ro - rsi(c) + lyr[:r])).clamp(DMIN, DMAX)
|
954
|
+
|
955
|
+
nom = "OSut:"
|
956
|
+
nom += layer.nameString.gsub(/[^a-z]/i, "").gsub("OSut", "")
|
957
|
+
nom += ":K#{format('%4.3f', k)}:#{format('%03d', d*1000)[-3..-1]}"
|
565
958
|
|
566
959
|
lyr = model.getStandardOpaqueMaterialByName(nom)
|
567
960
|
|
568
961
|
if lyr.empty?
|
569
962
|
layer.setName(nom)
|
963
|
+
layer.setConductivity(k)
|
570
964
|
layer.setThickness(d)
|
571
965
|
else
|
572
|
-
|
573
|
-
c.setLayer(index, omat)
|
966
|
+
c.setLayer(index, lyr.get)
|
574
967
|
end
|
575
968
|
end
|
576
969
|
end
|
@@ -619,20 +1012,20 @@ module OSut
|
|
619
1012
|
end
|
620
1013
|
|
621
1014
|
# Shading schedule.
|
622
|
-
id = "OSut
|
1015
|
+
id = "OSut:SHADE:Ruleset"
|
623
1016
|
sch = mdl.getScheduleRulesetByName(id)
|
624
1017
|
|
625
1018
|
if sch.empty?
|
626
1019
|
sch = OpenStudio::Model::ScheduleRuleset.new(mdl, 0)
|
627
1020
|
sch.setName(id)
|
628
1021
|
sch.setScheduleTypeLimits(onoff)
|
629
|
-
sch.defaultDaySchedule.setName("OSut
|
1022
|
+
sch.defaultDaySchedule.setName("OSut:Shade:Ruleset:Default")
|
630
1023
|
else
|
631
1024
|
sch = sch.get
|
632
1025
|
end
|
633
1026
|
|
634
1027
|
# Summer cooling rule.
|
635
|
-
id = "OSut
|
1028
|
+
id = "OSut:SHADE:ScheduleRule"
|
636
1029
|
rule = mdl.getScheduleRuleByName(id)
|
637
1030
|
|
638
1031
|
if rule.empty?
|
@@ -646,14 +1039,14 @@ module OSut
|
|
646
1039
|
rule.setStartDate(start)
|
647
1040
|
rule.setEndDate(finish)
|
648
1041
|
rule.setApplyAllDays(true)
|
649
|
-
rule.daySchedule.setName("OSut
|
1042
|
+
rule.daySchedule.setName("OSut:Shade:Rule:Default")
|
650
1043
|
rule.daySchedule.addValue(OpenStudio::Time.new(0,24,0,0), 1)
|
651
1044
|
else
|
652
1045
|
rule = rule.get
|
653
1046
|
end
|
654
1047
|
|
655
1048
|
# Shade object.
|
656
|
-
id = "OSut
|
1049
|
+
id = "OSut:Shade"
|
657
1050
|
shd = mdl.getShadeByName(id)
|
658
1051
|
|
659
1052
|
if shd.empty?
|
@@ -664,7 +1057,7 @@ module OSut
|
|
664
1057
|
end
|
665
1058
|
|
666
1059
|
# Shading control (unique to each call).
|
667
|
-
id = "OSut
|
1060
|
+
id = "OSut:ShadingControl"
|
668
1061
|
ctl = OpenStudio::Model::ShadingControl.new(shd)
|
669
1062
|
ctl.setName(id)
|
670
1063
|
ctl.setSchedule(sch)
|
@@ -700,7 +1093,7 @@ module OSut
|
|
700
1093
|
|
701
1094
|
# A single material.
|
702
1095
|
mdl = sps.first.model
|
703
|
-
id = "OSut
|
1096
|
+
id = "OSut:MASS:Material"
|
704
1097
|
mat = mdl.getOpaqueMaterialByName(id)
|
705
1098
|
|
706
1099
|
if mat.empty?
|
@@ -719,7 +1112,7 @@ module OSut
|
|
719
1112
|
end
|
720
1113
|
|
721
1114
|
# A single, 1x layered construction.
|
722
|
-
id = "OSut
|
1115
|
+
id = "OSut:MASS:Construction"
|
723
1116
|
con = mdl.getConstructionByName(id)
|
724
1117
|
|
725
1118
|
if con.empty?
|
@@ -732,7 +1125,7 @@ module OSut
|
|
732
1125
|
con = con.get
|
733
1126
|
end
|
734
1127
|
|
735
|
-
id = "OSut
|
1128
|
+
id = "OSut:InternalMassDefinition:" + (format "%.2f", ratio)
|
736
1129
|
df = mdl.getInternalMassDefinitionByName(id)
|
737
1130
|
|
738
1131
|
if df.empty?
|
@@ -746,7 +1139,7 @@ module OSut
|
|
746
1139
|
|
747
1140
|
sps.each do |sp|
|
748
1141
|
mass = OpenStudio::Model::InternalMass.new(df)
|
749
|
-
mass.setName("OSut
|
1142
|
+
mass.setName("OSut:InternalMass:#{sp.nameString}")
|
750
1143
|
mass.setSpace(sp)
|
751
1144
|
end
|
752
1145
|
|
@@ -754,13 +1147,13 @@ module OSut
|
|
754
1147
|
end
|
755
1148
|
|
756
1149
|
##
|
757
|
-
# Validates if a default construction set holds
|
1150
|
+
# Validates if a default construction set holds an opaque base construction.
|
758
1151
|
#
|
759
1152
|
# @param set [OpenStudio::Model::DefaultConstructionSet] a default set
|
760
|
-
# @param bse [OpenStudio::Model::ConstructionBase]
|
1153
|
+
# @param bse [OpenStudio::Model::ConstructionBase] an opaque construction base
|
761
1154
|
# @param gr [Bool] if ground-facing surface
|
762
1155
|
# @param ex [Bool] if exterior-facing surface
|
763
|
-
# @param tp [#
|
1156
|
+
# @param tp [#to_sym] surface type: "floor", "wall" or "roofceiling"
|
764
1157
|
#
|
765
1158
|
# @return [Bool] whether default set holds construction
|
766
1159
|
# @return [false] if invalid input (see logs)
|
@@ -779,7 +1172,7 @@ module OSut
|
|
779
1172
|
ck2 = bse.is_a?(cl2)
|
780
1173
|
ck3 = [true, false].include?(gr)
|
781
1174
|
ck4 = [true, false].include?(ex)
|
782
|
-
ck5 = tp.respond_to?(:
|
1175
|
+
ck5 = tp.respond_to?(:to_sym)
|
783
1176
|
return mismatch(id1, set, cl1, mth, DBG, false) unless ck1
|
784
1177
|
return mismatch(id2, bse, cl2, mth, DBG, false) unless ck2
|
785
1178
|
return invalid("ground" , mth, 3, DBG, false) unless ck3
|
@@ -859,6 +1252,9 @@ module OSut
|
|
859
1252
|
type = s.surfaceType
|
860
1253
|
ground = false
|
861
1254
|
exterior = false
|
1255
|
+
adjacent = s.adjacentSurface.empty? ? nil : s.adjacentSurface.get
|
1256
|
+
aspace = adjacent.nil? || adjacent.space.empty? ? nil : adjacent.space.get
|
1257
|
+
typ = adjacent.nil? ? nil : adjacent.surfaceType
|
862
1258
|
|
863
1259
|
if s.isGroundSurface
|
864
1260
|
ground = true
|
@@ -866,7 +1262,14 @@ module OSut
|
|
866
1262
|
exterior = true
|
867
1263
|
end
|
868
1264
|
|
869
|
-
|
1265
|
+
if space.defaultConstructionSet.empty?
|
1266
|
+
unless aspace.nil?
|
1267
|
+
unless aspace.defaultConstructionSet.empty?
|
1268
|
+
set = aspace.defaultConstructionSet.get
|
1269
|
+
return set if holdsConstruction?(set, base, ground, exterior, typ)
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
else
|
870
1273
|
set = space.defaultConstructionSet.get
|
871
1274
|
return set if holdsConstruction?(set, base, ground, exterior, type)
|
872
1275
|
end
|
@@ -880,6 +1283,17 @@ module OSut
|
|
880
1283
|
end
|
881
1284
|
end
|
882
1285
|
|
1286
|
+
unless aspace.nil? || aspace.spaceType.empty?
|
1287
|
+
unless aspace.spaceType.empty?
|
1288
|
+
spacetype = aspace.spaceType.get
|
1289
|
+
|
1290
|
+
unless spacetype.defaultConstructionSet.empty?
|
1291
|
+
set = spacetype.defaultConstructionSet.get
|
1292
|
+
return set if holdsConstruction?(set, base, ground, exterior, typ)
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
end
|
1296
|
+
|
883
1297
|
unless space.buildingStory.empty?
|
884
1298
|
story = space.buildingStory.get
|
885
1299
|
|
@@ -889,6 +1303,15 @@ module OSut
|
|
889
1303
|
end
|
890
1304
|
end
|
891
1305
|
|
1306
|
+
unless aspace.nil? || aspace.buildingStory.empty?
|
1307
|
+
story = aspace.buildingStory.get
|
1308
|
+
|
1309
|
+
unless spacetype.defaultConstructionSet.empty?
|
1310
|
+
set = spacetype.defaultConstructionSet.get
|
1311
|
+
return set if holdsConstruction?(set, base, ground, exterior, typ)
|
1312
|
+
end
|
1313
|
+
end
|
1314
|
+
|
892
1315
|
building = mdl.getBuilding
|
893
1316
|
|
894
1317
|
unless building.defaultConstructionSet.empty?
|
@@ -899,203 +1322,6 @@ module OSut
|
|
899
1322
|
nil
|
900
1323
|
end
|
901
1324
|
|
902
|
-
##
|
903
|
-
# Validates if every material in a layered construction is standard & opaque.
|
904
|
-
#
|
905
|
-
# @param lc [OpenStudio::LayeredConstruction] a layered construction
|
906
|
-
#
|
907
|
-
# @return [Bool] whether all layers are valid
|
908
|
-
# @return [false] if invalid input (see logs)
|
909
|
-
def standardOpaqueLayers?(lc = nil)
|
910
|
-
mth = "OSut::#{__callee__}"
|
911
|
-
cl = OpenStudio::Model::LayeredConstruction
|
912
|
-
return invalid("lc", mth, 1, DBG, false) unless lc.respond_to?(NS)
|
913
|
-
return mismatch(lc.nameString, lc, cl, mth, DBG, false) unless lc.is_a?(cl)
|
914
|
-
|
915
|
-
lc.layers.each { |m| return false if m.to_StandardOpaqueMaterial.empty? }
|
916
|
-
|
917
|
-
true
|
918
|
-
end
|
919
|
-
|
920
|
-
##
|
921
|
-
# Returns total (standard opaque) layered construction thickness (m).
|
922
|
-
#
|
923
|
-
# @param lc [OpenStudio::LayeredConstruction] a layered construction
|
924
|
-
#
|
925
|
-
# @return [Float] construction thickness
|
926
|
-
# @return [0.0] if invalid input (see logs)
|
927
|
-
def thickness(lc = nil)
|
928
|
-
mth = "OSut::#{__callee__}"
|
929
|
-
cl = OpenStudio::Model::LayeredConstruction
|
930
|
-
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
931
|
-
|
932
|
-
id = lc.nameString
|
933
|
-
return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl)
|
934
|
-
|
935
|
-
ok = standardOpaqueLayers?(lc)
|
936
|
-
log(ERR, "'#{id}' holds non-StandardOpaqueMaterial(s) (#{mth})") unless ok
|
937
|
-
return 0.0 unless ok
|
938
|
-
|
939
|
-
thickness = 0.0
|
940
|
-
lc.layers.each { |m| thickness += m.thickness }
|
941
|
-
|
942
|
-
thickness
|
943
|
-
end
|
944
|
-
|
945
|
-
##
|
946
|
-
# Returns total air film resistance of a fenestrated construction (m2•K/W)
|
947
|
-
#
|
948
|
-
# @param usi [Numeric] a fenestrated construction's U-factor (W/m2•K)
|
949
|
-
#
|
950
|
-
# @return [Float] total air film resistances
|
951
|
-
# @return [0.1216] if invalid input (see logs)
|
952
|
-
def glazingAirFilmRSi(usi = 5.85)
|
953
|
-
# The sum of thermal resistances of calculated exterior and interior film
|
954
|
-
# coefficients under standard winter conditions are taken from:
|
955
|
-
#
|
956
|
-
# https://bigladdersoftware.com/epx/docs/9-6/engineering-reference/
|
957
|
-
# window-calculation-module.html#simple-window-model
|
958
|
-
#
|
959
|
-
# These remain acceptable approximations for flat windows, yet likely
|
960
|
-
# unsuitable for subsurfaces with curved or projecting shapes like domed
|
961
|
-
# skylights. The solution here is considered an adequate fix for reporting,
|
962
|
-
# awaiting eventual OpenStudio (and EnergyPlus) upgrades to report NFRC 100
|
963
|
-
# (or ISO) air film resistances under standard winter conditions.
|
964
|
-
#
|
965
|
-
# For U-factors above 8.0 W/m2•K (or invalid input), the function returns
|
966
|
-
# 0.1216 m2•K/W, which corresponds to a construction with a single glass
|
967
|
-
# layer thickness of 2mm & k = ~0.6 W/m.K.
|
968
|
-
#
|
969
|
-
# The EnergyPlus Engineering calculations were designed for vertical
|
970
|
-
# windows - not horizontal, slanted or domed surfaces - use with caution.
|
971
|
-
mth = "OSut::#{__callee__}"
|
972
|
-
cl = Numeric
|
973
|
-
return mismatch("usi", usi, cl, mth, DBG, 0.1216) unless usi.is_a?(cl)
|
974
|
-
return invalid("usi", mth, 1, WRN, 0.1216) if usi > 8.0
|
975
|
-
return negative("usi", mth, WRN, 0.1216) if usi < 0
|
976
|
-
return zero("usi", mth, WRN, 0.1216) if usi.abs < TOL
|
977
|
-
|
978
|
-
rsi = 1 / (0.025342 * usi + 29.163853) # exterior film, next interior film
|
979
|
-
return rsi + 1 / (0.359073 * Math.log(usi) + 6.949915) if usi < 5.85
|
980
|
-
return rsi + 1 / (1.788041 * usi - 2.886625)
|
981
|
-
end
|
982
|
-
|
983
|
-
##
|
984
|
-
# Returns a construction's 'standard calc' thermal resistance (m2•K/W), which
|
985
|
-
# includes air film resistances. It excludes insulating effects of shades,
|
986
|
-
# screens, etc. in the case of fenestrated constructions.
|
987
|
-
#
|
988
|
-
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
|
989
|
-
# @param film [Numeric] thermal resistance of surface air films (m2•K/W)
|
990
|
-
# @param t [Numeric] gas temperature (°C) (optional)
|
991
|
-
#
|
992
|
-
# @return [Float] layered construction's thermal resistance
|
993
|
-
# @return [0.0] if invalid input (see logs)
|
994
|
-
def rsi(lc = nil, film = 0.0, t = 0.0)
|
995
|
-
# This is adapted from BTAP's Material Module "get_conductance" (P. Lopez)
|
996
|
-
#
|
997
|
-
# https://github.com/NREL/OpenStudio-Prototype-Buildings/blob/
|
998
|
-
# c3d5021d8b7aef43e560544699fb5c559e6b721d/lib/btap/measures/
|
999
|
-
# btap_equest_converter/envelope.rb#L122
|
1000
|
-
mth = "OSut::#{__callee__}"
|
1001
|
-
cl1 = OpenStudio::Model::LayeredConstruction
|
1002
|
-
cl2 = Numeric
|
1003
|
-
return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS)
|
1004
|
-
|
1005
|
-
id = lc.nameString
|
1006
|
-
return mismatch(id, lc, cl1, mth, DBG, 0.0) unless lc.is_a?(cl1)
|
1007
|
-
return mismatch("film", film, cl2, mth, DBG, 0.0) unless film.is_a?(cl2)
|
1008
|
-
return mismatch("temp K", t, cl2, mth, DBG, 0.0) unless t.is_a?(cl2)
|
1009
|
-
|
1010
|
-
t += 273.0 # °C to K
|
1011
|
-
return negative("temp K", mth, ERR, 0.0) if t < 0
|
1012
|
-
return negative("film", mth, ERR, 0.0) if film < 0
|
1013
|
-
|
1014
|
-
rsi = film
|
1015
|
-
|
1016
|
-
lc.layers.each do |m|
|
1017
|
-
# Fenestration materials first.
|
1018
|
-
empty = m.to_SimpleGlazing.empty?
|
1019
|
-
return 1 / m.to_SimpleGlazing.get.uFactor unless empty
|
1020
|
-
|
1021
|
-
empty = m.to_StandardGlazing.empty?
|
1022
|
-
rsi += m.to_StandardGlazing.get.thermalResistance unless empty
|
1023
|
-
empty = m.to_RefractionExtinctionGlazing.empty?
|
1024
|
-
rsi += m.to_RefractionExtinctionGlazing.get.thermalResistance unless empty
|
1025
|
-
empty = m.to_Gas.empty?
|
1026
|
-
rsi += m.to_Gas.get.getThermalResistance(t) unless empty
|
1027
|
-
empty = m.to_GasMixture.empty?
|
1028
|
-
rsi += m.to_GasMixture.get.getThermalResistance(t) unless empty
|
1029
|
-
|
1030
|
-
# Opaque materials next.
|
1031
|
-
empty = m.to_StandardOpaqueMaterial.empty?
|
1032
|
-
rsi += m.to_StandardOpaqueMaterial.get.thermalResistance unless empty
|
1033
|
-
empty = m.to_MasslessOpaqueMaterial.empty?
|
1034
|
-
rsi += m.to_MasslessOpaqueMaterial.get.thermalResistance unless empty
|
1035
|
-
empty = m.to_RoofVegetation.empty?
|
1036
|
-
rsi += m.to_RoofVegetation.get.thermalResistance unless empty
|
1037
|
-
empty = m.to_AirGap.empty?
|
1038
|
-
rsi += m.to_AirGap.get.thermalResistance unless empty
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
rsi
|
1042
|
-
end
|
1043
|
-
|
1044
|
-
##
|
1045
|
-
# Identifies a layered construction's (opaque) insulating layer. The method
|
1046
|
-
# returns a 3-keyed hash :index, the insulating layer index [0, n layers)
|
1047
|
-
# within the layered construction; :type, either :standard or :massless; and
|
1048
|
-
# :r, material thermal resistance in m2•K/W.
|
1049
|
-
#
|
1050
|
-
# @param lc [OpenStudio::Model::LayeredConstruction] a layered construction
|
1051
|
-
#
|
1052
|
-
# @return [Hash] index: (Integer), type: (Symbol), r: (Float)
|
1053
|
-
# @return [Hash] index: nil, type: nil, r: 0 if invalid input (see logs)
|
1054
|
-
def insulatingLayer(lc = nil)
|
1055
|
-
mth = "OSut::#{__callee__}"
|
1056
|
-
cl = OpenStudio::Model::LayeredConstruction
|
1057
|
-
res = { index: nil, type: nil, r: 0.0 }
|
1058
|
-
i = 0 # iterator
|
1059
|
-
return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
|
1060
|
-
|
1061
|
-
id = lc.nameString
|
1062
|
-
return mismatch(id, lc, cl, mth, DBG, res) unless lc.is_a?(cl)
|
1063
|
-
|
1064
|
-
lc.layers.each do |m|
|
1065
|
-
unless m.to_MasslessOpaqueMaterial.empty?
|
1066
|
-
m = m.to_MasslessOpaqueMaterial.get
|
1067
|
-
|
1068
|
-
if m.thermalResistance < 0.001 || m.thermalResistance < res[:r]
|
1069
|
-
i += 1
|
1070
|
-
next
|
1071
|
-
else
|
1072
|
-
res[:r ] = m.thermalResistance
|
1073
|
-
res[:index] = i
|
1074
|
-
res[:type ] = :massless
|
1075
|
-
end
|
1076
|
-
end
|
1077
|
-
|
1078
|
-
unless m.to_StandardOpaqueMaterial.empty?
|
1079
|
-
m = m.to_StandardOpaqueMaterial.get
|
1080
|
-
k = m.thermalConductivity
|
1081
|
-
d = m.thickness
|
1082
|
-
|
1083
|
-
if d < 0.003 || k > 3.0 || d / k < res[:r]
|
1084
|
-
i += 1
|
1085
|
-
next
|
1086
|
-
else
|
1087
|
-
res[:r ] = d / k
|
1088
|
-
res[:index] = i
|
1089
|
-
res[:type ] = :standard
|
1090
|
-
end
|
1091
|
-
end
|
1092
|
-
|
1093
|
-
i += 1
|
1094
|
-
end
|
1095
|
-
|
1096
|
-
res
|
1097
|
-
end
|
1098
|
-
|
1099
1325
|
##
|
1100
1326
|
# Validates whether opaque surface can be considered as a curtain wall (or
|
1101
1327
|
# similar technology) spandrel, regardless of construction layers, by looking
|
@@ -2216,7 +2442,7 @@ module OSut
|
|
2216
2442
|
cl = OpenStudio::Model::Model
|
2217
2443
|
limits = nil
|
2218
2444
|
return mismatch("model", model, cl, mth) unless model.is_a?(cl)
|
2219
|
-
return invalid("availability", avl, 2, mth) unless avl.respond_to?(:
|
2445
|
+
return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_sym)
|
2220
2446
|
|
2221
2447
|
# Either fetch availability ScheduleTypeLimits object, or create one.
|
2222
2448
|
model.getScheduleTypeLimitss.each do |l|
|
@@ -4648,7 +4874,7 @@ module OSut
|
|
4648
4874
|
#
|
4649
4875
|
# @param s [Set<OpenStudio::Point3d>] a (larger) parent set of points
|
4650
4876
|
# @param [Array<Hash>] set a collection of (smaller) sequenced points
|
4651
|
-
# @option [
|
4877
|
+
# @option [#to_sym] tag sequence of subset vertices to target
|
4652
4878
|
#
|
4653
4879
|
# @return [Integer] number of successfully anchored subsets (see logs)
|
4654
4880
|
def genAnchors(s = nil, set = [], tag = :box)
|
@@ -4656,18 +4882,20 @@ module OSut
|
|
4656
4882
|
n = 0
|
4657
4883
|
id = s.respond_to?(:nameString) ? "#{s.nameString}: " : ""
|
4658
4884
|
pts = poly(s)
|
4659
|
-
return invalid("#{id} polygon", mth, 1, DBG, n)
|
4660
|
-
return mismatch("set", set,
|
4885
|
+
return invalid("#{id} polygon", mth, 1, DBG, n) if pts.empty?
|
4886
|
+
return mismatch("set", set, Array, mth, DBG, n) unless set.respond_to?(:to_a)
|
4887
|
+
return mismatch("tag", tag, Symbol, mth, DBG, n) unless tag.respond_to?(:to_sym)
|
4661
4888
|
|
4662
4889
|
origin = OpenStudio::Point3d.new(0,0,0)
|
4663
4890
|
zenith = OpenStudio::Point3d.new(0,0,1)
|
4664
4891
|
ray = zenith - origin
|
4665
4892
|
set = set.to_a
|
4893
|
+
tag = tag.to_sym
|
4666
4894
|
|
4667
4895
|
# Validate individual subsets. Purge surface-specific leader line anchors.
|
4668
4896
|
set.each_with_index do |st, i|
|
4669
4897
|
str1 = id + "subset ##{i+1}"
|
4670
|
-
str2 = str1 + " #{tag
|
4898
|
+
str2 = str1 + " #{trim(tag)}"
|
4671
4899
|
return mismatch(str1, st, Hash, mth, DBG, n) unless st.respond_to?(:key?)
|
4672
4900
|
return hashkey( str1, st, tag, mth, DBG, n) unless st.key?(tag)
|
4673
4901
|
return empty("#{str2} vertices", mth, DBG, n) if st[tag].empty?
|
@@ -4810,7 +5038,7 @@ module OSut
|
|
4810
5038
|
# @param s [Set<OpenStudio::Point3d>] a larger (parent) set of points
|
4811
5039
|
# @param [Array<Hash>] set a collection of (smaller) sequenced vertices
|
4812
5040
|
# @option set [Hash] :ld a polygon-specific leader line anchors
|
4813
|
-
# @option [
|
5041
|
+
# @option [#to_sym] tag sequence of set vertices to target
|
4814
5042
|
#
|
4815
5043
|
# @return [OpenStudio::Point3dVector] extended vertices (see logs if empty)
|
4816
5044
|
def genExtendedVertices(s = nil, set = [], tag = :vtx)
|
@@ -4822,14 +5050,16 @@ module OSut
|
|
4822
5050
|
a = OpenStudio::Point3dVector.new
|
4823
5051
|
v = []
|
4824
5052
|
return a if pts.empty?
|
4825
|
-
return mismatch("set", set,
|
5053
|
+
return mismatch("set", set, Array, mth, DBG, a) unless set.respond_to?(:to_a)
|
5054
|
+
return mismatch("tag", tag, Symbol, mth, DBG, n) unless tag.respond_to?(:to_sym)
|
4826
5055
|
|
4827
5056
|
set = set.to_a
|
5057
|
+
tag = tag.to_sym
|
4828
5058
|
|
4829
5059
|
# Validate individual sets.
|
4830
5060
|
set.each_with_index do |st, i|
|
4831
5061
|
str1 = id + "subset ##{i+1}"
|
4832
|
-
str2 = str1 + " #{tag
|
5062
|
+
str2 = str1 + " #{trim(tag)}"
|
4833
5063
|
return mismatch(str1, st, Hash, mth, DBG, a) unless st.respond_to?(:key?)
|
4834
5064
|
next if st.key?(:void) && st[:void]
|
4835
5065
|
|
@@ -5121,8 +5351,8 @@ module OSut
|
|
5121
5351
|
# surface type filters if 'type' argument == "all".
|
5122
5352
|
#
|
5123
5353
|
# @param spaces [Set<OpenStudio::Model::Space>] target spaces
|
5124
|
-
# @param boundary [#
|
5125
|
-
# @param type [#
|
5354
|
+
# @param boundary [#to_sym] OpenStudio outside boundary condition
|
5355
|
+
# @param type [#to_sym] OpenStudio surface (or subsurface) type
|
5126
5356
|
# @param sides [Set<Symbols>] direction keys, e.g. :north (see OSut::SIDZ)
|
5127
5357
|
#
|
5128
5358
|
# @return [Array<OpenStudio::Model::Surface>] surfaces (may be empty, no logs)
|
@@ -5131,7 +5361,7 @@ module OSut
|
|
5131
5361
|
spaces = spaces.respond_to?(:to_a) ? spaces.to_a : []
|
5132
5362
|
return [] if spaces.empty?
|
5133
5363
|
|
5134
|
-
sides = sides.respond_to?(:to_sym) ? [sides] : sides
|
5364
|
+
sides = sides.respond_to?(:to_sym) ? [trim(sides).to_sym] : sides
|
5135
5365
|
sides = sides.respond_to?(:to_a) ? sides.to_a : []
|
5136
5366
|
|
5137
5367
|
faces = []
|
@@ -5405,7 +5635,7 @@ module OSut
|
|
5405
5635
|
# @param s [OpenStudio::Model::Surface] a model surface
|
5406
5636
|
# @param [Array<Hash>] subs requested attributes
|
5407
5637
|
# @option subs [#to_s] :id identifier e.g. "Window 007"
|
5408
|
-
# @option subs [#
|
5638
|
+
# @option subs [#to_sym] :type ("FixedWindow") OpenStudio subsurface type
|
5409
5639
|
# @option subs [#to_i] :count (1) number of individual subs per array
|
5410
5640
|
# @option subs [#to_i] :multiplier (1) OpenStudio subsurface multiplier
|
5411
5641
|
# @option subs [#frameWidth] :frame (nil) OpenStudio frame & divider object
|
@@ -5534,12 +5764,13 @@ module OSut
|
|
5534
5764
|
return mismatch("sub", sub, cl4, mth, DBG, no) unless sub.is_a?(cl3)
|
5535
5765
|
|
5536
5766
|
# Required key:value pairs (either set by the user or defaulted).
|
5537
|
-
sub[:frame ] = nil unless sub.key?(:frame
|
5538
|
-
sub[:assembly ] = nil unless sub.key?(:assembly
|
5539
|
-
sub[:count ] = 1 unless sub.key?(:count
|
5767
|
+
sub[:frame ] = nil unless sub.key?(:frame)
|
5768
|
+
sub[:assembly ] = nil unless sub.key?(:assembly)
|
5769
|
+
sub[:count ] = 1 unless sub.key?(:count)
|
5540
5770
|
sub[:multiplier] = 1 unless sub.key?(:multiplier)
|
5541
|
-
sub[:id ] = "" unless sub.key?(:id
|
5542
|
-
sub[:type ] = type unless sub.key?(:type
|
5771
|
+
sub[:id ] = "" unless sub.key?(:id)
|
5772
|
+
sub[:type ] = type unless sub.key?(:type)
|
5773
|
+
sub[:type ] = type unless sub[:type].respond_to?(:to_sym)
|
5543
5774
|
sub[:type ] = trim(sub[:type])
|
5544
5775
|
sub[:id ] = trim(sub[:id])
|
5545
5776
|
sub[:type ] = type if sub[:type].empty?
|
@@ -6368,7 +6599,7 @@ module OSut
|
|
6368
6599
|
# @option opts [Bool] :sloped (true) whether to consider sloped roof surfaces
|
6369
6600
|
# @option opts [Bool] :plenum (true) whether to consider plenum wells
|
6370
6601
|
# @option opts [Bool] :attic (true) whether to consider attic wells
|
6371
|
-
# @option opts [Array<#
|
6602
|
+
# @option opts [Array<#to_sym>] :patterns requested skylight allocation (3x)
|
6372
6603
|
# @example (a) consider 2D array of individual skylights, e.g. n(1.22m x 1.22m)
|
6373
6604
|
# opts[:patterns] = ["array"]
|
6374
6605
|
# @example (b) consider 'a', then array of 1x(size) x n(size) skylight strips
|
@@ -6685,7 +6916,7 @@ module OSut
|
|
6685
6916
|
if opts.key?(:patterns)
|
6686
6917
|
if opts[:patterns].is_a?(Array)
|
6687
6918
|
opts[:patterns].each_with_index do |pattern, i|
|
6688
|
-
pattern = trim(pattern).downcase
|
6919
|
+
pattern = pattern.respond_to?(:to_sym) ? trim(pattern).downcase : ""
|
6689
6920
|
|
6690
6921
|
if pattern.empty?
|
6691
6922
|
invalid("pattern #{i+1}", mth, 0, ERR)
|
data/lib/osut/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: osut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Bourgeois
|
@@ -89,7 +89,7 @@ licenses:
|
|
89
89
|
- BSD-3-Clause
|
90
90
|
metadata:
|
91
91
|
homepage_uri: https://github.com/rd2/osut
|
92
|
-
source_code_uri: https://github.com/rd2/osut/tree/v0.
|
92
|
+
source_code_uri: https://github.com/rd2/osut/tree/v0.8.0
|
93
93
|
bug_tracker_uri: https://github.com/rd2/osut/issues
|
94
94
|
rdoc_options: []
|
95
95
|
require_paths:
|