tbd 3.4.5 → 3.5.1
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/LICENSE.md +1 -1
- data/lib/measures/tbd/LICENSE.md +1 -1
- data/lib/measures/tbd/measure.rb +1 -1
- data/lib/measures/tbd/measure.xml +10 -10
- data/lib/measures/tbd/resources/geo.rb +11 -10
- data/lib/measures/tbd/resources/psi.rb +109 -85
- data/lib/measures/tbd/resources/tbd.rb +1 -1
- data/lib/measures/tbd/resources/ua.rb +66 -66
- data/lib/measures/tbd/resources/utils.rb +525 -315
- data/lib/measures/tbd/tests/tbd_tests.rb +1 -1
- data/lib/tbd/geo.rb +11 -10
- data/lib/tbd/psi.rb +109 -85
- data/lib/tbd/ua.rb +66 -66
- data/lib/tbd/version.rb +2 -2
- data/lib/tbd.rb +18 -12
- data/tbd.gemspec +1 -1
- metadata +9 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BSD 3-Clause License
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2022-
|
|
3
|
+
# Copyright (c) 2022-2026, Denis Bourgeois
|
|
4
4
|
# All rights reserved.
|
|
5
5
|
#
|
|
6
6
|
# Redistribution and use in source and binary forms, with or without
|
|
@@ -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#{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,14 @@ 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
|
-
if ro >
|
|
931
|
+
if ro > RMIN
|
|
539
932
|
if specs[:type] == :door # 1x layer, adjust conductivity
|
|
540
933
|
layer = c.getLayer(0).to_StandardOpaqueMaterial
|
|
541
934
|
return invalid("#{id} standard material?", mth, 0) if layer.empty?
|
|
@@ -543,35 +936,14 @@ module OSut
|
|
|
543
936
|
layer = layer.get
|
|
544
937
|
k = layer.thickness / ro
|
|
545
938
|
layer.setConductivity(k)
|
|
546
|
-
else # multiple layers, adjust
|
|
939
|
+
else # multiple layers, adjust layer conductivity, then thickness
|
|
547
940
|
lyr = insulatingLayer(c)
|
|
548
941
|
return invalid("#{id} construction", mth, 0) if lyr[:index].nil?
|
|
549
942
|
return invalid("#{id} construction", mth, 0) if lyr[:type ].nil?
|
|
550
|
-
return invalid("#{id} construction", mth, 0) if lyr[:r ].zero?
|
|
943
|
+
return invalid("#{id} construction", mth, 0) if lyr[:r ].to_i.zero?
|
|
551
944
|
|
|
552
945
|
index = lyr[:index]
|
|
553
|
-
|
|
554
|
-
return invalid("#{id} material @#{index}", mth, 0) if layer.empty?
|
|
555
|
-
|
|
556
|
-
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
|
-
|
|
561
|
-
nom = "OSut|"
|
|
562
|
-
nom += layer.nameString.gsub(/[^a-z]/i, "").gsub("OSut", "")
|
|
563
|
-
nom += "|"
|
|
564
|
-
nom += format("%03d", d*1000)[-3..-1]
|
|
565
|
-
|
|
566
|
-
lyr = model.getStandardOpaqueMaterialByName(nom)
|
|
567
|
-
|
|
568
|
-
if lyr.empty?
|
|
569
|
-
layer.setName(nom)
|
|
570
|
-
layer.setThickness(d)
|
|
571
|
-
else
|
|
572
|
-
omat = lyr.get
|
|
573
|
-
c.setLayer(index, omat)
|
|
574
|
-
end
|
|
946
|
+
resetUo(c, film, index, u)
|
|
575
947
|
end
|
|
576
948
|
end
|
|
577
949
|
end
|
|
@@ -619,20 +991,20 @@ module OSut
|
|
|
619
991
|
end
|
|
620
992
|
|
|
621
993
|
# Shading schedule.
|
|
622
|
-
id = "OSut
|
|
994
|
+
id = "OSut:SHADE:Ruleset"
|
|
623
995
|
sch = mdl.getScheduleRulesetByName(id)
|
|
624
996
|
|
|
625
997
|
if sch.empty?
|
|
626
998
|
sch = OpenStudio::Model::ScheduleRuleset.new(mdl, 0)
|
|
627
999
|
sch.setName(id)
|
|
628
1000
|
sch.setScheduleTypeLimits(onoff)
|
|
629
|
-
sch.defaultDaySchedule.setName("OSut
|
|
1001
|
+
sch.defaultDaySchedule.setName("OSut:Shade:Ruleset:Default")
|
|
630
1002
|
else
|
|
631
1003
|
sch = sch.get
|
|
632
1004
|
end
|
|
633
1005
|
|
|
634
1006
|
# Summer cooling rule.
|
|
635
|
-
id = "OSut
|
|
1007
|
+
id = "OSut:SHADE:ScheduleRule"
|
|
636
1008
|
rule = mdl.getScheduleRuleByName(id)
|
|
637
1009
|
|
|
638
1010
|
if rule.empty?
|
|
@@ -646,14 +1018,14 @@ module OSut
|
|
|
646
1018
|
rule.setStartDate(start)
|
|
647
1019
|
rule.setEndDate(finish)
|
|
648
1020
|
rule.setApplyAllDays(true)
|
|
649
|
-
rule.daySchedule.setName("OSut
|
|
1021
|
+
rule.daySchedule.setName("OSut:Shade:Rule:Default")
|
|
650
1022
|
rule.daySchedule.addValue(OpenStudio::Time.new(0,24,0,0), 1)
|
|
651
1023
|
else
|
|
652
1024
|
rule = rule.get
|
|
653
1025
|
end
|
|
654
1026
|
|
|
655
1027
|
# Shade object.
|
|
656
|
-
id = "OSut
|
|
1028
|
+
id = "OSut:Shade"
|
|
657
1029
|
shd = mdl.getShadeByName(id)
|
|
658
1030
|
|
|
659
1031
|
if shd.empty?
|
|
@@ -664,7 +1036,7 @@ module OSut
|
|
|
664
1036
|
end
|
|
665
1037
|
|
|
666
1038
|
# Shading control (unique to each call).
|
|
667
|
-
id = "OSut
|
|
1039
|
+
id = "OSut:ShadingControl"
|
|
668
1040
|
ctl = OpenStudio::Model::ShadingControl.new(shd)
|
|
669
1041
|
ctl.setName(id)
|
|
670
1042
|
ctl.setSchedule(sch)
|
|
@@ -700,7 +1072,7 @@ module OSut
|
|
|
700
1072
|
|
|
701
1073
|
# A single material.
|
|
702
1074
|
mdl = sps.first.model
|
|
703
|
-
id = "OSut
|
|
1075
|
+
id = "OSut:MASS:Material"
|
|
704
1076
|
mat = mdl.getOpaqueMaterialByName(id)
|
|
705
1077
|
|
|
706
1078
|
if mat.empty?
|
|
@@ -719,7 +1091,7 @@ module OSut
|
|
|
719
1091
|
end
|
|
720
1092
|
|
|
721
1093
|
# A single, 1x layered construction.
|
|
722
|
-
id = "OSut
|
|
1094
|
+
id = "OSut:MASS:Construction"
|
|
723
1095
|
con = mdl.getConstructionByName(id)
|
|
724
1096
|
|
|
725
1097
|
if con.empty?
|
|
@@ -732,7 +1104,7 @@ module OSut
|
|
|
732
1104
|
con = con.get
|
|
733
1105
|
end
|
|
734
1106
|
|
|
735
|
-
id = "OSut
|
|
1107
|
+
id = "OSut:InternalMassDefinition:" + (format "%.2f", ratio)
|
|
736
1108
|
df = mdl.getInternalMassDefinitionByName(id)
|
|
737
1109
|
|
|
738
1110
|
if df.empty?
|
|
@@ -746,7 +1118,7 @@ module OSut
|
|
|
746
1118
|
|
|
747
1119
|
sps.each do |sp|
|
|
748
1120
|
mass = OpenStudio::Model::InternalMass.new(df)
|
|
749
|
-
mass.setName("OSut
|
|
1121
|
+
mass.setName("OSut:InternalMass:#{sp.nameString}")
|
|
750
1122
|
mass.setSpace(sp)
|
|
751
1123
|
end
|
|
752
1124
|
|
|
@@ -754,13 +1126,13 @@ module OSut
|
|
|
754
1126
|
end
|
|
755
1127
|
|
|
756
1128
|
##
|
|
757
|
-
# Validates if a default construction set holds
|
|
1129
|
+
# Validates if a default construction set holds an opaque base construction.
|
|
758
1130
|
#
|
|
759
1131
|
# @param set [OpenStudio::Model::DefaultConstructionSet] a default set
|
|
760
|
-
# @param bse [OpenStudio::Model::ConstructionBase]
|
|
1132
|
+
# @param bse [OpenStudio::Model::ConstructionBase] an opaque construction base
|
|
761
1133
|
# @param gr [Bool] if ground-facing surface
|
|
762
1134
|
# @param ex [Bool] if exterior-facing surface
|
|
763
|
-
# @param tp [#
|
|
1135
|
+
# @param tp [#to_sym] surface type: "floor", "wall" or "roofceiling"
|
|
764
1136
|
#
|
|
765
1137
|
# @return [Bool] whether default set holds construction
|
|
766
1138
|
# @return [false] if invalid input (see logs)
|
|
@@ -779,7 +1151,7 @@ module OSut
|
|
|
779
1151
|
ck2 = bse.is_a?(cl2)
|
|
780
1152
|
ck3 = [true, false].include?(gr)
|
|
781
1153
|
ck4 = [true, false].include?(ex)
|
|
782
|
-
ck5 = tp.respond_to?(:
|
|
1154
|
+
ck5 = tp.respond_to?(:to_sym)
|
|
783
1155
|
return mismatch(id1, set, cl1, mth, DBG, false) unless ck1
|
|
784
1156
|
return mismatch(id2, bse, cl2, mth, DBG, false) unless ck2
|
|
785
1157
|
return invalid("ground" , mth, 3, DBG, false) unless ck3
|
|
@@ -859,6 +1231,9 @@ module OSut
|
|
|
859
1231
|
type = s.surfaceType
|
|
860
1232
|
ground = false
|
|
861
1233
|
exterior = false
|
|
1234
|
+
adjacent = s.adjacentSurface.empty? ? nil : s.adjacentSurface.get
|
|
1235
|
+
aspace = adjacent.nil? || adjacent.space.empty? ? nil : adjacent.space.get
|
|
1236
|
+
typ = adjacent.nil? ? nil : adjacent.surfaceType
|
|
862
1237
|
|
|
863
1238
|
if s.isGroundSurface
|
|
864
1239
|
ground = true
|
|
@@ -866,7 +1241,14 @@ module OSut
|
|
|
866
1241
|
exterior = true
|
|
867
1242
|
end
|
|
868
1243
|
|
|
869
|
-
|
|
1244
|
+
if space.defaultConstructionSet.empty?
|
|
1245
|
+
unless aspace.nil?
|
|
1246
|
+
unless aspace.defaultConstructionSet.empty?
|
|
1247
|
+
set = aspace.defaultConstructionSet.get
|
|
1248
|
+
return set if holdsConstruction?(set, base, ground, exterior, typ)
|
|
1249
|
+
end
|
|
1250
|
+
end
|
|
1251
|
+
else
|
|
870
1252
|
set = space.defaultConstructionSet.get
|
|
871
1253
|
return set if holdsConstruction?(set, base, ground, exterior, type)
|
|
872
1254
|
end
|
|
@@ -880,6 +1262,17 @@ module OSut
|
|
|
880
1262
|
end
|
|
881
1263
|
end
|
|
882
1264
|
|
|
1265
|
+
unless aspace.nil? || aspace.spaceType.empty?
|
|
1266
|
+
unless aspace.spaceType.empty?
|
|
1267
|
+
spacetype = aspace.spaceType.get
|
|
1268
|
+
|
|
1269
|
+
unless spacetype.defaultConstructionSet.empty?
|
|
1270
|
+
set = spacetype.defaultConstructionSet.get
|
|
1271
|
+
return set if holdsConstruction?(set, base, ground, exterior, typ)
|
|
1272
|
+
end
|
|
1273
|
+
end
|
|
1274
|
+
end
|
|
1275
|
+
|
|
883
1276
|
unless space.buildingStory.empty?
|
|
884
1277
|
story = space.buildingStory.get
|
|
885
1278
|
|
|
@@ -889,6 +1282,15 @@ module OSut
|
|
|
889
1282
|
end
|
|
890
1283
|
end
|
|
891
1284
|
|
|
1285
|
+
unless aspace.nil? || aspace.buildingStory.empty?
|
|
1286
|
+
story = aspace.buildingStory.get
|
|
1287
|
+
|
|
1288
|
+
unless spacetype.defaultConstructionSet.empty?
|
|
1289
|
+
set = spacetype.defaultConstructionSet.get
|
|
1290
|
+
return set if holdsConstruction?(set, base, ground, exterior, typ)
|
|
1291
|
+
end
|
|
1292
|
+
end
|
|
1293
|
+
|
|
892
1294
|
building = mdl.getBuilding
|
|
893
1295
|
|
|
894
1296
|
unless building.defaultConstructionSet.empty?
|
|
@@ -899,203 +1301,6 @@ module OSut
|
|
|
899
1301
|
nil
|
|
900
1302
|
end
|
|
901
1303
|
|
|
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
1304
|
##
|
|
1100
1305
|
# Validates whether opaque surface can be considered as a curtain wall (or
|
|
1101
1306
|
# similar technology) spandrel, regardless of construction layers, by looking
|
|
@@ -2216,7 +2421,7 @@ module OSut
|
|
|
2216
2421
|
cl = OpenStudio::Model::Model
|
|
2217
2422
|
limits = nil
|
|
2218
2423
|
return mismatch("model", model, cl, mth) unless model.is_a?(cl)
|
|
2219
|
-
return invalid("availability", avl, 2, mth) unless avl.respond_to?(:
|
|
2424
|
+
return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_sym)
|
|
2220
2425
|
|
|
2221
2426
|
# Either fetch availability ScheduleTypeLimits object, or create one.
|
|
2222
2427
|
model.getScheduleTypeLimitss.each do |l|
|
|
@@ -4648,7 +4853,7 @@ module OSut
|
|
|
4648
4853
|
#
|
|
4649
4854
|
# @param s [Set<OpenStudio::Point3d>] a (larger) parent set of points
|
|
4650
4855
|
# @param [Array<Hash>] set a collection of (smaller) sequenced points
|
|
4651
|
-
# @option [
|
|
4856
|
+
# @option [#to_sym] tag sequence of subset vertices to target
|
|
4652
4857
|
#
|
|
4653
4858
|
# @return [Integer] number of successfully anchored subsets (see logs)
|
|
4654
4859
|
def genAnchors(s = nil, set = [], tag = :box)
|
|
@@ -4656,18 +4861,20 @@ module OSut
|
|
|
4656
4861
|
n = 0
|
|
4657
4862
|
id = s.respond_to?(:nameString) ? "#{s.nameString}: " : ""
|
|
4658
4863
|
pts = poly(s)
|
|
4659
|
-
return invalid("#{id} polygon", mth, 1, DBG, n)
|
|
4660
|
-
return mismatch("set", set,
|
|
4864
|
+
return invalid("#{id} polygon", mth, 1, DBG, n) if pts.empty?
|
|
4865
|
+
return mismatch("set", set, Array, mth, DBG, n) unless set.respond_to?(:to_a)
|
|
4866
|
+
return mismatch("tag", tag, Symbol, mth, DBG, n) unless tag.respond_to?(:to_sym)
|
|
4661
4867
|
|
|
4662
4868
|
origin = OpenStudio::Point3d.new(0,0,0)
|
|
4663
4869
|
zenith = OpenStudio::Point3d.new(0,0,1)
|
|
4664
4870
|
ray = zenith - origin
|
|
4665
4871
|
set = set.to_a
|
|
4872
|
+
tag = tag.to_sym
|
|
4666
4873
|
|
|
4667
4874
|
# Validate individual subsets. Purge surface-specific leader line anchors.
|
|
4668
4875
|
set.each_with_index do |st, i|
|
|
4669
4876
|
str1 = id + "subset ##{i+1}"
|
|
4670
|
-
str2 = str1 + " #{tag
|
|
4877
|
+
str2 = str1 + " #{trim(tag)}"
|
|
4671
4878
|
return mismatch(str1, st, Hash, mth, DBG, n) unless st.respond_to?(:key?)
|
|
4672
4879
|
return hashkey( str1, st, tag, mth, DBG, n) unless st.key?(tag)
|
|
4673
4880
|
return empty("#{str2} vertices", mth, DBG, n) if st[tag].empty?
|
|
@@ -4810,7 +5017,7 @@ module OSut
|
|
|
4810
5017
|
# @param s [Set<OpenStudio::Point3d>] a larger (parent) set of points
|
|
4811
5018
|
# @param [Array<Hash>] set a collection of (smaller) sequenced vertices
|
|
4812
5019
|
# @option set [Hash] :ld a polygon-specific leader line anchors
|
|
4813
|
-
# @option [
|
|
5020
|
+
# @option [#to_sym] tag sequence of set vertices to target
|
|
4814
5021
|
#
|
|
4815
5022
|
# @return [OpenStudio::Point3dVector] extended vertices (see logs if empty)
|
|
4816
5023
|
def genExtendedVertices(s = nil, set = [], tag = :vtx)
|
|
@@ -4822,14 +5029,16 @@ module OSut
|
|
|
4822
5029
|
a = OpenStudio::Point3dVector.new
|
|
4823
5030
|
v = []
|
|
4824
5031
|
return a if pts.empty?
|
|
4825
|
-
return mismatch("set", set,
|
|
5032
|
+
return mismatch("set", set, Array, mth, DBG, a) unless set.respond_to?(:to_a)
|
|
5033
|
+
return mismatch("tag", tag, Symbol, mth, DBG, n) unless tag.respond_to?(:to_sym)
|
|
4826
5034
|
|
|
4827
5035
|
set = set.to_a
|
|
5036
|
+
tag = tag.to_sym
|
|
4828
5037
|
|
|
4829
5038
|
# Validate individual sets.
|
|
4830
5039
|
set.each_with_index do |st, i|
|
|
4831
5040
|
str1 = id + "subset ##{i+1}"
|
|
4832
|
-
str2 = str1 + " #{tag
|
|
5041
|
+
str2 = str1 + " #{trim(tag)}"
|
|
4833
5042
|
return mismatch(str1, st, Hash, mth, DBG, a) unless st.respond_to?(:key?)
|
|
4834
5043
|
next if st.key?(:void) && st[:void]
|
|
4835
5044
|
|
|
@@ -5121,8 +5330,8 @@ module OSut
|
|
|
5121
5330
|
# surface type filters if 'type' argument == "all".
|
|
5122
5331
|
#
|
|
5123
5332
|
# @param spaces [Set<OpenStudio::Model::Space>] target spaces
|
|
5124
|
-
# @param boundary [#
|
|
5125
|
-
# @param type [#
|
|
5333
|
+
# @param boundary [#to_sym] OpenStudio outside boundary condition
|
|
5334
|
+
# @param type [#to_sym] OpenStudio surface (or subsurface) type
|
|
5126
5335
|
# @param sides [Set<Symbols>] direction keys, e.g. :north (see OSut::SIDZ)
|
|
5127
5336
|
#
|
|
5128
5337
|
# @return [Array<OpenStudio::Model::Surface>] surfaces (may be empty, no logs)
|
|
@@ -5131,7 +5340,7 @@ module OSut
|
|
|
5131
5340
|
spaces = spaces.respond_to?(:to_a) ? spaces.to_a : []
|
|
5132
5341
|
return [] if spaces.empty?
|
|
5133
5342
|
|
|
5134
|
-
sides = sides.respond_to?(:to_sym) ? [sides] : sides
|
|
5343
|
+
sides = sides.respond_to?(:to_sym) ? [trim(sides).to_sym] : sides
|
|
5135
5344
|
sides = sides.respond_to?(:to_a) ? sides.to_a : []
|
|
5136
5345
|
|
|
5137
5346
|
faces = []
|
|
@@ -5405,7 +5614,7 @@ module OSut
|
|
|
5405
5614
|
# @param s [OpenStudio::Model::Surface] a model surface
|
|
5406
5615
|
# @param [Array<Hash>] subs requested attributes
|
|
5407
5616
|
# @option subs [#to_s] :id identifier e.g. "Window 007"
|
|
5408
|
-
# @option subs [#
|
|
5617
|
+
# @option subs [#to_sym] :type ("FixedWindow") OpenStudio subsurface type
|
|
5409
5618
|
# @option subs [#to_i] :count (1) number of individual subs per array
|
|
5410
5619
|
# @option subs [#to_i] :multiplier (1) OpenStudio subsurface multiplier
|
|
5411
5620
|
# @option subs [#frameWidth] :frame (nil) OpenStudio frame & divider object
|
|
@@ -5534,12 +5743,13 @@ module OSut
|
|
|
5534
5743
|
return mismatch("sub", sub, cl4, mth, DBG, no) unless sub.is_a?(cl3)
|
|
5535
5744
|
|
|
5536
5745
|
# 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
|
|
5746
|
+
sub[:frame ] = nil unless sub.key?(:frame)
|
|
5747
|
+
sub[:assembly ] = nil unless sub.key?(:assembly)
|
|
5748
|
+
sub[:count ] = 1 unless sub.key?(:count)
|
|
5540
5749
|
sub[:multiplier] = 1 unless sub.key?(:multiplier)
|
|
5541
|
-
sub[:id ] = "" unless sub.key?(:id
|
|
5542
|
-
sub[:type ] = type unless sub.key?(:type
|
|
5750
|
+
sub[:id ] = "" unless sub.key?(:id)
|
|
5751
|
+
sub[:type ] = type unless sub.key?(:type)
|
|
5752
|
+
sub[:type ] = type unless sub[:type].respond_to?(:to_sym)
|
|
5543
5753
|
sub[:type ] = trim(sub[:type])
|
|
5544
5754
|
sub[:id ] = trim(sub[:id])
|
|
5545
5755
|
sub[:type ] = type if sub[:type].empty?
|
|
@@ -6368,7 +6578,7 @@ module OSut
|
|
|
6368
6578
|
# @option opts [Bool] :sloped (true) whether to consider sloped roof surfaces
|
|
6369
6579
|
# @option opts [Bool] :plenum (true) whether to consider plenum wells
|
|
6370
6580
|
# @option opts [Bool] :attic (true) whether to consider attic wells
|
|
6371
|
-
# @option opts [Array<#
|
|
6581
|
+
# @option opts [Array<#to_sym>] :patterns requested skylight allocation (3x)
|
|
6372
6582
|
# @example (a) consider 2D array of individual skylights, e.g. n(1.22m x 1.22m)
|
|
6373
6583
|
# opts[:patterns] = ["array"]
|
|
6374
6584
|
# @example (b) consider 'a', then array of 1x(size) x n(size) skylight strips
|
|
@@ -6685,7 +6895,7 @@ module OSut
|
|
|
6685
6895
|
if opts.key?(:patterns)
|
|
6686
6896
|
if opts[:patterns].is_a?(Array)
|
|
6687
6897
|
opts[:patterns].each_with_index do |pattern, i|
|
|
6688
|
-
pattern = trim(pattern).downcase
|
|
6898
|
+
pattern = pattern.respond_to?(:to_sym) ? trim(pattern).downcase : ""
|
|
6689
6899
|
|
|
6690
6900
|
if pattern.empty?
|
|
6691
6901
|
invalid("pattern #{i+1}", mth, 0, ERR)
|