diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 8305f68..b0339fb 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -6,22 +6,6 @@ on:
- develop
jobs:
- test_300x:
- runs-on: ubuntu-22.04
- steps:
- - name: Check out repository
- uses: actions/checkout@v2
- - name: Run Tests
- run: |
- echo $(pwd)
- echo $(ls)
- docker pull nrel/openstudio:3.0.0
- docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.0.0
- docker exec -t test pwd
- docker exec -t test ls
- docker exec -t test bundle update
- docker exec -t test bundle exec rake
- docker kill test
test_321x:
runs-on: ubuntu-22.04
steps:
@@ -38,38 +22,6 @@ jobs:
docker exec -t test bundle update
docker exec -t test bundle exec rake
docker kill test
- test_330x:
- runs-on: ubuntu-22.04
- steps:
- - name: Check out repository
- uses: actions/checkout@v2
- - name: Run Tests
- run: |
- echo $(pwd)
- echo $(ls)
- docker pull nrel/openstudio:3.3.0
- docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.3.0
- docker exec -t test pwd
- docker exec -t test ls
- docker exec -t test bundle update
- docker exec -t test bundle exec rake
- docker kill test
- test_340x:
- runs-on: ubuntu-22.04
- steps:
- - name: Check out repository
- uses: actions/checkout@v2
- - name: Run Tests
- run: |
- echo $(pwd)
- echo $(ls)
- docker pull nrel/openstudio:3.4.0
- docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.4.0
- docker exec -t test pwd
- docker exec -t test ls
- docker exec -t test bundle update
- docker exec -t test bundle exec rake
- docker kill test
test_351x:
runs-on: ubuntu-22.04
steps:
@@ -150,3 +102,19 @@ jobs:
docker exec -t test bundle update
docker exec -t test bundle exec rake
docker kill test
+ test_3100x:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v2
+ - name: Run Tests
+ run: |
+ echo $(pwd)
+ echo $(ls)
+ docker pull nrel/openstudio:3.10.0
+ docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.10.0
+ docker exec -t test pwd
+ docker exec -t test ls
+ docker exec -t test bundle update
+ docker exec -t test bundle exec rake
+ docker kill test
diff --git a/LICENSE.md b/LICENSE.md
index a055dad..6b566ce 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index c53534a..303b031 100644
--- a/README.md
+++ b/README.md
@@ -37,11 +37,11 @@ bundler -v
gem install bundler -v 2.1
```
-Install OpenStudio [3.8.0](https://github.com/NREL/OpenStudio/releases/tag/v3.8.0), or the OpenStudioApplication [1.8.0](https://github.com/openstudiocoalition/OpenStudioApplication/releases/tag/v1.8.0).
+Install OpenStudio [3.10.0](https://github.com/NREL/OpenStudio/releases/tag/v3.10.0), or the OpenStudioApplication [1.10.0](https://github.com/openstudiocoalition/OpenStudioApplication/releases/tag/v1.10.0).
Create a new file ```C:\Ruby32-x64\lib\ruby\site_ruby\openstudio.rb``` (path may be different depending on the environment), and edit it so it _points_ to your new OpenStudio installation:
```
-require 'C:\openstudio-3.8.0\Ruby\openstudio.rb'
+require 'C:\openstudio-3.10.0\Ruby\openstudio.rb'
```
Verify your OpenStudio and Ruby configuration:
@@ -89,16 +89,16 @@ bundler -v
gem install bundler -v 2.4.10
```
-Install OpenStudio [3.8.0](https://github.com/NREL/OpenStudio/releases/tag/v3.8.0), or the OpenStudio Application [1.8.0](https://github.com/openstudiocoalition/OpenStudioApplication/releases/tag/v1.8.0).
+Install OpenStudio [3.10.0](https://github.com/NREL/OpenStudio/releases/tag/v3.10.0), or the OpenStudio Application [1.10.0](https://github.com/openstudiocoalition/OpenStudioApplication/releases/tag/v1.10.0).
Create a new file ```~/.rbenv/versions/3.2.2/lib/ruby/site_ruby/openstudio.rb``` (path may be different depending on the environment), and edit it so it _points_ to your new OpenStudio installation:
```
-require '/Applications/OpenStudio-3.8.0/Ruby/openstudio.rb'
+require '/Applications/OpenStudio-3.10.0/Ruby/openstudio.rb'
```
Verify your local OpenStudio and Ruby configuration:
```
-cd ~/Documents/sandbox380
+cd ~/Documents/sandbox310
ruby -e "require 'openstudio'" -e "puts OpenStudio::Model::Model.new"
```
@@ -121,14 +121,14 @@ bundle exec rake
Install [Docker](https://docs.docker.com/desktop/#download-and-install).
-Pull the OpenStudio v3.8.0 Docker image:
+Pull the OpenStudio v3.10.0 Docker image:
```
-docker pull nrel/openstudio:3.8.0
+docker pull nrel/openstudio:3.10.0
```
In the root repository:
```
-docker run --name test --rm -d -t -v ${PWD}:/work -w /work nrel/openstudio:3.8.0
+docker run --name test --rm -d -t -v ${PWD}:/work -w /work nrel/openstudio:3.10.0
docker exec -t test bundle update
docker exec -t test bundle exec rake
docker kill test
diff --git a/lib/measures/tbd/LICENSE.md b/lib/measures/tbd/LICENSE.md
index a055dad..6b566ce 100644
--- a/lib/measures/tbd/LICENSE.md
+++ b/lib/measures/tbd/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/measures/tbd/measure.rb b/lib/measures/tbd/measure.rb
index 5560f57..5d745a2 100644
--- a/lib/measures/tbd/measure.rb
+++ b/lib/measures/tbd/measure.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/measures/tbd/measure.xml b/lib/measures/tbd/measure.xml
index 424fe69..5d17eae 100644
--- a/lib/measures/tbd/measure.xml
+++ b/lib/measures/tbd/measure.xml
@@ -3,8 +3,8 @@
3.1
tbd_measure
8890787b-8c25-4dc8-8641-b6be1b6c2357
- 70223737-c3ea-4df0-8506-67083c92fb6e
- 2024-11-20T20:02:15Z
+ 7a2d773a-7a50-4c69-aa1f-93ed796e9ace
+ 2025-08-15T13:39:31Z
99772807
TBDMeasure
Thermal Bridging and Derating - TBD
@@ -464,7 +464,7 @@
LICENSE.md
md
license
- 5C9BFB50
+ 3EBCA5DB
README.md
@@ -493,13 +493,13 @@
measure.rb
rb
script
- A472E915
+ 3FBDA0C2
geo.rb
rb
resource
- 9FAC0CDC
+ EF3BA8F7
geometry.rb
@@ -517,19 +517,19 @@
oslog.rb
rb
resource
- 8CD57B9A
+ 586805C4
psi.rb
rb
resource
- 4B7F3586
+ 71AED953
tbd.rb
rb
resource
- FCCCAE84
+ 9E26251E
transformation.rb
@@ -541,13 +541,13 @@
ua.rb
rb
resource
- 626D3BE0
+ 022F6D10
utils.rb
rb
resource
- CBC3935D
+ 3CD8019A
version.rb
@@ -565,7 +565,7 @@
tbd_tests.rb
rb
test
- 2ECE06CA
+ 9C76CD98
diff --git a/lib/measures/tbd/resources/geo.rb b/lib/measures/tbd/resources/geo.rb
index 98a09b4..7dd2a6f 100644
--- a/lib/measures/tbd/resources/geo.rb
+++ b/lib/measures/tbd/resources/geo.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/measures/tbd/resources/oslog.rb b/lib/measures/tbd/resources/oslog.rb
index 47099c5..3e60b80 100644
--- a/lib/measures/tbd/resources/oslog.rb
+++ b/lib/measures/tbd/resources/oslog.rb
@@ -1,6 +1,6 @@
# BSD 3-Clause License
#
-# Copyright (c) 2022-2024, Denis Bourgeois
+# Copyright (c) 2022-2025, Denis Bourgeois
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -102,9 +102,9 @@ def info?
end
##
- # Returns whether current status is WARN.
+ # Returns whether current status is WARNING.
#
- # @return [Bool] whether current log status is WARN
+ # @return [Bool] whether current log status is WARNING
def warn?
@@status == WARN
end
@@ -159,19 +159,20 @@ def msg(stat)
end
##
- # Converts object to String and trims if necessary.
+ # Converts object to String, trims if requested.
#
# @param txt [#to_s] a stringable object
- # @param length [#to_i] maximum return string length
+ # @param len [Numeric] requested maximum string length (optional)
#
- # @return [String] a trimmed message string (empty unless stringable)
- def trim(txt = "", length = 60)
- length = 60 unless length.respond_to?(:to_i)
- length = length.to_i if length.respond_to?(:to_i)
+ # @return [String] a (trimmed) string (empty unless stringable)
+ def trim(txt = "", len = nil)
return "" unless txt.respond_to?(:to_s)
txt = txt.to_s.strip
- txt = txt[0...length] + " ..." if txt.length > length
+
+ if len.is_a?(Numeric)
+ txt = txt[0...len.to_i] + " ..." if txt.length > len.to_i
+ end
txt
end
@@ -193,21 +194,28 @@ def reset(lvl = DEBUG)
end
##
- # Logs a new entry, if provided arguments are valid.
+ # Logs a new entry. Overall log status is raised if new level is greater
+ # than current level (e.g. FATAL > ERROR). Candidate log entry is ignored and
+ # status remains unchanged if the new level cannot be converted to an integer,
+ # if not an OSlg constant (once converted), or if new level is below the
+ # current log level. Relies on OSlg method 'trim()': candidate log message is
+ # ignored and status unchanged if message is not a valid string.
#
# @param lvl [#to_i] DEBUG, INFO, WARN, ERROR or FATAL
# @param message [#to_s] user-provided log message
+ # @param len [Numeric] maximum log message length (optional)
#
# @example A user warning
# log(WARN, "Surface area < 100cm2")
#
# @return [DEBUG, INFO, WARN, ERROR, FATAL] updated/current status
- def log(lvl = DEBUG, message = "")
+ def log(lvl = DEBUG, message = "", len = nil)
return @@status unless lvl.respond_to?(:to_i)
return @@status unless message.respond_to?(:to_s)
lvl = lvl.to_i
- message = message.to_s
+ message = trim(message, len)
+ return @@status if message.empty?
return @@status if lvl < DEBUG
return @@status if lvl > FATAL
return @@status if lvl < @@level
@@ -220,19 +228,24 @@ def log(lvl = DEBUG, message = "")
##
# Logs template 'invalid object' message, if provided arguments are valid.
+ # Relies on OSlg method 'log()': first check out its own operation, exit
+ # conditions and module side effects. Candidate log entry is ignored and
+ # status remains unchanged if 'ord' cannot be converted to an integer.
+ # Argument 'ord' is ignored unless > 0.
#
# @param id [#to_s] 'invalid object' identifier
# @param mth [#to_s] calling method identifier
# @param ord [#to_i] calling method argument order number of obj (optional)
# @param lvl [#to_i] DEBUG, INFO, WARN, ERROR or FATAL (optional)
# @param res what to return (optional)
+ # @param len [Numeric] maximum log message length (optional)
#
# @example An invalid argument, logging a FATAL error, returning FALSE
# return invalid("area", "sum", 0, FATAL, false) if area > 1000000
#
# @return user-provided object
# @return [nil] if user hasn't provided an object to return
- def invalid(id = "", mth = "", ord = 0, lvl = DEBUG, res = nil)
+ def invalid(id = "", mth = "", ord = 0, lvl = DEBUG, res = nil, len = nil)
return res unless id.respond_to?(:to_s)
return res unless mth.respond_to?(:to_s)
return res unless ord.respond_to?(:to_i)
@@ -250,7 +263,7 @@ def invalid(id = "", mth = "", ord = 0, lvl = DEBUG, res = nil)
msg = "Invalid '#{id}' "
msg += "arg ##{ord} " if ord > 0
msg += "(#{mth})"
- log(lvl, msg)
+ log(lvl, msg, len)
res
end
@@ -266,13 +279,14 @@ def invalid(id = "", mth = "", ord = 0, lvl = DEBUG, res = nil)
# @param mth [#to_s] calling method identifier (optional)
# @param lvl [#to_i] DEBUG, INFO, WARN, ERROR or FATAL (optional)
# @param res what to return (optional)
+ # @param len [Numeric] maximum log message length (optional)
#
# @example A mismatched argument instance/class
# mismatch("area", area, Float, "sum") unless area.is_a?(Numeric)
#
# @return user-provided object
# @return [nil] if user hasn't provided an object to return
- def mismatch(id = "", obj = nil, cl = nil, mth = "", lvl = DEBUG, res = nil)
+ def mismatch(id = "", obj = nil, cl = nil, mth = "", lvl = DEBUG, res = nil, len = nil)
return res unless id.respond_to?(:to_s)
return res unless mth.respond_to?(:to_s)
return res unless cl.is_a?(Class)
@@ -287,7 +301,7 @@ def mismatch(id = "", obj = nil, cl = nil, mth = "", lvl = DEBUG, res = nil)
return res if lvl < DEBUG
return res if lvl > FATAL
- log(lvl, "'#{id}' #{obj.class}? expecting #{cl} (#{mth})")
+ log(lvl, "'#{id}' #{obj.class}? expecting #{cl} (#{mth})", len)
res
end
@@ -302,13 +316,14 @@ def mismatch(id = "", obj = nil, cl = nil, mth = "", lvl = DEBUG, res = nil)
# @param mth [#to_s] calling method identifier
# @param lvl [#to_i] DEBUG, INFO, WARN, ERROR or FATAL (optional)
# @param res what to return (optional)
+ # @param len [Numeric] maximum log message length (optional)
#
# @example A missing Hash key
# hashkey("floor area", floor, :area, "sum") unless floor.key?(:area)
#
# @return user-provided object
# @return [nil] if user hasn't provided an object to return
- def hashkey(id = "", hsh = {}, key = "", mth = "", lvl = DEBUG, res = nil)
+ def hashkey(id = "", hsh = {}, key = "", mth = "", lvl = DEBUG, res = nil, len = nil)
return res unless id.respond_to?(:to_s)
return res unless hsh.is_a?(Hash)
return res if hsh.key?(key)
@@ -323,7 +338,7 @@ def hashkey(id = "", hsh = {}, key = "", mth = "", lvl = DEBUG, res = nil)
return res if lvl < DEBUG
return res if lvl > FATAL
- log(lvl, "Missing '#{key}' key in '#{id}' Hash (#{mth})")
+ log(lvl, "Missing '#{key}' key in '#{id}' Hash (#{mth})", len)
res
end
@@ -335,13 +350,14 @@ def hashkey(id = "", hsh = {}, key = "", mth = "", lvl = DEBUG, res = nil)
# @param mth [#to_s] calling method identifier
# @param lvl [#to_i] DEBUG, INFO, WARN, ERROR or FATAL (optional)
# @param res what to return (optional)
+ # @param len [Numeric] maximum log message length (optional)
#
# @example An uninitialized variable, logging an ERROR, returning FALSE
# empty("zone", "conditioned?", FATAL, false) if space.thermalZone.empty?
#
# @return user-provided object
# @return [nil] if user hasn't provided an object to return
- def empty(id = "", mth = "", lvl = DEBUG, res = nil)
+ def empty(id = "", mth = "", lvl = DEBUG, res = nil, len = nil)
return res unless id.respond_to?(:to_s)
return res unless mth.respond_to?(:to_s)
return res unless lvl.respond_to?(:to_i)
@@ -354,7 +370,7 @@ def empty(id = "", mth = "", lvl = DEBUG, res = nil)
return res if lvl < DEBUG
return res if lvl > FATAL
- log(lvl, "Empty '#{id}' (#{mth})")
+ log(lvl, "Empty '#{id}' (#{mth})", len)
res
end
@@ -366,13 +382,14 @@ def empty(id = "", mth = "", lvl = DEBUG, res = nil)
# @param mth [#to_s] calling method identifier
# @param lvl [#to_i] DEBUG, INFO, WARN, ERROR or FATAL (optional)
# @param res what to return (optional)
+ # @param len [Numeric] maximum log message length (optional)
#
# @example A near-zero variable
# zero("floor area", "sum") if floor[:area].abs < TOL
#
# @return user-provided object
# @return [nil] if user hasn't provided an object to return
- def zero(id = "", mth = "", lvl = DEBUG, res = nil)
+ def zero(id = "", mth = "", lvl = DEBUG, res = nil, len = nil)
return res unless id.respond_to?(:to_s)
return res unless mth.respond_to?(:to_s)
return res unless lvl.respond_to?(:to_i)
@@ -386,7 +403,7 @@ def zero(id = "", mth = "", lvl = DEBUG, res = nil)
return res if lvl < DEBUG
return res if lvl > FATAL
- log(lvl, "Zero '#{id}' (#{mth})")
+ log(lvl, "Zero '#{id}' (#{mth})", len)
res
end
@@ -398,13 +415,14 @@ def zero(id = "", mth = "", lvl = DEBUG, res = nil)
# @param mth [String] calling method identifier
# @param lvl [Integer] DEBUG, INFO, WARN, ERROR or FATAL (optional)
# @param res [Object] what to return (optional)
+ # @param len [Numeric] maximum log message length (optional)
#
# @example A negative variable
# negative("floor area", "sum") if floor[:area] < 0
#
# @return user-provided object
# @return [nil] if user hasn't provided an object to return
- def negative(id = "", mth = "", lvl = DEBUG, res = nil)
+ def negative(id = "", mth = "", lvl = DEBUG, res = nil, len = nil)
return res unless id.respond_to?(:to_s)
return res unless mth.respond_to?(:to_s)
return res unless lvl.respond_to?(:to_i)
@@ -417,7 +435,7 @@ def negative(id = "", mth = "", lvl = DEBUG, res = nil)
return res if lvl < DEBUG
return res if lvl > FATAL
- log(lvl, "Negative '#{id}' (#{mth})")
+ log(lvl, "Negative '#{id}' (#{mth})", len)
res
end
diff --git a/lib/measures/tbd/resources/psi.rb b/lib/measures/tbd/resources/psi.rb
index 85d0132..8075770 100644
--- a/lib/measures/tbd/resources/psi.rb
+++ b/lib/measures/tbd/resources/psi.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/measures/tbd/resources/tbd.rb b/lib/measures/tbd/resources/tbd.rb
index c9bb51d..e49b4c4 100644
--- a/lib/measures/tbd/resources/tbd.rb
+++ b/lib/measures/tbd/resources/tbd.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/measures/tbd/resources/ua.rb b/lib/measures/tbd/resources/ua.rb
index e6f6ac2..41c1d5b 100644
--- a/lib/measures/tbd/resources/ua.rb
+++ b/lib/measures/tbd/resources/ua.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -1000,7 +1000,7 @@ def ua_md(ua = {}, lang = :en)
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
model += " (v#{ua[:version]})" if ua.key?(:version)
report << model unless model.empty?
- report << "* TBD : v3.4.4"
+ report << "* TBD : v3.4.5"
report << "* date : #{ua[:date]}"
if lang == :en
diff --git a/lib/measures/tbd/resources/utils.rb b/lib/measures/tbd/resources/utils.rb
index 4e560d8..35cb90c 100644
--- a/lib/measures/tbd/resources/utils.rb
+++ b/lib/measures/tbd/resources/utils.rb
@@ -1,6 +1,6 @@
# BSD 3-Clause License
#
-# Copyright (c) 2022-2024, Denis Bourgeois
+# Copyright (c) 2022-2025, Denis Bourgeois
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -220,13 +220,14 @@ def genConstruction(model = nil, specs = {})
chk = @@uo.keys.include?(specs[:type])
return invalid("surface type", mth, 2, ERR) unless chk
- specs[:uo] = @@uo[ specs[:type] ] unless specs.key?(:uo)
+ specs[:uo] = @@uo[ specs[:type] ] unless specs.key?(:uo) # can be nil
u = specs[:uo]
if u
- return mismatch("#{id} Uo", u, Numeric, mth) unless u.is_a?(Numeric)
- return invalid("#{id} Uo (> 5.678)", mth, 2, ERR) if u > 5.678
- return negative("#{id} Uo" , mth, ERR) if u < 0
+ return mismatch("#{id} Uo", u, Numeric, mth) unless u.is_a?(Numeric)
+ return invalid("#{id} Uo (> 5.678)", mth, 2, ERR) if u > 5.678
+ return zero("#{id} Uo", mth, ERR) if u.round(2) == 0.00
+ return negative("#{id} Uo", mth, ERR) if u < 0
end
# Optional specs. Log/reset if invalid.
@@ -466,14 +467,14 @@ def genConstruction(model = nil, specs = {})
a[:compo ][:d ] = d
a[:compo ][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}"
when :window
- a[:glazing][:u ] = specs[:uo ]
+ a[:glazing][:u ] = u ? u : @@uo[:window]
a[:glazing][:shgc] = 0.450
a[:glazing][:shgc] = specs[:shgc] if specs.key?(:shgc)
a[:glazing][:id ] = "OSut|window"
a[:glazing][:id ] += "|U#{format('%.1f', a[:glazing][:u])}"
a[:glazing][:id ] += "|SHGC#{format('%d', a[:glazing][:shgc]*100)}"
when :skylight
- a[:glazing][:u ] = specs[:uo ]
+ a[:glazing][:u ] = u ? u : @@uo[:skylight]
a[:glazing][:shgc] = 0.450
a[:glazing][:shgc] = specs[:shgc] if specs.key?(:shgc)
a[:glazing][:id ] = "OSut|skylight"
@@ -482,25 +483,11 @@ def genConstruction(model = nil, specs = {})
end
# Initiate layers.
- glazed = true
- glazed = false if a[:glazing].empty?
- layers = OpenStudio::Model::OpaqueMaterialVector.new unless glazed
- layers = OpenStudio::Model::FenestrationMaterialVector.new if glazed
+ unglazed = a[:glazing].empty? ? true : false
- if glazed
- u = a[:glazing][:u ]
- shgc = a[:glazing][:shgc]
- lyr = model.getSimpleGlazingByName(a[:glazing][:id])
-
- if lyr.empty?
- lyr = OpenStudio::Model::SimpleGlazing.new(model, u, shgc)
- lyr.setName(a[:glazing][:id])
- else
- lyr = lyr.get
- end
+ if unglazed
+ layers = OpenStudio::Model::OpaqueMaterialVector.new
- layers << lyr
- else
# Loop through each layer spec, and generate construction.
a.each do |i, l|
next if l.empty?
@@ -524,44 +511,68 @@ def genConstruction(model = nil, specs = {})
layers << lyr
end
+ else
+ layers = OpenStudio::Model::FenestrationMaterialVector.new
+
+ u0 = a[:glazing][:u ]
+ shgc = a[:glazing][:shgc]
+ lyr = model.getSimpleGlazingByName(a[:glazing][:id])
+
+ if lyr.empty?
+ lyr = OpenStudio::Model::SimpleGlazing.new(model, u0, shgc)
+ lyr.setName(a[:glazing][:id])
+ else
+ lyr = lyr.get
+ end
+
+ layers << lyr
end
c = OpenStudio::Model::Construction.new(layers)
c.setName(id)
# Adjust insulating layer thickness or conductivity to match requested Uo.
- unless glazed
- ro = 0
- ro = 1 / specs[:uo] - @@film[ specs[:type] ] if specs[:uo]
-
- if specs[:type] == :door # 1x layer, adjust conductivity
- layer = c.getLayer(0).to_StandardOpaqueMaterial
- return invalid("#{id} standard material?", mth, 0) if layer.empty?
-
- layer = layer.get
- k = layer.thickness / ro
- layer.setConductivity(k)
- elsif ro > 0 # multiple layers, adjust insulating layer thickness
- lyr = insulatingLayer(c)
- return invalid("#{id} construction", mth, 0) if lyr[:index].nil?
- return invalid("#{id} construction", mth, 0) if lyr[:type ].nil?
- return invalid("#{id} construction", mth, 0) if lyr[:r ].zero?
-
- index = lyr[:index]
- layer = c.getLayer(index).to_StandardOpaqueMaterial
- return invalid("#{id} material @#{index}", mth, 0) if layer.empty?
-
- layer = layer.get
- k = layer.conductivity
- d = (ro - rsi(c) + lyr[:r]) * k
- return invalid("#{id} adjusted m", mth, 0) if d < 0.03
-
- nom = "OSut|"
- nom += layer.nameString.gsub(/[^a-z]/i, "").gsub("OSut", "")
- nom += "|"
- nom += format("%03d", d*1000)[-3..-1]
- layer.setName(nom) if model.getStandardOpaqueMaterialByName(nom).empty?
- layer.setThickness(d)
+ if u and unglazed
+ ro = 1 / u - film
+
+ if ro > 0
+ if specs[:type] == :door # 1x layer, adjust conductivity
+ layer = c.getLayer(0).to_StandardOpaqueMaterial
+ return invalid("#{id} standard material?", mth, 0) if layer.empty?
+
+ layer = layer.get
+ k = layer.thickness / ro
+ layer.setConductivity(k)
+ else # multiple layers, adjust insulating layer thickness
+ lyr = insulatingLayer(c)
+ return invalid("#{id} construction", mth, 0) if lyr[:index].nil?
+ return invalid("#{id} construction", mth, 0) if lyr[:type ].nil?
+ return invalid("#{id} construction", mth, 0) if lyr[:r ].zero?
+
+ index = lyr[:index]
+ layer = c.getLayer(index).to_StandardOpaqueMaterial
+ return invalid("#{id} material @#{index}", mth, 0) if layer.empty?
+
+ layer = layer.get
+ k = layer.conductivity
+ d = (ro - rsi(c) + lyr[:r]) * k
+ return invalid("#{id} adjusted m", mth, 0) if d < 0.03
+
+ nom = "OSut|"
+ nom += layer.nameString.gsub(/[^a-z]/i, "").gsub("OSut", "")
+ nom += "|"
+ nom += format("%03d", d*1000)[-3..-1]
+
+ lyr = model.getStandardOpaqueMaterialByName(nom)
+
+ if lyr.empty?
+ layer.setName(nom)
+ layer.setThickness(d)
+ else
+ omat = lyr.get
+ c.setLayer(index, omat)
+ end
+ end
end
end
@@ -746,7 +757,7 @@ def genMass(sps = OpenStudio::Model::SpaceVector.new, ratio = 2.0)
# Validates if a default construction set holds a base construction.
#
# @param set [OpenStudio::Model::DefaultConstructionSet] a default set
- # @param bse [OpensStudio::Model::ConstructionBase] a construction base
+ # @param bse [OpenStudio::Model::ConstructionBase] a construction base
# @param gr [Bool] if ground-facing surface
# @param ex [Bool] if exterior-facing surface
# @param tp [#to_s] a surface type
@@ -1048,7 +1059,7 @@ def insulatingLayer(lc = nil)
return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS)
id = lc.nameString
- return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl)
+ return mismatch(id, lc, cl, mth, DBG, res) unless lc.is_a?(cl)
lc.layers.each do |m|
unless m.to_MasslessOpaqueMaterial.empty?
@@ -1102,7 +1113,7 @@ def spandrel?(s = nil)
id = s.nameString
m1 = "#{id}:spandrel"
m2 = "#{id}:spandrel:boolean"
- return mismatch(id, s, cl, mth) unless s.is_a?(cl)
+ return mismatch(id, s, cl, mth, false) unless s.is_a?(cl)
if s.additionalProperties.hasFeature("spandrel")
val = s.additionalProperties.getFeatureAsBoolean("spandrel")
@@ -1129,7 +1140,7 @@ def fenestration?(s = nil)
return invalid("subsurface", mth, 1, DBG, false) unless s.respond_to?(NS)
id = s.nameString
- return mismatch(id, s, cl, mth, false) unless s.is_a?(cl)
+ return mismatch(id, s, cl, mth, DBG, false) unless s.is_a?(cl)
# OpenStudio::Model::SubSurface.validSubSurfaceTypeValues
# "FixedWindow" : fenestration
@@ -1422,7 +1433,15 @@ def scheduleIntervalMinMax(sched = nil)
id = sched.nameString
return mismatch(id, sched, cl, mth, DBG, res) unless sched.is_a?(cl)
- vals = sched.timeSeries.values
+ values = sched.timeSeries.values
+
+ values.each do |value|
+ if value.respond_to?(:to_f)
+ vals << value.to_f
+ else
+ invalid("numerical at #{i}", mth, 1, ERR)
+ end
+ end
res[:min] = vals.min.is_a?(Numeric) ? vals.min : nil
res[:max] = vals.max.is_a?(Numeric) ? vals.min : nil
@@ -1529,6 +1548,16 @@ def maxHeatScheduledSetpoint(zone = nil)
res[:spt] = max if res[:spt] < max
end
end
+
+ unless sched.to_ScheduleInterval.empty?
+ sched = sched.to_ScheduleInterval.get
+ max = scheduleIntervalMinMax(sched)[:max]
+
+ if max
+ res[:spt] = max unless res[:spt]
+ res[:spt] = max if res[:spt] < max
+ end
+ end
end
return res if zone.thermostat.empty?
@@ -1586,6 +1615,16 @@ def maxHeatScheduledSetpoint(zone = nil)
end
end
+ unless sched.to_ScheduleInterval.empty?
+ sched = sched.to_ScheduleInterval.get
+ max = scheduleIntervalMinMax(sched)[:max]
+
+ if max
+ res[:spt] = max unless res[:spt]
+ res[:spt] = max if res[:spt] < max
+ end
+ end
+
unless sched.to_ScheduleYear.empty?
sched = sched.to_ScheduleYear.get
@@ -1707,6 +1746,16 @@ def minCoolScheduledSetpoint(zone = nil)
res[:spt] = min if res[:spt] > min
end
end
+
+ unless sched.to_ScheduleInterval.empty?
+ sched = sched.to_ScheduleInterval.get
+ min = scheduleIntervalMinMax(sched)[:min]
+
+ if min
+ res[:spt] = min unless res[:spt]
+ res[:spt] = min if res[:spt] > min
+ end
+ end
end
return res if zone.thermostat.empty?
@@ -1764,6 +1813,16 @@ def minCoolScheduledSetpoint(zone = nil)
end
end
+ unless sched.to_ScheduleInterval.empty?
+ sched = sched.to_ScheduleInerval.get
+ min = scheduleIntervalMinMax(sched)[:min]
+
+ if min
+ res[:spt] = min unless res[:spt]
+ res[:spt] = min if res[:spt] > min
+ end
+ end
+
unless sched.to_ScheduleYear.empty?
sched = sched.to_ScheduleYear.get
@@ -2351,7 +2410,7 @@ def transforms(group = nil)
# @return [OpenStudio::Vector3d] true normal vector
# @return [nil] if invalid input (see logs)
def trueNormal(s = nil, r = 0)
- mth = "TBD::#{__callee__}"
+ mth = "OSut::#{__callee__}"
cl = OpenStudio::Model::PlanarSurface
return mismatch("surface", s, cl, mth) unless s.is_a?(cl)
return invalid("rotation angle", mth, 2) unless r.respond_to?(:to_f)
@@ -2384,31 +2443,31 @@ def scalar(v = OpenStudio::Vector3d.new, m = 0)
##
# Returns OpenStudio 3D points as an OpenStudio point vector, validating
- # points in the process (if Array).
+ # points in the process (e.g. if Array).
#
# @param pts [Set] OpenStudio 3D points
#
# @return [OpenStudio::Point3dVector] 3D vector (see logs if empty)
def to_p3Dv(pts = nil)
mth = "OSut::#{__callee__}"
- cl1 = OpenStudio::Point3d
- cl2 = OpenStudio::Point3dVector
- cl3 = OpenStudio::Model::PlanarSurface
- cl4 = Array
v = OpenStudio::Point3dVector.new
- if pts.is_a?(cl1)
+ if pts.is_a?(OpenStudio::Point3d)
v << pts
return v
+ elsif pts.is_a?(OpenStudio::Point3dVector)
+ return pts
+ elsif pts.is_a?(OpenStudio::Model::PlanarSurface)
+ pts.vertices.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, pt.z) }
+ return v
end
- return pts if pts.is_a?(cl2)
- return pts.vertices if pts.is_a?(cl3)
-
- return mismatch("points", pts, cl1, mth, DBG, v) unless pts.is_a?(cl4)
+ return mismatch("points", pts, Array, mth, DBG, v) unless pts.is_a?(Array)
pts.each do |pt|
- return mismatch("point", pt, cl4, mth, DBG, v) unless pt.is_a?(cl1)
+ unless pt.is_a?(OpenStudio::Point3d)
+ return mismatch("point", pt, OpenStudio::Point3d, mth, DBG, v)
+ end
end
pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, pt.z) }
@@ -2684,7 +2743,7 @@ def nextUp(pts = nil, pt = nil)
pair = pts.each_cons(2).find { |p1, _| same?(p1, pt) }
- pair.nil? ? pts.first : pair.last
+ pair.nil? ? pts[0] : pair[-1]
end
##
@@ -2775,21 +2834,25 @@ def verticalPlane(p1 = nil, p2 = nil)
# @param pts [Set 0
- v = v[n..-1] if n < 0
+ n = 0 if n.abs > v.size
+ v = v[0..n-1] if n > 0
+ v = v[n..-1] if n < 0
v
end
@@ -2803,10 +2866,10 @@ def getUniques(pts = nil, n = 0)
# @param pts [Set] 3D points
#
# @return [OpenStudio::Point3dVectorVector] line segments (see logs if empty)
- def getSegments(pts = nil)
+ def segments(pts = nil)
mth = "OSut::#{__callee__}"
vv = OpenStudio::Point3dVectorVector.new
- pts = getUniques(pts)
+ pts = uniques(pts)
return vv if pts.size < 2
pts.each_with_index do |p1, i1|
@@ -2833,7 +2896,6 @@ def getSegments(pts = nil)
# @return [false] if invalid input (see logs)
def segment?(pts = nil)
pts = to_p3Dv(pts)
- return false if pts.empty?
return false unless pts.size == 2
return false if same?(pts[0], pts[1])
@@ -2850,10 +2912,10 @@ def segment?(pts = nil)
# @param pts [OpenStudio::Point3dVector] 3D points
#
# @return [OpenStudio::Point3dVectorVector] triads (see logs if empty)
- def getTriads(pts = nil, co = false)
+ def triads(pts = nil, co = false)
mth = "OSut::#{__callee__}"
vv = OpenStudio::Point3dVectorVector.new
- pts = getUniques(pts)
+ pts = uniques(pts)
return vv if pts.size < 2
pts.each_with_index do |p1, i1|
@@ -2880,7 +2942,7 @@ def getTriads(pts = nil, co = false)
# @param pts [Set] 3D points
#
# @return [Bool] whether set is a valid triad (i.e. a trio of 3D points)
- # @return [false] if invalid input (see logs)
+ # @return [false] if invalid input (see 'to_p3Dv' logs)
def triad?(pts = nil)
pts = to_p3Dv(pts)
return false if pts.empty?
@@ -2905,18 +2967,17 @@ def pointAlongSegment?(p0 = nil, sg = [])
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Point3d
cl2 = OpenStudio::Point3dVector
- return mismatch( "point", p0, cl1, mth, DBG, false) unless p0.is_a?(cl1)
- return mismatch("segment", sg, cl2, mth, DBG, false) unless segment?(sg)
-
+ return mismatch("point", p0, cl1, mth, DBG, false) unless p0.is_a?(cl1)
+ return false unless segment?(sg)
return true if holds?(sg, p0)
- a = sg.first
- b = sg.last
+ a = sg[ 0]
+ b = sg[-1]
ab = b - a
abn = b - a
abn.normalize
ap = p0 - a
- sp = ap.dot(abn)
+ sp = ap.dot(abn)
return false if sp < 0
apd = scalar(abn, sp)
@@ -2941,9 +3002,9 @@ def pointAlongSegments?(p0 = nil, sgs = [])
mth = "OSut::#{__callee__}"
cl1 = OpenStudio::Point3d
cl2 = OpenStudio::Point3dVectorVector
- sgs = sgs.is_a?(cl2) ? sgs : getSegments(sgs)
- return empty("segments", mth, DBG, false) if sgs.empty?
- return mismatch("point", p0, cl, mth, DBG, false) unless p0.is_a?(cl1)
+ sgs = sgs.is_a?(cl2) ? sgs : segments(sgs)
+ return empty("segments", mth, DBG, false) if sgs.empty?
+ return mismatch("point", p0, cl1, mth, DBG, false) unless p0.is_a?(cl1)
sgs.each { |sg| return true if pointAlongSegment?(p0, sg) }
@@ -2958,9 +3019,9 @@ def pointAlongSegments?(p0 = nil, sgs = [])
#
# @return [OpenStudio::Point3d] point of intersection of both lines
# @return [nil] if no intersection, equal, or invalid input (see logs)
- def getLineIntersection(s1 = [], s2 = [])
- s1 = getSegments(s1)
- s2 = getSegments(s2)
+ def lineIntersection(s1 = [], s2 = [])
+ s1 = segments(s1)
+ s2 = segments(s2)
return nil if s1.empty?
return nil if s2.empty?
@@ -2971,10 +3032,10 @@ def getLineIntersection(s1 = [], s2 = [])
return nil if same?(s1, s2)
return nil if same?(s1, s2.to_a.reverse)
- a1 = s1[0]
- a2 = s1[1]
- b1 = s2[0]
- b2 = s2[1]
+ a1 = s1.first
+ b1 = s2.first
+ a2 = s1.last
+ b2 = s2.last
# Matching segment endpoints?
return a1 if same?(a1, b1)
@@ -2983,18 +3044,18 @@ def getLineIntersection(s1 = [], s2 = [])
return a2 if same?(a2, b2)
# Segment endpoint along opposite segment?
- return a1 if pointAlongSegments?(a1, s2)
- return a2 if pointAlongSegments?(a2, s2)
- return b1 if pointAlongSegments?(b1, s1)
- return b2 if pointAlongSegments?(b2, s1)
+ return a1 if pointAlongSegment?(a1, s2)
+ return a2 if pointAlongSegment?(a2, s2)
+ return b1 if pointAlongSegment?(b1, s1)
+ return b2 if pointAlongSegment?(b2, s1)
- # Line segments as vectors. Skip if colinear.
+ # Line segments as vectors. Skip if collinear or parallel.
a = a2 - a1
b = b2 - b1
xab = a.cross(b)
return nil if xab.length.round(4) < TOL2
- # Link 1st point to other segment endpoints as vectors. Must be coplanar.
+ # Link 1st point to other segment endpoints, as vectors. Must be coplanar.
a1b1 = b1 - a1
a1b2 = b2 - a1
xa1b1 = a.cross(a1b1)
@@ -3035,7 +3096,7 @@ def getLineIntersection(s1 = [], s2 = [])
return nil if a.dot(p0 - a1) < 0
# Ensure intersection is sandwiched between endpoints.
- return nil unless pointAlongSegments?(p0, s2) && pointAlongSegments?(p0, s1)
+ return nil unless pointAlongSegment?(p0, s2) && pointAlongSegment?(p0, s1)
p0
end
@@ -3049,14 +3110,14 @@ def getLineIntersection(s1 = [], s2 = [])
# @return [Bool] whether 3D line intersects 3D segments
# @return [false] if invalid input (see logs)
def lineIntersects?(l = [], s = [])
- l = getSegments(l)
- s = getSegments(s)
+ l = segments(l)
+ s = segments(s)
return nil if l.empty?
return nil if s.empty?
l = l.first
- s.each { |segment| return true if getLineIntersection(l, segment) }
+ s.each { |segment| return true if lineIntersection(l, segment) }
false
end
@@ -3142,28 +3203,33 @@ def blc(pts = nil)
end
##
- # Returns sequential non-collinear points in an OpenStudio 3D point vector.
+ # Returns non-collinear points in an OpenStudio 3D point vector.
#
# @param pts [Set 0
- a = a[n-1..-1] if n < 0
+ n = 0 if n.abs > a.size
+ a = a[0..n-1] if n > 0
+ a = a[n..-1] if n < 0
to_p3Dv(a)
end
##
- # Returns sequential collinear points in an OpenStudio 3D point vector.
+ # Returns collinear points in an OpenStudio 3D point vector.
#
# @param pts [Set a.size
+ a = a[0..n-1] if n > 0
+ a = a[n..-1] if n < 0
+
+ a
end
##
@@ -3237,7 +3314,7 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no)
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
# Minimum 3 points?
- p3 = getNonCollinears(pts, 3)
+ p3 = nonCollinears(pts, 3)
return empty("polygon (non-collinears < 3)", mth, ERR, v) if p3.size < 3
# Coplanar?
@@ -3268,8 +3345,8 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no)
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
# Ensure uniqueness and/or non-collinearity. Preserve original sequence.
p0 = a.first
- a = getUniques(a).to_a if uq
- a = getNonCollinears(a).to_a if co
+ a = uniques(a).to_a if uq
+ a = nonCollinears(a).to_a if co
i0 = a.index { |pt| same?(pt, p0) }
a = a.rotate(i0) unless i0.nil?
@@ -3278,7 +3355,7 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no)
if vx && a.size > 3
zen = OpenStudio::Point3d.new(0, 0, 1000)
- getTriads(a).each do |trio|
+ triads(a).each do |trio|
p1 = trio[0]
p2 = trio[1]
p3 = trio[2]
@@ -3344,31 +3421,31 @@ def pointWithinPolygon?(p0 = nil, s = [], entirely = false)
return false unless pl.pointOnPlane(p0)
entirely = false unless [true, false].include?(entirely)
- segments = getSegments(s)
+ sgments = segments(s)
# Along polygon edges, or near vertices?
- if pointAlongSegments?(p0, segments)
+ if pointAlongSegments?(p0, sgments)
return false if entirely
return true unless entirely
end
- segments.each do |segment|
+ sgments.each do |sgment|
# - draw vector from segment midpoint to point
# - scale 1000x (assuming no building surface would be 1km wide)
# - convert vector to an independent line segment
# - loop through polygon segments, tally the number of intersections
# - avoid double-counting polygon vertices as intersections
# - return false if number of intersections is even
- mid = midpoint(segment.first, segment.last)
+ mid = midpoint(sgment.first, sgment.last)
mpV = scalar(mid - p0, 1000)
p1 = p0 + mpV
ctr = 0
# Skip if ~collinear.
- next if mpV.cross(segment.last - segment.first).length.round(4) < TOL2
+ next if mpV.cross(sgment.last - sgment.first).length.round(4) < TOL2
- segments.each do |sg|
- intersect = getLineIntersection([p0, p1], sg)
+ sgments.each do |sg|
+ intersect = lineIntersection([p0, p1], sg)
next unless intersect
# Skip test altogether if one of the polygon vertices.
@@ -3518,7 +3595,7 @@ def square?(pts = nil)
return false if pts.empty?
return false unless rectangular?(pts)
- getSegments(pts).each do |pt|
+ segments(pts).each do |pt|
l = (pt[1] - pt[0]).length
d = l unless d
return false unless l.round(2) == d.round(2)
@@ -3552,7 +3629,7 @@ def fits?(p1 = nil, p2 = nil, entirely = false)
p2.each { |p0| return false if pointWithinPolygon?(p0, p1, true) }
# p1 segment mid-points must not lie OUTSIDE of p2.
- getSegments(p1).each do |sg|
+ segments(p1).each do |sg|
mp = midpoint(sg.first, sg.last)
return false unless pointWithinPolygon?(mp, p2)
end
@@ -3595,22 +3672,17 @@ def overlap(p1 = nil, p2 = nil, flat = false)
cw1 = clockwise?(p01)
a1 = cw1 ? p01.to_a.reverse : p01.to_a
a2 = p02.to_a
- a2 = flatten(a2).to_a if flat
- return invalid("points 2", mth, 2, DBG, face) unless xyz?(a2, :z)
-
- cw2 = clockwise?(a2)
- a2 = a2.reverse if cw2
else
t = OpenStudio::Transformation.alignFace(p01)
a1 = t.inverse * p01
a2 = t.inverse * p02
- a2 = flatten(a2).to_a if flat
- return invalid("points 2", mth, 2, DBG, face) unless xyz?(a2, :z)
-
- cw2 = clockwise?(a2)
- a2 = a2.reverse if cw2
end
+ a2 = flatten(a2).to_a if flat
+ cw2 = clockwise?(a2)
+ a2 = a2.reverse if cw2
+ return invalid("points 2", mth, 2, DBG, face) unless xyz?(a2, :z)
+
# Return either (transformed) polygon if one fits into the other.
p1t = p01
@@ -3690,7 +3762,7 @@ def cast(p1 = nil, p2 = nil, ray = nil)
p2 = poly(p2)
return face if p1.empty?
return face if p2.empty?
- return mismatch("ray", ray, cl, mth) unless ray.is_a?(cl)
+ return mismatch("ray", ray, cl, mth, face) unless ray.is_a?(cl)
# From OpenStudio SDK v3.7.0 onwards, one could/should rely on:
#
@@ -4023,12 +4095,12 @@ def outline(a = [], bfr = 0, flat = true)
#
# @param [Set] a triad (3D points)
#
- # @return [Set] a rectangular ULC box (see logs if empty)
+ # @return [Set] a rectangular BLC box (see logs if empty)
def triadBox(pts = nil)
mth = "OSut::#{__callee__}"
bkp = OpenStudio::Point3dVector.new
box = []
- pts = getNonCollinears(pts)
+ pts = nonCollinears(pts)
return bkp if pts.empty?
t = xyz?(pts, :z) ? nil : OpenStudio::Transformation.alignFace(pts)
@@ -4065,7 +4137,7 @@ def triadBox(pts = nil)
box << OpenStudio::Point3d.new(p1.x, p1.y, p1.z)
box << OpenStudio::Point3d.new(p2.x, p2.y, p2.z)
box << OpenStudio::Point3d.new(p3.x, p3.y, p3.z)
- box = getNonCollinears(box, 4)
+ box = nonCollinears(box, 4)
return bkp unless box.size == 4
box = blc(box)
@@ -4098,7 +4170,7 @@ def medialBox(pts = nil)
# Generate vertical plane along longest segment.
mpoints = []
- sgs = getSegments(pts)
+ sgs = segments(pts)
longest = sgs.max_by { |s| OpenStudio.getDistanceSquared(s.first, s.last) }
plane = verticalPlane(longest.first, longest.last)
@@ -4112,7 +4184,7 @@ def medialBox(pts = nil)
box << mpoints.first
box << mpoints.last
box << plane.project(mpoints.last)
- box = getNonCollinears(box).to_a
+ box = nonCollinears(box).to_a
return bkp unless box.size == 4
box = clockwise?(box) ? blc(box.reverse) : blc(box)
@@ -4165,16 +4237,16 @@ def boundedBox(pts = nil)
aire = 0
# PATH C : Right-angle, midpoint triad approach.
- getSegments(pts).each do |sg|
+ segments(pts).each do |sg|
m0 = midpoint(sg.first, sg.last)
- getSegments(pts).each do |seg|
+ segments(pts).each do |seg|
p1 = seg.first
p2 = seg.last
next if same?(p1, sg.first)
next if same?(p1, sg.last)
next if same?(p2, sg.first)
- next if same?(p2, sg.first)
+ next if same?(p2, sg.last)
out = triadBox(OpenStudio::Point3dVector.new([m0, p1, p2]))
next if out.empty?
@@ -4194,7 +4266,7 @@ def boundedBox(pts = nil)
end
# PATH D : Right-angle triad approach, may override PATH C boxes.
- getSegments(pts).each do |sg|
+ segments(pts).each do |sg|
p0 = sg.first
p1 = sg.last
@@ -4227,7 +4299,7 @@ def boundedBox(pts = nil)
# PATH E : Medial box, segment approach.
aire = 0
- getSegments(pts).each do |sg|
+ segments(pts).each do |sg|
p0 = sg.first
p1 = sg.last
@@ -4260,7 +4332,7 @@ def boundedBox(pts = nil)
# PATH F : Medial box, triad approach.
aire = 0
- getTriads(pts).each do |sg|
+ triads(pts).each do |sg|
p0 = sg[0]
p1 = sg[1]
p2 = sg[2]
@@ -4292,7 +4364,7 @@ def boundedBox(pts = nil)
holes = OpenStudio::Point3dVectorVector.new
OpenStudio.computeTriangulation(outer, holes).each do |triangle|
- getSegments(triangle).each do |sg|
+ segments(triangle).each do |sg|
p0 = sg.first
p1 = sg.last
@@ -4349,7 +4421,7 @@ def boundedBox(pts = nil)
#
# @return [Hash] :set, :box, :bbox, :t, :r & :o
# @return [Hash] :set, :box, :bbox, :t, :r & :o (nil) if invalid (see logs)
- def getRealignedFace(pts = nil, force = false)
+ def realignedFace(pts = nil, force = false)
mth = "OSut::#{__callee__}"
out = { set: nil, box: nil, bbox: nil, t: nil, r: nil, o: nil }
pts = poly(pts, false, true)
@@ -4372,11 +4444,11 @@ def getRealignedFace(pts = nil, force = false)
box = boundedBox(pts)
return invalid("bounded box", mth, 0, DBG, out) if box.empty?
- segments = getSegments(box)
- return invalid("bounded box segments", mth, 0, DBG, out) if segments.empty?
+ sgments = segments(box)
+ return invalid("bounded box segments", mth, 0, DBG, out) if sgments.empty?
# Deterministic ID of box rotation/translation 'origin'.
- segments.each_with_index do |sg, idx|
+ sgments.each_with_index do |sg, idx|
sgs[sg] = {}
sgs[sg][:idx] = idx
sgs[sg][:mid] = midpoint(sg[0], sg[1])
@@ -4396,10 +4468,10 @@ def getRealignedFace(pts = nil, force = false)
i = sg0[:idx]
end
- k = i + 2 < segments.size ? i + 2 : i - 2
+ k = i + 2 < sgments.size ? i + 2 : i - 2
- origin = midpoint(segments[i][0], segments[i][1])
- terminal = midpoint(segments[k][0], segments[k][1])
+ origin = midpoint(sgments[i][0], sgments[i][1])
+ terminal = midpoint(sgments[k][0], sgments[k][1])
seg = terminal - origin
right = OpenStudio::Point3d.new(origin.x + d, origin.y , 0) - origin
north = OpenStudio::Point3d.new(origin.x, origin.y + d, 0) - origin
@@ -4441,7 +4513,7 @@ def getRealignedFace(pts = nil, force = false)
# @param pts [Set] 3D points, once re/aligned
# @param force [Bool] whether to force rotation of (narrow) bounded box
#
- # @return [Float] width al©ong X-axis, once re/aligned
+ # @return [Float] width along X-axis, once re/aligned
# @return [0.0] if invalid inputs
def alignedWidth(pts = nil, force = false)
mth = "OSut::#{__callee__}"
@@ -4453,7 +4525,7 @@ def alignedWidth(pts = nil, force = false)
force = false
end
- pts = getRealignedFace(pts, force)[:set]
+ pts = realignedFace(pts, force)[:set]
return 0 if pts.size < 2
pts.max_by(&:x).x - pts.min_by(&:x).x
@@ -4477,12 +4549,84 @@ def alignedHeight(pts = nil, force = false)
force = false
end
- pts = getRealignedFace(pts, force)[:set]
+ pts = realignedFace(pts, force)[:set]
return 0 if pts.size < 2
pts.max_by(&:y).y - pts.min_by(&:y).y
end
+ ##
+ # Fetch a space's full height (in space coordinates). The solution considers
+ # all surface types ("Floor" vs "Wall" vs "RoofCeiling").
+ #
+ # @param space [OpenStudio::Model::Space] a space
+ #
+ # @return [Float] full height of space (0 if invalid input)
+ def spaceHeight(space = nil)
+ return 0 unless space.is_a?(OpenStudio::Model::Space)
+
+ minZ = 10000
+ maxZ = -10000
+
+ space.surfaces.each do |surface|
+ minZ = [surface.vertices.min_by(&:z).z, minZ].min
+ maxZ = [surface.vertices.max_by(&:z).z, maxZ].max
+ end
+
+ maxZ < minZ ? 0 : maxZ - minZ
+ end
+
+ ##
+ # Fetch a space's width, based on the geometry of space floors.
+ #
+ # @param space [OpenStudio::Model::Space] a space
+ #
+ # @return [Float] width of a space (0 if invalid input)
+ def spaceWidth(space = nil)
+ return 0 unless space.is_a?(OpenStudio::Model::Space)
+
+ floors = facets(space, "all", "Floor")
+ return 0 if floors.empty?
+
+ # Automatically determining a space's "width" is not straightforward:
+ # - a space may hold multiple floor surfaces at various Z-axis levels
+ # - a space may hold multiple floor surfaces, with unique "widths"
+ # - a floor surface may expand/contract (in "width") along its length.
+ #
+ # First, attempt to merge all floor surfaces together as 1x polygon:
+ # - select largest floor surface (in area)
+ # - determine its 3D plane
+ # - retain only other floor surfaces sharing same 3D plane
+ # - recover potential union between floor surfaces
+ # - fall back to largest floor surface if invalid union
+ # - return width of largest bounded box
+ floors = floors.sort_by(&:grossArea).reverse
+ floor = floors.first
+ plane = floor.plane
+ t = OpenStudio::Transformation.alignFace(floor.vertices)
+ polyg = poly(floor, false, true, true, t, :ulc).to_a.reverse
+ return 0 if polyg.empty?
+
+ if floors.size > 1
+ floors = floors.select { |flr| plane.equal(flr.plane, 0.001) }
+
+ if floors.size > 1
+ polygs = floors.map { |flr| poly(flr, false, true, true, t, :ulc) }
+ polygs = polygs.reject { |plg| plg.empty? }
+ polygs = polygs.map { |plg| plg.to_a.reverse }
+ union = OpenStudio.joinAll(polygs, 0.01).first
+ polyg = poly(union, false, true, true)
+ return 0 if polyg.empty?
+ end
+ end
+
+ res = realignedFace(polyg.to_a.reverse)
+ return 0 if res[:box].nil?
+
+ # A bounded box's 'height', at its narrowest, is its 'width'.
+ height(res[:box])
+ end
+
##
# Identifies 'leader line anchors', i.e. specific 3D points of a (larger) set
# (e.g. delineating a larger, parent polygon), each anchor linking the BLC
@@ -4576,7 +4720,7 @@ def genAnchors(s = nil, set = [], tag = :box)
else
st[:t] = OpenStudio::Transformation.alignFace(pts) unless st.key?(:t)
tpts = st[:t].inverse * st[tag]
- o = getRealignedFace(tpts, true)
+ o = realignedFace(tpts, true)
tpts = st[:t] * (o[:r] * (o[:t] * o[:set]))
st[:out] = o
@@ -4594,7 +4738,7 @@ def genAnchors(s = nil, set = [], tag = :box)
nb = 0
# Check for intersections between leader line and larger polygon edges.
- getSegments(pts).each do |sg|
+ segments(pts).each do |sg|
break unless nb.zero?
next if holds?(sg, pt)
@@ -4608,7 +4752,7 @@ def genAnchors(s = nil, set = [], tag = :box)
ost = other[tag]
- getSegments(ost).each { |sg| nb += 1 if lineIntersects?(ld, sg) }
+ segments(ost).each { |sg| nb += 1 if lineIntersects?(ld, sg) }
end
# ... and previous leader lines (first come, first serve basis).
@@ -4626,7 +4770,7 @@ def genAnchors(s = nil, set = [], tag = :box)
end
# Finally, check for self-intersections.
- getSegments(tpts).each do |sg|
+ segments(tpts).each do |sg|
break unless nb.zero?
next if holds?(sg, tpts.first)
@@ -4686,8 +4830,9 @@ def genExtendedVertices(s = nil, set = [], tag = :vtx)
set.each_with_index do |st, i|
str1 = id + "subset ##{i+1}"
str2 = str1 + " #{tag.to_s}"
- next if st.key?(:void) && st[:void]
return mismatch(str1, st, Hash, mth, DBG, a) unless st.respond_to?(:key?)
+ next if st.key?(:void) && st[:void]
+
return hashkey( str1, st, tag, mth, DBG, a) unless st.key?(tag)
return empty("#{str2} vertices", mth, DBG, a) if st[tag].empty?
return hashkey( str1, st, :ld, mth, DBG, a) unless st.key?(:ld)
@@ -4696,9 +4841,9 @@ def genExtendedVertices(s = nil, set = [], tag = :vtx)
return invalid("#{str2} polygon", mth, 0, DBG, a) if stt.empty?
ld = st[:ld]
- return mismatch(str, ld, Hash, mth, DBG, a) unless ld.is_a?(Hash)
- return hashkey( str, ld, s, mth, DBG, a) unless ld.key?(s)
- return mismatch(str, ld[s], cl, mth, DBG, a) unless ld[s].is_a?(cl)
+ return mismatch(str2, ld, Hash, mth, DBG, a) unless ld.is_a?(Hash)
+ return hashkey( str2, ld, s, mth, DBG, a) unless ld.key?(s)
+ return mismatch(str2, ld[s], cl, mth, DBG, a) unless ld[s].is_a?(cl)
end
# Re-sequence polygon vertices.
@@ -5091,7 +5236,7 @@ def genSlab(pltz = [], z = 0)
pltz.each_with_index do |plt, i|
id = "plate # #{i+1} (index #{i})"
- return mismatch(id, plt, cl1, mth, DBG, slb) unless plt.is_a?(cl2)
+ return mismatch(id, plt, cl2, mth, DBG, slb) unless plt.is_a?(cl2)
return hashkey( id, plt, :x, mth, DBG, slb) unless plt.key?(:x )
return hashkey( id, plt, :y, mth, DBG, slb) unless plt.key?(:y )
return hashkey( id, plt, :dx, mth, DBG, slb) unless plt.key?(:dx)
@@ -5142,7 +5287,7 @@ def genSlab(pltz = [], z = 0)
end
# Once joined, re-adjust Z-axis coordinates.
- unless z.zero?
+ unless z.round(2) == 0.00
vtx = OpenStudio::Point3dVector.new
slb.each { |pt| vtx << OpenStudio::Point3d.new(pt.x, pt.y, z) }
slb = vtx
@@ -5162,18 +5307,18 @@ def genSlab(pltz = [], z = 0)
# @param spaces [Set] target spaces
#
# @return [Array] roofs (may be empty)
- def getRoofs(spaces = [])
+ def roofs(spaces = [])
mth = "OSut::#{__callee__}"
up = OpenStudio::Point3d.new(0,0,1) - OpenStudio::Point3d.new(0,0,0)
- roofs = []
+ rfs = []
spaces = spaces.is_a?(OpenStudio::Model::Space) ? [spaces] : spaces
spaces = spaces.respond_to?(:to_a) ? spaces.to_a : []
spaces = spaces.select { |space| space.is_a?(OpenStudio::Model::Space) }
# Space-specific outdoor-facing roof surfaces.
- roofs = facets(spaces, "Outdoors", "RoofCeiling")
- roofs = roofs.select { |roof| roof?(roof) }
+ rfs = facets(spaces, "Outdoors", "RoofCeiling")
+ rfs = rfs.select { |rf| roof?(rf) }
spaces.each do |space|
# When unoccupied spaces are involved (e.g. plenums, attics), the target
@@ -5209,12 +5354,12 @@ def getRoofs(spaces = [])
cst = cast(cv0, rvi, up)
next unless overlaps?(cst, rvi, false)
- roofs << ruf unless roofs.include?(ruf)
+ rfs << ruf unless rfs.include?(ruf)
end
end
end
- roofs
+ rfs
end
##
@@ -5240,10 +5385,10 @@ def daylit?(space = nil, sidelit = true, toplit = true, baselit = true)
return invalid("baselit" , mth, 4, DBG, false) unless ck4
walls = sidelit ? facets(space, "Outdoors", "Wall") : []
- roofs = toplit ? facets(space, "Outdoors", "RoofCeiling") : []
+ rufs = toplit ? facets(space, "Outdoors", "RoofCeiling") : []
floors = baselit ? facets(space, "Outdoors", "Floor") : []
- (walls + roofs + floors).each do |surface|
+ (walls + rufs + floors).each do |surface|
surface.subSurfaces.each do |sub|
# All fenestrated subsurface types are considered, as user can set these
# explicitly (e.g. skylight in a wall) in OpenStudio.
@@ -5370,11 +5515,11 @@ def addSubs(s = nil, subs = [], clear = false, bound = false, realign = false, b
box = boundedBox(s0)
if realign
- s00 = getRealignedFace(box, true)
+ s00 = realignedFace(box, true)
return invalid("bound realignment", mth, 0, DBG, false) unless s00[:set]
end
elsif realign
- s00 = getRealignedFace(s0, false)
+ s00 = realignedFace(s0, false)
return invalid("unbound realignment", mth, 0, DBG, false) unless s00[:set]
end
@@ -5983,7 +6128,6 @@ def grossRoofArea(spaces = [])
# previously-added leader lines.
#
# @todo: revise approach for attics ONCE skylight wells have been added.
- olap = nil
olap = overlap(cst, rvi, false)
next if olap.empty?
@@ -6013,24 +6157,24 @@ def grossRoofArea(spaces = [])
# (Array of 2x linked surfaces). Each surface may be linked to more than one
# horizontal ridge.
#
- # @param roofs [Array] target surfaces
+ # @param rfs [Array] target surfaces
#
# @return [Array] horizontal ridges (see logs if empty)
- def getHorizontalRidges(roofs = [])
+ def horizontalRidges(rfs = [])
mth = "OSut::#{__callee__}"
ridges = []
- return ridges unless roofs.is_a?(Array)
+ return ridges unless rfs.is_a?(Array)
- roofs = roofs.select { |s| s.is_a?(OpenStudio::Model::Surface) }
- roofs = roofs.select { |s| sloped?(s) }
+ rfs = rfs.select { |s| s.is_a?(OpenStudio::Model::Surface) }
+ rfs = rfs.select { |s| sloped?(s) }
- roofs.each do |roof|
- maxZ = roof.vertices.max_by(&:z).z
- next if roof.space.empty?
+ rfs.each do |rf|
+ maxZ = rf.vertices.max_by(&:z).z
+ next if rf.space.empty?
- space = roof.space.get
+ space = rf.space.get
- getSegments(roof).each do |edge|
+ segments(rf).each do |edge|
next unless xyz?(edge, :z, maxZ)
# Skip if already tracked.
@@ -6045,18 +6189,18 @@ def getHorizontalRidges(roofs = [])
next if match
- ridge = { edge: edge, length: (edge[1] - edge[0]).length, roofs: [roof] }
+ ridge = { edge: edge, length: (edge[1] - edge[0]).length, roofs: [rf] }
# Links another roof (same space)?
match = false
- roofs.each do |ruf|
+ rfs.each do |ruf|
break if match
- next if ruf == roof
+ next if ruf == rf
next if ruf.space.empty?
next unless ruf.space.get == space
- getSegments(ruf).each do |edg|
+ segments(ruf).each do |edg|
break if match
next unless same?(edge, edg) || same?(edge, edg.reverse)
@@ -6100,7 +6244,7 @@ def toToplit(spaces = [], opts = {})
if opts[:size].respond_to?(:to_f)
w = opts[:size].to_f
w2 = w * w
- return invalid(size, mth, 0, ERR, []) if w.round(2) < gap4
+ return invalid("size", mth, 0, ERR, []) if w.round(2) < gap4
else
return mismatch("size", opts[:size], Numeric, mth, DBG, [])
end
@@ -6123,12 +6267,12 @@ def toToplit(spaces = [], opts = {})
spaces = spaces.select { |sp| sp.partofTotalFloorArea }
spaces = spaces.reject { |sp| unconditioned?(sp) }
spaces = spaces.reject { |sp| vestibule?(sp) }
- spaces = spaces.reject { |sp| getRoofs(sp).empty? }
+ spaces = spaces.reject { |sp| roofs(sp).empty? }
spaces = spaces.reject { |sp| sp.floorArea < 4 * w2 }
spaces = spaces.sort_by(&:floorArea).reverse
- return empty("spaces", mth, WRN, 0) if spaces.empty?
+ return empty("spaces", mth, WRN, []) if spaces.empty?
else
- return mismatch("spaces", spaces, Array, mth, DBG, 0)
+ return mismatch("spaces", spaces, Array, mth, DBG, [])
end
# Unfenestrated spaces have no windows, glazed doors or skylights. By
@@ -6169,7 +6313,7 @@ def toToplit(spaces = [], opts = {})
# Gather roof surfaces - possibly those of attics or plenums above.
spaces.each do |sp|
- getRoofs(sp).each do |rf|
+ roofs(sp).each do |rf|
espaces[sp] = {roofs: []} unless espaces.key?(sp)
espaces[sp][:roofs] << rf unless espaces[sp][:roofs].include?(rf)
end
@@ -6244,6 +6388,7 @@ def addSkyLights(spaces = [], opts = {})
bfr = 0.005 # minimum array perimeter buffer (no wells)
w = 1.22 # default 48" x 48" skylight base
w2 = w * w # m2
+ v = OpenStudio.openStudioVersion.split(".").join.to_i
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
# Excerpts of ASHRAE 90.1 2022 definitions:
@@ -6360,10 +6505,10 @@ def addSkyLights(spaces = [], opts = {})
if frame.respond_to?(:frameWidth)
frame = nil if v < 321
- frame = nil if f.frameWidth.round(2) < 0
- frame = nil if f.frameWidth.round(2) > gap
+ frame = nil if frame.frameWidth.round(2) < 0
+ frame = nil if frame.frameWidth.round(2) > gap
- f = f.frameWidth if frame
+ f = frame.frameWidth if frame
log(WRN, "Skip Frame&Divider (#{mth})") unless frame
else
frame = nil
@@ -6420,7 +6565,7 @@ def addSkyLights(spaces = [], opts = {})
end
# Purge if requested.
- getRoofs(spaces).each { |s| s.subSurfaces.map(&:remove) } if clear
+ roofs(spaces).each { |s| s.subSurfaces.map(&:remove) } if clear
# Safely exit, e.g. if strictly called to purge existing roof subsurfaces.
return 0 if area && area.round(2) == 0
@@ -6588,14 +6733,14 @@ def addSkyLights(spaces = [], opts = {})
next unless opts[opt] == false
case opt
- when :sidelit then filters.map! { |f| f.include?("b") ? f.delete("b") : f }
- when :sloped then filters.map! { |f| f.include?("c") ? f.delete("c") : f }
- when :plenum then filters.map! { |f| f.include?("d") ? f.delete("d") : f }
- when :attic then filters.map! { |f| f.include?("e") ? f.delete("e") : f }
+ when :sidelit then filters.map! { |fl| fl.include?("b") ? fl.delete("b") : fl }
+ when :sloped then filters.map! { |fl| fl.include?("c") ? fl.delete("c") : fl }
+ when :plenum then filters.map! { |fl| fl.include?("d") ? fl.delete("d") : fl }
+ when :attic then filters.map! { |fl| fl.include?("e") ? fl.delete("e") : fl }
end
end
- filters.reject! { |f| f.empty? }
+ filters.reject! { |fl| fl.empty? }
filters.uniq!
# Remaining filters may be further pruned automatically after space/roof
@@ -6698,7 +6843,7 @@ def addSkyLights(spaces = [], opts = {})
# Process outdoor-facing roof surfaces of plenums and attics above.
rooms.each do |space, room|
t0 = room[:t0]
- rufs = getRoofs(space) - room[:roofs]
+ rufs = roofs(space) - room[:roofs]
rufs.each do |ruf|
next unless roof?(ruf)
@@ -6860,12 +7005,12 @@ def addSkyLights(spaces = [], opts = {})
# Ensure uniqueness of plenum roofs.
attics.values.each do |attic|
attic[:roofs ].uniq!
- attic[:ridges] = getHorizontalRidges(attic[:roofs]) # @todo
+ attic[:ridges] = horizontalRidges(attic[:roofs]) # @todo
end
plenums.values.each do |plenum|
plenum[:roofs ].uniq!
- plenum[:ridges] = getHorizontalRidges(plenum[:roofs]) # @todo
+ plenum[:ridges] = horizontalRidges(plenum[:roofs]) # @todo
end
# Regardless of the selected skylight arrangement pattern, the solution only
@@ -7388,7 +7533,7 @@ def addSkyLights(spaces = [], opts = {})
pattern = "array"
elsif fpm2.keys.include?("strips")
pattern = "strips"
- else fpm2.keys.include?("strip")
+ else # fpm2.keys.include?("strip")
pattern = "strip"
end
else
@@ -7399,7 +7544,7 @@ def addSkyLights(spaces = [], opts = {})
pattern = "strip"
elsif fpm2.keys.include?("strips")
pattern = "strips"
- else fpm2.keys.include?("array")
+ else # fpm2.keys.include?("array")
pattern = "array"
end
end
@@ -7502,7 +7647,7 @@ def addSkyLights(spaces = [], opts = {})
# Size contraction: round 2: prioritize larger sets.
adm2 = 0
- sets.each_with_index do |set, i|
+ sets.each_with_index do |set|
next if set[:w].round(2) <= w0
next if set[:d].round(2) <= w0
@@ -7674,12 +7819,12 @@ def addSkyLights(spaces = [], opts = {})
# Generate well walls.
vX = cast(roof, tile, ray)
- s0 = getSegments(t0 * roof.vertices)
- sX = getSegments(t0 * vX)
+ s0 = segments(t0 * roof.vertices)
+ sX = segments(t0 * vX)
s0.each_with_index do |sg, j|
- sg0 = sg.to_a
- sgX = sX[j].to_a
+ sg0 = sg
+ sgX = sX[j]
vec = OpenStudio::Point3dVector.new
vec << sg0.first
vec << sg0.last
diff --git a/lib/measures/tbd/tests/tbd_tests.rb b/lib/measures/tbd/tests/tbd_tests.rb
index fe4d4d4..4ae6888 100644
--- a/lib/measures/tbd/tests/tbd_tests.rb
+++ b/lib/measures/tbd/tests/tbd_tests.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/tbd.rb b/lib/tbd.rb
index 341f63c..06054de 100644
--- a/lib/tbd.rb
+++ b/lib/tbd.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/tbd/geo.rb b/lib/tbd/geo.rb
index 98a09b4..7dd2a6f 100644
--- a/lib/tbd/geo.rb
+++ b/lib/tbd/geo.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/tbd/psi.rb b/lib/tbd/psi.rb
index 85d0132..8075770 100644
--- a/lib/tbd/psi.rb
+++ b/lib/tbd/psi.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/tbd/ua.rb b/lib/tbd/ua.rb
index e6f6ac2..41c1d5b 100644
--- a/lib/tbd/ua.rb
+++ b/lib/tbd/ua.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -1000,7 +1000,7 @@ def ua_md(ua = {}, lang = :en)
model = "* modèle : #{ua[:file]}" if ua.key?(:file) && lang == :fr
model += " (v#{ua[:version]})" if ua.key?(:version)
report << model unless model.empty?
- report << "* TBD : v3.4.4"
+ report << "* TBD : v3.4.5"
report << "* date : #{ua[:date]}"
if lang == :en
diff --git a/lib/tbd/version.rb b/lib/tbd/version.rb
index 3e547f1..9f174ca 100644
--- a/lib/tbd/version.rb
+++ b/lib/tbd/version.rb
@@ -1,6 +1,6 @@
# MIT License
#
-# Copyright (c) 2020-2024 Denis Bourgeois & Dan Macumber
+# Copyright (c) 2020-2025 Denis Bourgeois & Dan Macumber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -21,5 +21,5 @@
# SOFTWARE.
module TBD
- VERSION = "3.4.4".freeze # TBD release version
+ VERSION = "3.4.5".freeze
end
diff --git a/spec/tbd_tests_spec.rb b/spec/tbd_tests_spec.rb
index c9f8d34..9e648b8 100644
--- a/spec/tbd_tests_spec.rb
+++ b/spec/tbd_tests_spec.rb
@@ -180,7 +180,7 @@
expect(open).to_not be_empty
open = open.get
- open_roofs = TBD.getRoofs(open)
+ open_roofs = TBD.roofs(open)
expect(open_roofs.size).to eq(1)
open_roof = open_roofs.first
roof_id = open_roof.nameString
@@ -2982,7 +2982,7 @@
expect(plenum.partofTotalFloorArea).to be false
expect(TBD.unconditioned?(plenum)).to be false
- open_roofs = TBD.getRoofs(open)
+ open_roofs = TBD.roofs(open)
expect(open_roofs.size).to eq(1)
open_roof = open_roofs.first
roof_id = open_roof.nameString
diff --git a/tbd.gemspec b/tbd.gemspec
index 14956fb..87a4ad5 100644
--- a/tbd.gemspec
+++ b/tbd.gemspec
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
s.metadata = {}
s.add_dependency "topolys", "~> 0"
- s.add_dependency "osut", "~> 0"
+ s.add_dependency "osut", "~> 0.7.0"
s.add_dependency "json-schema", "~> 4"
s.add_development_dependency "bundler", "~> 2.1"