Class: BTAP::Structure

Inherits:
Object
  • Object
show all
Extended by:
StructureData
Defined in:
lib/openstudio-standards/btap/structure.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from StructureData

data, extended

Constructor Details

#initialize(model = nil, activity = nil, massive = true) ⇒ Structure

Initialize BTAP STRUCTURE parameters.

Parameters:

  • model (OpenStudio::Model::Model) (defaults to: nil)

    a model

  • activity (BTAP::Activity) (defaults to: nil)

    a BTAP building ACTIVITY object

  • massive (Boolean) (defaults to: true)

    whether requesting internal mass generation



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/openstudio-standards/btap/structure.rb', line 287

def initialize(model = nil, activity = nil, massive = true)
  mth       = "BTAP::Structure::#{__callee__}"
  @feedback = {logs: []}
  lgs       = @feedback[:logs]
  massive   = false unless [true, false].include?(massive)

  unless model.is_a?(OpenStudio::Model::Model)
    lgs << "Invalid or empty OpenStudio model (#{mth})"
    return
  end

  unless activity.is_a?(BTAP::Activity)
    lgs << "Invalid or empty BTAP::Activity instance (#{mth})"
    return
  end

  cat = activity.category
  lload = activity.liveload

  if cat.respond_to?(:to_s)
    cat = cat.to_s.downcase

    if cat.empty?
      lgs << "Empty building category (#{mth})"
      return
    else
      unless data[:category].keys.include?(cat)
        lgs << "Unknown building category: #{cat} (#{mth})"
        return
      end
    end
  else
    lgs << "Invalid building category: #{cat.class} (#{mth})"
    return
  end

  if lload.respond_to?(:to_f)
    lload = lload.to_f
  else
    lgs << "Invalid live load (kg/m2): #{lload.class} (#{mth})"
    return
  end

  # Cap internal mass density to 1000 kg/m3, and thickness to 6".

  rho   = 1000.0
  th    = 0.150
  bldg  = model.getBuilding

  @category  = cat
  @structure = data[:category][cat][:small]

  # Switch to :large structure, instead of default :small.

  if data[:category][cat].key?(:stories)
    mx = data[:category][cat][:stories]
    n  = bldg.standardsNumberOfAboveGroundStories
    n  = n.empty? ? 1 : n.get

    @structure = data[:category][cat][:large] if n > mx
  elsif data[:category][cat].key?(:height)
    mx = data[:category][cat][:height]
    n  = bldg.standardsNumberOfAboveGroundStories
    n  = n.empty? ? 1 : n.get

    if n > 1
      @structure = data[:category][cat][:large]
    else
      h = 0

      model.getSpaces.each do |space|
        h = [mx, BTAP::Geometry::Spaces.space_height(space)].max
      end

      @structure = data[:category][cat][:large] if h > mx
    end
  end

  # Reset :clt and :metal structure selections - not yet available. @todo

  @structure = :steel    if @structure == :metal
  @structure = :concrete if @structure == :clt

  # Set building framing, e.g. light-gauge :steel.

  @framing = data[:structure][@structure][:framing]

  # Set exterior cladding.

  @cladding = :light
  @cladding = :medium if @structure == :cmu
  @cladding = :medium if @category  == "public"
  @cladding = :heavy  if @category  == "robust"

  # Set interior finish.

  @finish = :light
  @finish = :none  if @framing == :cmu
  @finish = :heavy if @category == "robust"

  # 'Dead load' refers to the self-weight of structural elements of a

  # building, as well as non-structural fixtures that are permanently

  # attached to the building. They are considered 'dead' as they typically

  # do not move around during the life of the building. If/once a building

  # is resold, its new owners recover dead load as 'real estate assets'.

  # Dead load typically falls under design scopes of architects/engineers.

  # Although there are obvious design constraints to consider (e.g. fire

  # safety, $), designers do get to make design decisions when it comes to

  # dead load, e.g.:

  #   - between steel vs concrete post/beam/slab structural options

  #   - between light-gauge steel vs CMU wall construction options

  #   - between foam vs fibrous insulation options

  #

  # Most dead load is modelled explicitly in OpenStudio, like envelope and

  # interzone sub/surfaces. Rough estimates of embodied carbon (in CO2-e

  # kg/m2) can be reasonably associated to selected construction assemblies

  # (based on m2), such as the embodied carbon of chosen insulation

  # materials or framing options. Other dead load, like lighting and HVAC,

  # are not modelled explicitly. Here, the 'deadload' attribute represents

  # a mass floor area density estimate (kg/m2) of non-modelled structural

  # and non-structural items like fixed furniture, partitions, columns,

  # beams, shear walls and bracing.


  # First, isolate occupied spaces & floors, as well as exposed surfaces.

  cspaces   = model.getSpaces.select { |sp| sp.partofTotalFloorArea }
  floor_m2  = TBD.facets(cspaces, "all", "floor").map(&:grossArea).sum
  ofloor_m2 = TBD.facets(cspaces, "outdoors", "floor").map(&:grossArea).sum
  ifloor_m2 = TBD.facets(cspaces, "surface", "floor").map(&:grossArea).sum
  roof_m2   = TBD.facets(cspaces, "outdoors", "roofceiling").map(&:grossArea).sum
  wall_m2   = TBD.facets(cspaces, "outdoors", "walls").map(&:grossArea).sum
  iwall_m2  = TBD.facets(cspaces, "surface", "wall").map(&:grossArea).sum

  # In OpenStudio, partitions are usually limited to interzone walls between

  # zones, in order to save on simulation times. Partitions typically absent

  # from a model include walls surrounding lobbies, stairwells, WCs and

  # technical rooms, as well as separations between similar rooms (e.g.

  # multiple, side-by-side enclosed offices, a row of hotel rooms).

  # Comparing BTAP prototype models and samples of building plans for

  # similar facilities suggest matching modelled partition m2 (or total

  # floor m2) as a suitable basis to determine the weight of non-modelled

  # partitions. As this estimate may be more on the high side for many

  # prototype models, fixed appliances (e.g. fixtures, counters, doors and

  # windows) are considered included.

  # partition_m2 = iwall_m2

  # partition_m2 = floor_m2 if partition_m2 > floor_m2

  partition_m2 = floor_m2

  # For wood-framed partitions, representative material volumes (per m2):

  #  - 16% wood-framing: 0.0224 m3/m2 x 540 kg/m3 =  12.1 kg/m2 (35.7%)

  #  - 84% insulation  : 0.1176 m3/m2 x  19 kg/m3 =   2.2 kg/m2 ( 6.5%)

  #  - drywall (2x)    : 0.0250 m3/m2 x 785 kg/m3 =  19.6 kg/m2 (57.8%)

  #                                               =  33.9 kg/m2

  #

  # For steel-framed partitions, representative material volumes (per m2):

  #  - 1% steel-framing:   1.25 x 2.5 x 1.5 kg/m  =   4.7 kg/m2 (17.3%)

  #  - 99% insulation  : 0.1504 m3/m2 x  19 kg/m3 =   2.9 kg/m2 (10.7%)

  #  - drywall (2x)    : 0.0250 m3/m2 x 785 kg/m3 =  19.6 kg/m2 (72.0%)

  #                                               =  27.2 kg/m2

  #

  # For CMU partitions, representative material volumes (per m2):

  #  - 10" medium weight CMU                      = 250.0 kg/m2 (approx.)


  case @framing
  when :cmu  then partition_kgm2 = 250.0 * partition_m2 / floor_m2
  when :wood then partition_kgm2 =  33.9 * partition_m2 / floor_m2
  else            partition_kgm2 =  27.2 * partition_m2 / floor_m2
  end

  # Structural dead load - not explicitly modelled - include columns,

  # bracing, connectors, etc. For BTAP purposes, some basic assumptions are

  # required:

  #   - 9m x 9m spans

  #   - approx. 15 columns / 1000 m2 of floor area

  #   - approx. 14" x 14" columns (0.126 m2)

  #     - if structure :steel or :metal (HP14x102):

  #       - 152 kg/m (x 125% for bracing, etc.)   = 190 kg/m

  #     - if structure :concrete

  #       - concrete: 2240 kg/m3 x 0.126 m2 x 97% = 274 kg/m

  #       - rebar:    7850 kg/m3 x 0.126 m2 x  3% =  30 kg/m

  #                                               = 304 kg/m (+11%)

  #     - if structure :cmu (mix of load bearing walls + smaller pours)

  #       - 1/2 :concrete                         = 150 kg/m

  #     - if structure :clt

  #       - wood:     540 kg/m3 x 0.126 m2 x 97%  =  66 kg/m

  #       - anchors: 7850 kg/m3 x 0.126 m2 x 3%   =  30 kg/m

  #                                               =  96 kg/m (+45%)

  #     - if structure :wood

  #       - 1/2 :clt                              =  48 kg/m


  # Fetch approx. total column height (m) in building (including plenums).

  column_m = 0

  model.getSpaces.each do |space|
    column_m += BTAP::Geometry::Spaces.space_height(space) * 15 / 1000
  end

  case @structure
  when :steel then column_kgm2 = 190 * column_m / floor_m2
  when :metal then column_kgm2 = 190 * column_m / floor_m2
  when :cmu   then column_kgm2 = 150 * column_m / floor_m2
  when :wood  then column_kgm2 =  48 * column_m / floor_m2
  when :clt   then column_kgm2 =  96 * column_m / floor_m2
  else             column_kgm2 = 304 * column_m / floor_m2
  end

  @deadload = partition_kgm2 + column_kgm2

  # The 'liveload' attribute represents the mass area density (kg/m2) of

  # dynamic, yet uniform floor live load from non-permanent items like

  # furniture, documents, copiers and computers, i.e. not real estate

  # assets. Architects and engineers deal with (fixed) live load as design

  # constraint - not as potential design option. Non-occupant live load is

  # taken into account when setting internal mass. Yet as a non-real estate

  # item, live load is not considered when tallying embodied carbon.

  #

  # Within BTAP, non-occupant live load estimates are stored in the

  # "NECB_building_types.csv" file, parsed/stored in a BTAP::Activity

  # instance (1x per building activity). These estimates are initially based

  # on NBC Part 4 minimum live load requirements (kPa), as well as data from

  # established structural engineering resources. Minimum live load kPa (or

  # psf) estimates, corresponding to hundreds of kg/m2 of floor area, are

  # strictly for structural dimensioning/safety purposes. They are not (or

  # are very rarely) representative of actual day-to-day loads. Back of the

  # envelope calculations suggest reducing live load code requirements down

  # to ~1/12th of their initial values for internal mass purposes. These

  # code requirements also include occupants, which should be set aside -

  # by subtracting the total building population mass:

  #

  #   - NECB building occupant density (occupant/m2) x avg. 80 kg/adult

  #

  # This gives for instance a resulting live load estimate of 23 kg/m2 for

  # housing (low) and a 61 kg/m2 for manufacturing (high). It is obviously

  # challenging to pin down a single-number estimate for several building

  # types, including bigbox retail and warehousing. Grain of salt.

  @liveload = lload

  # Add internal mass objects, 1x instance per occupied space.

  cspaces.each do |space|
    break unless massive

    matID = "#{space.nameString} : Mass Material"
    conID = "#{space.nameString} : Mass Construction"
    defID = "#{space.nameString} : Mass Definition"
    mssID = "#{space.nameString} : Mass"

    # Calculate total mass of internal mass (kg), then thickness.

    kg = space.floorArea * (@liveload + @deadload)
    m2 = kg / rho / th

    mat = OpenStudio::Model::StandardOpaqueMaterial.new(model)
    mat.setName(matID)
    mat.setRoughness("MediumRough")
    mat.setThickness(th)
    mat.setConductivity(1.0)
    mat.setDensity(rho)
    mat.setSpecificHeat(1000)
    mat.setThermalAbsorptance(0.9)
    mat.setSolarAbsorptance(0.7)
    mat.setVisibleAbsorptance(0.7)

    con = OpenStudio::Model::Construction.new(model)
    con.setName(conID)
    layers = OpenStudio::Model::MaterialVector.new
    layers << mat
    con.setLayers(layers)

    df = OpenStudio::Model::InternalMassDefinition.new(model)
    df.setName(defID)
    df.setConstruction(con)
    df.setSurfaceArea(space.floorArea)
    df.setSurfaceArea(m2)

    mass = OpenStudio::Model::InternalMass.new(df)
    mass.setName(mssID)
    mass.setSpace(space)
  end

  # Embodied CO2-e kg (A1-A3) of a model is tallied separately as follows:

  @co2              = {}
  @co2[:structure ] = 0
  @co2[:insulation] = 0
  @co2[:cladding  ] = 0

  # The :structure key/value pair includes above grade 'structures' (e.g.

  # slabs, columns) and 'framing' (e.g. wood-framed vs steel-framed). Why

  # grouped together? In many smaller-scale facilities, structure and

  # framing are synonymous, or at least tightly coupled, e.g.:

  #   - load-bearing wood-framed walls in small-scale residential

  #   - load-bearing CMU walls in small-scale industrial

  #   - metal buildings

  #

  # Below-grade structures (rebar + poured concrete) are ignored - no

  # alternative options are considered for the moment, e.g. lower carbon

  # concrete mixes.

  #

  # Upon initialization, only the :structure tally is set. It is assumed

  # that structure (and main framing) do not change with NECB U-factor

  # requirements, e.g.:

  #   - NECB 2011 vs 2020

  #   - Vancouver vs Calgary

  #

  # Once default construction sets are (later) established (+), followed by

  # TBD uprating assemblies as per NECB 2017 & 2020 requirements (++),

  # embodied carbon tallies can be updated/reset, which would include:

  #   - cladding

  #   - insulation

  #   - secondary framing (proportional to insulation thicknesses)

  #

  #  (+) openstudio-standards/standards/necb/NECB2011/building_envelope.rb

  # (++) openstudio-standards/btap/bridging.rb


  # Start with occupied floors & roofs (exclude slabs on grade).

  m2 = ofloor_m2 + ifloor_m2 + roof_m2

  case @structure
  when :wood     then # engineered I-joists + plywood

    floor_kg    = 50.0 * m2
    floor_m3    = floor_kg / 540.0                  # kg/m3

    floor_co2kg = floor_m3 * 55.0 / floor_kg        # kgCO2-e/kg

  when :concrete then # 200mm flat slab, 3% rebar + accessories

    floor_m3    = 0.200 * m2
    floor_kgm3  = (0.03 * 7850.0) + (0.97 * 2240.0) # kg/m3

    floor_co2kg = (0.03 * 0.854 ) + (0.97 *  0.250) # kgCO2-e/kg

    floor_kg    = floor_m3 * floor_kgm3
  else # :steel, includes joists/fasteners

    floor_m3    = 0.125 * m2
    floor_kgm3  = (0.08 * 7850.0) + (0.92 * 2240.0) # kg/m3

    floor_co2kg = (0.08 * 0.854 ) + (0.92 *  0.250) # kgCO2-e/kg

    floor_kg    = floor_m3 * floor_kgm3
  end

  @co2[:structure] += floor_co2kg * floor_kg

  # Add exposed walls and interior partitions.

  m2 = wall_m2 + iwall_m2 + partition_m2

  case @framing
  when :wood then # basic 2x6 construction, 16% of framing cavity

    wall_kg    =  12.1 * m2
    wall_co2kg =  55.0 / 540.0 # 55 kgCO2-e/m3 / density

  when :cmu  then # 250mm medium weight CMU, 200 kgCO2-e/m3

    wall_m3    = 0.250 * m2    # nominal m3

    wall_kg    = 250.0 * m2    # volume-weighted concrete/air/grout

    wall_kgm3  = 0.250 / 250.0
    wall_co2kg = 200.0 / wall_kgm3 # 200 kgCO2-e/m3 / density

  else # :steel, 1% lightweight steel-framing

    wall_kg    =   4.7 * m2
    wall_co2kg = 2.440
  end

  @co2[:structure] += wall_co2kg * wall_kg

  true
end

Instance Attribute Details

#categoryString (readonly)

Returns building type CATEGORY (e.g. “public”).

Returns:

  • (String)

    building type CATEGORY (e.g. “public”)



255
256
257
# File 'lib/openstudio-standards/btap/structure.rb', line 255

def category
  @category
end

#claddingSymbol (readonly)

Returns building cladding (e.g. :medium).

Returns:

  • (Symbol)

    building cladding (e.g. :medium)



264
265
266
# File 'lib/openstudio-standards/btap/structure.rb', line 264

def cladding
  @cladding
end

#co2Hash (readonly)

Returns calculated embodied carbon (CO2-e kg).

Returns:

  • (Hash)

    calculated embodied carbon (CO2-e kg)



276
277
278
# File 'lib/openstudio-standards/btap/structure.rb', line 276

def co2
  @co2
end

#deadloadFloat (readonly)

Returns estimated dead load, in kg/m2 of floor area.

Returns:

  • (Float)

    estimated dead load, in kg/m2 of floor area



270
271
272
# File 'lib/openstudio-standards/btap/structure.rb', line 270

def deadload
  @deadload
end

#feedbackHash (readonly)

Returns logged messages.

Returns:

  • (Hash)

    logged messages



279
280
281
# File 'lib/openstudio-standards/btap/structure.rb', line 279

def feedback
  @feedback
end

#finishSymbol (readonly)

Returns building finish (e.g. :light).

Returns:

  • (Symbol)

    building finish (e.g. :light)



267
268
269
# File 'lib/openstudio-standards/btap/structure.rb', line 267

def finish
  @finish
end

#framingSymbol (readonly)

Returns building framing (e.g. :steel).

Returns:

  • (Symbol)

    building framing (e.g. :steel)



261
262
263
# File 'lib/openstudio-standards/btap/structure.rb', line 261

def framing
  @framing
end

#liveloadFloat (readonly)

Returns estimated non-occupant live load, in kg/m2 of floor area.

Returns:

  • (Float)

    estimated non-occupant live load, in kg/m2 of floor area



273
274
275
# File 'lib/openstudio-standards/btap/structure.rb', line 273

def liveload
  @liveload
end

#structureSymbol (readonly)

Returns building STRUCTURE selection (e.g. :steel).

Returns:

  • (Symbol)

    building STRUCTURE selection (e.g. :steel)



258
259
260
# File 'lib/openstudio-standards/btap/structure.rb', line 258

def structure
  @structure
end

Instance Method Details

#tallyCO2(model) ⇒ Hash

Updates and returns embodied carbon estimates (A1-A3).

Parameters:

Returns:

  • (Hash)

    embodied carbon tally (CO2-e kg, A1-A3)



642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'lib/openstudio-standards/btap/structure.rb', line 642

def tallyCO2(model)
  mth = "BTAP::Structure::#{__callee__}"
  cl  = OpenStudio::Model::Model
  lgs = @feedback[:logs]

  unless model.is_a?(cl)
    lgs << "Invalid OpenStudio model (#{mth})"
    return 0
  end

  # @todo


  @co2
end