From a0c6ea3a2278cfb3272b1a186e8c7c29dbc9fa0d Mon Sep 17 00:00:00 2001 From: rd2 Date: Mon, 11 Mar 2024 08:02:00 -0400 Subject: [PATCH 01/13] Autogenerate skylight wells --- .github/workflows/pull_request.yml | 16 + lib/osut/utils.rb | 4862 +++++++++++++++---- lib/osut/version.rb | 2 +- spec/files/osms/in/warehouse.osm | 7121 ++++++++++++++++++++++++++++ spec/osut_tests_spec.rb | 1619 +++++-- 5 files changed, 12313 insertions(+), 1307 deletions(-) create mode 100644 spec/files/osms/in/warehouse.osm diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f2d56fe..3605bdb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -102,3 +102,19 @@ jobs: docker exec -t test bundle update docker exec -t test bundle exec rake docker kill test + test_370x: + 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:dev-3.7.0 + docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:dev-3.7.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/lib/osut/utils.rb b/lib/osut/utils.rb index a403d6d..a265a17 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -53,7 +53,7 @@ module OSut :east, # EAST :south, # SOUTH :west # WEST - ].freeze + ].freeze # This first set of utilities support OpenStudio materials, constructions, # construction sets, etc. If relying on default StandardOpaqueMaterial: @@ -1099,6 +1099,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) if s.additionalProperties.hasFeature("spandrel") val = s.additionalProperties.getFeatureAsBoolean("spandrel") @@ -1112,6 +1113,36 @@ def spandrel?(s = nil) id.downcase.include?("spandrel") end + ## + # Validates whether a sub surface is fenestrated. + # + # @param s [OpenStudio::Model::SubSurface] a sub surface + # + # @return [Bool] whether subsurface can be considered 'fenestrated' + # @return [false] if invalid input (see logs) + def fenestration?(s = nil) + mth = "OSut::#{__callee__}" + cl = OpenStudio::Model::SubSurface + 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) + + # OpenStudio::Model::SubSurface.validSubSurfaceTypeValues + # "FixedWindow" : fenestration + # "OperableWindow" : fenestration + # "Door" + # "GlassDoor" : fenestration + # "OverheadDoor" + # "Skylight" : fenestration + # "TubularDaylightDome" : fenestration + # "TubularDaylightDiffuser" : fenestration + return false if s.subSurfaceType.downcase == "door" + return false if s.subSurfaceType.downcase == "overheaddoor" + + true + end + # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- # # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- # # This next set of utilities (~850 lines) help distinguish spaces that are @@ -1881,14 +1912,14 @@ def plenum?(space = nil) # SDK's "partofTotalFloorArea" would be more suitable in such cases, as # long as modellers have, a priori, set this parameter to FALSE. # - # OpenStudio-Standards' "space_plenum?" catches a MUCH WIDER range of - # spaces, which aren't caught by "isPlenum". This includes attics, - # crawlspaces, non-plenum air spaces above ceiling tiles, and any other - # UNOCCUPIED space in a model. The term "plenum" in this context is more - # of a catch-all shorthand - to be used with caution. For instance, - # "space_plenum?" shouldn't be used (in isolation) to determine whether an - # UNOCCUPIED space should have its envelope insulated ("plenum") or not - # ("attic"). + # By initially relying on the SDK's "partofTotalFloorArea", "space_plenum?" + # ends up catching a MUCH WIDER range of spaces, which aren't caught by + # "isPlenum". This includes attics, crawlspaces, non-plenum air spaces above + # ceiling tiles, and any other UNOCCUPIED space in a model. The term + # "plenum" in this context is more of a catch-all shorthand - to be used + # with caution. For instance, "space_plenum?" shouldn't be used (in + # isolation) to determine whether an UNOCCUPIED space should have its + # envelope insulated ("plenum") or not ("attic"). # # In contrast to OpenStudio-Standards' "space_plenum?", this method # strictly returns FALSE if a space is indeed "partofTotalFloorArea". It @@ -1915,10 +1946,6 @@ def plenum?(space = nil) return false if space.partofTotalFloorArea return false if vestibule?(space) - id = space.nameString - m1 = "#{id}:plenum" - m1 = "#{id}:plenum boolean" - # CASE A: "plenum" spaceType. unless space.spaceType.empty? type = space.spaceType.get @@ -2259,84 +2286,25 @@ def transforms(group = nil) end ## - # Returns true if 2 OpenStudio 3D points are nearly equal - # - # @param p1 [OpenStudio::Point3d] 1st 3D point - # @param p2 [OpenStudio::Point3d] 2nd 3D point - # - # @return [Bool] whether equal points (within TOL) - # @return [false] if invalid input (see logs) - def same?(p1 = nil, p2 = nil) - mth = "OSut::#{__callee__}" - cl = OpenStudio::Point3d - return mismatch("point 1", p1, cl, mth, DBG, false) unless p1.is_a?(cl) - return mismatch("point 2", p2, cl, mth, DBG, false) unless p2.is_a?(cl) - - # OpenStudio.isAlmostEqual3dPt(p1, p2, TOL) # ... from v350 onwards. - (p1.x-p2.x).abs < TOL && (p1.y-p2.y).abs < TOL && (p1.z-p2.z).abs < TOL - end - - ## - # Returns true if a line segment is along the X-axis. - # - # @param p1 [OpenStudio::Point3d] 1st 3D point of a line segment - # @param p2 [OpenStudio::Point3d] 2nd 3D point of a line segment - # @param strict [Bool] whether segment shouldn't hold Y- or Z-axis components - # - # @return [Bool] whether along the X-axis - # @return [false] if invalid input (see logs) - def xx?(p1 = nil, p2 = nil, strict = true) - mth = "OSut::#{__callee__}" - cl = OpenStudio::Point3d - strict = true unless [true, false].include?(strict) - return mismatch("point 1", p1, cl, mth, DBG, false) unless p1.is_a?(cl) - return mismatch("point 2", p2, cl, mth, DBG, false) unless p2.is_a?(cl) - return false if (p1.y - p2.y).abs > TOL && strict - return false if (p1.z - p2.z).abs > TOL && strict - - (p1.x - p2.x).abs > TOL - end - - ## - # Returns true if a line segment is along the Y-axis. - # - # @param p1 [OpenStudio::Point3d] 1st 3D point of a line segment - # @param p2 [OpenStudio::Point3d] 2nd 3D point of a line segment - # @param strict [Bool] whether segment shouldn't hold X- or Z-axis components - # - # @return [Bool] whether along the Y-axis - # @return [false] if invalid input (see logs) - def yy?(p1 = nil, p2 = nil, strict = true) - mth = "OSut::#{__callee__}" - cl = OpenStudio::Point3d - strict = true unless [true, false].include?(strict) - return mismatch("point 1", p1, cl, mth, DBG, false) unless p1.is_a?(cl) - return mismatch("point 2", p2, cl, mth, DBG, false) unless p2.is_a?(cl) - return false if (p1.x - p2.x).abs > TOL && strict - return false if (p1.z - p2.z).abs > TOL && strict - - (p1.y - p2.y).abs > TOL - end - - ## - # Returns true if a line segment is along the Z-axis. + # Returns the site/true outward normal vector of a surface. # - # @param p1 [OpenStudio::Point3d] 1st 3D point of a line segment - # @param p2 [OpenStudio::Point3d] 2nd 3D point of a line segment - # @param strict [Bool] whether segment shouldn't hold X- or Y-axis components + # @param s [OpenStudio::Model::PlanarSurface] a surface + # @param r [#to_f] a group/site rotation angle [0,2PI) radians # - # @return [Bool] whether along the Z-axis - # @return [false] if invalid input (see logs) - def zz?(p1 = nil, p2 = nil, strict = true) - mth = "OSut::#{__callee__}" - cl = OpenStudio::Point3d - strict = true unless [true, false].include?(strict) - return mismatch("point 1", p1, cl, mth, DBG, false) unless p1.is_a?(cl) - return mismatch("point 2", p2, cl, mth, DBG, false) unless p2.is_a?(cl) - return false if (p1.x - p2.x).abs > TOL && strict - return false if (p1.y - p2.y).abs > TOL && strict - - (p1.z - p2.z).abs > TOL + # @return [OpenStudio::Vector3d] true normal vector + # @return [nil] if invalid input (see logs) + def trueNormal(s = nil, r = 0) + mth = "TBD::#{__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) + + r = -r.to_f * Math::PI / 180.0 + vx = s.outwardNormal.x * Math.cos(r) - s.outwardNormal.y * Math.sin(r) + vy = s.outwardNormal.x * Math.sin(r) + s.outwardNormal.y * Math.cos(r) + vz = s.outwardNormal.z + + OpenStudio::Point3d.new(vx, vy, vz) - OpenStudio::Point3d.new(0, 0, 0) end ## @@ -2384,6 +2352,37 @@ def to_p3Dv(pts = nil) v end + ## + # Returns true if 2 sets of OpenStudio 3D points are nearly equal. + # + # @param s1 [Set] 1st set of 3D point(s) + # @param s2 [Set] 2nd set of 3D point(s) + # + # @return [Bool] whether sets are nearly equal (within TOL) + # @return [false] if invalid input (see logs) + def same?(s1 = [], s2 = []) + mth = "OSut::#{__callee__}" + s1 = [s1] if s1.is_a?(OpenStudio::Point3d) + s2 = [s2] if s2.is_a?(OpenStudio::Point3d) + s1 = to_p3Dv(s1) + s2 = to_p3Dv(s2) + return false if s1.empty? + return false if s2.empty? + + size = s1.size + return invalid("set sizes", mth, 0, ERR, false) unless s2.size == size + + # OpenStudio.isAlmostEqual3dPt(p1, p2, TOL) # ... from v350 onwards. + size.times.each do |i| + xOK = (s1[i].x - s2[i].x).abs < TOL + yOK = (s1[i].y - s2[i].y).abs < TOL + zOK = (s1[i].z - s2[i].z).abs < TOL + return false unless xOK && yOK && zOK + end + + true + end + ## # Returns true if an OpenStudio 3D point is part of a set of 3D points. # @@ -2403,6 +2402,66 @@ def holds?(pts = nil, p1 = nil) false end + ## + # Returns OpenStudio 3D point (of a provided set) nearest to e.g. grid origin. + # + # @param pts [Set] 3D points + # @param p0 [OpenStudio::Point3d] e.g. grid origin coordinates + # + # @return [Integer] set index of nearest point to e.g. grid origin + # @return [nil] if invalid input (see logs) + def nearest(pts = nil, p0 = OpenStudio::Point3d.new(0,0,0)) + mth = "OSut::#{__callee__}" + l = 100000000000 + idx = nil + pts = to_p3Dv(pts) + cl = OpenStudio::Point3d + return mismatch("point", p0, cl, mth) unless p0.is_a?(cl) + + pts.each_with_index { |pt, i| return i if same?(pt, p0) } + + pts.each_with_index do |pt, i| + length = (pt - p0).length + + if length < l + l = length + idx = i + end + end + + idx + end + + ## + # Returns OpenStudio 3D point (of a provided set) farthest to e.g. grid origin. + # + # @param pts [Set] 3D points + # @param p0 [OpenStudio::Point3d] e.g. grid origin coordinates + # + # @return [Integer] set index of farthest point to e.g. grid origin + # @return [nil] if invalid input (see logs) + def farthest(pts = nil, p0 = OpenStudio::Point3d.new(0,0,0)) + mth = "OSut::#{__callee__}" + l = 0 + idx = nil + pts = to_p3Dv(pts) + cl = OpenStudio::Point3d + return mismatch("point", p0, cl, mth) unless p0.is_a?(cl) + + pts.each_with_index do |pt, i| + next if same?(pt, p0) + + length = (pt - p0).length + + if length > l + l = length + idx = i + end + end + + idx + end + ## # Flattens OpenStudio 3D points vs X, Y or Z axes. # @@ -2435,7 +2494,7 @@ def flatten(pts = nil, axs = :z, val = 0) end ## - # Returns true if OpenStudio 3D points share X, Y or Z coordinates. + # Validates whether 3D points share X, Y or Z coordinates. # # @param pts [Set] OpenStudio 3D points # @param axs [Symbol] if potentially along :x, :y or :z axis @@ -2450,7 +2509,7 @@ def xyz?(pts = nil, axs = :z, val = 0) ok2 = [:x, :y, :z].include?(axs) return false if pts.empty? return mismatch("val", val, Numeric, mth, DBG, false) unless ok1 - return invalid("axis (XYZ?)", mth, 2, DBG, false) unless ok2 + return invalid("axis", mth, 2, DBG, false) unless ok2 val = val.to_f @@ -2486,6 +2545,88 @@ def next(pts = nil, pt = nil) pair.nil? ? pts.first : pair.last end + ## + # Returns 'width' of a set of OpenStudio 3D points, once re/aligned. + # + # @param pts [Set] 3D points, once re/aligned + # + # @return [Float] width along X-axis, once re/aligned + # @return [0.0] if invalid inputs + def width(pts = nil) + pts = to_p3Dv(pts) + return 0 if pts.size < 2 + + pts.max_by(&:x).x - pts.min_by(&:x).x + end + + ## + # Returns 'height' of a set of OpenStudio 3D points. + # + # @param pts [Set] 3D points + # + # @return [Float] height along Z-axis, or Y-axis if flat + # @return [0.0] if invalid inputs + def height(pts = nil) + pts = to_p3Dv(pts) + return 0 if pts.size < 2 + + min = pts.min_by(&:z).z + max = pts.max_by(&:z).z + return max - min if (max - min).abs > TOL + + pts.max_by(&:y).y - pts.min_by(&:y).y + end + + ## + # Returns midpoint coordinates of line segment. + # + # @param p1 [OpenStudio::Point3d] 1st 3D point of a line segment + # @param p2 [OpenStudio::Point3d] 2nd 3D point of a line segment + # + # @return [OpenStudio::Point3d] midpoint + # @return [nil] if invalid input (see logs) + def midpoint(p1 = nil, p2 = nil) + mth = "OSut::#{__callee__}" + cl = OpenStudio::Point3d + return mismatch("point 1", p1, cl, mth) unless p1.is_a?(cl) + return mismatch("point 2", p2, cl, mth) unless p2.is_a?(cl) + return invalid("same points", mth, 0) if same?(p1, p2) + + midX = p1.x + (p2.x - p1.x)/2 + midY = p1.y + (p2.y - p1.y)/2 + midZ = p1.z + (p2.z - p1.z)/2 + + OpenStudio::Point3d.new(midX, midY, midZ) + end + + ## + # Returns a vertical 3D plane from 2x 3D points, right-hand rule. Input points + # are considered last 2 (of 3) points forming the plane; the first point is + # assumed zenithal. Input points cannot align vertically. + # + # @param p1 [OpenStudio::Point3d] 1st 3D point of a line segment + # @param p2 [OpenStudio::Point3d] 2nd 3D point of a line segment + # + # @return [OpenStudio::Plane] 3D plane + # @return [nil] if invalid input (see logs) + def verticalPlane(p1 = nil, p2 = nil) + mth = "OSut::#{__callee__}" + return mismatch("point 1", p1, cl, mth) unless p1.is_a?(OpenStudio::Point3d) + return mismatch("point 2", p2, cl, mth) unless p2.is_a?(OpenStudio::Point3d) + + if (p1.x - p2.x).abs < TOL && (p1.y - p2.y).abs < TOL + return invalid("vertically aligned points", mth) + end + + zenith = OpenStudio::Point3d.new(p1.x, p1.y, (p2 - p1).length) + points = OpenStudio::Point3dVector.new + points << zenith + points << p1 + points << p2 + + OpenStudio::Plane.new(points) + end + ## # Returns unique OpenStudio 3D points from an OpenStudio 3D point vector. # @@ -2511,48 +2652,6 @@ def getUniques(pts = nil, n = 0) v end - ## - # Returns sequential non-collinear points in an OpenStudio 3D point vector. - # - # @param pts [Set 0 - a = a[n..-1] if n < 0 - - to_p3Dv(a) - end - ## # Returns paired sequential points as (non-zero length) line segments. If the # set strictly holds 2x unique points, a single segment is returned. @@ -2561,16 +2660,13 @@ def getNonCollinears(pts = nil, n = 0) # segments equals the number of non-colliear points. # # @param pts [Set] 3D points - # @param co [Bool] whether to keep collinear points - # + #^ # @return [OpenStudio::Point3dVectorVector] line segments (see logs if empty) def getSegments(pts = nil, co = false) mth = "OSut::#{__callee__}" vv = OpenStudio::Point3dVectorVector.new - co = false unless [true, false].include?(co) - pts = getNonCollinears(pts) unless co - pts = getUniques(pts) if co - return vv if pts.size < 2 + pts = getUniques(pts) + return vv if pts.size < 2 pts.each_with_index do |p1, i1| i2 = i1 + 1 @@ -2580,13 +2676,29 @@ def getSegments(pts = nil, co = false) line = OpenStudio::Point3dVector.new line << p1 line << p2 - vv << line + vv << line break if pts.size == 2 end vv end + ## + # Determines if a set of 3D points if a valid segment. + # + # @param pts [Set] 3D points + # + # @return [Bool] whether set is a valid segment + # @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]) + + true + end + ## # Returns points as (non-zero length) 'triads', i.e. 3x sequential points. # If the set holds less than 3x unique points, an empty triad is @@ -2595,16 +2707,13 @@ def getSegments(pts = nil, co = false) # returned triads equals the number of non-collinear points. # # @param pts [OpenStudio::Point3dVector] 3D points - # @param co [Bool] whether to keep collinear points # # @return [OpenStudio::Point3dVectorVector] triads (see logs if empty) def getTriads(pts = nil, co = false) mth = "OSut::#{__callee__}" vv = OpenStudio::Point3dVectorVector.new - co = false unless [true, false].include?(co) - pts = getNonCollinears(pts) unless co - pts = getUniques(pts) if co - return vv if pts.size < 2 + pts = getUniques(pts) + return vv if pts.size < 2 pts.each_with_index do |p1, i1| i2 = i1 + 1 @@ -2618,12 +2727,169 @@ def getTriads(pts = nil, co = false) tri << p1 tri << p2 tri << p3 - vv << tri + vv << tri end vv end + ## + # Determines if a set of 3D points if a valid triad. + # + # @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) + def triad?(pts = nil) + pts = to_p3Dv(pts) + return false if pts.empty? + return false unless pts.size == 3 + return false if same?(pts[0], pts[1]) + return false if same?(pts[0], pts[2]) + return false if same?(pts[1], pts[2]) + + true + end + + ## + # Validates whether a 3D point lies along a set of 3D point segments. + # + # @param p0 [OpenStudio::Point3d] a 3D point + # @param s [Set 0.001 + return true if pal < abl && pbl < abl + end + + false + end + + ## + # Returns point of intersection of 2x 3D line segments. + # + # @param s1 [Set] 3D points - # @param vx [Bool] whether to check for convexity - # @param uq [Bool] whether to ensure uniqueness - # @param co [Bool] whether to ensure non-collinearity - # @param tt [Bool, OpenStudio::Transformation] whether to 'align' - # @param sq [:no, :ulc, :cw] unaltered, ULC or clockwise sequence + # @param pts [Set 0 + a = a[n..-1] if n < 0 + + to_p3Dv(a) + end + + ## + # Returns sequential collinear points in an OpenStudio 3D point vector. + # + # @param pts [Set] 3D points + # @param vx [Bool] whether to check for convexity + # @param uq [Bool] whether to ensure uniqueness + # @param co [Bool] whether to ensure non-collinearity + # @param tt [Bool, OpenStudio::Transformation] whether to 'align' + # @param sq [:no, :ulc, :cw] unaltered, ULC or clockwise sequence + # + # @return [OpenStudio::Point3dVector] 3D points (see logs if empty) + def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no) + mth = "OSut::#{__callee__}" + pts = to_p3Dv(pts) + cl = OpenStudio::Transformation v = OpenStudio::Point3dVector.new vx = false unless [true, false].include?(vx) uq = false unless [true, false].include?(uq) @@ -2709,76 +3039,69 @@ def poly(pts = nil, vx = false, uq = false, co = true, tt = false, sq = :no) return invalid("sequence", mth, 6, DBG, v) unless ok2 # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Basic tests: + # Minimum 3 points? p3 = getNonCollinears(pts, 3) return empty("polygon", mth, ERR, v) if p3.size < 3 + # Coplanar? pln = OpenStudio::Plane.new(p3) pts.each do |pt| return empty("plane", mth, ERR, v) unless pln.pointOnPlane(pt) end - t = tt - t = OpenStudio::Transformation.alignFace(pts) unless tt.is_a?(cl) + # A self-intersecting polygon will log the following: + # [utilities.Transformation] <1> Cannot compute outward normal for vertices + t = tt.is_a?(cl) ? tt : OpenStudio::Transformation.alignFace(pts) a = (t.inverse * pts).reverse + # May reactivate the following in the future. For now, OpenStudio's + # selfIntersect will correctly catch a typical self-intersecting polygon + # (like a "bowtie"), yet unfortunately will also catch a valid polygon that + # may surround another (through the use of leader lines). + # + # acw = clockwise?(a) ? a : a.reverse + # + # if OpenStudio.selfIntersects(acw, TOL) + # return invalid("polygon", mth, 1, ERR, v) + # end + if tt.is_a?(cl) # Using a transformation that is most likely not specific to pts. The - # most probable reason to retain this option is when testing for polygon + # most likely reason to retain this option is when testing for polygon # intersections, unions, etc., operations that typically require that # points remain nonetheless 'aligned'. If re-activated, this logs a - # warning if aligned points aren't @Z =0, before 'flattening'. + # warning if aligned points aren't @Z = 0, before 'flattening'. # # invalid("points (non-aligned)", mth, 1, WRN) unless xyz?(a, :z, 0) - a = flatten(a).to_a unless xyz?(a, :z, 0) + a = flatten(a).to_a unless xyz?(a, :z) + else + return invalid("(unaligned) points", mth, 1, ERR, v) unless xyz?(a, :z, 0) end - # The following 2x lines are commented out. This is a very commnon and very - # useful test, yet tested cases are first caught by the 'pointOnPlane' - # test above. Keeping it for possible further testing. - # bad = OpenStudio.selfIntersects(a, TOL) - # return invalid("points (intersecting)", mth, 1, ERR, v) if bad - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Ensure uniqueness and/or non-collinearity. Preserve original sequence. p0 = a.first - a = OpenStudio.simplify(a, false, TOL) if uq - a = OpenStudio.simplify(a, true, TOL) unless co + a = getUniques(a).to_a if uq + a = getNonCollinears(a).to_a if co i0 = a.index { |pt| same?(pt, p0) } - a = a.rotate(i0) unless i0.nil? + a = a.rotate(i0) unless i0.nil? # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Check for convexity (optional). - if vx - a1 = OpenStudio.simplify(a, true, TOL).reverse - dX = a1.max_by(&:x).x.abs - dY = a1.max_by(&:y).y.abs - d = [dX, dY].max - return false if d < TOL - - u = OpenStudio::Vector3d.new(0, 0, d) - - a1.each_with_index do |p1, i1| - i2 = i1 + 1 - i2 = 0 if i2 == a1.size - p2 = a1[i2] - pi = p1 + u - vi = OpenStudio::Point3dVector.new - vi << pi - vi << p1 - vi << p2 - plane = OpenStudio::Plane.new(vi) - normal = plane.outwardNormal - - a1.each do |p3| - next if same?(p1, p3) - next if same?(p2, p3) - next if plane.pointOnPlane(p3) - next if normal.dot(p3 - p1) < 0 - - return invalid("points (non-convex)", mth, 1, ERR, v) - end + if vx && p3.size > 3 + zen = OpenStudio::Point3d.new(0, 0, 1000) + acw = clockwise?(a) ? a.clone : a.reverse + acw = getNonCollinears(acw) + + getTriads(acw).each do |trio| + p1 = trio[0] + p2 = trio[1] + p3 = trio[2] + v12 = p2 - p1 + v13 = p3 - p1 + x = (zen - p1).cross(v12) + return invalid("points (non-convex)", mth, 1, ERR, v) if x.dot(v13) > 0 end end @@ -2810,56 +3133,183 @@ def poly(pts = nil, vx = false, uq = false, co = true, tt = false, sq = :no) end ## - # Returns 'width' of a set of OpenStudio 3D points (perpendicular view). + # Validates whether 3D point is entirely within a 3D polygon. False if point + # lies along any of the polygon edges, or is near any of its vertices. # - # @param pts [Set] 3D points + # @param p0 [OpenStudio::Point3d] a 3D point + # @param s [Set] 1st set of 3D points + # @param p2 [Set] 2nd set of 3D points + # + # @return [Bool] whether 2 polygons are parallel + # @return [false] if invalid input (see logs) + def parallel?(p1 = nil, p2 = nil) + mth = "OSut::#{__callee__}" + p1 = poly(p1, false, true, false) + p2 = poly(p2, false, true, false) + return false if p1.empty? + return false if p2.empty? + + p1 = getNonCollinears(p1, 3) + p2 = getNonCollinears(p2, 3) + return false if p1.empty? + return false if p2.empty? + + pl1 = OpenStudio::Plane.new(p1) + pl2 = OpenStudio::Plane.new(p2) + + pl1.outwardNormal.dot(pl2.outwardNormal).abs > 0.99 + end + + ## + # Validates whether a polygon faces upwards. + # + # @param pts [Set] OpenStudio 3D points + # + # @return [Bool] if facing upwards + # @return [false] if invalid input (see logs) + def facingUp?(pts = nil) + mth = "OSut::#{__callee__}" + up = OpenStudio::Point3d.new(0,0,1) - OpenStudio::Point3d.new(0,0,0) + pts = poly(pts, false, true, false) + return false if pts.empty? + + pts = getNonCollinears(pts, 3) + return false if pts.empty? + + OpenStudio::Plane.new(pts).outwardNormal.dot(up) > 0.99 + end + + ## + # Validates whether a polygon faces downwards. + # + # @param pts [Set] OpenStudio 3D points + # + # @return [Bool] if facing downwards + # @return [false] if invalid input (see logs) + def facingDown?(pts = nil) mth = "OSut::#{__callee__}" + lo = OpenStudio::Point3d.new(0,0,-1) - OpenStudio::Point3d.new(0,0,0) + pts = poly(pts, false, true, false) + return false if pts.empty? + + pts = getNonCollinears(pts, 3) + return false if pts.empty? - poly(pts, false, true, false, true).max_by(&:x).x + OpenStudio::Plane.new(pts).outwardNormal.dot(lo) > 0.99 end ## - # Returns 'height' of a set of OpenStudio 3D points (perpendicular view). + # Validates whether an OpenStudio polygon is a rectangle (4x sides + 2x + # diagonals of equal length). # # @param pts [Set] 3D points # - # @return [Float] top-to-bottom height - # @return [0.0] if invalid inputs (see logs) - def height(pts = nil) + # @return [Bool] whether polygon is rectangular + # @return [false] if invalid input (see logs) + def rectangular?(pts = nil) mth = "OSut::#{__callee__}" + pts = poly(pts, false, false, false) + return false if pts.empty? + return false unless pts.size == 4 + + m1 = midpoint(pts[0], pts[2]) + m2 = midpoint(pts[1], pts[3]) + return false unless same?(m1, m2) - poly(pts, false, true, false, true).max_by(&:y).y + diag1 = pts[2] - pts[0] + diag2 = pts[3] - pts[1] + return true if (diag1.length - diag2.length).abs < TOL + + false end ## - # Determines whether a 1st OpenStudio polygon fits in a 2nd polygon. + # Determines whether a 1st OpenStudio polygon fits in a 2nd polygon. If the + # optional 3rd argument is set to false, the 1st polygon may only fit if it + # shares the 3D plane equation of the 2nd one. If the 3rd argument instead + # remains true (default), then the 1st polygon is first cast onto the 3D plane + # of the 2nd one; the method therefore returns true if the projection of the + # 1st polygon fits in the 2nd one. # # @param p1 [Set] 1st set of 3D points # @param p2 [Set] 2nd set of 3D points - # @param flat [Bool] whether points are to be pre-flattened (Z=0) + # @param flat [Bool] whether to first cast the 1st set onto the 2nd set plane # # @return [Bool] whether 1st polygon fits within the 2nd polygon # @return [false] if invalid input (see logs) def fits?(p1 = nil, p2 = nil, flat = true) mth = "OSut::#{__callee__}" + flat = true unless [true, false].include?(flat) p1 = poly(p1, false, true, false) p2 = poly(p2, false, true, false) - flat = true unless [true, false].include?(flat) - return false if p1.empty? - return false if p2.empty? + return false if p1.empty? + return false if p2.empty? # Aligned, clockwise points using transformation from 2nd polygon. t = OpenStudio::Transformation.alignFace(p2) p1 = poly(p1, false, false, true, t, :cw) p2 = poly(p2, false, false, true, t, :cw) - p1 = flatten(p1) if flat - p2 = flatten(p2) if flat - return false if p1.empty? - return false if p2.empty? + return false if p1.empty? + return false if p2.empty? + + # If flat == false, return false unless p1 is flat. + if flat + p1 = flatten(p1) + else + return false unless xyz?(p1, :z) + end area1 = OpenStudio.getArea(p1) area2 = OpenStudio.getArea(p2) @@ -2885,66 +3335,120 @@ def fits?(p1 = nil, p2 = nil, flat = true) end ## - # Determines whether OpenStudio polygons overlap. + # Returns largest intersection of overlapping polygons, empty if non + # intersecting. If the optional 3rd argument is set to false, the 2nd polygon + # may only overlap if it shares the 3D plane equation of the 1st one. If the + # 3rd argument instead remains true (default), then the 2nd polygon is first + # cast onto the 3D plane of the 1st one; the method therefore returns (as + # overlap) the intersection of a projection of the 2nd polygon onto the 1st + # one. The method returns the smallest of the 2 polygons if either fits within + # the larger one. # # @param p1 [Set] 1st set of 3D points # @param p2 [Set] 2nd set of 3D points - # @param flat [Bool] whether points are to be pre-flattened (Z=0) + # @param flat [Bool] whether to first align the 2nd set onto the 1st set plane # - # @return [Bool] whether polygons overlap (or fit) - # @return [false] if invalid input (see logs) - def overlaps?(p1 = nil, p2 = nil, flat = true) + # @return [OpenStudio::Point3dVector] largest intersection (see logs if empty) + def overlap(p1 = nil, p2 = nil, flat = true) mth = "OSut::#{__callee__}" + flat = true unless [true, false].include?(flat) + face = OpenStudio::Point3dVector.new p1 = poly(p1, false, true, false) p2 = poly(p2, false, true, false) - flat = true unless [true, false].include?(flat) - return false if p1.empty? - return false if p2.empty? - - # Aligned, clockwise & convex points using transformation from 1st polygon. - t = OpenStudio::Transformation.alignFace(p1) - p1 = poly(p1, false, false, true, t, :cw) - p2 = poly(p2, false, false, true, t, :cw) - p1 = flatten(p1) if flat - p2 = flatten(p2) if flat - return false if p1.empty? - return false if p2.empty? - - return true if fits?(p1, p2) - return true if fits?(p2, p1) + return face if p1.empty? + return face if p2.empty? + + pl1 = OpenStudio::Plane.new(getNonCollinears(p1, 3)) + pl2 = OpenStudio::Plane.new(getNonCollinears(p2, 3)) + n1 = pl1.outwardNormal + n2 = pl2.outwardNormal + t = OpenStudio::Transformation.alignFace(p1) + a1 = poly(p1, false, false, true, t, :cw) + a2 = poly(p2, false, false, true, t, :cw) + + # If flat == false, return face unless a2 is flat. + if flat + a2 = flatten(a2) + else + return face unless xyz?(a2, :z) + end - area1 = OpenStudio.getArea(p1) - area2 = OpenStudio.getArea(p2) - return empty("points 1 area", mth, ERR, false) if area1.empty? - return empty("points 2 area", mth, ERR, false) if area2.empty? + if fits?(a1, a2) + return p1 + elsif fits?(a2, a1) + pl02 = OpenStudio::Plane.new(getNonCollinears(a2, 3)) + n02 = pl02.outwardNormal + a2 = to_p3Dv(a2.to_a.reverse) if n1.dot(n02) < 0 + return to_p3Dv(t * a2) + end - area1 = area1.get - area2 = area2.get - union = OpenStudio.join(p1, p2, TOL2) - return false if union.empty? + res = OpenStudio.intersect(a1, a2, TOL) + return face if res.empty? - union = union.get - area = OpenStudio.getArea(union) - return false if area.empty? + to_p3Dv(t * res.get.polygon1.reverse) + end - area = area.get - delta = area1 + area2 - area + ## + # Determines whether OpenStudio polygons overlap. + # + # @param p1 [Set] 1st set of 3D points + # @param p2 [Set] 2nd set of 3D points + # @param flat [Bool] whether points are to be pre-flattened (Z=0) + # + # @return [Bool] whether polygons overlap (or fit) + # @return [false] if invalid input (see logs) + def overlaps?(p1 = nil, p2 = nil, flat = true) + overlap(p1, p2, flat).empty? ? false : true + end - if area > TOL - return false if (area - area1).abs < TOL - return false if (area - area2).abs < TOL - return false if delta.abs < TOL - return true if delta > TOL + ## + # Casts an OpenStudio polygon onto the 3D plane of a 2nd polygon, relying on + # an independent 3D ray vector. + # + # @param p1 [Set] 1st set of 3D points + # @param p2 [Set] 2nd set of 3D points + # @param ray [OpenStudio::Vector3d] a vector + # + # @return [OpenStudio::Point3dVector] cast of p1 onto p2 (see logs if empty) + def cast(p1 = nil, p2 = nil, ray = nil) + mth = "OSut::#{__callee__}" + cl = OpenStudio::Vector3d + face = OpenStudio::Point3dVector.new + p1 = poly(p1, false, false, false) + p2 = poly(p2, false, false, false) + return face if p1.empty? + return face if p2.empty? + return mismatch("ray", ray, cl, mth) unless ray.is_a?(cl) + + # From OpenStudio SDK v3.7.0 onwards, one could/should rely on: + # + # s3.amazonaws.com/openstudio-sdk-documentation/cpp/OpenStudio-3.7.0-doc/ + # utilities/html/classopenstudio_1_1_plane.html + # #abc4747b1b041a7f09a6887bc0e5abce1 + # + # e.g. p1.each { |pt| face << pl.rayIntersection(pt, ray) } + # + # The following +/- replicates the same solution, based on: + # https://stackoverflow.com/a/65832417 + p0 = p2.first + pl = OpenStudio::Plane.new(getNonCollinears(p2, 3)) + n = pl.outwardNormal + return face if n.dot(ray).abs < TOL + + p1.each do |pt| + length = n.dot(pt - p0) / n.dot(ray.reverseVector) + face << pt + scalar(ray, length) end - false + face end ## - # Generates offset vertices (by width) for a 3- or 4-sided, convex polygon. + # Generates offset vertices (by width) for a 3- or 4-sided, convex polygon. If + # width is negative, the vertices are contracted inwards. # # @param p1 [Set] OpenStudio 3D points - # @param w [#to_f] offset width (min: 0.0254m) + # @param w [#to_f] offset width (absolute min: 0.0254m) # @param v [#to_i] OpenStudio SDK version, eg '321' for "v3.2.1" (optional) # # @return [OpenStudio::Point3dVector] offset points (see logs if unaltered) @@ -2956,14 +3460,11 @@ def offset(p1 = nil, w = 0, v = 0) mismatch("width", w, Numeric, mth) unless w.respond_to?(:to_f) mismatch("version", v, Integer, mth) unless v.respond_to?(:to_i) + iv = pts.size == 4 ? true : false vs = OpenStudio.openStudioVersion.split(".").join.to_i - iv = true if pts.size == 4 - v = v.to_i if v.respond_to?(:to_i) - v = -1 unless v.respond_to?(:to_i) - v = vs if v < 0 - w = w.to_f if w.respond_to?(:to_f) - w = 0 unless w.respond_to?(:to_f) - w = 0 if w < 0.0254 + v = v.respond_to?(:to_i) ? v.to_i : vs + w = w.respond_to?(:to_f) ? w.to_f : 0 + return p1 if w.abs < 0.0254 unless v < 340 t = OpenStudio::Transformation.alignFace(p1) @@ -2971,7 +3472,7 @@ def offset(p1 = nil, w = 0, v = 0) return p1 if offset.empty? return to_p3Dv(t * offset.get.reverse) - else # brute force approach + else # brute force approach pz = {} pz[:A] = {} pz[:B] = {} @@ -3184,8 +3685,9 @@ def outline(a = [], bfr = 0, flat = true) bfr = 0 unless bfr.respond_to?(:to_f) bfr = 0 if bfr < 0.0254 vtx = poly(a.first) - t = OpenStudio::Transformation.alignFace(vtx) unless vtx.empty? - return out if vtx.empty? + return out if vtx.empty? + + t = OpenStudio::Transformation.alignFace(vtx) a.each do |pts| points = poly(pts, false, true, false, t) @@ -3244,759 +3746,3323 @@ def outline(a = [], bfr = 0, flat = true) end ## - # Returns an array of OpenStudio space-specific surfaces that match criteria, - # e.g. exterior, north-east facing walls in hotel "lobby". Note 'sides' rely - # on space coordinates (not absolute model coordinates). And 'sides' are - # exclusive, not inclusive (e.g. walls strictly north-facing or strictly - # east-facing would not be returned if 'sides' holds [:north, :east]). + # Generates a box from a triad (3D points). Points must be unique and + # non-collinear. # - # @param spaces [Array] target spaces - # @param boundary [#to_s] OpenStudio outside boundary condition - # @param type [#to_s] OpenStudio surface type - # @param sides [Array] direction keys, e.g. :north (see OSut::SIDZ) + # @param [Set] a triad (3D points) # - # @return [Array] surfaces (may be empty) - def facets(spaces = [], boundary = "Outdoors", type = "Wall", sides = []) - return [] unless spaces.respond_to?(:&) - return [] unless sides.respond_to?(:&) - return [] if sides.empty? - - faces = [] - boundary = trim(boundary).downcase - type = trim(type).downcase - return [] if boundary.empty? - return [] if type.empty? + # @return [Set] a generated rectangle (see logs if empty) + def triadBox(pts = nil) + mth = "OSut::#{__callee__}" + bkp = OpenStudio::Point3dVector.new + box = OpenStudio::Point3dVector.new + pts = getNonCollinears(pts) + return bkp if pts.empty? + + t = xyz?(pts, :z) ? nil : OpenStudio::Transformation.alignFace(pts) + pts = poly(pts, false, false, false, t) if t + return bkp if pts.empty? + return invalid("triad", mth, 1, ERR, bkp) unless pts.size == 3 + + cw = clockwise?(pts) + + p0 = pts[0] + p1 = pts[1] + p2 = pts[2] + + # Cast p1 unto vertical plane defined by p1/p2. + pp0 = verticalPlane(p1, p2).project(p0) + v00 = p0 - pp0 + v11 = pp0 - p1 + v10 = p0 - p1 + v12 = p2 - p1 + + # Reset p0 and/or p1 if obtuse or acute. + if v12.dot(v10) < 0 + p0 = p1 + v00 + elsif v12.dot(v10) > 0 + if v11.length < v12.length + p1 = pp0 + else + p0 = p1 + v00 + end + end - # Keep valid sides. - sides = sides.select { |side| SIDZ.include?(side) } - return [] if sides.empty? + p3 = p2 + v00 - spaces.each do |space| - return [] unless space.respond_to?(:setSpaceType) + box << OpenStudio::Point3d.new(p0.x, p0.y, p0.z) + 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) - space.surfaces.each do |s| - next unless s.outsideBoundaryCondition.downcase == boundary - next unless s.surfaceType.downcase == type + return invalid("box", mth, 0, ERR, bkp) unless rectangular?(box) - orientations = [] - orientations << :top if s.outwardNormal.z > TOL - orientations << :bottom if s.outwardNormal.z < -TOL - orientations << :north if s.outwardNormal.y > TOL - orientations << :east if s.outwardNormal.x > TOL - orientations << :south if s.outwardNormal.y < -TOL - orientations << :west if s.outwardNormal.x < -TOL + clockwise = clockwise?(box) - faces << s if sides.all? { |o| orientations.include?(o) } - end + if cw + box = OpenStudio.reverse(box) unless clockwise + else + box = OpenStudio.reverse(box) if clockwise end - faces + box = ulc(box) unless cw + box = to_p3Dv(t * box) if t + box end ## - # Generates an OpenStudio 3D point vector of a composite floor "slab", a - # 'union' of multiple rectangular, horizontal floor "plates". Each plate - # must either share an edge with (or encompass or overlap) any of the - # preceding plates in the array. The generated slab may not be convex. + # Generates a box bounded within a triangle (midpoint theorem). # - # @param [Array] pltz individual floor plates, each holding: - # @option pltz [Numeric] :x left corner of plate origin (bird's eye view) - # @option pltz [Numeric] :y bottom corner of plate origin (bird's eye view) - # @option pltz [Numeric] :dx plate width (bird's eye view) - # @option pltz [Numeric] :dy plate depth (bird's eye view) - # @param z [Numeric] Z-axis coordinate + # pts [Set] triangular polygon # - # @return [OpenStudio::Point3dVector] slab vertices (see logs if empty) - def genSlab(pltz = [], z = 0) + # @return [OpenStudio::Point3dVector] medial bounded box (see logs if empty) + def medialBox(pts = nil) mth = "OSut::#{__callee__}" - slb = OpenStudio::Point3dVector.new bkp = OpenStudio::Point3dVector.new - cl1 = Array - cl2 = Hash - cl3 = Numeric + box = OpenStudio::Point3dVector.new + pts = poly(pts, true, true, true) + return bkp if pts.empty? + return invalid("triangle", mth, 1, ERR, bkp) unless pts.size == 3 - # Input validation. - return mismatch("plates", pltz, cl1, mth, DBG, slb) unless pltz.is_a?(cl1) - return mismatch( "Z", z, cl3, mth, DBG, slb) unless z.is_a?(cl3) + t = xyz?(pts, :z) ? nil : OpenStudio::Transformation.alignFace(pts) + pts = poly(pts, false, false, false, t) if t + return bkp if pts.empty? - pltz.each_with_index do |plt, i| - id = "plate # #{i+1} (index #{i})" + cw = clockwise?(pts) - return mismatch(id, plt, cl1, 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) - return hashkey( id, plt, :dy, mth, DBG, slb) unless plt.key?(:dy) + # Generate vertical plane along longest segment. + mpoints = [] + sgs = getSegments(pts) + longest = sgs.max_by { |s| OpenStudio.getDistanceSquared(s.first, s.last) } + plane = verticalPlane(longest.first, longest.last) - x = plt[:x ] - y = plt[:y ] - dx = plt[:dx] - dy = plt[:dy] + # Fetch midpoints of other 2 segments. + sgs.each { |s| mpoints << midpoint(s.first, s.last) unless s == longest } - return mismatch("#{id} X", x, cl3, mth, DBG, slb) unless x.is_a?(cl3) - return mismatch("#{id} Y", y, cl3, mth, DBG, slb) unless y.is_a?(cl3) - return mismatch("#{id} dX", dx, cl3, mth, DBG, slb) unless dx.is_a?(cl3) - return mismatch("#{id} dY", dy, cl3, mth, DBG, slb) unless dy.is_a?(cl3) - return zero( "#{id} dX", mth, ERR, slb) if dx.abs < TOL - return zero( "#{id} dY", mth, ERR, slb) if dy.abs < TOL - end + return invalid("midpoints", mth, 0, ERR, bkp) unless mpoints.size == 2 - # Join plates. - pltz.each_with_index do |plt, i| - id = "plate # #{i+1} (index #{i})" - x = plt[:x ] - y = plt[:y ] - dx = plt[:dx] - dy = plt[:dy] + # Generate medial bounded box. + box << plane.project(mpoints.first) + box << mpoints.first + box << mpoints.last + box << plane.project(mpoints.last) - # Adjust X if dX < 0. - x -= -dx if dx < 0 - dx = -dx if dx < 0 + return invalid("box", mth, 0, ERR, bkp) unless rectangular?(box) - # Adjust Y if dY < 0. - y -= -dy if dy < 0 - dy = -dy if dy < 0 + clockwise = clockwise?(box) - vtx = [] - vtx << OpenStudio::Point3d.new(x + dx, y + dy, 0) - vtx << OpenStudio::Point3d.new(x + dx, y, 0) - vtx << OpenStudio::Point3d.new(x, y, 0) - vtx << OpenStudio::Point3d.new(x, y + dy, 0) + if cw + box = OpenStudio.reverse(box) unless clockwise + else + box = OpenStudio.reverse(box) if clockwise + end - if slb.empty? - slb = vtx - else - slab = OpenStudio.join(slb, vtx, TOL2) - slb = slab.get unless slab.empty? - return invalid(id, mth, 0, ERR, bkp) if slab.empty? - end - end - - # Once joined, re-adjust Z-axis coordinates. - unless z.zero? - vtx = OpenStudio::Point3dVector.new - slb.each { |pt| vtx << OpenStudio::Point3d.new(pt.x, pt.y, z) } - slb = vtx - end + return invalid("unfit medial", mth, 1, ERR, bkp) unless fits?(box, pts) - slb + box = ulc(box) unless cw + box = to_p3Dv(t * box) if t + box end ## - # Returns outdoor-facing, space-(related) roof/ceiling surfaces. These - # include outdoor-facing roof/ceilings of the space per se, as well as - # any outside-facing roof/ceiling surface of an unoccupied space - # immediately above (e.g. a plenum) overlapping any of the roof/ceilings - # of the space itself. + # Generates a bounded box within a polygon. Returns a ULC sequence if original + # points are counterclockwise. # - # @param space [OpenStudio::Model::Space] a space + # @param pts [Set] OpenStudio 3D points # - # @return [Array] surfaces (see logs if empty) - def getRoofs(space = nil) + # @return [OpenStudio::Point3dVector] bounded box (see logs if empty) + def boundedBox(pts = nil) + str = ".*(?] subs requested attributes - # @option subs [#to_s] :id identifier e.g. "Window 007" - # @option subs [#to_s] :type ("FixedWindow") OpenStudio subsurface type - # @option subs [#to_i] :count (1) number of individual subs per array - # @option subs [#to_i] :multiplier (1) OpenStudio subsurface multiplier - # @option subs [#frameWidth] :frame (nil) OpenStudio frame & divider object - # @option subs [#isFenestration] :assembly (nil) OpenStudio construction - # @option subs [#to_f] :ratio e.g. %FWR [0.0, 1.0] - # @option subs [#to_f] :head (OSut::HEAD) e.g. door height (incl frame) - # @option subs [#to_f] :sill (OSut::SILL) e.g. window sill (incl frame) - # @option subs [#to_f] :height sill-to-head height - # @option subs [#to_f] :width e.g. door width - # @option subs [#to_f] :offset left-right centreline dX e.g. between doors - # @option subs [#to_f] :centreline left-right dX (sub/array vs base) - # @option subs [#to_f] :r_buffer gap between sub/array and right corner - # @option subs [#to_f] :l_buffer gap between sub/array and left corner - # @param clear [Bool] whether to remove current sub surfaces - # @param bfr [#to_f] safety buffer, to maintain near other edges - # - # @return [Bool] whether addition is successful - # @return [false] if invalid input (see logs) - def addSubs(s = nil, subs = [], clear = false, bfr = 0.005) - mth = "OSut::#{__callee__}" - v = OpenStudio.openStudioVersion.split(".").join.to_i - cl1 = OpenStudio::Model::Surface - cl2 = Array - cl3 = Hash - min = 0.050 # minimum ratio value ( 5%) - max = 0.950 # maximum ratio value (95%) - no = false + # PATH D : Right-angle triad approach, may override PATH C boxes. + getSegments(pts).each do |sg| + p0 = sg.first + p1 = sg.last - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Exit if mismatched or invalid argument classes. - return mismatch("surface", s, cl2, mth, DBG, no) unless s.is_a?(cl1) - return mismatch("subs", subs, cl3, mth, DBG, no) unless subs.is_a?(cl2) - return empty("surface points", mth, DBG, no) if poly(s).empty? + pts.each do |p2| + next if same?(p2, p0) + next if same?(p2, p1) - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Clear existing sub surfaces if requested. - nom = s.nameString - mdl = s.model + out = triadBox(OpenStudio::Point3dVector.new([p0, p1, p2])) + next if out.empty? + next unless fits?(out, pts, false) - unless [true, false].include?(clear) - log(WRN, "#{nom}: Keeping existing sub surfaces (#{mth})") - clear = false - end + area = OpenStudio.getArea(out) + next if area.empty? - s.subSurfaces.map(&:remove) if clear + area = area.get + next if area < TOL + next if area < aire - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Ensure minimum safety buffer. - if bfr.respond_to?(:to_f) - bfr = bfr.to_f - return negative("safety buffer", mth, ERR, no) if bfr < 0 + aire = area + box = out + end + end - msg = "Safety buffer < 5mm may generate invalid geometry (#{mth})" - log(WRN, msg) if bfr < 0.005 - else - log(ERR, "Setting safety buffer to 5mm (#{mth})") - bfr = 0.005 + unless aire < TOL + box = ulc(box) unless cw + box = to_p3Dv(t * box) if t + return box end - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Allowable sub surface types ... & Frame&Divider enabled - # - "FixedWindow" | true - # - "OperableWindow" | true - # - "Door" | false - # - "GlassDoor" | true - # - "OverheadDoor" | false - # - "Skylight" | false if v < 321 - # - "TubularDaylightDome" | false - # - "TubularDaylightDiffuser" | false - type = "FixedWindow" - types = OpenStudio::Model::SubSurface.validSubSurfaceTypeValues - stype = s.surfaceType # Wall, RoofCeiling or Floor + # PATH E : Medial box, segment approach. + aire = 0 - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - t = OpenStudio::Transformation.alignFace(s.vertices) - max_x = width(s) - max_y = height(s) - mid_x = max_x / 2 - mid_y = max_y / 2 + getSegments(pts).each do |sg| + p0 = sg.first + p1 = sg.last - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Assign default values to certain sub keys (if missing), +more validation. - subs.each_with_index do |sub, index| - return mismatch("sub", sub, cl4, mth, DBG, no) unless sub.is_a?(cl3) + pts.each do |p2| + next if same?(p2, p0) + next if same?(p2, p1) - # Required key:value pairs (either set by the user or defaulted). - sub[:frame ] = nil unless sub.key?(:frame ) - sub[:assembly ] = nil unless sub.key?(:assembly ) - sub[:count ] = 1 unless sub.key?(:count ) - sub[:multiplier] = 1 unless sub.key?(:multiplier) - sub[:id ] = "" unless sub.key?(:id ) - sub[:type ] = type unless sub.key?(:type ) - sub[:type ] = trim(sub[:type]) - sub[:id ] = trim(sub[:id]) - sub[:type ] = type if sub[:type].empty? - sub[:id ] = "OSut|#{nom}|#{index}" if sub[:id ].empty? - sub[:count ] = 1 unless sub[:count ].respond_to?(:to_i) - sub[:multiplier] = 1 unless sub[:multiplier].respond_to?(:to_i) - sub[:count ] = sub[:count ].to_i - sub[:multiplier] = sub[:multiplier].to_i - sub[:count ] = 1 if sub[:count ] < 1 - sub[:multiplier] = 1 if sub[:multiplier] < 1 + out = medialBox(OpenStudio::Point3dVector.new(p0, p1, p2)) + next if out.empty? + next unless fits?(out, pts, false) - id = sub[:id] + area = OpenStudio.getArea(box) + next if area.empty? - # If sub surface type is invalid, log/reset. Additional corrections may - # be enabled once a sub surface is actually instantiated. - unless types.include?(sub[:type]) - log(WRN, "Reset invalid '#{id}' type to '#{type}' (#{mth})") - sub[:type] = type - end + area = area.get + next if area < TOL + next if area < aire - # Log/ignore (optional) frame & divider object. - unless sub[:frame].nil? - if sub[:frame].respond_to?(:frameWidth) - sub[:frame] = nil if sub[:type] == "Skylight" && v < 321 - sub[:frame] = nil if sub[:type] == "Door" - sub[:frame] = nil if sub[:type] == "OverheadDoor" - sub[:frame] = nil if sub[:type] == "TubularDaylightDome" - sub[:frame] = nil if sub[:type] == "TubularDaylightDiffuser" - log(WRN, "Skip '#{id}' FrameDivider (#{mth})") if sub[:frame].nil? - else - sub[:frame] = nil - log(WRN, "Skip '#{id}' invalid FrameDivider object (#{mth})") - end + aire = area + box = out end + end - # The (optional) "assembly" must reference a valid OpenStudio - # construction base, to explicitly assign to each instantiated sub - # surface. If invalid, log/reset/ignore. Additional checks are later - # activated once a sub surface is actually instantiated. - unless sub[:assembly].nil? - unless sub[:assembly].respond_to?(:isFenestration) - log(WRN, "Skip invalid '#{id}' construction (#{mth})") - sub[:assembly] = nil - end - end + unless aire < TOL + box = ulc(box) unless cw + box = to_p3Dv(t * box) if t + return box + end - # Log/reset negative float values. Set ~0.0 values to 0.0. - sub.each do |key, value| - next if key == :count - next if key == :multiplier - next if key == :type - next if key == :id - next if key == :frame - next if key == :assembly + # PATH F : Medial box, triad approach. + aire = 0 - ok = value.respond_to?(:to_f) - return mismatch(key, value, Float, mth, DBG, no) unless ok - next if key == :centreline + getTriads(pts).each do |sg| + p0 = sg[0] + p1 = sg[1] + p2 = sg[2] - negative(key, mth, WRN) if value < 0 - value = 0.0 if value.abs < TOL - end + out = medialBox(OpenStudio::Point3dVector.new(p0, p1, p2)) + next if out.empty? + next unless fits?(out, pts, false) + + area = OpenStudio.getArea(box) + next if area.empty? + + area = area.get + next if area < TOL + next if area < aire + + aire = area + box = out end - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Log/reset (or abandon) conflicting user-set geometry key:value pairs: - # :head e.g. std 80" door + frame/buffers (+ m) - # :sill e.g. std 30" sill + frame/buffers (+ m) - # :height any sub surface height, below "head" (+ m) - # :width e.g. 1.200 m - # :offset if array (+ m) - # :centreline left or right of base surface centreline (+/- m) - # :r_buffer buffer between sub/array and right-side corner (+ m) - # :l_buffer buffer between sub/array and left-side corner (+ m) - # - # If successful, this will generate sub surfaces and add them to the model. - subs.each do |sub| - # Set-up unique sub parameters: - # - Frame & Divider "width" - # - minimum "clear glazing" limits - # - buffers, etc. - id = sub[:id] - frame = 0 - frame = sub[:frame].frameWidth unless sub[:frame].nil? - frames = 2 * frame - buffer = frame + bfr - buffers = 2 * buffer - dim = 0.200 unless (3 * frame) > 0.200 - dim = 3 * frame if (3 * frame) > 0.200 - glass = dim - frames - min_sill = buffer - min_head = buffers + glass - max_head = max_y - buffer - max_sill = max_head - (buffers + glass) - min_ljamb = buffer - max_ljamb = max_x - (buffers + glass) - min_rjamb = buffers + glass - max_rjamb = max_x - buffer - max_height = max_y - buffers - max_width = max_x - buffers + unless aire < TOL + box = ulc(box) unless cw + box = to_p3Dv(t * box) if t + return box + end - # Default sub surface "head" & "sill" height, unless user-specified. - typ_head = HEAD - typ_sill = SILL + # PATH G : Medial box, triangulated approach. + aire = 0 + outer = poly(pts, false, true, true, false, :cw) + holes = OpenStudio::Point3dVectorVector.new - if sub.key?(:ratio) - typ_head = mid_y * (1 + sub[:ratio]) if sub[:ratio] > 0.75 - typ_head = mid_y * (1 + sub[:ratio]) unless stype.downcase == "wall" - typ_sill = mid_y * (1 - sub[:ratio]) if sub[:ratio] > 0.75 - typ_sill = mid_y * (1 - sub[:ratio]) unless stype.downcase == "wall" - end + OpenStudio.computeTriangulation(outer, holes).each do |triangle| + getSegments(triangle).each do |sg| + p0 = sg.first + p1 = sg.last - # Log/reset "height" if beyond min/max. - if sub.key?(:height) - unless sub[:height].between?(glass, max_height) - sub[:height] = glass if sub[:height] < glass - sub[:height] = max_height if sub[:height] > max_height - log(WRN, "Reset '#{id}' height to #{sub[:height]} m (#{mth})") - end - end + pts.each do |p2| + next if same?(p2, p0) + next if same?(p2, p1) - # Log/reset "head" height if beyond min/max. - if sub.key?(:head) - unless sub[:head].between?(min_head, max_head) - sub[:head] = max_head if sub[:head] > max_head - sub[:head] = min_head if sub[:head] < min_head - log(WRN, "Reset '#{id}' head height to #{sub[:head]} m (#{mth})") - end - end + out = medialBox(OpenStudio::Point3dVector.new(p0, p1, p2)) + next if out.empty? + next unless fits?(out, pts, false) - # Log/reset "sill" height if beyond min/max. - if sub.key?(:sill) - unless sub[:sill].between?(min_sill, max_sill) - sub[:sill] = max_sill if sub[:sill] > max_sill - sub[:sill] = min_sill if sub[:sill] < min_sill - log(WRN, "Reset '#{id}' sill height to #{sub[:sill]} m (#{mth})") - end - end + area = OpenStudio.getArea(out) + next if area.empty? - # At this point, "head", "sill" and/or "height" have been tentatively - # validated (and/or have been corrected) independently from one another. - # Log/reset "head" & "sill" heights if conflicting. - if sub.key?(:head) && sub.key?(:sill) && sub[:head] < sub[:sill] + glass - sill = sub[:head] - glass + area = area.get + next if area < TOL + next if area < aire - if sill < min_sill - sub[:ratio ] = 0 if sub.key?(:ratio) - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: invalid '#{id}' head/sill combo (#{mth})") - next - else - sub[:sill] = sill - log(WRN, "(Re)set '#{id}' sill height to #{sub[:sill]} m (#{mth})") + aire = area + box = out end end + end - # Attempt to reconcile "head", "sill" and/or "height". If successful, + return bkp if aire < TOL + + clockwise = clockwise?(box) + + if cw + box = OpenStudio.reverse(box) unless clockwise + else + box = OpenStudio.reverse(box) if clockwise + end + + box = ulc(box) unless cw + box = to_p3Dv(t * box) if t + box + end + + ## + # Generates re-'aligned' polygon vertices wrt main axis of symmetry of its + # largest bounded box. A Hash is returned with 6x key:value pairs ... + # set: realigned (cloned) polygon vertices, box: its bounded box (wrt to :set), + # bbox: its bounding box, t: its translation transformation, r: its rotation + # transformation, and o: the origin coordinates of its axis of rotation. First, + # cloned polygon vertices are rotated so the longest axis of symmetry of its + # bounded box lies parallel to the X-axis; :o being the midpoint of the narrow + # side (of the bounded box) nearest to grid origin (0,0,0). Once rotated, + # polygon vertices are then translated as to ensure one or more vertices are + # aligned along the X-axis and one or more vertices are aligned along the + # Y-axis (no vertices with negative X or Y coordinate values). To unalign the + # returned set of vertices (or its bounded box, or its bounding box), first + # inverse the translation transformation, then inverse the rotation + # transformation. + # + # @param pts [Set] OpenStudio 3D points + # + # @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) + mth = "OSut::#{__callee__}" + out = { set: nil, box: nil, bbox: nil, t: nil, r: nil, o: nil } + pts = poly(pts, false, true) + return out if pts.empty? + return invalid("aligned plane", mth, 1, DBG, out) unless xyz?(pts, :z) + + cw = clockwise?(pts) + w = width(pts) + h = height(pts) + d = h > w ? h : w + box = boundedBox(pts) + return invalid("bounded box" , mth, 0, DBG, out) if box.empty? + + s = getSegments(box) + return invalid("bounded box segments", mth, 0, DBG, out) if s.empty? + + i = ((s[0][1] - s[0][0]).length < (s[1][1] - s[1][0]).length) ? 0 : 1 + k = i + 2 + mid0 = midpoint(s[i][0], s[i][1]) + mid1 = midpoint(s[k][0], s[k][1]) + o = OpenStudio::Point3d.new(0, 0, 0) + origin = ((o - mid0).length < (o - mid1).length) ? mid0 : mid1 + terminal = ((o - mid0).length < (o - mid1).length) ? mid1 : mid0 + 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 + axis = OpenStudio::Point3d.new(origin.x, origin.y , d) - origin + angle = OpenStudio::getAngle(right, seg) + angle = -angle if north.dot(seg) < 0 + r = OpenStudio.createRotation(origin, axis, angle) + pts = poly(r.inverse * pts) + box = poly(r.inverse * box) + dX = pts.min_by(&:x).x + dY = pts.min_by(&:y).y + xy = OpenStudio::Point3d.new(origin.x + dX, origin.y + dY, 0) + origin2 = xy - origin + t = OpenStudio.createTranslation(origin2) + set = poly(t.inverse * pts) + box = poly(t.inverse * box, false, false, false, false, :ulc) + bbox = outline([set]) + clckwise = clockwise?(bbox) + + if cw + bbox = OpenStudio.reverse(bbox) unless clckwise + else + bbox = OpenStudio.reverse(bbox) if clckwise + end + + out[:set ] = set + out[:box ] = box + out[:bbox] = bbox + out[:t ] = t + out[:r ] = r + out[:o ] = origin + + out + end + + ## + # Returns 'width' of a set of OpenStudio 3D points, once re/aligned. + # + # @param pts [Set] 3D points, once re/aligned + # + # @return [Float] width along X-axis, once re/aligned + # @return [0.0] if invalid inputs + def alignedWidth(pts = nil) + pts = poly(pts, false, true, true, true) + return 0 if pts.size < 2 + + pts = getRealignedFace(pts)[:set] + return 0 if pts.size < 2 + + pts.max_by(&:x).x - pts.min_by(&:x).x + end + + ## + # Returns 'height' of a set of OpenStudio 3D points, once re/aligned. + # + # @param pts [Set] 3D points, once re/aligned + # + # @return [Float] height along Y-axis, once re/aligned + # @return [0.0] if invalid inputs + def alignedHeight(pts = nil) + pts = pts = poly(pts, false, true, true, true) + return 0 if pts.size < 2 + + pts = getRealignedFace(pts)[:set] + return 0 if pts.size < 2 + + pts.max_by(&:y).y - pts.min_by(&:y).y + end + + ## + # Generates leader line anchors, linking polygon vertices to one or more sets + # (Hashes) of sequenced vertices. By default, the method seeks to link set + # :vtx (key) vertices (users can select another collection of vertices, e.g. + # tag == :box). The method does not validate individual sets of vertices (e.g. + # non-coplanarity, self-intersecting, inter-set conflicts). Potential leader + # lines cannot intersect each other, other 'tagged' set vertices or original + # polygon edges. For highly-articulated cases (e.g. a narrow polygon with + # multiple concavities, holding multiple sets), such leader line conflicts + # will undoubtedly occur. The method relies on a 'first-come-first-served' + # approach: sets without leader lines are ignored (check for set :void keys, + # see error logs). It is recommended to sort sets prior to calling the method. + # + # @param s [Set] a larger (parent) set of points + # @param [Array] set a collection of sequenced vertices + # @option [Symbol] tag sequence of set vertices to target + # + # @return [Integer] number of successfully-generated anchors (check logs) + def genAnchors(s = nil, set = [], tag = :vtx) + mth = "OSut::#{__callee__}" + id = s.respond_to?(:nameString) ? "#{s.nameString}: " : "" + f = false + pts = poly(s, f, f, f) + n = 0 + return n if pts.empty? + return mismatch("set", set, Array, mth, DBG, n) unless set.respond_to?(:to_a) + + set = set.to_a + t = OpenStudio::Transformation.alignFace(pts) + pts = t.inverse * pts + + # Validate individual sets. Purge surface-specific leader line anchors. + set.each_with_index do |st, i| + str = id + "set ##{i+1}" + return mismatch(str, st, Hash, mth, DBG, a) unless st.respond_to?(:key?) + return hashkey( str, st, tag, mth, DBG, a) unless st.key?(tag) + return empty("#{str} vertices", mth, DBG, a) if st[tag].empty? + + if st.key?(:ld) + ld = st[:ld] + return invalid("#{id} leaders", mth, 0, DBG, a) unless ld.is_a?(Hash) + + ld.reject! { |k, _| k == s } + else + st[:ld] = {} + end + end + + # Set leader lines anchors. + pts.each_with_index do |pt, k| + set.each_with_index do |st, i| + next if st[:ld].key?(s) + + p1 = (t.inverse * st[tag]).first + ld = [pt, p1] + nb = 0 + + # Check for intersections between leader line and polygon edges. + getSegments(pts).each do |sg| + next unless nb.zero? + next if holds?(sg, pt) + + nb += 1 unless getLineIntersection(sg, ld).nil? + end + + next unless nb.zero? + + # Check for intersections between candidate leader line and other sets. + set.each_with_index do |other, j| + next unless nb.zero? + next if i == j + + sgj = getSegments(t.inverse * other[tag]) + sgj.each { |sg| nb += 1 if lineIntersects?(ld, sg) } + end + + next unless nb.zero? + + # ... and previous leader lines (first come, first serve basis). + set.each_with_index do |other, j| + next unless nb.zero? + next if i == j + next unless other[:ld].key?(s) + + pj = other[tag].first + ldj = [ other[:ld][s], pj ] + nb += 1 if lineIntersects?(ld, t.inverse * ldj) + end + + next unless nb.zero? + + # Finally, check for self-intersections. + getSegments(t.inverse * st[tag]).each do |sg| + next unless nb.zero? + next if holds?(sg, p1) + + nb += 1 unless getLineIntersection(sg, ld).nil? + end + + # Only consider sets with valid leader line anchor points. + if nb.zero? + st[:ld][s] = t * pt + n += 1 + end + end + end + + # Log unsuccessful attempts. + set.each_with_index do |st, i| + unless st[:ld].key?(s) + str = id + "set ##{i+1}" + log(ERR, "#{str}: unable to anchor leader line (#{mth})") + st[:void] = true + end + end + + n + end + + ## + # Generates extended polygon vertices to circumscribe one or more sets + # (Hashes) of sequenced vertices. The method neither alters the original + # polygon vertices, nor validates individual sets of vertices (e.g. + # non-coplanarity, self-intersecting, inter-set conflicts). Valid leader line + # anchors (set key :ld) need to be generated prior to calling the method (see + # genAnchors). By default, the method seeks to link leader line anchors to + # set :vtx (key) vertices (users can select another collection of vertices, + # e.g. tag == :box). + # + # @param s [Set] a larger (parent) set of points + # @param [Array] set a collection of sequenced vertices + # @option set [Hash] :ld a collection of polygon-specific leader line anchors + # @option [Symbol] tag sequence of set vertices to target + # + # @return [OpenStudio::Point3dVector] extended vertices (see logs if empty) + def genExtendedVertices(s = nil, set = [], tag = :vtx) + mth = "OSut::#{__callee__}" + id = s.respond_to?(:nameString) ? "#{s.nameString}: " : "" + f = false + pts = poly(s, f, f, f) + cl = OpenStudio::Point3d + a = OpenStudio::Point3dVector.new + v = [] + return a if pts.empty? + return mismatch("set", set, Array, mth, DBG, a) unless set.respond_to?(:to_a) + + set = set.to_a + t = OpenStudio::Transformation.alignFace(pts) + + # Validate individual sets. + set.each_with_index do |st, i| + str = id + "set ##{i+1}" + return mismatch(str, st, Hash, mth, DBG, a) unless st.respond_to?(:key?) + return hashkey( str, st, :ld, mth, DBG, a) unless st.key?(:ld) + + 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) + end + + # Re-sequence polygon vertices. + t.inverse * pts.each do |pt| + v << pt + + # Loop through each valid set; concatenate circumscribing vertices. + set.each do |st| + next unless st.key?(:ld) + next unless st[:ld].key?(s) + next unless same?(st[:ld][s], pt) + next unless st.key?(tag) + + v += st[tag].to_a + v << pt + end + end + + to_p3Dv(v) + end + + ## + # Generates arrays of rectangular polygon inserts within a larger polygon. If + # successful, each set inherits additional key:value pairs: namely :vtx + # (subset of polygon circumscribing vertices), and :vts (collection of + # indivudual polygon insert vertices). Valid leader line anchors (set key :ld) + # need to be generated prior to calling the method (see genAnchors, and + # genExtendedvertices). + # + # @param s [Set] a larger polygon + # @param [Array] set a collection of polygon insert instructions + # @option set [Set] :box bounding box of each collection + # @option set [Hash] :ld a collection of polygon-specific leader line anchors + # @option set [Integer] :rows (1) number of rows of inserts + # @option set [Integer] :cols (1) number of columns of inserts + # @option set [Numeric] :w0 (1.4) width of individual inserts (wrt cols) min 0.4 + # @option set [Numeric] :d0 (1.4) depth of individual inserts (wrt rows) min 0.4 + # @option set [Numeric] :dX (0) optional left/right X-axis buffer + # @option set [Numeric] :dY (0) optional top/bottom Y-axis buffer + # + # @return [OpenStudio::Point3dVector] new polygon vertices (see logs if empty) + def genInserts(s = nil, set = []) + mth = "OSut::#{__callee__}" + id = s.respond_to?(:nameString) ? "#{s.nameString}:" : "" + f = false + pts = poly(s) + cl = OpenStudio::Point3d + a = OpenStudio::Point3dVector.new + return a if pts.empty? + return mismatch("set", set, Array, mth, DBG, a) unless set.respond_to?(:to_a) + + set = set.to_a + gap = 0.1 + gap4 = 0.4 # minimum insert width/depth + + # Validate/reset individual set collections. + set.each_with_index do |st, i| + str1 = id + "set ##{i+1}" + return mismatch(str1, st, Hash, mth, DBG, a) unless st.respond_to?(:key?) + return hashkey( str1, st, :box, mth, DBG, a) unless st.key?(:box) + return hashkey( str1, st, :ld, mth, DBG, a) unless st.key?(:ld) + + str2 = str1 + " anchor" + ld = st[:ld] + return mismatch(str2, ld, Hash, mth, DBG, a) unless ld.respond_to?(:key?) + 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) + + bx = poly(st[:box], true, true) + return invalid("#{str1} box", mth, 0, DBG, a) if bx.empty? + return invalid("#{str1} rectangle", mth, 0, DBG, a) unless rectangular?(bx) + + # Ensure each set bounding box is safely within larger polygon boundaries. + # TO DO: In line with related addSkylights "TO DO", expand method to + # safely handle 'side' cutouts (i.e. no need for leader lines). In + # so doing, boxes could eventually align along surface edges. + bx.each do |pt| + unless pointWithinPolygon?(pt, s) + return invalid("#{id} point", mth, 0, DBG, a) + end + end + + if st.key?(:rows) + rws = st[:rows] + return invalid("#{id} rows", mth, 0, DBG, a) unless rws.is_a?(Integer) + return zero( "#{id} rows", mth, DBG, a) if rws < 1 + else + st[:rows] = 1 + end + + if st.key?(:cols) + cls = st[:cols] + return invalid("#{id} cols", mth, 0, DBG, a) unless cls.is_a?(Integer) + return zero( "#{id} cols", mth, DBG, a) if cls < 1 + else + st[:cols] = 1 + end + + if st.key?(:w0) + w0 = st[:w0] + return invalid("#{id} width", mth, 0, DBG, a) unless w0.is_a?(Numeric) + + w0 = w0.to_f + return zero("#{id} width", mth, DBG, a) if w0.round(2) < gap4 + else + st[:w0] = 1.4 + end + + if st.key?(:d0) + d0 = st[:d0] + return invalid("#{id} depth", mth, 0, DBG, a) unless d0.is_a?(Numeric) + + d0 = d0.to_f + return zero("#{id} depth", mth, DBG, a) if d0.round(2) < gap4 + else + st[:d0] = 1.4 + end + + if st.key?(:dX) + dX = st[:dX] + return invalid( "#{id} dX", mth, 0, DBG, a) unless dX.is_a?(Numeric) + else + st[:dX] = nil + end + + if st.key?(:dY) + dY = st[:dY] + return invalid( "#{id} dY", mth, 0, DBG, a) unless dY.is_a?(Numeric) + else + st[:dY] = nil + end + end + + # Flag conflicts between set bounding boxes. TO DO: ease up for ridges. + set.each_with_index do |st, i| + str = id + "set ##{i+1}" + bx = st[:box] + + set.each_with_index do |other, j| + next if i == j + + bx2 = other[:box] + sgs = getSegments(bx2) + + bx.each do |pt| + if pointAlongSegments?(pt, getSegments(bx2)) + return invalid("#{str} collinear point", mth, 0, DBG, a) + end + + if pointWithinPolygon?(pt, bx2) + return invalid("#{str} overlapping point", mth, 0, DBG, a) + end + end + end + end + + t = OpenStudio::Transformation.alignFace(pts) + pts = poly(pts, f, f, f, t) + sgs = getSegments(pts) + + set.each do |st| + bx = t.inverse * poly(st[:box]) + + out = getRealignedFace(bx) + next unless out[:set] + + # Overwrite original box vertex sequence ... redundant? + st[:out] = out + st[:bx ] = out[:r] * (out[:t] * out[:set]) + st[:box] = t * st[:bx] + end + + # Loop through each 'valid' set (i.e. linking a valid leader line anchor), + # generate set vertex array based on user-provided specs. Reset BLC vertex + # coordinates once completed. + set.each_with_index do |st, i| + str = id + "set ##{i+1}" + next unless st.key?(:out) + + vts = {} # collection of individual (named) polygon insert vertices + vtx = [] # sequence of circumscribing polygon vertices + o = st[:out] + bx = o[:set] + w = width(bx) # overall sandbox width + d = height(bx) # overall sandbox depth + dX = st[:dX ] # left/right buffer (array vs bx) + dY = st[:dY ] # top/bottom buffer (array vs bx) + cols = st[:cols] # number of array columns + rows = st[:rows] # number of array rows + x = st[:w0 ] # width of individual insert + y = st[:d0 ] # depth of indivual insert + gX = 0 # gap between insert columns + gY = 0 # gap between insert rows + + # Gap between insert columns. + if cols > 1 + dX = ( (w - cols * x) / cols) / 2 unless dX + gX = (w - 2 * dX - cols * x) / (cols - 1) + gX = gap if gX.round(2) < gap + dX = (w - cols * x - (cols - 1) * gX) / 2 + else + dX = (w - x) / 2 + end + + if dX.round(2) < 0 + log(ERR, "Skipping #{str}: Negative dX {#{mth}}") + next + end + + # Gap between insert rows. + if rows > 1 + dY = ( (d - rows * y) / rows) / 2 unless dY + gY = (d - 2 * dY - rows * y) / (rows - 1) + gY = gap if gY.round(2) < gap + dY = (d - rows * y - (rows - 1) * gY) / 2 + else + dY = (d - y) / 2 + end + + if dY.round(2) < 0 + log(ERR, "Skipping #{str}: Negative dY {#{mth}}") + next + end + + st[:dX] = dX + st[:gX] = gX + st[:dY] = dY + st[:gY] = gY + + x0 = bx.min_by(&:x).x + dX # X-axis starting point + y0 = bx.min_by(&:y).y + dY # X-axis starting point + xC = x0 # current X-axis position + yC = y0 # current Y-axis position + + # BLC of array. + vtx << OpenStudio::Point3d.new(xC, yC, 0) + + # Move up incrementally along left side of sandbox. + rows.times.each do |iY| + unless iY.zero? + yC += gY + vtx << OpenStudio::Point3d.new(xC, yC, 0) + end + + yC += y + vtx << OpenStudio::Point3d.new(xC, yC, 0) + end + + # Loop through each row: left-to-right, then right-to-left. + rows.times.each do |iY| + (cols - 1).times.each do |iX| + xC += x + vtx << OpenStudio::Point3d.new(xC, yC, 0) + + xC += gX + vtx << OpenStudio::Point3d.new(xC, yC, 0) + end + + # Generate individual polygon inserts, left-to-right. + cols.times.each do |iX| + nom = "#{i}:#{iX}:#{iY}" + vec = [] + vec << OpenStudio::Point3d.new(xC , yC , 0) + vec << OpenStudio::Point3d.new(xC , yC - y, 0) + vec << OpenStudio::Point3d.new(xC + x, yC - y, 0) + vec << OpenStudio::Point3d.new(xC + x, yC , 0) + + # Store. + vertices = o[:r] * (o[:t] * vec) + vts[nom] = to_p3Dv(t * vertices) + + # Add reverse vertices, circumscribing each insert. + vec.reverse! + vec.pop if iX == cols - 1 + vtx += vec + + xC -= gX + x unless iX == cols - 1 + end + + unless iY == rows - 1 + yC -= gY + y + vtx << OpenStudio::Point3d.new(xC, yC, 0) + end + end + + vtx = o[:r] * (o[:t] * vtx) + vtx = to_p3Dv(t * vtx) + + st[:vts] = vts + st[:vtx] = vtx + end + + # Extended vertex sequence of the larger polygon. + genExtendedVertices(s, set) + end + + ## + # Returns an array of OpenStudio space surfaces or subsurfaces that match + # criteria, e.g. exterior, north-east facing walls in hotel "lobby". Note that + # 'sides' rely on space coordinates (not absolute model coordinates). Also, + # 'sides' are exclusive (not inclusive), e.g. walls strictly north-facing or + # strictly east-facing would not be returned if 'sides' holds [:north, :east]. + # + # @param spaces [Set] target spaces + # @param boundary [#to_s] OpenStudio outside boundary condition + # @param type [#to_s] OpenStudio surface (or subsurface) type + # @param sides [Set] direction keys, e.g. :north (see OSut::SIDZ) + # + # @return [Array] surfaces (may be empty, no logs) + def facets(spaces = [], boundary = "Outdoors", type = "Wall", sides = []) + spaces = spaces.is_a?(OpenStudio::Model::Space) ? [spaces] : spaces + spaces = spaces.respond_to?(:to_a) ? spaces.to_a : [] + return [] if spaces.empty? + + sides = sides.respond_to?(:to_sym) ? [sides] : sides + sides = sides.respond_to?(:to_a) ? sides.to_a : [] + + faces = [] + boundary = trim(boundary).downcase + type = trim(type).downcase + return [] if boundary.empty? + return [] if type.empty? + + # Filter sides. If sides is initially empty, return all surfaces of matching + # type and outside boundary condition. + unless sides.empty? + sides = sides.select { |side| SIDZ.include?(side) } + return [] if sides.empty? + end + + spaces.each do |space| + return [] unless space.respond_to?(:setSpaceType) + + space.surfaces.each do |s| + next unless s.outsideBoundaryCondition.downcase == boundary + next unless s.surfaceType.downcase == type + + if sides.empty? + faces << s + else + orientations = [] + orientations << :top if s.outwardNormal.z > TOL + orientations << :bottom if s.outwardNormal.z < -TOL + orientations << :north if s.outwardNormal.y > TOL + orientations << :east if s.outwardNormal.x > TOL + orientations << :south if s.outwardNormal.y < -TOL + orientations << :west if s.outwardNormal.x < -TOL + + faces << s if sides.all? { |o| orientations.include?(o) } + end + end + end + + # SubSurfaces? + spaces.each do |space| + break unless faces.empty? + + space.surfaces.each do |s| + next unless s.outsideBoundaryCondition.downcase == boundary + + s.subSurfaces.each do |sub| + next unless sub.subSurfaceType.downcase == type + + if sides.empty? + faces << sub + else + orientations = [] + orientations << :top if sub.outwardNormal.z > TOL + orientations << :bottom if sub.outwardNormal.z < -TOL + orientations << :north if sub.outwardNormal.y > TOL + orientations << :east if sub.outwardNormal.x > TOL + orientations << :south if sub.outwardNormal.y < -TOL + orientations << :west if sub.outwardNormal.x < -TOL + + faces << sub if sides.all? { |o| orientations.include?(o) } + end + end + end + end + + faces + end + + ## + # Generates an OpenStudio 3D point vector of a composite floor "slab", a + # 'union' of multiple rectangular, horizontal floor "plates". Each plate + # must either share an edge with (or encompass or overlap) any of the + # preceding plates in the array. The generated slab may not be convex. + # + # @param [Array] pltz individual floor plates, each holding: + # @option pltz [Numeric] :x left corner of plate origin (bird's eye view) + # @option pltz [Numeric] :y bottom corner of plate origin (bird's eye view) + # @option pltz [Numeric] :dx plate width (bird's eye view) + # @option pltz [Numeric] :dy plate depth (bird's eye view) + # @param z [Numeric] Z-axis coordinate + # + # @return [OpenStudio::Point3dVector] slab vertices (see logs if empty) + def genSlab(pltz = [], z = 0) + mth = "OSut::#{__callee__}" + slb = OpenStudio::Point3dVector.new + bkp = OpenStudio::Point3dVector.new + cl1 = Array + cl2 = Hash + cl3 = Numeric + + # Input validation. + return mismatch("plates", pltz, cl1, mth, DBG, slb) unless pltz.is_a?(cl1) + return mismatch( "Z", z, cl3, mth, DBG, slb) unless z.is_a?(cl3) + + 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 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) + return hashkey( id, plt, :dy, mth, DBG, slb) unless plt.key?(:dy) + + x = plt[:x ] + y = plt[:y ] + dx = plt[:dx] + dy = plt[:dy] + + return mismatch("#{id} X", x, cl3, mth, DBG, slb) unless x.is_a?(cl3) + return mismatch("#{id} Y", y, cl3, mth, DBG, slb) unless y.is_a?(cl3) + return mismatch("#{id} dX", dx, cl3, mth, DBG, slb) unless dx.is_a?(cl3) + return mismatch("#{id} dY", dy, cl3, mth, DBG, slb) unless dy.is_a?(cl3) + return zero( "#{id} dX", mth, ERR, slb) if dx.abs < TOL + return zero( "#{id} dY", mth, ERR, slb) if dy.abs < TOL + end + + # Join plates. + pltz.each_with_index do |plt, i| + id = "plate # #{i+1} (index #{i})" + x = plt[:x ] + y = plt[:y ] + dx = plt[:dx] + dy = plt[:dy] + + # Adjust X if dX < 0. + x -= -dx if dx < 0 + dx = -dx if dx < 0 + + # Adjust Y if dY < 0. + y -= -dy if dy < 0 + dy = -dy if dy < 0 + + vtx = [] + vtx << OpenStudio::Point3d.new(x + dx, y + dy, 0) + vtx << OpenStudio::Point3d.new(x + dx, y, 0) + vtx << OpenStudio::Point3d.new(x, y, 0) + vtx << OpenStudio::Point3d.new(x, y + dy, 0) + + if slb.empty? + slb = vtx + else + slab = OpenStudio.join(slb, vtx, TOL2) + slb = slab.get unless slab.empty? + return invalid(id, mth, 0, ERR, bkp) if slab.empty? + end + end + + # Once joined, re-adjust Z-axis coordinates. + unless z.zero? + vtx = OpenStudio::Point3dVector.new + slb.each { |pt| vtx << OpenStudio::Point3d.new(pt.x, pt.y, z) } + slb = vtx + end + + slb + end + + ## + # Returns outdoor-facing, space-related roof surfaces. These include + # outdoor-facing roofs of each space per se, as well as any outdoor-facing + # roof surface of unoccupied spaces immediately above (e.g. plenums, attics) + # overlapping any of the ceiling surfaces of each space. + # + # @param spaces [Set] target spaces + # + # @return [Array] roofs (may be empty) + def getRoofs(spaces = []) + mth = "OSut::#{__callee__}" + up = OpenStudio::Point3d.new(0,0,1) - OpenStudio::Point3d.new(0,0,0) + roofs = [] + 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") + + # Outdoor-facing roof surfaces of unoccupied plenums or attics above? + spaces.each do |space| + # When multiple spaces are involved (e.g. plenums, attics), the target + # space may not share the same local transformation as the space(s) above. + # Fetching local transformation. + t0 = transforms(space) + next unless t0[:t] + + t0 = t0[:t] + + facets(space, "Surface", "RoofCeiling").each do |ceiling| + cv0 = t0 * ceiling.vertices + + floor = ceiling.adjacentSurface + next if floor.empty? + + other = floor.get.space + next if other.empty? + + other = other.get + next if other.partofTotalFloorArea + + ti = transforms(other) + next unless ti[:t] + + ti = ti[:t] + + # TO DO: recursive call for stacked spaces as atria (via AirBoundaries). + facets(other, "Outdoors", "RoofCeiling").each do |ruf| + rvi = ti * ruf.vertices + cst = cast(cv0, rvi, up) + next unless overlaps?(cst, rvi, false) + + roofs << ruf unless roofs.include?(ruf) + end + end + end + + roofs + end + + ## + # Validates whether space has outdoor-facing surfaces with fenestration. + # + # @param space [OpenStudio::Model::Space] a space + # @param sidelit [Bool] whether to check for sidelighting, e.g. windows + # @param toplit [Bool] whether to check for toplighting, e.g. skylights + # @param baselit [Bool] whether to check for baselighting, e.g. glazed floors + # + # @return [Bool] whether space is daylit + # @return [false] if invalid input (see logs) + def daylit?(space = nil, sidelit = true, toplit = true, baselit = true) + mth = "OSut::#{__callee__}" + cl = OpenStudio::Model::Space + ck1 = space.is_a?(cl) + ck2 = [true, false].include?(sidelit) + ck3 = [true, false].include?(toplit) + ck4 = [true, false].include?(baselit) + return mismatch("space", space, cl, mth, DBG, false) unless ck1 + return invalid("sidelit" , mth, 2, DBG, false) unless ck2 + return invalid("toplit" , mth, 3, DBG, false) unless ck3 + return invalid("baselit" , mth, 4, DBG, false) unless ck4 + + walls = sidelit ? facets(space, "Outdoors", "Wall") : [] + roofs = toplit ? facets(space, "Outdoors", "RoofCeiling") : [] + floors = baselit ? facets(space, "Outdoors", "Floor") : [] + + (walls + roofs + floors).each do |surface| + surface.subSurfaces.each do |sub| + # All fenestrated subsurface types are considered, as user can set these + # explicitely (e.g. skylight in a wall) in OpenStudio. + return true if fenestration?(sub) + end + end + + false + end + + ## + # Adds sub surfaces (e.g. windows, doors, skylights) to surface. + # + # @param s [OpenStudio::Model::Surface] a model surface + # @param [Array] subs requested attributes + # @option subs [#to_s] :id identifier e.g. "Window 007" + # @option subs [#to_s] :type ("FixedWindow") OpenStudio subsurface type + # @option subs [#to_i] :count (1) number of individual subs per array + # @option subs [#to_i] :multiplier (1) OpenStudio subsurface multiplier + # @option subs [#frameWidth] :frame (nil) OpenStudio frame & divider object + # @option subs [#isFenestration] :assembly (nil) OpenStudio construction + # @option subs [#to_f] :ratio e.g. %FWR [0.0, 1.0] + # @option subs [#to_f] :head (OSut::HEAD) e.g. door height (incl frame) + # @option subs [#to_f] :sill (OSut::SILL) e.g. window sill (incl frame) + # @option subs [#to_f] :height sill-to-head height + # @option subs [#to_f] :width e.g. door width + # @option subs [#to_f] :offset left-right centreline dX e.g. between doors + # @option subs [#to_f] :centreline left-right dX (sub/array vs base) + # @option subs [#to_f] :r_buffer gap between sub/array and right corner + # @option subs [#to_f] :l_buffer gap between sub/array and left corner + # @param clear [Bool] whether to remove current sub surfaces + # @param bfr [#to_f] safety buffer, to maintain near other edges + # + # @return [Bool] whether addition is successful + # @return [false] if invalid input (see logs) + def addSubs(s = nil, subs = [], clear = false, bfr = 0.005) + mth = "OSut::#{__callee__}" + v = OpenStudio.openStudioVersion.split(".").join.to_i + cl1 = OpenStudio::Model::Surface + cl2 = Array + cl3 = Hash + min = 0.050 # minimum ratio value ( 5%) + max = 0.950 # maximum ratio value (95%) + no = false + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Exit if mismatched or invalid argument classes. + return mismatch("surface", s, cl2, mth, DBG, no) unless s.is_a?(cl1) + return mismatch("subs", subs, cl3, mth, DBG, no) unless subs.is_a?(cl2) + return empty("surface points", mth, DBG, no) if poly(s).empty? + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Clear existing sub surfaces if requested. + nom = s.nameString + mdl = s.model + + unless [true, false].include?(clear) + log(WRN, "#{nom}: Keeping existing sub surfaces (#{mth})") + clear = false + end + + s.subSurfaces.map(&:remove) if clear + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Ensure minimum safety buffer. + if bfr.respond_to?(:to_f) + bfr = bfr.to_f + return negative("safety buffer", mth, ERR, no) if bfr.round(2) < 0 + + msg = "Safety buffer < 5mm may generate invalid geometry (#{mth})" + log(WRN, msg) if bfr.round(2) < 0.005 + else + log(ERR, "Setting safety buffer to 5mm (#{mth})") + bfr = 0.005 + end + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Allowable sub surface types ... & Frame&Divider enabled + # - "FixedWindow" | true + # - "OperableWindow" | true + # - "Door" | false + # - "GlassDoor" | true + # - "OverheadDoor" | false + # - "Skylight" | false if v < 321 + # - "TubularDaylightDome" | false + # - "TubularDaylightDiffuser" | false + type = "FixedWindow" + types = OpenStudio::Model::SubSurface.validSubSurfaceTypeValues + stype = s.surfaceType # Wall, RoofCeiling or Floor + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + t = OpenStudio::Transformation.alignFace(s.vertices) + s0 = poly(s, false, false, true, t, :ulc) + s00 = nil + + if facingUp?(s) || facingDown?(s) # TODO: redundant check? + s00 = getRealignedFace(s0) + return false unless s00[:set] + + s0 = s00[:set] + end + + max_x = width(s0) + max_y = height(s0) + mid_x = max_x / 2 + mid_y = max_y / 2 + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Assign default values to certain sub keys (if missing), +more validation. + subs.each_with_index do |sub, index| + return mismatch("sub", sub, cl4, mth, DBG, no) unless sub.is_a?(cl3) + + # Required key:value pairs (either set by the user or defaulted). + sub[:frame ] = nil unless sub.key?(:frame ) + sub[:assembly ] = nil unless sub.key?(:assembly ) + sub[:count ] = 1 unless sub.key?(:count ) + sub[:multiplier] = 1 unless sub.key?(:multiplier) + sub[:id ] = "" unless sub.key?(:id ) + sub[:type ] = type unless sub.key?(:type ) + sub[:type ] = trim(sub[:type]) + sub[:id ] = trim(sub[:id]) + sub[:type ] = type if sub[:type].empty? + sub[:id ] = "OSut|#{nom}|#{index}" if sub[:id ].empty? + sub[:count ] = 1 unless sub[:count ].respond_to?(:to_i) + sub[:multiplier] = 1 unless sub[:multiplier].respond_to?(:to_i) + sub[:count ] = sub[:count ].to_i + sub[:multiplier] = sub[:multiplier].to_i + sub[:count ] = 1 if sub[:count ] < 1 + sub[:multiplier] = 1 if sub[:multiplier] < 1 + + id = sub[:id] + + # If sub surface type is invalid, log/reset. Additional corrections may + # be enabled once a sub surface is actually instantiated. + unless types.include?(sub[:type]) + log(WRN, "Reset invalid '#{id}' type to '#{type}' (#{mth})") + sub[:type] = type + end + + # Log/ignore (optional) frame & divider object. + unless sub[:frame].nil? + if sub[:frame].respond_to?(:frameWidth) + sub[:frame] = nil if sub[:type] == "Skylight" && v < 321 + sub[:frame] = nil if sub[:type] == "Door" + sub[:frame] = nil if sub[:type] == "OverheadDoor" + sub[:frame] = nil if sub[:type] == "TubularDaylightDome" + sub[:frame] = nil if sub[:type] == "TubularDaylightDiffuser" + log(WRN, "Skip '#{id}' FrameDivider (#{mth})") if sub[:frame].nil? + else + sub[:frame] = nil + log(WRN, "Skip '#{id}' invalid FrameDivider object (#{mth})") + end + end + + # The (optional) "assembly" must reference a valid OpenStudio + # construction base, to explicitly assign to each instantiated sub + # surface. If invalid, log/reset/ignore. Additional checks are later + # activated once a sub surface is actually instantiated. + unless sub[:assembly].nil? + unless sub[:assembly].respond_to?(:isFenestration) + log(WRN, "Skip invalid '#{id}' construction (#{mth})") + sub[:assembly] = nil + end + end + + # Log/reset negative float values. Set ~0.0 values to 0.0. + sub.each do |key, value| + next if key == :count + next if key == :multiplier + next if key == :type + next if key == :id + next if key == :frame + next if key == :assembly + + ok = value.respond_to?(:to_f) + return mismatch(key, value, Float, mth, DBG, no) unless ok + next if key == :centreline + + negative(key, mth, WRN) if value < 0 + value = 0.0 if value.abs < TOL + end + end + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Log/reset (or abandon) conflicting user-set geometry key:value pairs: + # :head e.g. std 80" door + frame/buffers (+ m) + # :sill e.g. std 30" sill + frame/buffers (+ m) + # :height any sub surface height, below "head" (+ m) + # :width e.g. 1.200 m + # :offset if array (+ m) + # :centreline left or right of base surface centreline (+/- m) + # :r_buffer buffer between sub/array and right-side corner (+ m) + # :l_buffer buffer between sub/array and left-side corner (+ m) + # + # If successful, this will generate sub surfaces and add them to the model. + subs.each do |sub| + # Set-up unique sub parameters: + # - Frame & Divider "width" + # - minimum "clear glazing" limits + # - buffers, etc. + id = sub[:id] + frame = sub[:frame] ? sub[:frame].frameWidth : 0 + frames = 2 * frame + buffer = frame + bfr + buffers = 2 * buffer + dim = 3 * frame > 0.200 ? 3 * frame : 0.200 + glass = dim - frames + min_sill = buffer + min_head = buffers + glass + max_head = max_y - buffer + max_sill = max_head - (buffers + glass) + min_ljamb = buffer + max_ljamb = max_x - (buffers + glass) + min_rjamb = buffers + glass + max_rjamb = max_x - buffer + max_height = max_y - buffers + max_width = max_x - buffers + + # Default sub surface "head" & "sill" height, unless user-specified. + typ_head = HEAD + typ_sill = SILL + + if sub.key?(:ratio) + typ_head = mid_y * (1 + sub[:ratio]) if sub[:ratio] > 0.75 + typ_head = mid_y * (1 + sub[:ratio]) unless stype.downcase == "wall" + typ_sill = mid_y * (1 - sub[:ratio]) if sub[:ratio] > 0.75 + typ_sill = mid_y * (1 - sub[:ratio]) unless stype.downcase == "wall" + end + + # Log/reset "height" if beyond min/max. + if sub.key?(:height) + unless sub[:height].between?(glass - TOL2, max_height + TOL2) + sub[:height] = glass if sub[:height] < glass + sub[:height] = max_height if sub[:height] > max_height + log(WRN, "Reset '#{id}' height to #{sub[:height]} m (#{mth})") + end + end + + # Log/reset "head" height if beyond min/max. + if sub.key?(:head) + unless sub[:head].between?(min_head - TOL2, max_head + TOL2) + sub[:head] = max_head if sub[:head] > max_head + sub[:head] = min_head if sub[:head] < min_head + log(WRN, "Reset '#{id}' head height to #{sub[:head]} m (#{mth})") + end + end + + # Log/reset "sill" height if beyond min/max. + if sub.key?(:sill) + unless sub[:sill].between?(min_sill - TOL2, max_sill + TOL2) + sub[:sill] = max_sill if sub[:sill] > max_sill + sub[:sill] = min_sill if sub[:sill] < min_sill + log(WRN, "Reset '#{id}' sill height to #{sub[:sill]} m (#{mth})") + end + end + + # At this point, "head", "sill" and/or "height" have been tentatively + # validated (and/or have been corrected) independently from one another. + # Log/reset "head" & "sill" heights if conflicting. + if sub.key?(:head) && sub.key?(:sill) && sub[:head] < sub[:sill] + glass + sill = sub[:head] - glass + + if sill < min_sill - TOL2 + sub[:ratio ] = 0 if sub.key?(:ratio) + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: invalid '#{id}' head/sill combo (#{mth})") + next + else + sub[:sill] = sill + log(WRN, "(Re)set '#{id}' sill height to #{sub[:sill]} m (#{mth})") + end + end + + # Attempt to reconcile "head", "sill" and/or "height". If successful, # all 3x parameters are set (if missing), or reset if invalid. if sub.key?(:head) && sub.key?(:sill) height = sub[:head] - sub[:sill] - if sub.key?(:height) && (sub[:height] - height).abs > TOL - log(WRN, "(Re)set '#{id}' height to #{height} m (#{mth})") + if sub.key?(:height) && (sub[:height] - height).abs > TOL2 + log(WRN, "(Re)set '#{id}' height to #{height} m (#{mth})") + end + + sub[:height] = height + elsif sub.key?(:head) # no "sill" + if sub.key?(:height) + sill = sub[:head] - sub[:height] + + if sill < min_sill - TOL2 + sill = min_sill + height = sub[:head] - sill + + if height < glass + sub[:ratio ] = 0 if sub.key?(:ratio) + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: invalid '#{id}' head/height combo (#{mth})") + next + else + sub[:sill ] = sill + sub[:height] = height + log(WRN, "(Re)set '#{id}' height to #{sub[:height]} m (#{mth})") + end + else + sub[:sill] = sill + end + else + sub[:sill ] = typ_sill + sub[:height] = sub[:head] - sub[:sill] + end + elsif sub.key?(:sill) # no "head" + if sub.key?(:height) + head = sub[:sill] + sub[:height] + + if head > max_head - TOL2 + head = max_head + height = head - sub[:sill] + + if height < glass + sub[:ratio ] = 0 if sub.key?(:ratio) + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: invalid '#{id}' sill/height combo (#{mth})") + next + else + sub[:head ] = head + sub[:height] = height + log(WRN, "(Re)set '#{id}' height to #{sub[:height]} m (#{mth})") + end + else + sub[:head] = head + end + else + sub[:head ] = typ_head + sub[:height] = sub[:head] - sub[:sill] + end + elsif sub.key?(:height) # neither "head" nor "sill" + head = s00 ? mid_y + sub[:height]/2 : typ_head + sill = head - sub[:height] + + if sill < min_sill + sill = min_sill + head = sill + sub[:height] + end + + sub[:head] = head + sub[:sill] = sill + else + sub[:head ] = typ_head + sub[:sill ] = typ_sill + sub[:height] = sub[:head] - sub[:sill] + end + + # Log/reset "width" if beyond min/max. + if sub.key?(:width) + unless sub[:width].between?(glass - TOL2, max_width + TOL2) + sub[:width] = glass if sub[:width] < glass + sub[:width] = max_width if sub[:width] > max_width + log(WRN, "Reset '#{id}' width to #{sub[:width]} m (#{mth})") + end + end + + # Log/reset "count" if < 1 (or not an Integer) + if sub[:count].respond_to?(:to_i) + sub[:count] = sub[:count].to_i + + if sub[:count] < 1 + sub[:count] = 1 + log(WRN, "Reset '#{id}' count to #{sub[:count]} (#{mth})") + end + else + sub[:count] = 1 + end + + sub[:count] = 1 unless sub.key?(:count) + + # Log/reset if left-sided buffer under min jamb position. + if sub.key?(:l_buffer) + if sub[:l_buffer] < min_ljamb - TOL + sub[:l_buffer] = min_ljamb + log(WRN, "Reset '#{id}' left buffer to #{sub[:l_buffer]} m (#{mth})") + end + end + + # Log/reset if right-sided buffer beyond max jamb position. + if sub.key?(:r_buffer) + if sub[:r_buffer] > max_rjamb - TOL + sub[:r_buffer] = min_rjamb + log(WRN, "Reset '#{id}' right buffer to #{sub[:r_buffer]} m (#{mth})") + end + end + + centre = mid_x + centre += sub[:centreline] if sub.key?(:centreline) + n = sub[:count ] + h = sub[:height ] + frames + w = 0 # overall width of sub(s) bounding box (to calculate) + x0 = 0 # left-side X-axis coordinate of sub(s) bounding box + xf = 0 # right-side X-axis coordinate of sub(s) bounding box + + # Log/reset "offset", if conflicting vs "width". + if sub.key?(:ratio) + if sub[:ratio] < TOL + sub[:ratio ] = 0 + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: '#{id}' ratio ~0 (#{mth})") + next + end + + # Log/reset if "ratio" beyond min/max? + unless sub[:ratio].between?(min, max) + sub[:ratio] = min if sub[:ratio] < min + sub[:ratio] = max if sub[:ratio] > max + log(WRN, "Reset ratio (min/max) to #{sub[:ratio]} (#{mth})") + end + + # Log/reset "count" unless 1. + unless sub[:count] == 1 + sub[:count] = 1 + log(WRN, "Reset count (ratio) to 1 (#{mth})") + end + + area = s.grossArea * sub[:ratio] # sub m2, including (optional) frames + w = area / h + width = w - frames + x0 = centre - w/2 + xf = centre + w/2 + + if sub.key?(:l_buffer) + if sub.key?(:centreline) + log(WRN, "Skip #{id} left buffer (vs centreline) (#{mth})") + else + x0 = sub[:l_buffer] - frame + xf = x0 + w + centre = x0 + w/2 + end + elsif sub.key?(:r_buffer) + if sub.key?(:centreline) + log(WRN, "Skip #{id} right buffer (vs centreline) (#{mth})") + else + xf = max_x - sub[:r_buffer] + frame + x0 = xf - w + centre = x0 + w/2 + end + end + + # Too wide? + if x0 < min_ljamb - TOL2 || xf > max_rjamb - TOL2 + sub[:ratio ] = 0 if sub.key?(:ratio) + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: invalid (ratio) width/centreline (#{mth})") + next + end + + if sub.key?(:width) && (sub[:width] - width).abs > TOL + sub[:width] = width + log(WRN, "Reset width (ratio) to #{sub[:width]} (#{mth})") + end + + sub[:width] = width unless sub.key?(:width) + else + unless sub.key?(:width) + sub[:ratio ] = 0 if sub.key?(:ratio) + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: missing '#{id}' width (#{mth})") + next + end + + width = sub[:width] + frames + gap = (max_x - n * width) / (n + 1) + gap = sub[:offset] - width if sub.key?(:offset) + gap = 0 if gap < bfr + offset = gap + width + + if sub.key?(:offset) && (offset - sub[:offset]).abs > TOL + sub[:offset] = offset + log(WRN, "Reset sub offset to #{sub[:offset]} m (#{mth})") + end + + sub[:offset] = offset unless sub.key?(:offset) + + # Overall width (including frames) of bounding box around array. + w = n * width + (n - 1) * gap + x0 = centre - w/2 + xf = centre + w/2 + + if sub.key?(:l_buffer) + if sub.key?(:centreline) + log(WRN, "Skip #{id} left buffer (vs centreline) (#{mth})") + else + x0 = sub[:l_buffer] - frame + xf = x0 + w + centre = x0 + w/2 + end + elsif sub.key?(:r_buffer) + if sub.key?(:centreline) + log(WRN, "Skip #{id} right buffer (vs centreline) (#{mth})") + else + xf = max_x - sub[:r_buffer] + frame + x0 = xf - w + centre = x0 + w/2 + end + end + + # Too wide? + if x0 < bfr - TOL2 || xf > max_x - bfr - TOL2 + sub[:ratio ] = 0 if sub.key?(:ratio) + sub[:count ] = 0 + sub[:multiplier] = 0 + sub[:height ] = 0 if sub.key?(:height) + sub[:width ] = 0 if sub.key?(:width) + log(ERR, "Skip: invalid array width/centreline (#{mth})") + next + end + end + + # Initialize left-side X-axis coordinate of only/first sub. + pos = x0 + frame + + # Generate sub(s). + sub[:count].times do |i| + name = "#{id}|#{i}" + fr = 0 + fr = sub[:frame].frameWidth if sub[:frame] + + vec = OpenStudio::Point3dVector.new + vec << OpenStudio::Point3d.new(pos, sub[:head], 0) + vec << OpenStudio::Point3d.new(pos, sub[:sill], 0) + vec << OpenStudio::Point3d.new(pos + sub[:width], sub[:sill], 0) + vec << OpenStudio::Point3d.new(pos + sub[:width], sub[:head], 0) + vec = s00 ? t * (s00[:r] * (s00[:t] * vec)) : t * vec + + # Log/skip if conflict between individual sub and base surface. + vc = vec + vc = offset(vc, fr, 300) if fr > 0 + ok = fits?(vc, s) + log(ERR, "Skip '#{name}': won't fit in '#{nom}' (#{mth})") unless ok + break unless ok + + # Log/skip if conflicts with existing subs (even if same array). + s.subSurfaces.each do |sb| + nome = sb.nameString + fd = sb.windowPropertyFrameAndDivider + fr = 0 if fd.empty? + fr = fd.get.frameWidth unless fd.empty? + vk = sb.vertices + vk = offset(vk, fr, 300) if fr > 0 + oops = overlaps?(vc, vk) + log(ERR, "Skip '#{name}': overlaps '#{nome}' (#{mth})") if oops + ok = false if oops + break if oops + end + + break unless ok + + sb = OpenStudio::Model::SubSurface.new(vec, mdl) + sb.setName(name) + sb.setSubSurfaceType(sub[:type]) + sb.setConstruction(sub[:assembly]) if sub[:assembly] + ok = sb.allowWindowPropertyFrameAndDivider + sb.setWindowPropertyFrameAndDivider(sub[:frame]) if sub[:frame] && ok + sb.setMultiplier(sub[:multiplier]) if sub[:multiplier] > 1 + sb.setSurface(s) + + # Reset "pos" if array. + pos += sub[:offset] if sub.key?(:offset) + end + end + + true + end + + ## + # Validates whether surface is considered a sloped roof (outdoor-facing, + # 10% < tilt < 90%). + # + # @param s [OpenStudio::Model::Surface] a model surface + # + # @return [Bool] whether surface is a sloped roof + # @return [false] if invalid input (see logs) + def slopedRoof?(s = nil) + mth = "OSut::#{__callee__}" + cl = OpenStudio::Model::Surface + return mismatch("surface", s, cl, mth, DBG, false) unless s.is_a?(cl) + + return false if facingUp?(s) + return false if facingDown?(s) + + true + end + + ## + # Returns the "gross roof area" above selected conditioned, occupied spaces. + # This includes all roof surfaces of indirectly-conditioned, unoccupied spaces + # like plenums (if located above any of the selected spaces). This also + # includes roof surfaces of unconditioned or unenclosed spaces like attics, if + # vertically-overlapping any ceiling of occupied spaces below; attic roof + # sections above uninsulated soffits are excluded, for instance. + def grossRoofArea(spaces = []) + mth = "OSut::#{__callee__}" + up = OpenStudio::Point3d.new(0,0,1) - OpenStudio::Point3d.new(0,0,0) + rm2 = 0 + 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) } + spaces = spaces.select { |space| space.partofTotalFloorArea } + return invalid("spaces", mth, 1, DBG, 0) if spaces.empty? + + # The method is very similar to OpenStudio-Standards' : + # find_exposed_conditioned_roof_surfaces(model) + # + # github.com/NREL/openstudio-standards/blob/ + # be81bd88dc55a44d8cce3ee6daf29c768032df6a/lib/openstudio-standards/ + # standards/Standards.Surface.rb#L99 + # + # ... yet differs with regards to attics with overhangs/soffits. + + # Start with roof surfaces of occupied spaces. + spaces.each do |space| + facets(space, "Outdoors", "RoofCeiling").each do |roof| + next if rfs.key?(roof) + + rfs[roof] = {m2: roof.grossArea, m: space.multiplier} + end + end + + # Roof surfaces of unoccupied, conditioned spaces above (e.g. plenums)? + # TO DO: recursive call for stacked spaces as atria (via AirBoundaries). + spaces.each do |space| + facets(space, "Surface", "RoofCeiling").each do |ceiling| + floor = ceiling.adjacentSurface + next if floor.empty? + + other = floor.get.space + next if other.empty? + + other = other.get + next if other.partofTotalFloorArea + next if unconditioned?(other) + + facets(other, "Outdoors", "RoofCeiling").each do |roof| + next if rfs.key?(roof) + + rfs[roof] = {m2: roof.grossArea, m: other.multiplier} + end + end + end + + # Roof surfaces of unoccupied, unconditioned spaces above (e.g. attics)? + # TO DO: recursive call for stacked spaces as atria (via AirBoundaries). + spaces.each do |space| + # When taking overlaps into account, the target space may not share the + # same local transformation as the space(s) above. + t0 = transforms(space) + next unless t0[:t] + + t0 = t0[:t] + + facets(space, "Surface", "RoofCeiling").each do |ceiling| + cv0 = t0 * ceiling.vertices + + floor = ceiling.adjacentSurface + next if floor.empty? + + other = floor.get.space + next if other.empty? + + other = other.get + next if other.partofTotalFloorArea + next unless unconditioned?(other) + + ti = transforms(other) + next unless ti[:t] + + ti = ti[:t] + + facets(other, "Outdoors", "RoofCeiling").each do |roof| + rvi = ti * roof.vertices + cst = cast(cv0, rvi, up) + + # The overlap calculation fails for roof and ceiling surfaces with + # previously-added leader lines. + # + # TODO: revise approach for attics ONCE skylight wells have been added. + olap = overlap(cst, rvi, false) + next if olap.empty? + + m2 = OpenStudio.getArea(olap) + next if m2.empty? + + m2 = m2.get + next unless m2.round(2) > 0 + + rfs[roof] = {m2: 0, m: other.multiplier} unless rfs.key?(roof) + + rfs[roof][:m2] += m2 end + end + end - sub[:height] = height - elsif sub.key?(:head) # no "sill" - if sub.key?(:height) - sill = sub[:head] - sub[:height] + rfs.values.each { |rf| rm2 += rf[:m2] * rf[:m] } - if sill < min_sill - sill = min_sill - height = sub[:head] - sill + rm2 + end - if height < glass - sub[:ratio ] = 0 if sub.key?(:ratio) - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: invalid '#{id}' head/height combo (#{mth})") + ## + # Identifies horizontal ridges between 2x sloped roof surfaces (same space). + # If successful, the returned Array holds 'ridge' Hashes. Each Hash holds: an + # :edge (OpenStudio::Point3dVector), the edge :length (Numeric), and :roofs + # (Array of 2x linked roof surfaces). Each roof surface may be linked to more + # than one horizontal ridge. + # + # @param roofs [Array] target roof surfaces + # + # @return [Array] horizontal ridges (see logs if empty) + def getHorizontalRidges(roofs = []) + mth = "OSut::#{__callee__}" + ridges = [] + return ridges unless roofs.is_a?(Array) + + roofs = roofs.select { |s| s.is_a?(OpenStudio::Model::Surface) } + roofs = roofs.select { |s| slopedRoof?(s) } + + roofs.each do |roof| + maxZ = roof.vertices.max_by(&:z).z + next if roof.space.empty? + + space = roof.space.get + + getSegments(roof).each do |edge| + next unless xyz?(edge, :z, maxZ) + + # Skip if already tracked. + match = false + + ridges.each do |ridge| + break if match + + edg = ridge[:edge] + match = same?(edge, edg) || same?(edge, edg.reverse) + # match = true if same?(edge, edg) || same?(edge, edg.reverse) + end + + next if match + + ridge = { edge: edge, length: (edge[1] - edge[0]).length, roofs: [roof] } + + # Links another roof (same space)? + match = false + + roofs.each do |ruf| + break if match + next if ruf == roof + next if ruf.space.empty? + next unless ruf.space.get == space + + getSegments(ruf).each do |edg| + break if match + next unless same?(edge, edg) || same?(edge, edg.reverse) + + ridge[:roofs] << ruf + ridges << ridge + match = true + end + end + end + end + + ridges + end + + ## + # Adds skylights to toplight selected OpenStudio (occupied, conditioned) + # spaces, based on requested skylight-to-roof (SRR%) options (max 10%). If the + # user selects 0% (0.0) as the :srr while keeping :clear as true, the method + # simply purges all pre-existing roof subsurfaces (whether glazed or not) of + # selected spaces, and exits while returning 0 (without logging an error or + # warning). Pre-toplit spaces are otherwise ignored. Boolean options :attic, + # :plenum, :sloped and :sidelit, further restrict candidate roof surfaces. If + # applicable, options :attic and :plenum add skylight wells. Option :patterns + # restricts preset skylight allocation strategies in order of preference; if + # left empty, all preset patterns are considered, also in order of preference + # (see examples). + # + # @param spaces [Array] space(s) to toplight + # @param [Hash] opts requested skylight attributes + # @option opts [#to_f] :srr skylight-to-roof ratio (0.00, 0.10] + # @option opts [#to_f] :size (1.22) template skylight width/depth (min 0.4m) + # @option opts [#frameWidth] :frame (nil) OpenStudio Frame & Divider (optional) + # @option opts [Bool] :clear (true) whether to first purge existing skylights + # @option opts [Bool] :sidelit (true) whether to consider sidelit spaces + # @option opts [Bool] :sloped (true) whether to consider sloped roof surfaces + # @option opts [Bool] :plenum (true) whether to consider plenum wells + # @option opts [Bool] :attic (true) whether to consider attic wells + # @option opts [Array<#to_s>] :patterns requested skylight allocation (3x) + # @example (a) consider 2D array of individual skylights, e.g. n(1.2m x 1.2m) + # opts[:patterns] = ["array"] + # @example (b) consider 'a', then array of 1x(size) x n(size) skylight strips + # opts[:patterns] = ["array", "strips"] + # + # @return [Float] returns gross roof area if successful (see logs if 0 m2) + def addSkyLights(spaces = [], opts = {}) + mth = "OSut::#{__callee__}" + clear = true + srr = 0.0 + frame = nil # FrameAndDivider object + f = 0.0 # FrameAndDivider frame width + gap = 0.1 # min 2" around well (2x), as well as max frame width + gap2 = 0.2 # 2x gap + gap4 = 0.4 # minimum skylight 16" width/depth (excluding frame width) + bfr = 0.005 # minimum array perimeter buffer (no wells) + w = 1.22 # default 48" x 48" skylight base + w2 = w * w # m2 + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Excerpts of ASHRAE 90.1 2022 definitions: + # + # "ROOF": + # + # "the upper portion of the building envelope, including opaque areas and + # fenestration, that is horizontal or tilted at an angle of less than 60 + # degrees from horizontal. For the purposes of determining building + # envelope requirements, the classifications are defined as follows + # (inter alia): + # + # - attic and other roofs: all other roofs, including roofs with + # insulation ENTIRELY BELOW (inside of) the roof structure (i.e., + # attics, cathedral ceilings, and single-rafter ceilings), roofs with + # insulation both above and BELOW the roof structure, and roofs + # without insulation but excluding metal building roofs. [...]" + # + # "ROOF AREA, GROSS": + # + # "the area of the roof measured from the EXTERIOR faces of walls or from + # the centerline of party walls." + # + # + # For the simple case below (steep 4-sided hip roof, UNENCLOSED ventilated + # attic), 90.1 users typically choose between either: + # 1. modelling the ventilated attic explicitely, or + # 2. ignoring the ventilated attic altogether. + # + # If skylights were added to the model, option (1) would require one or more + # skylight wells (light shafts leading to occupied spaces below), with + # insulated well walls separating CONDITIONED spaces from an UNENCLOSED, + # UNCONDITIONED space (i.e. attic). + # + # Determining which roof surfaces (or which portion of roof surfaces) need + # to be considered when calculating "GROSS ROOF AREA" may be subject to some + # interpretation. From the above definitions: + # + # - the uninsulated, tilted hip-roof attic surfaces are considered "ROOF" + # surfaces, provided they 'shelter' insulation below (i.e. insulated + # attic floor). + # - however, only the 'projected' portion of such "ROOF" surfaces, i.e. + # areas between axes AA` and BB` (along exterior walls)) would be + # considered. + # - the portions above uninsulated soffits (illustrated on the right) + # would be excluded from the "GROSS ROOF AREA" as they are beyond the + # exterior wall projections. + # + # A B + # | | + # _________ + # / \ /| |\ + # / \ / | | \ + # /_ ________ _\ = > /_ | | _\ ... excluded portions + # | | + # |__________| + # . . + # A` B` + # + # If the unoccupied space (directly under the hip roof) were instead an + # INDIRECTLY-CONDITIONED plenum (not an attic), then there would be no need + # to exclude portions of any roof surface: all plenum roof surfaces (in + # addition to soffit surfaces) would need to be insulated). The method takes + # such circumstances into account, which requires vertically casting of + # surfaces ontoothers, as well as overlap calculations. If successful, the + # method returns the "GROSS ROOF AREA" (in m2), based on the above rationale. + # + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Excerpts of similar NECB requirements (unchanged from 2011 through 2020): + # + # 3.2.1.4. 2). "The total skylight area shall be less than 2% of the GROSS + # ROOF AREA as determined in Article 3.1.1.6." (5% in earlier versions) + # + # 3.1.1.6. 5). "In the calculation of allowable skylight area, the GROSS + # ROOF AREA shall be calculated as the sum of the areas of insulated + # roof including skylights." + # + # There are NO additional details or NECB appendix notes on the matter. It + # is unclear if the NECB's looser definition of GROSS ROOF AREA includes + # (uninsulated) sloped roof surfaces above (insulated) flat ceilings (e.g. + # attics), as with 90.1. It would be definitely odd if it didn't. For + # instance, if the GROSS ROOF AREA were based on insulated ceiling surfaces, + # there would be a topological disconnect between flat ceiling and sloped + # skylights above. Should NECB users first 'project' (sloped) skylight rough + # openings onto flat ceilings when calculating %SRR? Without much needed + # clarification, the (clearer) 90.1 rules equally apply here to NECB cases. + + # If skylight wells are indeed required, well wall edges are always vertical + # (i.e. never splayed), requiring a vertical ray. + origin = OpenStudio::Point3d.new(0,0,0) + zenith = OpenStudio::Point3d.new(0,0,1) + ray = zenith - origin + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Accept a single 'OpenStudio::Model::Space' (vs an array of spaces). + if spaces.respond_to?(:spaceType) || spaces.respond_to?(:to_a) + spaces = spaces.respond_to?(:to_a) ? spaces.to_a : [spaces] + spaces = spaces.select { |space| space.respond_to?(:spaceType) } + spaces = spaces.select { |space| space.partofTotalFloorArea } + spaces = spaces.reject { |space| unconditioned?(space) } + return empty("spaces", mth, DBG, 0) if spaces.empty? + else + return mismatch("spaces", spaces, Array, mth, DBG, 0) + end + + mdl = spaces.first.model + + # Exit if mismatched or invalid argument classes/keys. + return mismatch("opts", opts, Hash, mth, DBG, 0) unless opts.is_a?(Hash) + return hashkey( "srr", opts, :srr, mth, ERR, 0) unless opts.key?(:srr) + + # Validate requested skylight-to-roof ratio. + if opts[:srr].respond_to?(:to_f) + srr = opts[:srr].to_f + log(WRN, "Resetting srr to 0% (#{mth})") if srr < 0 + log(WRN, "Resetting srr to 10% (#{mth})") if srr > 0.10 + srr = srr.clamp(0.00, 0.10) + else + return mismatch("srr", opts[:srr], Numeric, mth, DBG, 0) + end + + # Validate Frame & Divider object, if provided. + if opts.key?(:frame) + frame = opts[:frame] + + 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 + + f = f.frameWidth if frame + log(WRN, "Skip Frame&Divider (#{mth})") unless frame + else + frame = nil + log(ERR, "Skip invalid Frame&Divider object (#{mth})") + end + end + + # Validate skylight size, if provided. + if opts.key?(:size) + if opts[:size].respond_to?(:to_f) + w = opts[:size].to_f + w2 = w * w + return invalid(size, mth, 0, ERR, 0) if w.round(2) < gap4 + else + return mismatch("size", opts[:size], Numeric, mth, DBG, 0) + end + end + + f2 = 2 * f + w0 = w + f2 + w02 = w0 * w0 + wl = w0 + gap + wl2 = wl * wl + + # Validate purge request, if provided. + if opts.key?(:clear) + clear = opts[:clear] + + unless [true, false].include?(clear) + log(WRN, "Purging existing skylights by default (#{mth})") + clear = true + end + end + + getRoofs(spaces).each { |s| s.subSurfaces.map(&:remove) } if clear + + # Safely exit, e.g. if strictly called to purge existing roof subsurfaces. + return 0 if srr < TOL + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # The method seeks to insert a skylight array within the largest rectangular + # 'bounded box' that neatly 'fits' within a given roof surface. This equally + # applies to any vertically-cast overlap between roof and plenum (or attic) + # floor, which in turn generates skylight wells. Skylight arrays are + # inserted from left/right + top/bottom (as illustrated below), once a roof + # (or cast 3D overlap) is 'aligned' in 2D (possibly also 'realigned'). + # + # Depending on geometric complexity (e.g. building/roof concavity, + # triangulation), the total area of bounded boxes may be significantly less + # than the calculated "GROSS ROOF AREA", which can make it challenging to + # attain the desired %SRR. If :patterns are left unaltered, the method will + # select patterns that maximize the likelihood of attaining the requested + # %SRR, to the detriment of spatial distribution of daylighting. + # + # The default skylight module size is 1.2m x 1.2m (4' x 4'), which be + # overridden by the user, e.g. 2.4m x 2.4m (8' x 8'). + # + # Preset skylight allocation patterns (in order of precedence): + # 1. "array" + # _____________________ + # | _ _ _ | - ?x columns ("cols") >= ?x rows (min 2x2) + # | |_| |_| |_| | - SRR ~5% (1.2m x 1.2m), as illustrated + # | | - SRR ~19% (2.4m x 2.4m) + # | _ _ _ | - +suitable for wide spaces (storage, retail) + # | |_| |_| |_| | - ~1.4x height + skylight width 'ideal' rule + # |_____________________| - better daylight distribution, many wells + # + # 2. "strips" + # _____________________ + # | _ _ _ | - ?x columns (min 2), 1x row + # | | | | | | | | - ~doubles %SRR ... + # | | | | | | | | - SRR ~10% (1.2m x ?1.2m), as illustrated + # | | | | | | | | - SRR ~19% (2.4m x ?1.2m) + # | |_| |_| |_| | - ~roof monitor layout + # |_____________________| - fewer wells + # + # 3. "strip" + # ____________________ + # | | - 1x column, 1x row (min 1x) + # | ______________ | - SRR ~11% (1.2m x ?1.2m) + # | | ............ | | - SRR ~22% (2.4m x ?1.2m), as illustrated + # | |______________| | - +suitable for elongated bounded boxes + # | | - 1x well + # |____________________| + # + # TO-DO: Support strips/strip patterns along ridge of paired roof surfaces. + layouts = ["array", "strips", "strip"] + patterns = [] + + # Validate skylight placement patterns, if provided. + if opts.key?(:patterns) + if opts[:patterns].is_a?(Array) + opts[:patterns].each_with_index do |pattern, i| + pattern = trim(pattern).downcase + + if pattern.empty? + invalid("pattern #{i+1}", mth, 0, ERR) + next + end + + patterns << pattern if layouts.include?(pattern) + end + else + mismatch("patterns", opts[:patterns], Array, mth, DBG) + end + end + + patterns = layouts if patterns.empty? + + # The method first attempts to add skylights in ideal candidate spaces: + # - large roof surface areas (e.g. retail, classrooms ... not corridors) + # - not sidelit (favours core spaces) + # - having flat roofs (avoids sloped roofs) + # - not under plenums, nor attics (avoids wells) + # + # This ideal (albeit stringent) set of conditions is "combo a". + # + # If required %SRR has not yet been achieved, the method decrementally drops + # selection criteria and starts over, e.g.: + # - then considers sidelit spaces + # - then considers sloped roofs + # - then considers skylight wells + # + # A maximum number of skylights are allocated to roof surfaces matching a + # given combo. Priority is always given to larger roof areas. If + # unsuccessful in meeting the required %SRR target, a single criterion is + # then dropped (e.g. b, then c, etc.), and the allocation process is + # relaunched. An error message is logged if the %SRR isn't ultimately met. + # + # Through filters, users may restrict candidate roof surfaces: + # b. above occupied sidelit spaces ('false' restricts to core spaces) + # c. that are sloped ('false' restricts to flat roofs) + # d. above indirectly conditioned spaces (e.g. plenums, uninsulated wells) + # e. above unconditioned spaces (e.g. attics, insulated wells) + filters = ["a", "b", "bc", "bcd", "bcde"] + + # Prune filters, based on user-selected options. + [:sidelit, :sloped, :plenum, :attic].each do |opt| + next unless opts.key?(opt) + 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 } + end + end + + filters.reject! { |f| f.empty? } + filters.uniq! + + # Remaining filters may be further reduced (after space/roof processing), + # depending on geometry, e.g.: + # - if there are no sidelit spaces: filter "b" will be pruned away + # - if there are no sloped roofs : filter "c" will be pruned away + # - if no plenums are identified : filter "d" will be pruned away + # - if no attics are identified : filter "e" will be pruned away + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Break down spaces (and connected spaces) into groups. + sets = [] # collection of skylight arrays to deploy + rooms = {} # occupied CONDITIONED spaces to toplight + plenums = {} # unoccupied (INDIRECTLY-) CONDITIONED spaces above rooms + attics = {} # unoccupied UNCONDITIONED spaces above rooms + ceilings = {} # of occupied CONDITIONED space (if plenums/attics) + + # Select candidate 'rooms' to toplit - excludes plenums/attics. + spaces.each do |space| + next if unconditioned?(space) # e.g. attic + next unless space.partofTotalFloorArea # occupied (not plenum) + + # Already toplit? + if daylit?(space, false, true, false) + log(WRN, "#{id} is already toplit, skipping (#{mth})") + next + end + + # When unoccupied spaces are involved (e.g. plenums, attics), the occupied + # space (to toplight) may not share the same local transformation as its + # unoccupied space(s) above. Fetching local transformation. + t0 = transforms(space) + next unless t0[:t] + + rooms[space] = {} + rooms[space][:t ] = t0[:t] + rooms[space][:m ] = space.multiplier + rooms[space][:h ] = space.ceilingHeight + rooms[space][:roofs ] = facets(space, "Outdoors", "RoofCeiling") + rooms[space][:sidelit] = daylit?(space, true, false, false) + + # Fetch and process room-specific outdoor-facing roof surfaces, the most + # basic 'set' to track: + # - no skylight wells + # - 1x skylight array per roof surface + # - no need to preprocess space transformation + rooms[space][:roofs].each do |roof| + box = boundedBox(roof) + next if box.empty? + + bm2 = OpenStudio.getArea(box) + next if bm2.empty? + + bm2 = bm2.get + next if bm2.round(2) < w02.round(2) + + # Track if bounded box is significantly smaller than roof. + tight = bm2 < roof.grossArea / 2 ? true : false + + set = {} + set[:box ] = box + set[:bm2 ] = bm2 + set[:tight ] = tight + set[:roof ] = roof + set[:space ] = space + set[:sidelit] = rooms[space][:sidelit] + set[:t ] = rooms[space][:t ] + set[:sloped ] = slopedRoof?(roof) + sets << set + end + end + + # Process outdoor-facing roof surfaces of plenums and attics above. + rooms.each do |space, room| + t0 = room[:t] + toits = getRoofs(space) + rufs = room.key?(:roofs) ? toits - room[:roofs] : toits + next if rufs.empty? + + # Process room ceilings, as 1x or more are overlapping roofs above. Fetch + # vertically-cast overlaps. + rufs.each do |ruf| + espace = ruf.space + next if espace.empty? + + espace = espace.get + next if espace.partofTotalFloorArea + + m = espace.multiplier + ti = transforms(espace) + next unless ti[:t] + + ti = ti[:t] + ri = ti * ruf.vertices + + facets(space, "Surface", "RoofCeiling").each do |tile| + ci0 = cast(t0 * tile.vertices, ri, ray) + next if ci0.empty? + + olap = overlap(ri, ci0, false) + next if olap.empty? + + box = boundedBox(olap) + next if box.empty? + + # Adding skylight wells (plenums/attics) is contingent to safely + # linking new base roof 'inserts' through leader lines. Currently, + # this requires an offset from main roof surface edges. + # + # TO DO: expand the method to factor in cases where simple 'side' + # cutouts can be supported (no need for leader lines), e.g. + # skylight strips along roof ridges. + box = offset(box, -gap, 340) + next if box.empty? + + bm2 = OpenStudio.getArea(box) + next if bm2.empty? + + bm2 = bm2.get + next if bm2.round(2) < w02.round(2) + + # Vertically-cast box onto ceiling below. + cbox = cast(box, t0 * tile.vertices, ray) + next if cbox.empty? + + cm2 = OpenStudio.getArea(cbox) + next if cm2.empty? + + cm2 = cm2.get + + # Track if bounded boxes are significantly smaller than either roof + # or ceiling. + tight = bm2 < ruf.grossArea / 2 ? true : false + tight = cm2 < tile.grossArea / 2 ? true : tight + + unless ceilings.key?(tile) + floor = tile.adjacentSurface + + if floor.empty? + log(ERR, "#{tile.nameString} adjacent floor? (#{mth})") + next + end + + floor = floor.get + + if floor.space.empty? + log(ERR, "#{floor.nameString} space? (#{mth})") + next + end + + espce = floor.space.get + + unless espce == espace + log(ERR, "#{espce.nameString} != #{espace.nameString}? (#{mth})") next + end + + ceilings[tile] = {} + ceilings[tile][:roofs] = [] + ceilings[tile][:space] = space + ceilings[tile][:floor] = floor + end + + ceilings[tile][:roofs] << ruf + + # More detailed skylight set entries with suspended ceilings. + set = {} + set[:olap ] = olap + set[:box ] = box + set[:cbox ] = cbox + set[:bm2 ] = bm2 + set[:cm2 ] = cm2 + set[:tight ] = tight + set[:roof ] = ruf + set[:space ] = space + set[:clng ] = tile + set[:t ] = t0 + set[:sidelit] = room[:sidelit] + set[:sloped ] = slopedRoof?(ruf) + + if unconditioned?(espace) # e.g. attic + unless attics.key?(espace) + attics[espace] = {t: ti, m: m, bm2: 0, roofs: []} + end + + attics[espace][:bm2 ] += bm2 + attics[espace][:roofs] << ruf + + set[:attic] = espace + + ceilings[tile][:attic] = espace + else # e.g. plenum + unless plenums.key?(espace) + plenums[espace] = {t: ti, m: m, bm2: 0, roofs: []} + end + + plenums[espace][:bm2 ] += bm2 + plenums[espace][:roofs] << ruf + + set[:plenum] = espace + + ceilings[tile][:plenum] = espace + end + + sets << set + break # only 1x unique ruf/ceiling pair. + end + end + end + + # Ensure uniqueness of plenum roofs, and set GROSS ROOF AREA. + attics.values.each do |attic| + attic[:roofs ].uniq! + attic[:ridges] = getHorizontalRidges(attic[:roofs]) # TO-DO + end + + plenums.values.each do |plenum| + plenum[:roofs ].uniq! + # plenum[:m2 ] = plenum[:roofs].sum(&:grossArea) + plenum[:ridges] = getHorizontalRidges(plenum[:roofs]) # TO-DO + end + + # Regardless of the selected skylight arrangement pattern, the current + # solution may only consider attic/plenum sets that can be successfully + # linked to leader line anchors, for both roof and ceiling surfaces. + [attics, plenums].each do |greniers| + k = greniers == attics ? :attic : :plenum + + greniers.each do |spce, grenier| + grenier[:roofs].each do |roof| + sts = sets + + sts = sts.select { |st| st.key?(k) } + sts = sts.select { |st| st.key?(:box) } + sts = sts.select { |st| st.key?(:bm2) } + sts = sts.select { |st| st.key?(:roof) } + sts = sts.select { |st| st.key?(:space) } + sts = sts.select { |st| st[k ] == spce } + sts = sts.select { |st| st[:roof] == roof } + next if sts.empty? + + sts = sts.sort_by { |st| st[:bm2] } + genAnchors(roof, sts, :box) + end + end + end + + # Delete voided sets. + sets.reject! { |set| set.key?(:void) } + + # Repeat leader line loop for ceilings. + ceilings.each do |tile, ceiling| + k = ceiling.key?(:attic) ? :attic : :plenum + next unless ceiling.key?(k) + + space = ceiling[:space] + spce = ceiling[k ] + next unless ceiling.key?(:roofs) + next unless rooms.key?(space) + + stz = [] + + ceiling[:roofs].each do |roof| + sts = sets + + sts = sts.select { |st| st.key?(k) } + sts = sts.select { |st| st.key?(:cbox) } + stz = stz.select { |st| st.key?(:cm2) } + sts = sts.select { |st| st.key?(:roof) } + sts = sts.select { |st| st.key?(:clng) } + sts = sts.select { |st| st.key?(:space) } + sts = sts.select { |st| st[k ] == spce } + sts = sts.select { |st| st[:roof ] == roof } + sts = sts.select { |st| st[:clng ] == tile } + sts = sts.select { |st| st[:space] == space } + next unless sts.size == 1 + + stz << sts.first + end + + next if stz.empty? + + stz = stz.sort_by { |st| st[:cm2] } + genAnchors(tile, stz, :cbox) + end + + # Delete voided sets. + sets.reject! { |set| set.key?(:void) } + + m2 = 0 # existing skylight rough opening area + rm2 = grossRoofArea(spaces) + + # Tally existing skylight rough opening areas (%SRR calculations). + rooms.values.each do |room| + m = room[:m] + + room[:roofs].each do |roof| + roof.subSurfaces.each do |sub| + next unless fenestration?(sub) + + id = sub.nameString + xm2 = sub.grossArea + + if sub.allowWindowPropertyFrameAndDivider + unless sub.windowPropertyFrameAndDivider.empty? + fw = sub.windowPropertyFrameAndDivider.get.frameWidth + vec = offset(sub.vertices, fw, 300) + aire = OpenStudio.getArea(vec) + + if aire.empty? + log(ERR, "Skipping '#{id}': invalid Frame&Divider (#{mth})") + else + xm2 = aire.get + end + end + end + + m2 += xm2 * sub.multiplier * m + end + end + end + + # Required skylight area to add. + sm2 = rm2 * srr - m2 + + # Skip if existing skylights exceed or ~roughly match requested %SRR. + if sm2.round(2) < w02.round(2) + log(INF, "Skipping: existing srr > requested srr (#{mth})") + return 0 + end + + # Any sidelit/sloped roofs being targeted? + # + # TODO: enable double-ridged, sloped roofs have double-sloped + # skylights/wells (patterns "strip"/"strips"). + sidelit = sets.any? { |set| set[:sidelit] } + sloped = sets.any? { |set| set[:sloped ] } + + # Precalculate skylight rows + cols, for each selected pattern. In the case + # of 'cols x rows' arrays of skylights, the method initially overshoots + # with regards to ideal skylight placement, e.g.: + # + # aceee.org/files/proceedings/2004/data/papers/SS04_Panel3_Paper18.pdf + # + # ... yet skylight areas are subsequently contracted to strictly meet SRR%. + sets.each_with_index do |set, i| + id = "set #{i+1}" + well = set.key?(:clng) + space = set[:space] + tight = set[:tight] + factor = tight ? 1.75 : 1.25 + room = rooms[space] + h = room[:h] + t = OpenStudio::Transformation.alignFace(set[:box]) + abox = poly(set[:box], false, false, false, t, :ulc) + obox = getRealignedFace(abox) + next unless obox[:set] + + width = width(obox[:set]) + depth = height(obox[:set]) + area = width * depth + skym2 = srr * area + + # Flag sets if too narrow/shallow to hold a single skylight. + if well + if width.round(2) < wl.round(2) + log(ERR, "#{id}: Too narrow") + set[:void] = true + next + end + + if depth.round(2) < wl.round(2) + log(ERR, "#{id}: Too shallow") + set[:void] = true + next + end + else + if width.round(2) < w0.round(2) + log(ERR, "#{id}: Too narrow") + set[:void] = true + next + end + + if depth.round(2) < w0.round(2) + log(ERR, "#{id}: Too shallow") + set[:void] = true + next + end + end + + # Estimate number of skylight modules per 'pattern'. Default spacing + # varies based on bounded box size (i.e. larger vs smaller rooms). + patterns.each do |pattern| + cols = 1 + rows = 1 + wx = w0 + wy = w0 + wxl = wl + wyl = wl + dX = nil + dY = nil + + case pattern + when "array" # min 2x cols x min 2x rows + cols = 2 + rows = 2 + + if tight + sp = 1.4 * h / 2 + lx = well ? width - cols * wxl : width - cols * wx + ly = well ? depth - rows * wyl : depth - rows * wy + next if lx.round(2) < sp.round(2) + next if ly.round(2) < sp.round(2) + + if well + cols = ((width - wxl) / (wxl + sp)).round(2).to_i + 1 + rows = ((depth - wyl) / (wyl + sp)).round(2).to_i + 1 + else + cols = ((width - wx) / (wx + sp)).round(2).to_i + 1 + rows = ((depth - wy) / (wy + sp)).round(2).to_i + 1 + end + + next if cols < 2 + next if rows < 2 + + dX = well ? 0.0 : bfr + f + dY = well ? 0.0 : bfr + f + else + sp = 1.4 * h + lx = well ? (width - cols * wxl) / cols : (width - cols * wx) / cols + ly = well ? (depth - rows * wyl) / rows : (depth - rows * wy) / cols + next if lx.round(2) < sp.round(2) + next if ly.round(2) < sp.round(2) + + if well + cols = (width / (wxl + sp)).round(2).to_i + rows = (depth / (wyl + sp)).round(2).to_i + else + cols = (width / (wx + sp)).round(2).to_i + rows = (depth / (wy + sp)).round(2).to_i + end + + next if cols < 2 + next if rows < 2 + + ly = well ? (depth - rows * wyl) / rows : (depth - rows * wy) / cols + dY = ly / 2 + end + + # Current skylight area. If undershooting, adjust skylight width/depth + # as well as reduce spacing. For geometrical constrained cases, + # undershooting means not reaching 1.75x the required SRR%. Otherwise, + # undershooting means not reaching 1.25x the required SRR%. Any + # consequent overshooting is later corrected. + tm2 = wx * cols * wy * rows + undershot = tm2.round(2) < factor * skym2.round(2) ? true : false + + # Inflate skylight width/depth (and reduce spacing) to reach SRR%. + if undershot + ratio2 = 1 + (factor * skym2 - tm2) / tm2 + ratio = Math.sqrt(ratio2) + + sp = w + wx *= ratio + wy *= ratio + wxl = wx + gap + wyl = wy + gap + + if tight + if well + lx = (width - cols * wxl) / (cols - 1) + ly = (depth - rows * wyl) / (rows - 1) + else + lx = (width - cols * wx) / (cols - 1) + ly = (depth - rows * wy) / (rows - 1) + end + + lx = lx.round(2) < sp.round(2) ? sp : lx + ly = ly.round(2) < sp.round(2) ? sp : ly + + if well + wxl = (width - (cols - 1) * lx) / cols + wyl = (depth - (rows - 1) * ly) / rows + wx = wxl - gap + wy = wyl - gap + else + wx = (width - (cols - 1) * lx) / cols + wy = (depth - (rows - 1) * ly) / rows + wxl = wx + gap + wyl = wy + gap + end + else + if well + lx = (width - cols * wxl) / cols + ly = (depth - rows * wyl) / rows + else + lx = (width - cols * wx) / cols + ly = (depth - rows * wy) / rows + end + + lx = lx.round(2) < sp.round(2) ? sp : lx + ly = ly.round(2) < sp.round(2) ? sp : ly + + if well + wxl = (width - cols * lx) / cols + wyl = (depth - rows * ly) / rows + wx = wxl - gap + wy = wyl - gap + lx = (width - cols * wxl) / cols + ly = (depth - rows * wyl) / rows + else + wx = (width - cols * lx) / cols + wy = (depth - rows * ly) / rows + wxl = wx + gap + wyl = wy + gap + lx = (width - cols * wx) / cols + ly = (depth - rows * wy) / rows + end + end + + dY = ly / 2 + end + when "strips" # min 2x cols x 1x row + cols = 2 + + if tight + sp = h / 2 + lx = well ? width - cols * wxl : width - cols * wx + ly = well ? depth - wyl : depth - wy + next if lx.round(2) < sp.round(2) + next if ly.round(2) < sp.round(2) + + if well + cols = ((width - wxl) / (wxl + sp)).round(2).to_i + 1 + else + cols = ((width - wx) / (wx + sp)).round(2).to_i + 1 + end + + next if cols < 2 + + if well + wyl = depth - ly + wy = wyl - gap + else + wy = depth - ly + wyl = wy + gap + end + + dX = well ? 0 : bfr + f + dY = ly / 2 + else + sp = h + lx = well ? (width - cols * wxl) / cols : (width - cols * wx) / cols + ly = well ? depth - wyl : depth - wy + next if lx.round(2) < sp.round(2) + next if ly.round(2) < w.round(2) + + if well + cols = (width / (wxl + sp)).round(2).to_i + else + cols = (width / (wx + sp)).round(2).to_i + end + + next if cols < 2 + + if well + wyl = depth - ly + wy = wyl - gap + else + wy = depth - ly + wyl = wy + gap + end + + dY = ly / 2 + end + + tm2 = wx * cols * wy + undershot = tm2.round(2) < factor * skym2.round(2) ? true : false + + # Inflate skylight width (and reduce spacing) to reach SRR%. + if undershot + ratio2 = 1 + (factor * skym2 - tm2) / tm2 + + sp = w + wx *= ratio2 + wxl = wx + gap + + if tight + if well + lx = (width - cols * wxl) / (cols - 1) + else + lx = (width - cols * wx) / (cols - 1) + end + + lx = lx.round(2) < sp.round(2) ? sp : lx + + if well + wxl = (width - (cols - 1) * lx) / cols + wx = wxl - gap + else + wx = (width - (cols - 1) * lx) / cols + wxl = wx + gap + end else - sub[:sill ] = sill - sub[:height] = height - log(WRN, "(Re)set '#{id}' height to #{sub[:height]} m (#{mth})") + if well + lx = (width - cols * wxl) / cols + else + lx = (width - cols * wx) / cols + end + + lx = lx.round(2) < sp.round(2) ? sp : lx + + if well + wxl = (width - cols * lx) / cols + wx = wxl - gap + lx = (width - cols * wxl) / cols + else + wx = (width - cols * lx) / cols + wxl = wx + gap + lx = (width - cols * wx) / cols + end + end + end + else # "strip" 1 (long?) row x 1 column + sp = w + lx = well ? width - wxl : width - wx + ly = well ? depth - wyl : depth - wy + + if tight + next if lx.round(2) < sp.round(2) + next if ly.round(2) < sp.round(2) + + if well + wxl = width - lx + wyl = depth - ly + wx = wxl - gap + wy = wyl - gap + else + wx = width - lx + wy = depth - ly + wxl = wx + gap + wyl = wy + gap end + + dX = well ? 0.0 : bfr + f + dY = ly / 2 else - sub[:sill] = sill + next if lx.round(2) < sp.round(2) + next if ly.round(2) < sp.round(2) + + if well + wxl = width - lx + wyl = depth - ly + wx = wxl - gap + wy = wyl - gap + else + wx = width - lx + wy = depth - ly + wxl = wx + gap + wyl = wy + gap + end + + dY = ly / 2 end - else - sub[:sill ] = typ_sill - sub[:height] = sub[:head] - sub[:sill] - end - elsif sub.key?(:sill) # no "head" - if sub.key?(:height) - head = sub[:sill] + sub[:height] - if head > max_head - head = max_head - height = head - sub[:sill] + tm2 = wx * wy + undershot = tm2.round(2) < factor * skym2.round(2) ? true : false - if height < glass - sub[:ratio ] = 0 if sub.key?(:ratio) - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: invalid '#{id}' sill/height combo (#{mth})") - next + # Inflate skylight depth to reach SRR%. + if undershot + ratio2 = 1 + (factor * skym2 - tm2) / tm2 + + sp = w + wy *= ratio2 + wyl = wy + gap + + ly = well ? depth - wy : depth - wyl + ly = ly.round(2) < sp.round(2) ? sp : lx + + if well + wyl = depth - ly + wy = wyl - gap else - sub[:head ] = head - sub[:height] = height - log(WRN, "(Re)set '#{id}' height to #{sub[:height]} m (#{mth})") + wy = depth - ly + wyl = wy + gap end - else - sub[:head] = head end - else - sub[:head ] = typ_head - sub[:height] = sub[:head] - sub[:sill] end - elsif sub.key?(:height) # neither "head" nor "sill" - head = typ_head - sill = head - sub[:height] - if sill < min_sill - sill = min_sill - head = sill + sub[:height] - end + st = {} + st[:tight] = tight + st[:cols ] = cols + st[:rows ] = rows + st[:wx ] = wx + st[:wy ] = wy + st[:wxl ] = wxl + st[:wyl ] = wyl + st[:dX ] = dX if dX + st[:dY ] = dY if dY + + set[pattern] = st + end + end - sub[:head] = head - sub[:sill] = sill + # Delete voided sets. + sets.reject! { |set| set.key?(:void) } + + # Final reset of filters. + filters.map! { |f| f.include?("b") ? f.delete("b") : f } unless sidelit + filters.map! { |f| f.include?("c") ? f.delete("c") : f } unless sloped + filters.map! { |f| f.include?("d") ? f.delete("d") : f } if plenums.empty? + filters.map! { |f| f.include?("e") ? f.delete("e") : f } if attics.empty? + + filters.reject! { |f| f.empty? } + filters.uniq! + + # Initialize skylight area tally. + skm2 = 0 + + # Assign skylight pattern. + filters.each_with_index do |filter, i| + next if skm2.round(2) >= sm2.round(2) + + sts = sets + sts = sts.sort_by { |st| st[:bm2] }.reverse! + sts = sts.reject { |st| st.key?(:pattern) } + + if filter.include?("a") + # Start with the default (ideal) allocation selection: + # - large roof surface areas (e.g. retail, classrooms not corridors) + # - not sidelit (favours core spaces) + # - having flat roofs (avoids sloped roofs) + # - not under plenums, nor attics (avoids wells) + sts = sts.reject { |st| st[:sidelit] } + sts = sts.reject { |st| st[:sloped ] } + sts = sts.reject { |st| st.key?(:clng) } else - sub[:head ] = typ_head - sub[:sill ] = typ_sill - sub[:height] = sub[:head] - sub[:sill] + sts = sts.reject { |st| st[:sidelit] } unless filter.include?("b") + sts = sts.reject { |st| st[:sloped] } unless filter.include?("c") + sts = sts.reject { |st| st.key?(:plenum) } unless filter.include?("d") + sts = sts.reject { |st| st.key?(:attic) } unless filter.include?("e") end - # Log/reset "width" if beyond min/max. - if sub.key?(:width) - unless sub[:width].between?(glass, max_width) - sub[:width] = glass if sub[:width] < glass - sub[:width] = max_width if sub[:width] > max_width - log(WRN, "Reset '#{id}' width to #{sub[:width]} m (#{mth})") - end - end + next if sts.empty? - # Log/reset "count" if < 1 (or not an Integer) - if sub[:count].respond_to?(:to_i) - sub[:count] = sub[:count].to_i + # Tally precalculated skylights per pattern (once filtered). + fpm2 = {} - if sub[:count] < 1 - sub[:count] = 1 - log(WRN, "Reset '#{id}' count to #{sub[:count]} (#{mth})") - end - else - sub[:count] = 1 - end + patterns.each do |pattern| + sts.each do |st| + next unless st.key?(pattern) - sub[:count] = 1 unless sub.key?(:count) + cols = st[pattern][:cols] + rows = st[pattern][:rows] + wx = st[pattern][:wx ] + wy = st[pattern][:wy ] - # Log/reset if left-sided buffer under min jamb position. - if sub.key?(:l_buffer) - if sub[:l_buffer] < min_ljamb - sub[:l_buffer] = min_ljamb - log(WRN, "Reset '#{id}' left buffer to #{sub[:l_buffer]} m (#{mth})") - end - end + fpm2[pattern] = {m2: 0, tight: false} unless fpm2.key?(pattern) - # Log/reset if right-sided buffer beyond max jamb position. - if sub.key?(:r_buffer) - if sub[:r_buffer] > max_rjamb - sub[:r_buffer] = min_rjamb - log(WRN, "Reset '#{id}' right buffer to #{sub[:r_buffer]} m (#{mth})") + fpm2[pattern][:m2 ] += wx * wy * cols * rows + fpm2[pattern][:tight] = st[:tight] ? true : false end end - centre = mid_x - centre += sub[:centreline] if sub.key?(:centreline) - n = sub[:count ] - h = sub[:height ] + frames - w = 0 # overall width of sub(s) bounding box (to calculate) - x0 = 0 # left-side X-axis coordinate of sub(s) bounding box - xf = 0 # right-side X-axis coordinate of sub(s) bounding box + pattern = nil + next if fpm2.empty? - # Log/reset "offset", if conflicting vs "width". - if sub.key?(:ratio) - if sub[:ratio] < TOL - sub[:ratio ] = 0 - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: '#{id}' ratio ~0 (#{mth})") - next - end + fpm2 = fpm2.sort_by { |_, fm2| fm2[:m2] }.to_h - # Log/reset if "ratio" beyond min/max? - unless sub[:ratio].between?(min, max) - sub[:ratio] = min if sub[:ratio] < min - sub[:ratio] = max if sub[:ratio] > max - log(WRN, "Reset ratio (min/max) to #{sub[:ratio]} (#{mth})") + # Select suitable pattern, often overshooting. Favour array unless + # geometrically constrainted. + if fpm2.keys.include?("array") + if (fpm2["array"][:m2]).round(2) >= sm2.round(2) + pattern = "array" unless fpm2[:tight] end + end - # Log/reset "count" unless 1. - unless sub[:count] == 1 - sub[:count] = 1 - log(WRN, "Reset count (ratio) to 1 (#{mth})") + unless pattern + if fpm2.values.first[:m2].round(2) >= sm2.round(2) + pattern = fpm2.keys.first + elsif fpm2.values.last[:m2].round(2) <= sm2.round(2) + pattern = fpm2.keys.last + else + fpm2.keep_if { |_, fm2| fm2[:m2].round(2) >= sm2.round(2) } + + pattern = fpm2.keys.first end + end - area = s.grossArea * sub[:ratio] # sub m2, including (optional) frames - w = area / h - width = w - frames - x0 = centre - w/2 - xf = centre + w/2 + skm2 += fpm2[pattern][:m2] - if sub.key?(:l_buffer) - if sub.key?(:centreline) - log(WRN, "Skip #{id} left buffer (vs centreline) (#{mth})") - else - x0 = sub[:l_buffer] - frame - xf = x0 + w - centre = x0 + w/2 - end - elsif sub.key?(:r_buffer) - if sub.key?(:centreline) - log(WRN, "Skip #{id} right buffer (vs centreline) (#{mth})") - else - xf = max_x - sub[:r_buffer] + frame - x0 = xf - w - centre = x0 + w/2 + # Update matching sets. + sts.each do |st| + sets.each do |set| + next unless set.key?(pattern) + next unless st[:roof] == set[:roof] + next unless same?(st[:box], set[:box]) + + if st.key?(:clng) + next unless set.key?(:clng) + next unless st[:clng] == set[:clng] end - end - # Too wide? - if x0 < min_ljamb || xf > max_rjamb - sub[:ratio ] = 0 if sub.key?(:ratio) - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: invalid (ratio) width/centreline (#{mth})") - next + set[:pattern] = pattern + set[:cols ] = set[pattern][:cols] + set[:rows ] = set[pattern][:rows] + set[:w ] = set[pattern][:wx ] + set[:d ] = set[pattern][:wy ] + set[:w0 ] = set[pattern][:wxl ] + set[:d0 ] = set[pattern][:wyl ] + set[:dX ] = set[pattern][:dX ] + set[:dY ] = set[pattern][:dY ] end + end + end - if sub.key?(:width) && (sub[:width] - width).abs > TOL - sub[:width] = width - log(WRN, "Reset width (ratio) to #{sub[:width]} (#{mth})") + # Skylight size contraction if overshot (e.g. -13.2% if overshot by +13.2%). + # This is applied on a surface/pattern basis; individual skylight sizes may + # vary from one surface to the next, depending on respective patterns. + if skm2.round(2) > sm2.round(2) + ratio2 = 1 - (skm2 - sm2) / skm2 + ratio = Math.sqrt(ratio2) + skm2 *= ratio2 + + sets.each do |set| + next if set.key?(:void) + next unless set.key?(:pattern) + + pattern = set[:pattern] + next unless set.key?(pattern) + + case pattern + when "array" # equally adjust both width and depth + xr = set[:w] * ratio + yr = set[:d] * ratio + dyr = set[:d] - yr + + set[:w ] = xr + set[:d ] = yr + set[:w0] = set[:w] + gap + set[:d0] = set[:d] + gap + set[:dY] += dyr / 2 + when "strips" # adjust depth + xr2 = set[:w] * ratio2 + + set[:w ] = xr2 + set[:w0] = set[:w] + gap + else # "strip", adjust width + yr2 = set[:d] * ratio2 + dyr = set[:d] - yr2 + + set[:d ] = yr2 + set[:d0] = set[:w] + gap + set[:dY] += dyr / 2 end + end + end - sub[:width] = width unless sub.key?(:width) - else - unless sub.key?(:width) - sub[:ratio ] = 0 if sub.key?(:ratio) - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: missing '#{id}' width (#{mth})") - next + # Generate skylight well roofs for attics & plenums. + [attics, plenums].each do |greniers| + k = greniers == attics ? :attic : :plenum + + greniers.each do |spce, grenier| + ti = grenier[:t] + + grenier[:roofs].each do |roof| + sts = sets + + sts = sts.select { |st| st.key?(k) } + sts = sts.select { |st| st.key?(:pattern) } + sts = sts.select { |st| st.key?(:clng) } + sts = sts.select { |st| st.key?(:roof) } + sts = sts.select { |st| st.key?(:space) } + sts = sts.select { |st| st[:roof] == roof } + sts = sts.select { |st| st[k ] == spce } + sts = sts.select { |st| st.key?(st[:pattern]) } + sts = sts.select { |st| rooms.key?(st[:space]) } + sts = sts.select { |st| st.key?(:ld) } + sts = sts.select { |st| st[:ld].key?(roof) } + next if sts.empty? + + # If successful, 'genInserts' returns extended roof surface vertices, + # including leader lines to support cutouts. The final selection is + # contingent to successfully inserting corresponding room ceiling + # inserts (vis-à-vis attic/plenum floor below). The method also + # generates new roof inserts. See key:value pair :vts. + vertices = genInserts(roof, sts) + next if vertices.empty? + + roof.setVertices(ti * vertices) end + end + end - width = sub[:width] + frames - gap = (max_x - n * width) / (n + 1) - gap = sub[:offset] - width if sub.key?(:offset) - gap = 0 if gap < bfr - offset = gap + width - - if sub.key?(:offset) && (offset - sub[:offset]).abs > TOL - sub[:offset] = offset - log(WRN, "Reset sub offset to #{sub[:offset]} m (#{mth})") - end + # Repeat for ceilings below attic/plenum floors. + ceilings.each do |tile, ceiling| + k = ceiling.key?(:attic) ? :attic : :plenum + next unless ceiling.key?(k) + next unless ceiling.key?(:roofs) + + greniers = ceiling.key?(:attic) ? attics : plenums + space = ceiling[:space] + spce = ceiling[k ] + floor = ceiling[:floor] + next unless rooms.key?(space) + next unless greniers.key?(spce) + + room = rooms[space] + grenier = greniers[spce] + ti = grenier[:t] + t0 = room[:t] + stz = [] + + ceiling[:roofs].each do |roof| + sts = sets + + sts = sts.select { |st| st.key?(k) } + sts = sts.select { |st| st.key?(:clng) } + sts = sts.select { |st| st.key?(:cm2) } + sts = sts.select { |st| st.key?(:roof) } + sts = sts.select { |st| st.key?(:space) } + sts = sts.select { |st| st[:clng] == tile } + sts = sts.select { |st| st[:roof] == roof } + sts = sts.select { |st| st[k ] == spce } + sts = sts.select { |st| rooms.key?(st[:space]) } + sts = sts.select { |st| st.key?(:ld) } + sts = sts.select { |st| st.key?(:vtx) } + sts = sts.select { |st| st.key?(:vts) } + sts = sts.select { |st| st[:ld].key?(roof) } + sts = sts.select { |st| st[:ld].key?(tile) } + next unless sts.size == 1 + + stz << sts.first + end - sub[:offset] = offset unless sub.key?(:offset) + next if stz.empty? - # Overall width (including frames) of bounding box around array. - w = n * width + (n - 1) * gap - x0 = centre - w/2 - xf = centre + w/2 + # Vertically-cast set roof :vtx onto ceiling. + stz.each do |st| + cvtx = cast(ti * st[:vtx], t0 * tile.vertices, ray) + st[:cvtx] = t0.inverse * cvtx + end - if sub.key?(:l_buffer) - if sub.key?(:centreline) - log(WRN, "Skip #{id} left buffer (vs centreline) (#{mth})") - else - x0 = sub[:l_buffer] - frame - xf = x0 + w - centre = x0 + w/2 - end - elsif sub.key?(:r_buffer) - if sub.key?(:centreline) - log(WRN, "Skip #{id} right buffer (vs centreline) (#{mth})") - else - xf = max_x - sub[:r_buffer] + frame - x0 = xf - w - centre = x0 + w/2 + # Extended ceiling vertices. + stz = stz.sort_by { |st| st[:cm2] } + vertices = genExtendedVertices(tile, stz, :cvtx) + next if vertices.empty? + + # Reset ceiling and adjacent floor vertices. + tile.setVertices(t0.inverse * vertices) + floor.setVertices(ti.inverse * vertices.to_a.reverse) + + # Add new roof inserts & skylights for the (now) toplit space. + stz.each_with_index do |st, i| + sub = {} + sub[:type ] = "Skylight" + sub[:width ] = st[:w] - f2 + sub[:height] = st[:d] - f2 + sub[:sill ] = gap / 2 + sub[:frame ] = frame if frame + + st[:vts].each do |id, vt| + roof = OpenStudio::Model::Surface.new(t0.inverse * vt, mdl) + roof.setSpace(space) + roof.setName("#{i}:#{id}:#{space.nameString}") + + # Generate well walls. + v0 = roof.vertices + vX = cast(roof, tile, ray) + s0 = getSegments(v0) + sX = getSegments(vX) + + s0.each_with_index do |sg, j| + sg0 = sg.to_a + sgX = sX[j].to_a + vec = OpenStudio::Point3dVector.new + vec << sg0.first + vec << sg0.last + vec << sgX.last + vec << sgX.first + + grenier_wall = OpenStudio::Model::Surface.new(vec, mdl) + grenier_wall.setSpace(spce) + grenier_wall.setName("#{id}:#{j}:#{spce.nameString}") + + room_wall = OpenStudio::Model::Surface.new(vec.to_a.reverse, mdl) + room_wall.setSpace(space) + room_wall.setName("#{id}:#{j}:#{space.nameString}") + + grenier_wall.setAdjacentSurface(room_wall) + room_wall.setAdjacentSurface(grenier_wall) end - end - # Too wide? - if x0 < bfr || xf > max_x - bfr - sub[:ratio ] = 0 if sub.key?(:ratio) - sub[:count ] = 0 - sub[:multiplier] = 0 - sub[:height ] = 0 if sub.key?(:height) - sub[:width ] = 0 if sub.key?(:width) - log(ERR, "Skip: invalid array width/centreline (#{mth})") - next + # Add individual skylights. + addSubs(roof, [sub]) end end + end - # Initialize left-side X-axis coordinate of only/first sub. - pos = x0 + frame - - # Generate sub(s). - sub[:count].times do |i| - name = "#{id}|#{i}" - fr = 0 - fr = sub[:frame].frameWidth if sub[:frame] - - vec = OpenStudio::Point3dVector.new - vec << OpenStudio::Point3d.new(pos, sub[:head], 0) - vec << OpenStudio::Point3d.new(pos, sub[:sill], 0) - vec << OpenStudio::Point3d.new(pos + sub[:width], sub[:sill], 0) - vec << OpenStudio::Point3d.new(pos + sub[:width], sub[:head], 0) - vec = t * vec - - # Log/skip if conflict between individual sub and base surface. - vc = vec - vc = offset(vc, fr, 300) if fr > 0 - ok = fits?(vc, s) - log(ERR, "Skip '#{name}': won't fit in '#{nom}' (#{mth})") unless ok - break unless ok - - # Log/skip if conflicts with existing subs (even if same array). - s.subSurfaces.each do |sb| - nome = sb.nameString - fd = sb.windowPropertyFrameAndDivider - fr = 0 if fd.empty? - fr = fd.get.frameWidth unless fd.empty? - vk = sb.vertices - vk = offset(vk, fr, 300) if fr > 0 - oops = overlaps?(vc, vk) - log(ERR, "Skip '#{name}': overlaps '#{nome}' (#{mth})") if oops - ok = false if oops - break if oops + # New direct roof loop. No overlaps, so no need for relative space + # coordinate adjustments. + rooms.each do |space, room| + room[:roofs].each do |roof| + sets.each_with_index do |set, i| + next if set.key?(:clng) + next unless set.key?(:box) + next unless set.key?(:roof) + next unless set.key?(:cols) + next unless set.key?(:rows) + next unless set.key?(:d) + next unless set.key?(:w) + next unless set.key?(:dX) + next unless set.key?(:dY) + next unless set.key?(:tight) + next unless set[:roof] == roof + + tight = set[:tight] + + d1 = set[:d] - f2 + w1 = set[:w] - f2 + + # Y-axis 'height' of the roof, once re/aligned. + # TODO: retrieve st[:out], +efficient + y = alignedHeight(set[:box]) + dY = set[:dY] if set[:dY] + + set[:rows].times.each do |j| + sub = {} + sub[:type ] = "Skylight" + sub[:count ] = set[:cols] + sub[:width ] = w1 + sub[:height ] = d1 + sub[:frame ] = frame if frame + sub[:id ] = "set #{i+1}:#{j+1}" + sub[:sill ] = dY + j * (2 * dY + d1) + sub[:r_buffer] = set[:dX] if set[:dX] + sub[:l_buffer] = set[:dX] if set[:dX] + addSubs(roof, [sub]) + end end - - break unless ok - - sb = OpenStudio::Model::SubSurface.new(vec, mdl) - sb.setName(name) - sb.setSubSurfaceType(sub[:type]) - sb.setConstruction(sub[:assembly]) if sub[:assembly] - ok = sb.allowWindowPropertyFrameAndDivider - sb.setWindowPropertyFrameAndDivider(sub[:frame]) if sub[:frame] && ok - sb.setMultiplier(sub[:multiplier]) if sub[:multiplier] > 1 - sb.setSurface(s) - - # Reset "pos" if array. - pos += sub[:offset] if sub.key?(:offset) end end - true + rm2 end ## diff --git a/lib/osut/version.rb b/lib/osut/version.rb index 0a6db76..7baa8ae 100644 --- a/lib/osut/version.rb +++ b/lib/osut/version.rb @@ -29,5 +29,5 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. module OSut - VERSION = "0.4.0".freeze # OSut version + VERSION = "0.5.0".freeze # OSut version end diff --git a/spec/files/osms/in/warehouse.osm b/spec/files/osms/in/warehouse.osm new file mode 100644 index 0000000..5cc3b49 --- /dev/null +++ b/spec/files/osms/in/warehouse.osm @@ -0,0 +1,7121 @@ + +OS:Version, + {3984e9e8-62e7-4e57-87b4-79a3e4ecb931}, !- Handle + 2.9.1; !- Version Identifier + +OS:Schedule:Constant, + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Handle + Always On Discrete, !- Name + {1ab205d4-a33a-4074-bbea-78f9a7c19099}, !- Schedule Type Limits Name + 1; !- Value + +OS:ScheduleTypeLimits, + {1ab205d4-a33a-4074-bbea-78f9a7c19099}, !- Handle + Always On Discrete Limits, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + Discrete, !- Numeric Type + Availability; !- Unit Type + +OS:Timestep, + {b0fa043b-3e91-4705-b798-a26bbea47620}, !- Handle + 6; !- Number of Timesteps per Hour + +OS:Sizing:Parameters, + {004b243b-4b6a-4f4c-8e8e-be8491724b37}, !- Handle + 1.2, !- Heating Sizing Factor + 1.2, !- Cooling Sizing Factor + 6; !- Timesteps in Averaging Window + +OS:ConvergenceLimits, + {ceac8795-dbb3-4040-a5d6-64105977a538}, !- Handle + 1, !- Minimum System Timestep {minutes} + 20; !- Maximum HVAC Iterations + +OS:Connection, + {b742b2ef-79b6-4b4e-bc85-5c20c1c8300f}, !- Handle + {15de7a4c-1724-4ba3-a1e6-a66b0ba90514}, !- Name + , !- Source Object + 11, !- Outlet Port + , !- Target Object + 2; !- Inlet Port + +OS:Space, + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Handle + Zone3 Bulk Storage, !- Name + {aabbd8d8-a688-47dd-8dd9-7290bdad9599}, !- Space Type Name + , !- Default Construction Set Name + , !- Default Schedule Set Name + 0, !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + {db75d8b1-6332-4998-b7c2-ad46d4bbd08b}, !- Building Story Name + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- Thermal Zone Name + Yes; !- Part of Total Floor Area + +OS:SubSurface, + {6dac61d8-7411-4423-985c-36b2c5e8b7cf}, !- Handle + Skylight_r2c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 37.39673, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 37.39673, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 36.17753, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 36.17753, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Handle + Bulk Storage Roof, !- Name + RoofCeiling, !- Surface Type + , !- Construction Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 100.579094839222, 8.53398380454007, !- X,Y,Z Vertex 2 {m} + 0, 100.579094839222, 8.53398380454007, !- X,Y,Z Vertex 3 {m} + 0, 30.4785135876431, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {5985fe05-ad32-4b65-b0c2-46302891e062}, !- Handle + Fine Storage Left Door, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {e7c4e556-9445-43b4-bfcc-f9fc21f24f21}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 20.2682115357827, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 0, 20.2682115357827, 0, !- X,Y,Z Vertex 2 {m} + 0, 19.3538561281534, 0, !- X,Y,Z Vertex 3 {m} + 0, 19.3538561281534, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {e7c4e556-9445-43b4-bfcc-f9fc21f24f21}, !- Handle + Fine Storage Left Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 0, 30.4785135876431, 0, !- X,Y,Z Vertex 2 {m} + 0, 9.14355407629293, 0, !- X,Y,Z Vertex 3 {m} + 0, 9.14355407629293, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:Connection, + {dc04fefb-3f18-45be-8f68-9d2cd3eb4a21}, !- Handle + {e66b1d83-cc39-44fb-8764-30d98d02c75a}, !- Name + , !- Source Object + 11, !- Outlet Port + , !- Target Object + 2; !- Inlet Port + +OS:Space, + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Handle + Zone2 Fine Storage, !- Name + {fa494797-3b47-43b7-8d83-65371f4bbb13}, !- Space Type Name + , !- Default Construction Set Name + , !- Default Schedule Set Name + 0, !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + {db75d8b1-6332-4998-b7c2-ad46d4bbd08b}, !- Building Story Name + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- Thermal Zone Name + Yes; !- Part of Total Floor Area + +OS:Surface, + {b8444fba-ff4e-4e81-b369-e9f3efc4a2d6}, !- Handle + Office Roof, !- Name + RoofCeiling, !- Surface Type + , !- Construction Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + Surface, !- Outside Boundary Condition + {a273ffc4-632f-4b89-9d6d-f94185f45049}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 25.9067365494966, 0, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 25.9067365494966, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 2 {m} + 0, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 3 {m} + 0, 0, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:Connection, + {3386e9fe-e830-491d-a1bc-dbb0ba0af916}, !- Handle + {20ab44d7-01dc-49cd-b623-cd8f0a86b916}, !- Name + , !- Source Object + 11, !- Outlet Port + , !- Target Object + 2; !- Inlet Port + +OS:Space, + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Handle + Zone1 Office, !- Name + {626ccb3f-df17-4651-ba00-0696cb5a3ca7}, !- Space Type Name + , !- Default Construction Set Name + , !- Default Schedule Set Name + 0, !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + {db75d8b1-6332-4998-b7c2-ad46d4bbd08b}, !- Building Story Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Thermal Zone Name + Yes; !- Part of Total Floor Area + +OS:Surface, + {a273ffc4-632f-4b89-9d6d-f94185f45049}, !- Handle + Office Roof Reversed, !- Name + Floor, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Surface, !- Outside Boundary Condition + {b8444fba-ff4e-4e81-b369-e9f3efc4a2d6}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 0, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 0, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 0, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {cc340669-2635-4608-ad68-8448960033df}, !- Handle + Skylight_r7c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 9.50753, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 9.50753, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 8.28833, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 8.28833, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {ca7079b5-217f-4c8e-9b5f-7ce447ae706c}, !- Handle + Skylight_r1c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 42.97457, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 42.97457, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 41.75537, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 41.75537, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {b8b06532-b20c-4b27-b353-fb685ea15ba8}, !- Handle + Bulk Storage Rear Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 100.579094839222, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 100.579094839222, 0, !- X,Y,Z Vertex 2 {m} + 0, 100.579094839222, 0, !- X,Y,Z Vertex 3 {m} + 0, 100.579094839222, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {b189551d-e42f-4762-b7d3-22cefb330f22}, !- Handle + Skylight_r5c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 20.66321, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 20.66321, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 19.44401, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 19.44401, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {e86312f3-df9c-4a72-8bf5-276fd9968053}, !- Handle + Bulk Storage Floor, !- Name + Floor, !- Surface Type + , !- Construction Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space Name + Ground, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 100.579094839222, 0, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 30.4785135876431, 0, !- X,Y,Z Vertex 2 {m} + 0, 30.4785135876431, 0, !- X,Y,Z Vertex 3 {m} + 0, 100.579094839222, 0; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {9f541237-384a-4267-84c0-29c65974973b}, !- Handle + Skylight_r1c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 42.97457, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 42.97457, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 41.75537, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 41.75537, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {2d7ae645-bbe8-4865-b17c-c19f599fae46}, !- Handle + Skylight_r4c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 26.24105, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 26.24105, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 25.02185, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 25.02185, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {d7dee294-b5d7-44b5-95a1-700bae533d52}, !- Handle + Skylight_r5c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 20.66321, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 20.66321, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 19.44401, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 19.44401, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {21b9512b-88b8-4df7-8ddf-b5b9f2c9f530}, !- Handle + Overhead Door 3, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 75.8153025492622, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 75.8153025492622, 0, !- X,Y,Z Vertex 2 {m} + 0, 73.3770214622508, 0, !- X,Y,Z Vertex 3 {m} + 0, 73.3770214622508, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Handle + Bulk Storage Left Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 100.579094839222, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 0, 100.579094839222, 0, !- X,Y,Z Vertex 2 {m} + 0, 30.4785135876431, 0, !- X,Y,Z Vertex 3 {m} + 0, 30.4785135876431, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {eafc8f7a-abc5-4cd4-9dbf-f615e529ab78}, !- Handle + Skylight_r3c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 31.81889, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 31.81889, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 30.59969, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 30.59969, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {fc6efd96-2946-4c9e-b61a-a97517f30481}, !- Handle + Overhead Door 7, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 39.5458713799669, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 39.5458713799669, 0, !- X,Y,Z Vertex 2 {m} + 0, 37.1075902929555, 0, !- X,Y,Z Vertex 3 {m} + 0, 37.1075902929555, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {eb38baf8-4819-4f38-99d7-de17082804a6}, !- Handle + Fine Storage Floor, !- Name + Floor, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Ground, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 30.4785135876431, 0, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 0, 0, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 0, 0, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 9.14355407629293, 0, !- X,Y,Z Vertex 4 {m} + 0, 9.14355407629293, 0, !- X,Y,Z Vertex 5 {m} + 0, 30.4785135876431, 0; !- X,Y,Z Vertex 6 {m} + +OS:SubSurface, + {deca29f3-9920-47db-8341-dfaef5245578}, !- Handle + Fine Storage Right Door, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {a0ba0fa9-aa51-4fbe-bdcc-bf0a22e397dd}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 45.7177703814647, 14.7820790900069, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 14.7820790900069, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 15.6964344976362, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 15.6964344976362, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {a0ba0fa9-aa51-4fbe-bdcc-bf0a22e397dd}, !- Handle + Fine Storage Right Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 0, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 0, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 30.4785135876431, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 30.4785135876431, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {71b64072-759e-42a0-8204-04b81749a0fb}, !- Handle + Skylight_r3c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 31.81889, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 31.81889, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 30.59969, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 30.59969, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {65a4927d-efbe-4ffa-bee2-c79215c46ccd}, !- Handle + Skylight_r4c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 26.24105, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 26.24105, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 25.02185, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 25.02185, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {000ddd2c-3ea4-4776-9e8e-e4d848761cac}, !- Handle + Skylight_r6c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 15.08537, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 15.08537, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 13.86617, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 13.86617, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {74e5cc19-6b77-40e3-bb48-dd2003bc1d25}, !- Handle + FineStorage_skylight_5, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 40.6146, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 40.6146, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 39.3954, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 39.3954, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Handle + Fine Storage Roof, !- Name + RoofCeiling, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 0, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 2 {m} + 0, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 3 {m} + 0, 0, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {5bb15698-f168-4579-82ad-82ac03a40e93}, !- Handle + Skylight_r7c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 9.50753, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 9.50753, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 8.28833, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 8.28833, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {723b4775-b4d1-44f3-962a-910d9d76a9e6}, !- Handle + FineStorage_skylight_14, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 17.7546, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 17.7546, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 16.5354, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 16.5354, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {cad599ac-bdde-493c-83a4-f9617becb319}, !- Handle + FineStorage_skylight_6, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 17.7546, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 17.7546, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 16.5354, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 16.5354, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {9068a0fd-e4ed-49cc-abe3-df5dbff873c7}, !- Handle + Skylight_r2c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 37.39673, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 37.39673, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 36.17753, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 36.17753, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {acb38544-5d65-4bc8-9c65-f5b58d540d1a}, !- Handle + Skylight_r6c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 15.08537, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 15.08537, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 13.86617, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 13.86617, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {4163e0f8-32c9-4f3c-a01f-27d86e349545}, !- Handle + Skylight_r3c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 31.81889, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 31.81889, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 30.59969, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 30.59969, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {1b2bce67-d0d6-4a6e-9c3c-4796cf73b0e2}, !- Handle + Office Left Wall Window1, !- Name + FixedWindow, !- Sub Surface Type + , !- Construction Name + {af7ffab5-cb7d-4793-a68a-918ca67f7479}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 7.51904930207155, 2.43929602151392, !- X,Y,Z Vertex 1 {m} + 0, 7.51904930207155, 0.914355407629293, !- X,Y,Z Vertex 2 {m} + 0, 5.38555335093654, 0.914355407629293, !- X,Y,Z Vertex 3 {m} + 0, 5.38555335093654, 2.43929602151392; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {af7ffab5-cb7d-4793-a68a-918ca67f7479}, !- Handle + Office Left Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 0, 9.14355407629293, 0, !- X,Y,Z Vertex 2 {m} + 0, 0, 0, !- X,Y,Z Vertex 3 {m} + 0, 0, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {a966bae9-9301-4492-ae85-a3416cb9da09}, !- Handle + Office Floor, !- Name + Floor, !- Surface Type + , !- Construction Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + Ground, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 25.9067365494966, 9.14355407629293, 0, !- X,Y,Z Vertex 1 {m} + 25.9067365494966, 0, 0, !- X,Y,Z Vertex 2 {m} + 0, 0, 0, !- X,Y,Z Vertex 3 {m} + 0, 9.14355407629293, 0; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {fbc62a85-f0b5-40f2-be1b-0a363ae6f388}, !- Handle + FineStorage_skylight_10, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 40.6146, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 40.6146, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 39.3954, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 39.3954, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {a3206222-0b02-4aa5-bcc4-af8f70e1eec4}, !- Handle + FineStorage_skylight_12, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 6.3246, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 6.3246, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 5.1054, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 5.1054, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {7f4aebb1-9044-44cb-91ee-e1a559b436a0}, !- Handle + Office Right Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + Surface, !- Outside Boundary Condition + {12dbc2fb-3517-42f9-9f00-f6147aaade5b}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 25.9067365494966, 0, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 25.9067365494966, 0, 0, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 9.14355407629293, 0, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 9.14355407629293, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {12dbc2fb-3517-42f9-9f00-f6147aaade5b}, !- Handle + Office Right Wall Reversed, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Surface, !- Outside Boundary Condition + {7f4aebb1-9044-44cb-91ee-e1a559b436a0}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 25.9067365494966, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 25.9067365494966, 9.14355407629293, 0, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 0, 0, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 0, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {ae336c51-d6bc-43de-b204-2d60f16d3d8e}, !- Handle + Office Front Door, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {a722f50f-fda4-4b2c-843f-44ea72a832c9}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 12.039012867119, 0, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 12.039012867119, 0, 0, !- X,Y,Z Vertex 2 {m} + 13.8677236823776, 0, 0, !- X,Y,Z Vertex 3 {m} + 13.8677236823776, 0, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {a722f50f-fda4-4b2c-843f-44ea72a832c9}, !- Handle + Office Front Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 0, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 0, 0, 0, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 0, 0, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 0, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {ddb38950-c035-4666-b655-f318ba7f0342}, !- Handle + Overhead Door 2, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 84.882660341586, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 84.882660341586, 0, !- X,Y,Z Vertex 2 {m} + 0, 82.4443792545746, 0, !- X,Y,Z Vertex 3 {m} + 0, 82.4443792545746, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {26cfb3f0-e78e-41d5-8957-e61cf58a1db9}, !- Handle + Skylight_r2c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 37.39673, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 37.39673, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 36.17753, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 36.17753, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {b269fc4f-b539-46ab-a05e-93c4dd91399b}, !- Handle + FineStorage_skylight_13, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 12.0396, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 12.0396, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 10.8204, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 10.8204, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {2ce21271-4aa0-4b4e-af87-47994c8beac1}, !- Handle + Bulk Storage Front Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space Name + Surface, !- Outside Boundary Condition + {139e955e-746e-43dc-9033-026507021b5a}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 30.4785135876431, 0, !- X,Y,Z Vertex 2 {m} + 0, 30.4785135876431, 0, !- X,Y,Z Vertex 3 {m} + 0, 30.4785135876431, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {139e955e-746e-43dc-9033-026507021b5a}, !- Handle + Bulk Storage Front Wall Reversed, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Surface, !- Outside Boundary Condition + {2ce21271-4aa0-4b4e-af87-47994c8beac1}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 0, 30.4785135876431, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 30.4785135876431, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 30.4785135876431, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {bfd58ce8-1370-448e-89bc-dc1d1aa79552}, !- Handle + Office Front Wall Window2, !- Name + FixedWindow, !- Sub Surface Type + , !- Construction Name + {a722f50f-fda4-4b2c-843f-44ea72a832c9}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 18.5918932884623, 0, 2.43929602151392, !- X,Y,Z Vertex 1 {m} + 18.5918932884623, 0, 0.914355407629293, !- X,Y,Z Vertex 2 {m} + 22.2493149189795, 0, 0.914355407629293, !- X,Y,Z Vertex 3 {m} + 22.2493149189795, 0, 2.43929602151392; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {ff679961-81cf-4795-9c24-deb699dd97b4}, !- Handle + FineStorage_skylight_7, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 23.4696, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 23.4696, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 22.2504, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 22.2504, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {145bfd04-ce48-4581-a2a9-e8970362044f}, !- Handle + Fine Storage Office Left Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 9.14355407629293, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 0, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 2 {m} + 0, 0, 4.26699190227003, !- X,Y,Z Vertex 3 {m} + 0, 0, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {de362f6f-5304-403a-9858-e02e47775660}, !- Handle + Bulk Storage Door-1, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {93a00761-ab33-4e9d-bff7-fed32e66feb1}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 45.7177703814647, 47.5464811967232, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 47.5464811967232, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 48.4608366043525, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 48.4608366043525, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {93a00761-ab33-4e9d-bff7-fed32e66feb1}, !- Handle + Bulk Storage Right Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 45.7177703814647, 30.4785135876431, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 30.4785135876431, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 100.579094839222, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 100.579094839222, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {63248e82-ac72-47a3-bc8f-4fca4c4cc602}, !- Handle + FineStorage_skylight_11, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 6.3246, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 6.3246, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 5.1054, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 5.1054, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {06c61cc1-a825-4c84-afd9-2f77dd05133f}, !- Handle + Skylight_r1c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 42.97457, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 42.97457, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 41.75537, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 41.75537, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {fe5ff198-983e-4083-9bbd-55550c42673c}, !- Handle + Office Front Wall Window 1, !- Name + FixedWindow, !- Sub Surface Type + , !- Construction Name + {a722f50f-fda4-4b2c-843f-44ea72a832c9}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 3.65742163051717, 0, 2.43929602151392, !- X,Y,Z Vertex 1 {m} + 3.65742163051717, 0, 0.914355407629293, !- X,Y,Z Vertex 2 {m} + 7.31484326103434, 0, 0.914355407629293, !- X,Y,Z Vertex 3 {m} + 7.31484326103434, 0, 2.43929602151392; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {77c7155f-c910-4a10-b06a-073c4dc43d10}, !- Handle + Overhead Door 4, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 66.7479447569384, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 66.7479447569384, 0, !- X,Y,Z Vertex 2 {m} + 0, 64.3096636699269, 0, !- X,Y,Z Vertex 3 {m} + 0, 64.3096636699269, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {203ed4e9-1069-4391-b393-bf5122d95e51}, !- Handle + Office Left Wall Door, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {af7ffab5-cb7d-4793-a68a-918ca67f7479}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 5.02895474196111, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 0, 5.02895474196111, 0, !- X,Y,Z Vertex 2 {m} + 0, 4.11459933433182, 0, !- X,Y,Z Vertex 3 {m} + 0, 4.11459933433182, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {0a0502e2-60ef-4c6a-b711-c7eef0da342c}, !- Handle + Skylight_r1c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 42.97457, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 42.97457, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 41.75537, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 41.75537, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {16519e3d-8e15-4801-a2f2-c20194bff92d}, !- Handle + Skylight_r5c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 20.66321, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 20.66321, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 19.44401, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 19.44401, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {14bef6c2-f2cb-44cc-a950-c64694551653}, !- Handle + Skylight_r4c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 26.24105, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 26.24105, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 25.02185, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 25.02185, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {3e0e2784-6483-4b29-9e66-6e25721edc69}, !- Handle + Overhead Door 6, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 48.6132291722907, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 48.6132291722907, 0, !- X,Y,Z Vertex 2 {m} + 0, 46.1749480852793, 0, !- X,Y,Z Vertex 3 {m} + 0, 46.1749480852793, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {c82475ee-c086-4192-b350-9a8ca99d1640}, !- Handle + Overhead Door 1, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 93.9500181339099, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 93.9500181339099, 0, !- X,Y,Z Vertex 2 {m} + 0, 91.5117370468984, 0, !- X,Y,Z Vertex 3 {m} + 0, 91.5117370468984, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {f77edabe-bbe0-41d3-b18f-375612f55f46}, !- Handle + FineStorage_skylight_3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 29.1846, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 29.1846, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 27.9654, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 27.9654, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {81ba3a04-e516-409e-a5a3-f95b0f1c45a2}, !- Handle + Skylight_r8c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 3.92969, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 3.92969, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 2.71049, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 2.71049, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {ddfb59e9-8491-4429-8cc9-ede8a5389d06}, !- Handle + FineStorage_skylight_2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 23.4696, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 23.4696, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 22.2504, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 22.2504, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {893f81b8-5a3d-4ade-bae7-a4ae16f2e54f}, !- Handle + Skylight_r7c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 9.50753, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 9.50753, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 8.28833, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 8.28833, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {06082934-0979-49fc-be68-3926952b5b43}, !- Handle + Overhead Door 5, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {2eb36d53-8956-4573-9d42-f3de2486a693}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 57.6805869646146, 3.04785135876431, !- X,Y,Z Vertex 1 {m} + 0, 57.6805869646146, 0, !- X,Y,Z Vertex 2 {m} + 0, 55.2423058776031, 0, !- X,Y,Z Vertex 3 {m} + 0, 55.2423058776031, 3.04785135876431; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {55af93ba-f5f7-4ea3-b37f-75d24979c576}, !- Handle + Skylight_r7c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 9.50753, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 9.50753, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 8.28833, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 8.28833, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {857f9ef3-3061-45eb-b04c-b4a4b76ff6cd}, !- Handle + Office Rear Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + Surface, !- Outside Boundary Condition + {4047aa22-d7b0-4e4a-a8a7-99a21122cc53}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 25.9067365494966, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 25.9067365494966, 9.14355407629293, 0, !- X,Y,Z Vertex 2 {m} + 0, 9.14355407629293, 0, !- X,Y,Z Vertex 3 {m} + 0, 9.14355407629293, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {4047aa22-d7b0-4e4a-a8a7-99a21122cc53}, !- Handle + Office Rear Wall Reversed, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Surface, !- Outside Boundary Condition + {857f9ef3-3061-45eb-b04c-b4a4b76ff6cd}, !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 9.14355407629293, 4.26699190227003, !- X,Y,Z Vertex 1 {m} + 0, 9.14355407629293, 0, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 9.14355407629293, 0, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 9.14355407629293, 4.26699190227003; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {4961241f-39bd-4641-a266-965f00075f05}, !- Handle + Office Left Wall Window2, !- Name + FixedWindow, !- Sub Surface Type + , !- Construction Name + {af7ffab5-cb7d-4793-a68a-918ca67f7479}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 0, 3.75800072535639, 2.43929602151392, !- X,Y,Z Vertex 1 {m} + 0, 3.75800072535639, 0.914355407629293, !- X,Y,Z Vertex 2 {m} + 0, 1.62450477422138, 0.914355407629293, !- X,Y,Z Vertex 3 {m} + 0, 1.62450477422138, 2.43929602151392; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {08c17da5-fd99-4f4f-baf6-5047f75e5f9b}, !- Handle + Skylight_r8c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 3.92969, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 3.92969, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 2.71049, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 2.71049, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {0a7f9d39-e106-4c1b-b394-2e172c184462}, !- Handle + FineStorage_skylight_8, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 29.1846, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 29.1846, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 27.9654, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 27.9654, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {fd037e5f-9b53-49b8-bdb3-18ca014ef11e}, !- Handle + Bulk Storage Door-2, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {93a00761-ab33-4e9d-bff7-fed32e66feb1}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 45.7177703814647, 65.071626509618, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 65.071626509618, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 65.9859819172473, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 65.9859819172473, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {1ceb88b4-39b3-42d2-85d2-298472205cfe}, !- Handle + Skylight_r8c1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 3.92969, 34.440914, 8.533984, !- X,Y,Z Vertex 1 {m} + 3.92969, 35.660114, 8.533984, !- X,Y,Z Vertex 2 {m} + 2.71049, 35.660114, 8.533984, !- X,Y,Z Vertex 3 {m} + 2.71049, 34.440914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {e59e81bf-fe4e-491f-a56e-ae1fab10310f}, !- Handle + Skylight_r5c3, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 20.66321, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 20.66321, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 19.44401, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 19.44401, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {a50eff28-8688-422b-9443-0a4fe183df9f}, !- Handle + Fine Storage Front Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 25.9067365494966, 0, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 25.9067365494966, 0, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 0, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 0, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {110a020c-6a19-4d4a-8aec-67e0503fa40e}, !- Handle + Skylight_r6c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 15.08537, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 15.08537, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 13.86617, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 13.86617, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {d237d869-aeeb-49fc-9e36-79d55341f2c9}, !- Handle + Bulk Storage Door-3, !- Name + Door, !- Sub Surface Type + , !- Construction Name + {93a00761-ab33-4e9d-bff7-fed32e66feb1}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + 1, !- Multiplier + , !- Number of Vertices + 45.7177703814647, 82.5967718225128, 2.13349595113502, !- X,Y,Z Vertex 1 {m} + 45.7177703814647, 82.5967718225128, 0, !- X,Y,Z Vertex 2 {m} + 45.7177703814647, 83.5111272301421, 0, !- X,Y,Z Vertex 3 {m} + 45.7177703814647, 83.5111272301421, 2.13349595113502; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {7d251698-d80f-4ca6-9fb9-a26a36c0f281}, !- Handle + FineStorage_skylight_4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 34.8996, 26.058914, 8.533984, !- X,Y,Z Vertex 1 {m} + 34.8996, 27.278114, 8.533984, !- X,Y,Z Vertex 2 {m} + 33.6804, 27.278114, 8.533984, !- X,Y,Z Vertex 3 {m} + 33.6804, 26.058914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {0ff5fa4f-c6c0-4cf3-b75e-3745e253f927}, !- Handle + FineStorage_skylight_1, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 12.0396, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 12.0396, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 10.8204, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 10.8204, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:Surface, + {5b1ce779-a299-4910-83a2-6fb6d1cee24f}, !- Handle + Fine Storage Office Front Wall, !- Name + Wall, !- Surface Type + , !- Construction Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space Name + Outdoors, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + SunExposed, !- Sun Exposure + WindExposed, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0, 0, 8.53398380454007, !- X,Y,Z Vertex 1 {m} + 0, 0, 4.26699190227003, !- X,Y,Z Vertex 2 {m} + 25.9067365494966, 0, 4.26699190227003, !- X,Y,Z Vertex 3 {m} + 25.9067365494966, 0, 8.53398380454007; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {af4a4bae-04c9-4642-b347-76863b4b56bf}, !- Handle + Skylight_r2c2, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 37.39673, 43.076914, 8.533984, !- X,Y,Z Vertex 1 {m} + 37.39673, 44.296114, 8.533984, !- X,Y,Z Vertex 2 {m} + 36.17753, 44.296114, 8.533984, !- X,Y,Z Vertex 3 {m} + 36.17753, 43.076914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {891a1ea7-f396-4874-ac74-83aab7bdef53}, !- Handle + Skylight_r3c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 31.81889, 51.712914, 8.533984, !- X,Y,Z Vertex 1 {m} + 31.81889, 52.932114, 8.533984, !- X,Y,Z Vertex 2 {m} + 30.59969, 52.932114, 8.533984, !- X,Y,Z Vertex 3 {m} + 30.59969, 51.712914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {85aa7a62-9fef-4759-9a95-6835bafd426b}, !- Handle + Skylight_r8c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 3.92969, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 3.92969, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 2.71049, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 2.71049, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {854ce644-01ce-4842-bb1c-ba3491ad5197}, !- Handle + FineStorage_skylight_9, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {dcc10c35-212a-4ba7-8909-e38fa0c8d3e8}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 34.8996, 18.438914, 8.533984, !- X,Y,Z Vertex 1 {m} + 34.8996, 19.658114, 8.533984, !- X,Y,Z Vertex 2 {m} + 33.6804, 19.658114, 8.533984, !- X,Y,Z Vertex 3 {m} + 33.6804, 18.438914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {49483e8c-e8d5-4cf0-a3a3-01c73866ba6b}, !- Handle + Skylight_r6c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 15.08537, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 15.08537, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 13.86617, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 13.86617, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:SubSurface, + {b376154b-e1fb-47a6-a929-969dc028bada}, !- Handle + Skylight_r4c4, !- Name + Skylight, !- Sub Surface Type + , !- Construction Name + {516ff702-eb03-4a25-9228-e1ad9dc5895c}, !- Surface Name + , !- Outside Boundary Condition Object + , !- View Factor to Ground + , !- Shading Control Name + , !- Frame and Divider Name + , !- Multiplier + , !- Number of Vertices + 26.24105, 60.348914, 8.533984, !- X,Y,Z Vertex 1 {m} + 26.24105, 61.568114, 8.533984, !- X,Y,Z Vertex 2 {m} + 25.02185, 61.568114, 8.533984, !- X,Y,Z Vertex 3 {m} + 25.02185, 60.348914, 8.533984; !- X,Y,Z Vertex 4 {m} + +OS:ClimateZones, + {376b058c-4e5c-42e7-91c5-a7e3e46c14bb}, !- Handle + , !- Active Institution + , !- Active Year + ASHRAE, !- Climate Zone Institution Name 1 + ANSI/ASHRAE Standard 169, !- Climate Zone Document Name 1 + 2006, !- Climate Zone Document Year 1 + 3B; !- Climate Zone Value 1 + +OS:LifeCycleCost:Parameters, + {9b956084-335f-4719-be60-6c3e1f278aa5}, !- Handle + FEMP, !- Analysis Type + , !- Discounting Convention + , !- Inflation Approach + , !- Real Discount Rate + , !- Nominal Discount Rate + , !- Inflation + , !- Base Date Month + , !- Base Date Year + , !- Service Date Month + , !- Service Date Year + , !- Length of Study Period in Years + , !- Tax Rate + , !- Depreciation Method + Yes; !- Use NIST Fuel Escalation Rates + +OS:YearDescription, + {429de7de-6704-419c-b62c-ed3f08bd405f}, !- Handle + , !- Calendar Year + Sunday; !- Day of Week for Start Day + +OS:Building, + {c23ded4b-6f18-4e30-b6db-cc00e1198751}, !- Handle + -Warehouse-ASHRAE 169-2013-3B created: 2020-09-13 14:34:30 -0400, !- Name + , !- Building Sector Type + , !- North Axis {deg} + , !- Nominal Floor to Floor Height {m} + , !- Space Type Name + {c99232a4-00e1-48ec-aa23-aecb05855d37}, !- Default Construction Set Name + , !- Default Schedule Set Name + 1, !- Standards Number of Stories + 1, !- Standards Number of Above Ground Stories + , !- Standards Template + Warehouse; !- Standards Building Type + +OS:Facility, + {df4e9cbf-49a4-4de4-b2f6-6cba678a29a8}; !- Handle + +OS:BuildingStory, + {db75d8b1-6332-4998-b7c2-ad46d4bbd08b}, !- Handle + Building Story 1, !- Name + 0, !- Nominal Z Coordinate {m} + , !- Nominal Floor to Floor Height {m} + , !- Default Construction Set Name + , !- Default Schedule Set Name + ; !- Group Rendering Name + +OS:SpaceType, + {aabbd8d8-a688-47dd-8dd9-7290bdad9599}, !- Handle + Warehouse Bulk, !- Name + {ed15bfb0-21d6-4e35-bd9f-898c0e2a0040}, !- Default Construction Set Name + {2d495c68-5976-4304-87a6-a9db83980fd9}, !- Default Schedule Set Name + {397a9834-ca46-44f2-bf97-1aa5fc23a67d}, !- Group Rendering Name + {942fa9b8-a027-44bc-b161-52b25ef42125}, !- Design Specification Outdoor Air Object Name + , !- Standards Template + Warehouse, !- Standards Building Type + Bulk; !- Standards Space Type + +OS:Rendering:Color, + {397a9834-ca46-44f2-bf97-1aa5fc23a67d}, !- Handle + Warehouse Bulk, !- Name + 41, !- Rendering Red Value + 31, !- Rendering Green Value + 169; !- Rendering Blue Value + +OS:SpaceType, + {fa494797-3b47-43b7-8d83-65371f4bbb13}, !- Handle + Warehouse Fine, !- Name + {d62c5b92-0d22-4e3e-ab39-1e07e2a927e9}, !- Default Construction Set Name + {09d862b0-be72-4723-bac9-41e51e79fba0}, !- Default Schedule Set Name + {f4306e95-b5d7-4c53-b22d-f8fa8e9ac849}, !- Group Rendering Name + {228500fa-fa0e-46e7-9bf3-f46bd8b5720b}, !- Design Specification Outdoor Air Object Name + , !- Standards Template + Warehouse, !- Standards Building Type + Fine; !- Standards Space Type + +OS:Rendering:Color, + {f4306e95-b5d7-4c53-b22d-f8fa8e9ac849}, !- Handle + Warehouse Fine, !- Name + 120, !- Rendering Red Value + 149, !- Rendering Green Value + 230; !- Rendering Blue Value + +OS:SpaceType, + {626ccb3f-df17-4651-ba00-0696cb5a3ca7}, !- Handle + Warehouse Office, !- Name + {1d26ed1d-dcb3-4e00-8739-759394e87a9c}, !- Default Construction Set Name + {bd895036-261d-454c-b19d-59c5e8629c2a}, !- Default Schedule Set Name + {c5bf423d-0ee6-40ac-b721-797e5dbdcfa2}, !- Group Rendering Name + {7ca430ea-1111-4060-84a7-2dea28f9e67f}, !- Design Specification Outdoor Air Object Name + , !- Standards Template + Warehouse, !- Standards Building Type + Office; !- Standards Space Type + +OS:Rendering:Color, + {c5bf423d-0ee6-40ac-b721-797e5dbdcfa2}, !- Handle + Warehouse Office, !- Name + 120, !- Rendering Red Value + 230, !- Rendering Green Value + 199; !- Rendering Blue Value + +OS:Lights:Definition, + {3d4f7a61-52bb-41ab-9bd6-52e305812218}, !- Handle + Warehouse Bulk Lights Definition, !- Name + Watts/Area, !- Design Level Calculation Method + , !- Lighting Level {W} + 6.24306804169164, !- Watts per Space Floor Area {W/m2} + , !- Watts per Person {W/person} + 0.42, !- Fraction Radiant + 0.18; !- Fraction Visible + +OS:Lights, + {5e66653c-6672-4aa7-9c7b-b9e14dc74a98}, !- Handle + Warehouse Bulk Lights, !- Name + {3d4f7a61-52bb-41ab-9bd6-52e305812218}, !- Lights Definition Name + {aabbd8d8-a688-47dd-8dd9-7290bdad9599}, !- Space or SpaceType Name + , !- Schedule Name + 1, !- Fraction Replaceable + , !- Multiplier + General; !- End-Use Subcategory + +OS:AdditionalProperties, + {ac500663-3e34-4cda-a3ad-577fbce1aae9}, !- Handle + {3d4f7a61-52bb-41ab-9bd6-52e305812218}, !- Object Name + lpd_fraction_linear_fluorescent, !- Feature Name 1 + Double, !- Feature Data Type 1 + 1; !- Feature Value 1 + +OS:ElectricEquipment:Definition, + {4a4ce912-19e3-4d03-9377-ebd6b62fda62}, !- Handle + Warehouse Bulk Elec Equip Definition, !- Name + Watts/Area, !- Design Level Calculation Method + , !- Design Level {W} + 2.55841615086008, !- Watts per Space Floor Area {W/m2} + ; !- Watts per Person {W/person} + +OS:ElectricEquipment, + {27a25be9-0180-4f23-9337-adb392ffa97d}, !- Handle + Warehouse Bulk Elec Equip, !- Name + {4a4ce912-19e3-4d03-9377-ebd6b62fda62}, !- Electric Equipment Definition Name + {aabbd8d8-a688-47dd-8dd9-7290bdad9599}, !- Space or SpaceType Name + , !- Schedule Name + , !- Multiplier + General; !- End-Use Subcategory + +OS:DesignSpecification:OutdoorAir, + {942fa9b8-a027-44bc-b161-52b25ef42125}, !- Handle + Warehouse Bulk Ventilation, !- Name + Sum, !- Outdoor Air Method + , !- Outdoor Air Flow per Person {m3/s-person} + 0.0003048, !- Outdoor Air Flow per Floor Area {m3/s-m2} + , !- Outdoor Air Flow Rate {m3/s} + , !- Outdoor Air Flow Air Changes per Hour {1/hr} + ; !- Outdoor Air Flow Rate Fraction Schedule Name + +OS:DefaultScheduleSet, + {2d495c68-5976-4304-87a6-a9db83980fd9}, !- Handle + Warehouse Bulk Schedule Set, !- Name + , !- Hours of Operation Schedule Name + {4ba9fc77-4cc5-416c-a8c6-7cda5a62c47f}, !- Number of People Schedule Name + {07fef766-cf84-4a84-a794-8ea3f92c8e73}, !- People Activity Level Schedule Name + {5c128e1d-bfc6-4304-99b3-6c5f446e2bb8}, !- Lighting Schedule Name + {167bfc28-d976-4f1c-8e8c-4d05109ec795}, !- Electric Equipment Schedule Name + , !- Gas Equipment Schedule Name + , !- Hot Water Equipment Schedule Name + {01fc67bb-02c0-4027-b29d-14c133cad401}, !- Infiltration Schedule Name + , !- Steam Equipment Schedule Name + ; !- Other Equipment Schedule Name + +OS:Schedule:Ruleset, + {4ba9fc77-4cc5-416c-a8c6-7cda5a62c47f}, !- Handle + Warehouse BLDG_OCC_SCH, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {923f2674-2f30-461e-a17f-32aa9e67947c}, !- Default Day Schedule Name + {ab0c2b81-43ae-4f8a-ba46-d4821e1f3161}, !- Summer Design Day Schedule Name + {8c09733c-d28b-4774-9807-683cb94345f0}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {923f2674-2f30-461e-a17f-32aa9e67947c}, !- Handle + Warehouse BLDG_OCC_SCH Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Day, + {8c09733c-d28b-4774-9807-683cb94345f0}, !- Handle + Warehouse BLDG_OCC_SCH Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.11, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.21, !- Value Until Time 3 + 12, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 13, !- Hour 5 + 0, !- Minute 5 + 0.53, !- Value Until Time 5 + 17, !- Hour 6 + 0, !- Minute 6 + 1, !- Value Until Time 6 + 18, !- Hour 7 + 0, !- Minute 7 + 0.32, !- Value Until Time 7 + 24, !- Hour 8 + 0, !- Minute 8 + 0; !- Value Until Time 8 + +OS:Schedule:Day, + {ab0c2b81-43ae-4f8a-ba46-d4821e1f3161}, !- Handle + Warehouse BLDG_OCC_SCH Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.11, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.21, !- Value Until Time 3 + 12, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 13, !- Hour 5 + 0, !- Minute 5 + 0.53, !- Value Until Time 5 + 17, !- Hour 6 + 0, !- Minute 6 + 1, !- Value Until Time 6 + 18, !- Hour 7 + 0, !- Minute 7 + 0.32, !- Value Until Time 7 + 24, !- Hour 8 + 0, !- Minute 8 + 0; !- Value Until Time 8 + +OS:Schedule:Rule, + {5f56eca6-ccee-4733-ba3c-4ba523edecd1}, !- Handle + Schedule Rule 1, !- Name + {4ba9fc77-4cc5-416c-a8c6-7cda5a62c47f}, !- Schedule Ruleset Name + 0, !- Rule Order + {5f7ae38d-0ee3-4220-a4e9-8e33595158d9}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {5f7ae38d-0ee3-4220-a4e9-8e33595158d9}, !- Handle + Warehouse BLDG_OCC_SCH WntrDsn|SmrDsn|Sat|Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.11, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.21, !- Value Until Time 3 + 12, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 13, !- Hour 5 + 0, !- Minute 5 + 0.53, !- Value Until Time 5 + 17, !- Hour 6 + 0, !- Minute 6 + 1, !- Value Until Time 6 + 18, !- Hour 7 + 0, !- Minute 7 + 0.32, !- Value Until Time 7 + 24, !- Hour 8 + 0, !- Minute 8 + 0; !- Value Until Time 8 + +OS:ScheduleTypeLimits, + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Handle + Fractional, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + Continuous; !- Numeric Type + +OS:Schedule:Ruleset, + {07fef766-cf84-4a84-a794-8ea3f92c8e73}, !- Handle + Warehouse Office Activity Schedule, !- Name + {289d92ae-e5d9-4315-ad44-a07a0081d6f8}, !- Schedule Type Limits Name + {58aab38e-ba76-4f1d-a8ec-f500aedba243}, !- Default Day Schedule Name + {6136d8ec-772b-4233-a74e-4b6396af5630}, !- Summer Design Day Schedule Name + {0575cd8e-4ed9-4246-b694-acc73dff3e75}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {58aab38e-ba76-4f1d-a8ec-f500aedba243}, !- Handle + Warehouse Office Activity Schedule Default, !- Name + {289d92ae-e5d9-4315-ad44-a07a0081d6f8}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 131.85; !- Value Until Time 1 + +OS:Schedule:Day, + {0575cd8e-4ed9-4246-b694-acc73dff3e75}, !- Handle + Warehouse Office Activity Schedule Winter Design Day, !- Name + {289d92ae-e5d9-4315-ad44-a07a0081d6f8}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 131.85; !- Value Until Time 1 + +OS:Schedule:Day, + {6136d8ec-772b-4233-a74e-4b6396af5630}, !- Handle + Warehouse Office Activity Schedule Summer Design Day, !- Name + {289d92ae-e5d9-4315-ad44-a07a0081d6f8}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 131.85; !- Value Until Time 1 + +OS:ScheduleTypeLimits, + {289d92ae-e5d9-4315-ad44-a07a0081d6f8}, !- Handle + ActivityLevel, !- Name + 0, !- Lower Limit Value + , !- Upper Limit Value + Continuous, !- Numeric Type + ActivityLevel; !- Unit Type + +OS:Schedule:Ruleset, + {5c128e1d-bfc6-4304-99b3-6c5f446e2bb8}, !- Handle + Warehouse BLDG_LIGHT_STORAGE_SCH, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {719bb0eb-6821-4b6a-a230-0ea7decbfc62}, !- Default Day Schedule Name + {9d13e7c3-4b95-4696-9f41-51bf915d01e7}, !- Summer Design Day Schedule Name + {9a69f479-88bf-48c7-9fcf-f5deabfd5888}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {719bb0eb-6821-4b6a-a230-0ea7decbfc62}, !- Handle + Warehouse BLDG_LIGHT_STORAGE_SCH Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.1; !- Value Until Time 1 + +OS:Schedule:Rule, + {bd3426b3-1ee4-496f-bc64-a1044269a98e}, !- Handle + Schedule Rule 2, !- Name + {5c128e1d-bfc6-4304-99b3-6c5f446e2bb8}, !- Schedule Ruleset Name + 0, !- Rule Order + {e307ec97-1c59-4f5c-af9b-d580bf9878dc}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {e307ec97-1c59-4f5c-af9b-d580bf9878dc}, !- Handle + Warehouse BLDG_LIGHT_STORAGE_SCH Sat|Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 7, !- Hour 1 + 0, !- Minute 1 + 0.1, !- Value Until Time 1 + 8, !- Hour 2 + 0, !- Minute 2 + 0.6, !- Value Until Time 2 + 9, !- Hour 3 + 0, !- Minute 3 + 0.75, !- Value Until Time 3 + 16, !- Hour 4 + 0, !- Minute 4 + 0.85, !- Value Until Time 4 + 17, !- Hour 5 + 0, !- Minute 5 + 0.75, !- Value Until Time 5 + 18, !- Hour 6 + 0, !- Minute 6 + 0.6, !- Value Until Time 6 + 24, !- Hour 7 + 0, !- Minute 7 + 0.1; !- Value Until Time 7 + +OS:Schedule:Day, + {9d13e7c3-4b95-4696-9f41-51bf915d01e7}, !- Handle + Warehouse BLDG_LIGHT_STORAGE_SCH Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Day, + {9a69f479-88bf-48c7-9fcf-f5deabfd5888}, !- Handle + Warehouse BLDG_LIGHT_STORAGE_SCH Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {167bfc28-d976-4f1c-8e8c-4d05109ec795}, !- Handle + Warehouse Bldg Equip, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {26a285f6-dde2-4289-9116-db942cacbc15}, !- Default Day Schedule Name + {eefe20b8-b436-44e0-b9a1-6f8884580d66}, !- Summer Design Day Schedule Name + {3d716541-abea-4979-bca8-4312391e3bc6}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {26a285f6-dde2-4289-9116-db942cacbc15}, !- Handle + Warehouse Bldg Equip Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Rule, + {b45de0fd-837b-479c-9814-5b8b48edc579}, !- Handle + Schedule Rule 3, !- Name + {167bfc28-d976-4f1c-8e8c-4d05109ec795}, !- Schedule Ruleset Name + 1, !- Rule Order + {e74565fb-dfb5-40d7-9b00-760e64f5c75d}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {e74565fb-dfb5-40d7-9b00-760e64f5c75d}, !- Handle + Warehouse Bldg Equip Mon|Tue|Wed|Thu|Fri|Sat Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 8, !- Hour 1 + 0, !- Minute 1 + 0.25, !- Value Until Time 1 + 12, !- Hour 2 + 0, !- Minute 2 + 1, !- Value Until Time 2 + 13, !- Hour 3 + 0, !- Minute 3 + 0.25, !- Value Until Time 3 + 17, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 0.25; !- Value Until Time 5 + +OS:Schedule:Day, + {eefe20b8-b436-44e0-b9a1-6f8884580d66}, !- Handle + Warehouse Bldg Equip Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 8, !- Hour 1 + 0, !- Minute 1 + 0.25, !- Value Until Time 1 + 12, !- Hour 2 + 0, !- Minute 2 + 1, !- Value Until Time 2 + 13, !- Hour 3 + 0, !- Minute 3 + 0.25, !- Value Until Time 3 + 17, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 0.25; !- Value Until Time 5 + +OS:Schedule:Rule, + {4b4193cd-d60e-43b2-bc7a-3ec542a462c9}, !- Handle + Schedule Rule 4, !- Name + {167bfc28-d976-4f1c-8e8c-4d05109ec795}, !- Schedule Ruleset Name + 0, !- Rule Order + {c149ab08-d5fd-426c-b33c-2abd900c3c59}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {c149ab08-d5fd-426c-b33c-2abd900c3c59}, !- Handle + Warehouse Bldg Equip Sun|Hol Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Day, + {3d716541-abea-4979-bca8-4312391e3bc6}, !- Handle + Warehouse Bldg Equip Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 8, !- Hour 1 + 0, !- Minute 1 + 0.25, !- Value Until Time 1 + 12, !- Hour 2 + 0, !- Minute 2 + 1, !- Value Until Time 2 + 13, !- Hour 3 + 0, !- Minute 3 + 0.25, !- Value Until Time 3 + 17, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 0.25; !- Value Until Time 5 + +OS:Schedule:Ruleset, + {01fc67bb-02c0-4027-b29d-14c133cad401}, !- Handle + Warehouse Bulk Infil Schedule, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {3a2a761b-d249-4386-95c5-9806ea15b785}; !- Default Day Schedule Name + +OS:Schedule:Day, + {3a2a761b-d249-4386-95c5-9806ea15b785}, !- Handle + Schedule Day 9, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Rule, + {c8a5aeb1-47d1-4b74-ba2d-3c799df3d592}, !- Handle + Schedule Rule 5, !- Name + {01fc67bb-02c0-4027-b29d-14c133cad401}, !- Schedule Ruleset Name + 1, !- Rule Order + {2bc22b29-95e2-40f4-8379-d40fb32d1a25}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {2bc22b29-95e2-40f4-8379-d40fb32d1a25}, !- Handle + Warehouse Bulk Infil Schedule Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 8, !- Hour 1 + 0, !- Minute 1 + 0.765, !- Value Until Time 1 + 12, !- Hour 2 + 0, !- Minute 2 + 1, !- Value Until Time 2 + 13, !- Hour 3 + 0, !- Minute 3 + 0.765, !- Value Until Time 3 + 17, !- Hour 4 + 0, !- Minute 4 + 1, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 0.765; !- Value Until Time 5 + +OS:Schedule:Rule, + {a3802381-7eaa-4368-aff9-16ca97770029}, !- Handle + Schedule Rule 6, !- Name + {01fc67bb-02c0-4027-b29d-14c133cad401}, !- Schedule Ruleset Name + 0, !- Rule Order + {7e1c12a7-a774-4753-9bf3-b37452da3ae1}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {7e1c12a7-a774-4753-9bf3-b37452da3ae1}, !- Handle + Warehouse Bulk Infil Schedule Wknd Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.765; !- Value Until Time 1 + +OS:ThermostatSetpoint:DualSetpoint, + {603a1523-c2e1-4939-8bde-1e7cdec3f0a1}, !- Handle + Warehouse Bulk Thermostat, !- Name + {481cbfc4-91f3-4cee-b788-6270a8fa877a}, !- Heating Setpoint Temperature Schedule Name + {eabcb337-3952-4a02-ab7e-24bdce96b93c}; !- Cooling Setpoint Temperature Schedule Name + +OS:Schedule:Ruleset, + {481cbfc4-91f3-4cee-b788-6270a8fa877a}, !- Handle + Warehouse Bulk Storage Heating Setpoint Schedule, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {7ad00489-7990-4cf4-8b38-0f24129a12d8}, !- Default Day Schedule Name + {c11afae3-4a58-4a7e-837a-ed11b562fcc8}, !- Summer Design Day Schedule Name + {b02e72d9-f3e2-4da7-a6c7-365f6e946aba}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {7ad00489-7990-4cf4-8b38-0f24129a12d8}, !- Handle + Warehouse Bulk Storage Heating Setpoint Schedule Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 10; !- Value Until Time 1 + +OS:Schedule:Day, + {b02e72d9-f3e2-4da7-a6c7-365f6e946aba}, !- Handle + Warehouse Bulk Storage Heating Setpoint Schedule Winter Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 10; !- Value Until Time 1 + +OS:Schedule:Day, + {c11afae3-4a58-4a7e-837a-ed11b562fcc8}, !- Handle + Warehouse Bulk Storage Heating Setpoint Schedule Summer Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 10; !- Value Until Time 1 + +OS:ScheduleTypeLimits, + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Handle + Temperature, !- Name + , !- Lower Limit Value + , !- Upper Limit Value + Continuous, !- Numeric Type + Temperature; !- Unit Type + +OS:Schedule:Ruleset, + {eabcb337-3952-4a02-ab7e-24bdce96b93c}, !- Handle + Warehouse ClgSetp BulkStorage, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {c808bff4-7abf-46c8-a621-108b3fbf5239}, !- Default Day Schedule Name + {ac2cb378-97e7-4441-bf03-2e06368f2c0b}, !- Summer Design Day Schedule Name + {9c388830-71c7-45a3-8008-b29202ab55a2}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {c808bff4-7abf-46c8-a621-108b3fbf5239}, !- Handle + Warehouse ClgSetp BulkStorage Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 50; !- Value Until Time 1 + +OS:Schedule:Day, + {9c388830-71c7-45a3-8008-b29202ab55a2}, !- Handle + Warehouse ClgSetp BulkStorage Winter Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 50; !- Value Until Time 1 + +OS:Schedule:Day, + {ac2cb378-97e7-4441-bf03-2e06368f2c0b}, !- Handle + Warehouse ClgSetp BulkStorage Summer Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 50; !- Value Until Time 1 + +OS:Lights:Definition, + {05a5917a-7c5e-4f17-a765-9640681bbec8}, !- Handle + Warehouse Fine Lights Definition, !- Name + Watts/Area, !- Design Level Calculation Method + , !- Lighting Level {W} + 10.2257148958742, !- Watts per Space Floor Area {W/m2} + , !- Watts per Person {W/person} + 0.42, !- Fraction Radiant + 0.18; !- Fraction Visible + +OS:Lights, + {88699197-f06a-4a36-8b5d-cce152d4023e}, !- Handle + Warehouse Fine Lights, !- Name + {05a5917a-7c5e-4f17-a765-9640681bbec8}, !- Lights Definition Name + {fa494797-3b47-43b7-8d83-65371f4bbb13}, !- Space or SpaceType Name + , !- Schedule Name + 1, !- Fraction Replaceable + , !- Multiplier + General; !- End-Use Subcategory + +OS:AdditionalProperties, + {69817dd6-4941-4be8-b473-97ced65496c2}, !- Handle + {05a5917a-7c5e-4f17-a765-9640681bbec8}, !- Object Name + lpd_fraction_linear_fluorescent, !- Feature Name 1 + Double, !- Feature Data Type 1 + 1; !- Feature Value 1 + +OS:DesignSpecification:OutdoorAir, + {228500fa-fa0e-46e7-9bf3-f46bd8b5720b}, !- Handle + Warehouse Fine Ventilation, !- Name + Sum, !- Outdoor Air Method + 0, !- Outdoor Air Flow per Person {m3/s-person} + 0.0003048, !- Outdoor Air Flow per Floor Area {m3/s-m2} + 0, !- Outdoor Air Flow Rate {m3/s} + 0, !- Outdoor Air Flow Air Changes per Hour {1/hr} + ; !- Outdoor Air Flow Rate Fraction Schedule Name + +OS:DefaultScheduleSet, + {09d862b0-be72-4723-bac9-41e51e79fba0}, !- Handle + Warehouse Fine Schedule Set, !- Name + , !- Hours of Operation Schedule Name + {4ba9fc77-4cc5-416c-a8c6-7cda5a62c47f}, !- Number of People Schedule Name + {07fef766-cf84-4a84-a794-8ea3f92c8e73}, !- People Activity Level Schedule Name + {5c128e1d-bfc6-4304-99b3-6c5f446e2bb8}, !- Lighting Schedule Name + {167bfc28-d976-4f1c-8e8c-4d05109ec795}, !- Electric Equipment Schedule Name + , !- Gas Equipment Schedule Name + , !- Hot Water Equipment Schedule Name + {8efe48f5-f09d-4c96-958a-2ae30b0eb0e4}, !- Infiltration Schedule Name + , !- Steam Equipment Schedule Name + ; !- Other Equipment Schedule Name + +OS:Schedule:Ruleset, + {8efe48f5-f09d-4c96-958a-2ae30b0eb0e4}, !- Handle + Warehouse Fine Infil Schedule, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {786fec26-e3be-42d8-88a6-fe8325f1dafa}; !- Default Day Schedule Name + +OS:Schedule:Day, + {786fec26-e3be-42d8-88a6-fe8325f1dafa}, !- Handle + Schedule Day 14, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Rule, + {b830e1d3-d950-4cb0-8459-1fd204089b7b}, !- Handle + Schedule Rule 7, !- Name + {8efe48f5-f09d-4c96-958a-2ae30b0eb0e4}, !- Schedule Ruleset Name + 1, !- Rule Order + {9f941589-fba2-480b-b7c1-f6270875c844}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {9f941589-fba2-480b-b7c1-f6270875c844}, !- Handle + Warehouse Fine Infil Schedule Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 9, !- Hour 1 + 0, !- Minute 1 + 1, !- Value Until Time 1 + 16, !- Hour 2 + 0, !- Minute 2 + 0.5, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 1; !- Value Until Time 3 + +OS:Schedule:Rule, + {5ecffc0c-b630-469e-9bbd-5c258a71c42a}, !- Handle + Schedule Rule 8, !- Name + {8efe48f5-f09d-4c96-958a-2ae30b0eb0e4}, !- Schedule Ruleset Name + 0, !- Rule Order + {1a7fa3e7-d3c2-44ac-b7e1-37dc4a0df4c0}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {1a7fa3e7-d3c2-44ac-b7e1-37dc4a0df4c0}, !- Handle + Warehouse Fine Infil Schedule Wknd Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:ThermostatSetpoint:DualSetpoint, + {5d44f15a-3280-473a-8a51-ecfec9250355}, !- Handle + Warehouse Fine Thermostat, !- Name + {40c3c1fd-bef2-48f5-807c-11dc1230fe44}, !- Heating Setpoint Temperature Schedule Name + {7f2d99f8-e224-475c-9e77-c3acd1e33004}; !- Cooling Setpoint Temperature Schedule Name + +OS:Schedule:Ruleset, + {40c3c1fd-bef2-48f5-807c-11dc1230fe44}, !- Handle + Warehouse Fine Storage Heating Setpoint Schedule, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {328a5c7d-6486-4951-92ea-296601002976}, !- Default Day Schedule Name + {3f323d53-45ee-4ee8-9f5e-dd9193a8bfcd}, !- Summer Design Day Schedule Name + {6478e3eb-d22b-461f-b824-b621363056e3}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {328a5c7d-6486-4951-92ea-296601002976}, !- Handle + Warehouse Fine Storage Heating Setpoint Schedule Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 15.56; !- Value Until Time 1 + +OS:Schedule:Day, + {6478e3eb-d22b-461f-b824-b621363056e3}, !- Handle + Warehouse Fine Storage Heating Setpoint Schedule Winter Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 15.56; !- Value Until Time 1 + +OS:Schedule:Day, + {3f323d53-45ee-4ee8-9f5e-dd9193a8bfcd}, !- Handle + Warehouse Fine Storage Heating Setpoint Schedule Summer Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 15.56; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {7f2d99f8-e224-475c-9e77-c3acd1e33004}, !- Handle + Warehouse Fine Storage Cooling Setpoint Schedule, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {5bfce755-1cc2-4f38-8b09-4985d5d536a4}, !- Default Day Schedule Name + {7ef012dc-c76b-442d-95d1-5ee3bf396f9f}, !- Summer Design Day Schedule Name + {cef91877-3134-49e1-9df5-70e80453a124}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {5bfce755-1cc2-4f38-8b09-4985d5d536a4}, !- Handle + Warehouse Fine Storage Cooling Setpoint Schedule Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 26.7; !- Value Until Time 1 + +OS:Schedule:Day, + {cef91877-3134-49e1-9df5-70e80453a124}, !- Handle + Warehouse Fine Storage Cooling Setpoint Schedule Winter Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 26.7; !- Value Until Time 1 + +OS:Schedule:Day, + {7ef012dc-c76b-442d-95d1-5ee3bf396f9f}, !- Handle + Warehouse Fine Storage Cooling Setpoint Schedule Summer Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 26.7; !- Value Until Time 1 + +OS:People:Definition, + {67884c5e-fe25-494a-95af-79b3f6c427e5}, !- Handle + Warehouse Office People Definition, !- Name + People/Area, !- Number of People Calculation Method + , !- Number of People {people} + 0.0210972644167511, !- People per Space Floor Area {person/m2} + , !- Space Floor Area per Person {m2/person} + 0.3; !- Fraction Radiant + +OS:People, + {5ab1c3bc-beb0-4ceb-975b-263319b2047c}, !- Handle + Warehouse Office People, !- Name + {67884c5e-fe25-494a-95af-79b3f6c427e5}, !- People Definition Name + {626ccb3f-df17-4651-ba00-0696cb5a3ca7}, !- Space or SpaceType Name + , !- Number of People Schedule Name + , !- Activity Level Schedule Name + , !- Surface Name/Angle Factor List Name + {94f36268-5fe5-4885-8cad-366c5f8d1164}, !- Work Efficiency Schedule Name + {74b19b73-b881-4184-aa17-3ea220738f6b}, !- Clothing Insulation Schedule Name + {ceafe7c9-7daf-45a0-af1a-bc2cd9cf0a67}, !- Air Velocity Schedule Name + 1; !- Multiplier + +OS:Schedule:Ruleset, + {74b19b73-b881-4184-aa17-3ea220738f6b}, !- Handle + Clothing Schedule, !- Name + {e3b6afa2-1db8-4715-b9e2-ca4c30349e3a}, !- Schedule Type Limits Name + {74d8f7ec-66a5-4561-be81-4e20b1be2ff9}; !- Default Day Schedule Name + +OS:Schedule:Day, + {74d8f7ec-66a5-4561-be81-4e20b1be2ff9}, !- Handle + Clothing Schedule Default Winter Clothes, !- Name + {e3b6afa2-1db8-4715-b9e2-ca4c30349e3a}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Rule, + {155d9fe3-0e39-49a8-92aa-b68464f0820c}, !- Handle + Schedule Rule 9, !- Name + {74b19b73-b881-4184-aa17-3ea220738f6b}, !- Schedule Ruleset Name + 0, !- Rule Order + {5e9f72e3-2c41-412c-a24a-247eaef2cc5a}, !- Day Schedule Name + , !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 5, !- Start Month + 1, !- Start Day + 9, !- End Month + 30; !- End Day + +OS:Schedule:Day, + {5e9f72e3-2c41-412c-a24a-247eaef2cc5a}, !- Handle + Clothing Schedule Summer Clothes, !- Name + {e3b6afa2-1db8-4715-b9e2-ca4c30349e3a}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.5; !- Value Until Time 1 + +OS:ScheduleTypeLimits, + {e3b6afa2-1db8-4715-b9e2-ca4c30349e3a}, !- Handle + ClothingInsulation, !- Name + 0, !- Lower Limit Value + , !- Upper Limit Value + Continuous, !- Numeric Type + ClothingInsulation; !- Unit Type + +OS:Schedule:Ruleset, + {ceafe7c9-7daf-45a0-af1a-bc2cd9cf0a67}, !- Handle + Air Velocity Schedule, !- Name + {6e1b06ea-3134-4f43-b2ed-fedef1a8482a}, !- Schedule Type Limits Name + {a643679e-a68f-47ad-aa79-9f266f495feb}; !- Default Day Schedule Name + +OS:Schedule:Day, + {a643679e-a68f-47ad-aa79-9f266f495feb}, !- Handle + Air Velocity Schedule Default, !- Name + {6e1b06ea-3134-4f43-b2ed-fedef1a8482a}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.2; !- Value Until Time 1 + +OS:ScheduleTypeLimits, + {6e1b06ea-3134-4f43-b2ed-fedef1a8482a}, !- Handle + Velocity, !- Name + 0, !- Lower Limit Value + , !- Upper Limit Value + Continuous, !- Numeric Type + Velocity; !- Unit Type + +OS:Schedule:Ruleset, + {94f36268-5fe5-4885-8cad-366c5f8d1164}, !- Handle + Work Efficiency Schedule, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {513b19b1-0867-4324-9f14-5895f61d68b6}; !- Default Day Schedule Name + +OS:Schedule:Day, + {513b19b1-0867-4324-9f14-5895f61d68b6}, !- Handle + Work Efficiency Schedule Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Lights:Definition, + {be1a2234-30f2-48d3-9db0-24fe6656e1c5}, !- Handle + Warehouse Office Lights Definition, !- Name + Watts/Area, !- Design Level Calculation Method + , !- Lighting Level {W} + 9.68751937503875, !- Watts per Space Floor Area {W/m2} + , !- Watts per Person {W/person} + 0.37, !- Fraction Radiant + 0.18; !- Fraction Visible + +OS:Lights, + {5fc29a6d-4035-4e8d-b70f-0d83c3ef83d2}, !- Handle + Warehouse Office Lights, !- Name + {be1a2234-30f2-48d3-9db0-24fe6656e1c5}, !- Lights Definition Name + {626ccb3f-df17-4651-ba00-0696cb5a3ca7}, !- Space or SpaceType Name + , !- Schedule Name + 1, !- Fraction Replaceable + , !- Multiplier + General; !- End-Use Subcategory + +OS:AdditionalProperties, + {729005ea-1c50-405e-822a-7ee8b4a20940}, !- Handle + {be1a2234-30f2-48d3-9db0-24fe6656e1c5}, !- Object Name + lpd_fraction_linear_fluorescent, !- Feature Name 1 + Double, !- Feature Data Type 1 + 1; !- Feature Value 1 + +OS:ElectricEquipment:Definition, + {5a175c2e-a4a8-4899-a989-618743296c23}, !- Handle + Warehouse Office Elec Equip Definition, !- Name + Watts/Area, !- Design Level Calculation Method + , !- Design Level {W} + 8.07293281253229, !- Watts per Space Floor Area {W/m2} + ; !- Watts per Person {W/person} + +OS:ElectricEquipment, + {6c6e0ce0-0783-473e-80e5-213dfbfb1b28}, !- Handle + Warehouse Office Elec Equip, !- Name + {5a175c2e-a4a8-4899-a989-618743296c23}, !- Electric Equipment Definition Name + {626ccb3f-df17-4651-ba00-0696cb5a3ca7}, !- Space or SpaceType Name + , !- Schedule Name + , !- Multiplier + General; !- End-Use Subcategory + +OS:DesignSpecification:OutdoorAir, + {7ca430ea-1111-4060-84a7-2dea28f9e67f}, !- Handle + Warehouse Office Ventilation, !- Name + Sum, !- Outdoor Air Method + 0, !- Outdoor Air Flow per Person {m3/s-person} + 0.000354584, !- Outdoor Air Flow per Floor Area {m3/s-m2} + 0, !- Outdoor Air Flow Rate {m3/s} + 0, !- Outdoor Air Flow Air Changes per Hour {1/hr} + ; !- Outdoor Air Flow Rate Fraction Schedule Name + +OS:DefaultScheduleSet, + {bd895036-261d-454c-b19d-59c5e8629c2a}, !- Handle + Warehouse Office Schedule Set, !- Name + , !- Hours of Operation Schedule Name + {4ba9fc77-4cc5-416c-a8c6-7cda5a62c47f}, !- Number of People Schedule Name + {07fef766-cf84-4a84-a794-8ea3f92c8e73}, !- People Activity Level Schedule Name + {2597f80b-9115-412b-b4ea-ab0a5ffa4c21}, !- Lighting Schedule Name + {da99fe61-f94e-4faa-8f00-c7195a4a89f0}, !- Electric Equipment Schedule Name + , !- Gas Equipment Schedule Name + , !- Hot Water Equipment Schedule Name + {62c14ee4-73e5-436c-aa16-98d54617aa62}, !- Infiltration Schedule Name + , !- Steam Equipment Schedule Name + ; !- Other Equipment Schedule Name + +OS:Schedule:Ruleset, + {2597f80b-9115-412b-b4ea-ab0a5ffa4c21}, !- Handle + Warehouse BLDG_LIGHT_OFFICE_SCH_2010, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {302220b7-a94d-4f09-a2c0-f5cc04144445}, !- Default Day Schedule Name + {a2f64224-0966-4230-a131-4e054ddec513}, !- Summer Design Day Schedule Name + {cb45f6fa-5ead-4ffc-bffe-93b91504ef2c}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {302220b7-a94d-4f09-a2c0-f5cc04144445}, !- Handle + Warehouse BLDG_LIGHT_OFFICE_SCH_2010 Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.18; !- Value Until Time 1 + +OS:Schedule:Rule, + {4c61f110-5220-4e0d-b1f9-43268dbfe723}, !- Handle + Schedule Rule 10, !- Name + {2597f80b-9115-412b-b4ea-ab0a5ffa4c21}, !- Schedule Ruleset Name + 0, !- Rule Order + {2e882730-4818-491e-93a4-8436b79b4615}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {2e882730-4818-491e-93a4-8436b79b4615}, !- Handle + Warehouse BLDG_LIGHT_OFFICE_SCH_2010 Sat|Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0.18, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.196742, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.359268, !- Value Until Time 3 + 12, !- Hour 4 + 0, !- Minute 4 + 0.658658, !- Value Until Time 4 + 13, !- Hour 5 + 0, !- Minute 5 + 0.581672, !- Value Until Time 5 + 17, !- Hour 6 + 0, !- Minute 6 + 0.658658, !- Value Until Time 6 + 18, !- Hour 7 + 0, !- Minute 7 + 0.521794, !- Value Until Time 7 + 24, !- Hour 8 + 0, !- Minute 8 + 0.18; !- Value Until Time 8 + +OS:Schedule:Day, + {a2f64224-0966-4230-a131-4e054ddec513}, !- Handle + Warehouse BLDG_LIGHT_OFFICE_SCH_2010 Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Day, + {cb45f6fa-5ead-4ffc-bffe-93b91504ef2c}, !- Handle + Warehouse BLDG_LIGHT_OFFICE_SCH_2010 Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {da99fe61-f94e-4faa-8f00-c7195a4a89f0}, !- Handle + Warehouse Office_Plug_SCH, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {b1ccba65-dbca-4e88-bcdd-d35bd0db0270}, !- Default Day Schedule Name + {a80a764a-d638-4ac6-8a28-92cf8a44be6a}, !- Summer Design Day Schedule Name + {93ff9377-7d09-4600-8544-7fb35e960d3b}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {b1ccba65-dbca-4e88-bcdd-d35bd0db0270}, !- Handle + Warehouse Office_Plug_SCH Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.25545; !- Value Until Time 1 + +OS:Schedule:Rule, + {cb1115a6-c5b1-4db5-8afc-6e25070a4de9}, !- Handle + Schedule Rule 11, !- Name + {da99fe61-f94e-4faa-8f00-c7195a4a89f0}, !- Schedule Ruleset Name + 0, !- Rule Order + {e7899373-9b29-4642-a340-32f05fc2491c}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {e7899373-9b29-4642-a340-32f05fc2491c}, !- Handle + Warehouse Office_Plug_SCH Sat|Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0.25545, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.2908065, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.4846775, !- Value Until Time 3 + 12, !- Hour 4 + 0, !- Minute 4 + 0.969355, !- Value Until Time 4 + 13, !- Hour 5 + 0, !- Minute 5 + 0.9111937, !- Value Until Time 5 + 17, !- Hour 6 + 0, !- Minute 6 + 0.969355, !- Value Until Time 6 + 18, !- Hour 7 + 0, !- Minute 7 + 0.4846775, !- Value Until Time 7 + 24, !- Hour 8 + 0, !- Minute 8 + 0.25545; !- Value Until Time 8 + +OS:Schedule:Day, + {a80a764a-d638-4ac6-8a28-92cf8a44be6a}, !- Handle + Warehouse Office_Plug_SCH Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Day, + {93ff9377-7d09-4600-8544-7fb35e960d3b}, !- Handle + Warehouse Office_Plug_SCH Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {62c14ee4-73e5-436c-aa16-98d54617aa62}, !- Handle + Warehouse Office Infil Schedule, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {1d2ac9b7-9881-4dca-8b3d-91fb313daf63}; !- Default Day Schedule Name + +OS:Schedule:Day, + {1d2ac9b7-9881-4dca-8b3d-91fb313daf63}, !- Handle + Schedule Day 23, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Rule, + {4d28fa1d-6359-4131-8961-67dd4cce86a8}, !- Handle + Schedule Rule 12, !- Name + {62c14ee4-73e5-436c-aa16-98d54617aa62}, !- Schedule Ruleset Name + 1, !- Rule Order + {4ff5418b-d1f5-4bc8-85a7-a920b3a16cea}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {4ff5418b-d1f5-4bc8-85a7-a920b3a16cea}, !- Handle + Warehouse Office Infil Schedule Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 9, !- Hour 1 + 0, !- Minute 1 + 1, !- Value Until Time 1 + 16, !- Hour 2 + 0, !- Minute 2 + 0.5, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 1; !- Value Until Time 3 + +OS:Schedule:Rule, + {f576f1b2-c82d-41b3-9686-878932381de6}, !- Handle + Schedule Rule 13, !- Name + {62c14ee4-73e5-436c-aa16-98d54617aa62}, !- Schedule Ruleset Name + 0, !- Rule Order + {880d178a-04b2-4b49-9c6e-3d1d46c5e77b}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {880d178a-04b2-4b49-9c6e-3d1d46c5e77b}, !- Handle + Warehouse Office Infil Schedule Wknd Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:ThermostatSetpoint:DualSetpoint, + {6c5a9908-65f5-4b54-bb7b-5638a6f198ac}, !- Handle + Warehouse Office Thermostat, !- Name + {9a9222af-93c5-4a4d-b07a-a532a98f368e}, !- Heating Setpoint Temperature Schedule Name + {e493d4b4-3e09-4a3e-889c-f74841df5c78}; !- Cooling Setpoint Temperature Schedule Name + +OS:Schedule:Ruleset, + {9a9222af-93c5-4a4d-b07a-a532a98f368e}, !- Handle + Warehouse HtgSetp, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {2a8832b9-19ae-4fcf-b6fd-079197dca5f6}, !- Default Day Schedule Name + {a924dcba-b3b1-46b5-858c-fe502ed70256}, !- Summer Design Day Schedule Name + {1deee009-4493-4f11-ac8a-a9de0e05c99b}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {2a8832b9-19ae-4fcf-b6fd-079197dca5f6}, !- Handle + Warehouse HtgSetp Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 15.6, !- Value Until Time 1 + 18, !- Hour 2 + 0, !- Minute 2 + 21.1, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 15.6; !- Value Until Time 3 + +OS:Schedule:Rule, + {58f2caa2-3b6c-4d50-94f5-6a5c5021cfb5}, !- Handle + Schedule Rule 14, !- Name + {9a9222af-93c5-4a4d-b07a-a532a98f368e}, !- Schedule Ruleset Name + 1, !- Rule Order + {27ed4aab-1cf4-487b-956a-87bb9e375e53}, !- Day Schedule Name + , !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {27ed4aab-1cf4-487b-956a-87bb9e375e53}, !- Handle + Warehouse HtgSetp Sat Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 15.6, !- Value Until Time 1 + 18, !- Hour 2 + 0, !- Minute 2 + 21.1, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 15.6; !- Value Until Time 3 + +OS:Schedule:Rule, + {85bb8466-8f2e-4af3-923b-d409607bce04}, !- Handle + Schedule Rule 15, !- Name + {9a9222af-93c5-4a4d-b07a-a532a98f368e}, !- Schedule Ruleset Name + 0, !- Rule Order + {c178c252-332c-4cad-b28c-420a3df04060}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {c178c252-332c-4cad-b28c-420a3df04060}, !- Handle + Warehouse HtgSetp Sun Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 15.6; !- Value Until Time 1 + +OS:Schedule:Day, + {1deee009-4493-4f11-ac8a-a9de0e05c99b}, !- Handle + Warehouse HtgSetp Winter Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 15.6, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 17.8, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 20, !- Value Until Time 3 + 18, !- Hour 4 + 0, !- Minute 4 + 21, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 15.6; !- Value Until Time 5 + +OS:Schedule:Day, + {a924dcba-b3b1-46b5-858c-fe502ed70256}, !- Handle + Warehouse HtgSetp Summer Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 15.6, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 17.8, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 20, !- Value Until Time 3 + 18, !- Hour 4 + 0, !- Minute 4 + 21, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 15.6; !- Value Until Time 5 + +OS:Schedule:Ruleset, + {e493d4b4-3e09-4a3e-889c-f74841df5c78}, !- Handle + Warehouse ClgSetp, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {bc147f41-ba2d-417f-ad2c-cab77e45ed98}, !- Default Day Schedule Name + {a6080d6a-536a-4553-b088-1c056caba81a}, !- Summer Design Day Schedule Name + {dc291efa-da7f-4220-9f93-95b201146d4e}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {bc147f41-ba2d-417f-ad2c-cab77e45ed98}, !- Handle + Warehouse ClgSetp Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 29.4, !- Value Until Time 1 + 18, !- Hour 2 + 0, !- Minute 2 + 23.9, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 29.4; !- Value Until Time 3 + +OS:Schedule:Rule, + {5c320861-7243-4542-bb19-19a722fb25c2}, !- Handle + Schedule Rule 16, !- Name + {e493d4b4-3e09-4a3e-889c-f74841df5c78}, !- Schedule Ruleset Name + 0, !- Rule Order + {066eda78-165d-4ca2-a199-0501f6a6c212}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {066eda78-165d-4ca2-a199-0501f6a6c212}, !- Handle + Warehouse ClgSetp Sun Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 29.4; !- Value Until Time 1 + +OS:Schedule:Day, + {dc291efa-da7f-4220-9f93-95b201146d4e}, !- Handle + Warehouse ClgSetp Winter Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 29.4, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 27.8, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 25.6, !- Value Until Time 3 + 18, !- Hour 4 + 0, !- Minute 4 + 23.9, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 29.4; !- Value Until Time 5 + +OS:Schedule:Day, + {a6080d6a-536a-4553-b088-1c056caba81a}, !- Handle + Warehouse ClgSetp Summer Design Day, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 29.4, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 27.8, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 25.6, !- Value Until Time 3 + 18, !- Hour 4 + 0, !- Minute 4 + 23.9, !- Value Until Time 4 + 24, !- Hour 5 + 0, !- Minute 5 + 29.4; !- Value Until Time 5 + +OS:SpaceInfiltration:DesignFlowRate, + {8950111d-dcc3-4d5a-8d48-477c89f4d44d}, !- Handle + Zone1 Office Infiltration, !- Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space or SpaceType Name + {62c14ee4-73e5-436c-aa16-98d54617aa62}, !- Schedule Name + Flow/ExteriorArea, !- Design Flow Rate Calculation Method + , !- Design Flow Rate {m3/s} + , !- Flow per Space Floor Area {m3/s-m2} + 0.0005695733896, !- Flow per Exterior Surface Area {m3/s-m2} + , !- Air Changes per Hour {1/hr} + 0, !- Constant Term Coefficient + 0, !- Temperature Term Coefficient + 0.224, !- Velocity Term Coefficient + 0; !- Velocity Squared Term Coefficient + +OS:SpaceInfiltration:DesignFlowRate, + {1f199c14-1cb3-4821-9dfa-9ec06d36da7c}, !- Handle + Zone2 Fine Storage Infiltration, !- Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space or SpaceType Name + {8efe48f5-f09d-4c96-958a-2ae30b0eb0e4}, !- Schedule Name + Flow/ExteriorArea, !- Design Flow Rate Calculation Method + , !- Design Flow Rate {m3/s} + , !- Flow per Space Floor Area {m3/s-m2} + 0.0002011559319, !- Flow per Exterior Surface Area {m3/s-m2} + , !- Air Changes per Hour {1/hr} + 0, !- Constant Term Coefficient + 0, !- Temperature Term Coefficient + 0.224, !- Velocity Term Coefficient + 0; !- Velocity Squared Term Coefficient + +OS:SpaceInfiltration:DesignFlowRate, + {a587b8f8-178d-45f9-ac39-ace135166641}, !- Handle + Zone3 Bulk Storage Infiltration, !- Name + {39655c19-15b3-4081-92c2-4613ae84d8e6}, !- Space or SpaceType Name + {01fc67bb-02c0-4027-b29d-14c133cad401}, !- Schedule Name + Flow/ExteriorArea, !- Design Flow Rate Calculation Method + , !- Design Flow Rate {m3/s} + , !- Flow per Space Floor Area {m3/s-m2} + 0.0001886063105, !- Flow per Exterior Surface Area {m3/s-m2} + , !- Air Changes per Hour {1/hr} + 0, !- Constant Term Coefficient + 0, !- Temperature Term Coefficient + 0.224, !- Velocity Term Coefficient + 0; !- Velocity Squared Term Coefficient + +OS:Site, + {c74e9698-cd78-4149-9047-58811f9a5767}, !- Handle + El Paso International Ap Ut_TX_USA, !- Name + 31, !- Latitude {deg} + -106, !- Longitude {deg} + -7, !- Time Zone {hr} + 1186, !- Elevation {m} + Urban; !- Terrain + +OS:SurfaceConvectionAlgorithm:Inside, + {088709d1-6981-4820-a264-f1cc5542ed4e}, !- Handle + TARP; !- Algorithm + +OS:SurfaceConvectionAlgorithm:Outside, + {434b4790-6094-453d-9843-11feede8ca76}, !- Handle + TARP; !- Algorithm + +OS:ThermalZone, + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Handle + Zone1 Office ZN, !- Name + , !- Multiplier + , !- Ceiling Height {m} + , !- Volume {m3} + , !- Floor Area {m2} + , !- Zone Inside Convection Algorithm + , !- Zone Outside Convection Algorithm + , !- Zone Conditioning Equipment List Name + {14fdfff4-47f8-49a3-aa88-85135633f226}, !- Zone Air Inlet Port List + {41e992a1-e6b0-4445-8995-d72c17889195}, !- Zone Air Exhaust Port List + {4f295cac-5d09-4fe3-aa10-746aa4af5de7}, !- Zone Air Node Name + {faeaa9b9-5a41-4020-b1de-80ba96ba34b1}, !- Zone Return Air Port List + {810ebe1b-d9eb-428d-b046-e2dc0d3fe399}, !- Primary Daylighting Control Name + 0.085, !- Fraction of Zone Controlled by Primary Daylighting Control + {ef04a2f5-10cb-4990-900e-5910323b3e66}, !- Secondary Daylighting Control Name + 0.085, !- Fraction of Zone Controlled by Secondary Daylighting Control + , !- Illuminance Map Name + , !- Group Rendering Name + {29614c52-cc79-49a2-9859-61fd532d0057}, !- Thermostat Name + No; !- Use Ideal Air Loads + +OS:Node, + {42201296-c3a3-4b1a-9aeb-fcadc441bb51}, !- Handle + Zone1 Office ZN Zone Air Node, !- Name + {4f295cac-5d09-4fe3-aa10-746aa4af5de7}, !- Inlet Port + ; !- Outlet Port + +OS:Connection, + {4f295cac-5d09-4fe3-aa10-746aa4af5de7}, !- Handle + {bce1c3d0-95a8-4b82-962b-2dd63181cbfb}, !- Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Source Object + 11, !- Outlet Port + {42201296-c3a3-4b1a-9aeb-fcadc441bb51}, !- Target Object + 2; !- Inlet Port + +OS:PortList, + {14fdfff4-47f8-49a3-aa88-85135633f226}, !- Handle + {d9844bbf-08fd-4728-a872-92b9ee06379a}, !- Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- HVAC Component + {0148dad5-4a85-482f-92fc-7eca6b3ec49b}; !- Port 1 + +OS:PortList, + {41e992a1-e6b0-4445-8995-d72c17889195}, !- Handle + {c3a34385-8dfc-4688-98a0-9c5dec95fc98}, !- Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}; !- HVAC Component + +OS:PortList, + {faeaa9b9-5a41-4020-b1de-80ba96ba34b1}, !- Handle + {d22d6c0a-e4af-45bd-b987-5da4ee15d0ed}, !- Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- HVAC Component + {909d945c-8464-4456-9973-c93b78ddb182}; !- Port 1 + +OS:Sizing:Zone, + {5fec605f-723b-4070-89d5-01e633ab8118}, !- Handle + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Zone or ZoneList Name + SupplyAirTemperature, !- Zone Cooling Design Supply Air Temperature Input Method + 12.7777777777778, !- Zone Cooling Design Supply Air Temperature {C} + 11.11, !- Zone Cooling Design Supply Air Temperature Difference {deltaC} + SupplyAirTemperature, !- Zone Heating Design Supply Air Temperature Input Method + 50.0000000000001, !- Zone Heating Design Supply Air Temperature {C} + 11.11, !- Zone Heating Design Supply Air Temperature Difference {deltaC} + 0.0085, !- Zone Cooling Design Supply Air Humidity Ratio {kg-H2O/kg-air} + 0.008, !- Zone Heating Design Supply Air Humidity Ratio {kg-H2O/kg-air} + , !- Zone Heating Sizing Factor + , !- Zone Cooling Sizing Factor + DesignDay, !- Cooling Design Air Flow Method + , !- Cooling Design Air Flow Rate {m3/s} + , !- Cooling Minimum Air Flow per Zone Floor Area {m3/s-m2} + , !- Cooling Minimum Air Flow {m3/s} + , !- Cooling Minimum Air Flow Fraction + DesignDay, !- Heating Design Air Flow Method + , !- Heating Design Air Flow Rate {m3/s} + , !- Heating Maximum Air Flow per Zone Floor Area {m3/s-m2} + , !- Heating Maximum Air Flow {m3/s} + , !- Heating Maximum Air Flow Fraction + , !- Design Zone Air Distribution Effectiveness in Cooling Mode + , !- Design Zone Air Distribution Effectiveness in Heating Mode + No, !- Account for Dedicated Outdoor Air System + NeutralSupplyAir, !- Dedicated Outdoor Air System Control Strategy + autosize, !- Dedicated Outdoor Air Low Setpoint Temperature for Design {C} + autosize; !- Dedicated Outdoor Air High Setpoint Temperature for Design {C} + +OS:ZoneHVAC:EquipmentList, + {5e932f27-6db0-4ae4-9686-52117869b1d5}, !- Handle + Zone1 Office ZN Zone HVAC Equipment List, !- Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Thermal Zone + , !- Load Distribution Scheme + {7919ed84-1780-45df-aa2c-df7dc40765e4}, !- Zone Equipment 1 + 1, !- Zone Equipment Cooling Sequence 1 + 1, !- Zone Equipment Heating or No-Load Sequence 1 + , !- Zone Equipment Sequential Cooling Fraction Schedule Name 1 + ; !- Zone Equipment Sequential Heating Fraction Schedule Name 1 + +OS:ThermostatSetpoint:DualSetpoint, + {29614c52-cc79-49a2-9859-61fd532d0057}, !- Handle + Warehouse Office Thermostat 1, !- Name + {9a9222af-93c5-4a4d-b07a-a532a98f368e}, !- Heating Setpoint Temperature Schedule Name + {e493d4b4-3e09-4a3e-889c-f74841df5c78}; !- Cooling Setpoint Temperature Schedule Name + +OS:ThermalZone, + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- Handle + Zone2 Fine Storage ZN, !- Name + , !- Multiplier + , !- Ceiling Height {m} + , !- Volume {m3} + , !- Floor Area {m2} + , !- Zone Inside Convection Algorithm + , !- Zone Outside Convection Algorithm + , !- Zone Conditioning Equipment List Name + {770bfe67-c97d-4c2a-a623-abc337ee287b}, !- Zone Air Inlet Port List + {0451efed-c4c6-48dd-9ea9-a15fde96faf9}, !- Zone Air Exhaust Port List + {0313b8dc-3939-4edb-9816-e929d3fd903d}, !- Zone Air Node Name + {d00784c7-878c-48bd-bb56-db5008cd76f8}, !- Zone Return Air Port List + , !- Primary Daylighting Control Name + , !- Fraction of Zone Controlled by Primary Daylighting Control + , !- Secondary Daylighting Control Name + , !- Fraction of Zone Controlled by Secondary Daylighting Control + , !- Illuminance Map Name + , !- Group Rendering Name + {694aa827-664c-4582-9180-84ad7fed88cc}, !- Thermostat Name + No; !- Use Ideal Air Loads + +OS:Node, + {a86d64db-eb75-4b2a-b031-0c43e5298d98}, !- Handle + Zone2 Fine Storage ZN Zone Air Node, !- Name + {0313b8dc-3939-4edb-9816-e929d3fd903d}, !- Inlet Port + ; !- Outlet Port + +OS:Connection, + {0313b8dc-3939-4edb-9816-e929d3fd903d}, !- Handle + {f4008f2e-f9c1-4ac9-9e4b-448f39f6c5a7}, !- Name + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- Source Object + 11, !- Outlet Port + {a86d64db-eb75-4b2a-b031-0c43e5298d98}, !- Target Object + 2; !- Inlet Port + +OS:PortList, + {770bfe67-c97d-4c2a-a623-abc337ee287b}, !- Handle + {c93ab01b-fcf8-4e59-91be-aadc75a5ca3c}, !- Name + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- HVAC Component + {caf7517e-d99a-4bcb-b6c3-7fbbf19a73eb}; !- Port 1 + +OS:PortList, + {0451efed-c4c6-48dd-9ea9-a15fde96faf9}, !- Handle + {65f3cb36-804f-41d9-8273-1cb98db99969}, !- Name + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}; !- HVAC Component + +OS:PortList, + {d00784c7-878c-48bd-bb56-db5008cd76f8}, !- Handle + {1ecb1f14-af6d-4198-bd92-4dd32318c80a}, !- Name + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- HVAC Component + {58e42913-adcf-41a6-80cf-f2a51bb4da83}; !- Port 1 + +OS:Sizing:Zone, + {7b152e06-5546-4be5-a209-38c9f0fe1c96}, !- Handle + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- Zone or ZoneList Name + SupplyAirTemperature, !- Zone Cooling Design Supply Air Temperature Input Method + 12.7777777777778, !- Zone Cooling Design Supply Air Temperature {C} + 11.11, !- Zone Cooling Design Supply Air Temperature Difference {deltaC} + SupplyAirTemperature, !- Zone Heating Design Supply Air Temperature Input Method + 50.0000000000001, !- Zone Heating Design Supply Air Temperature {C} + 11.11, !- Zone Heating Design Supply Air Temperature Difference {deltaC} + 0.0085, !- Zone Cooling Design Supply Air Humidity Ratio {kg-H2O/kg-air} + 0.008, !- Zone Heating Design Supply Air Humidity Ratio {kg-H2O/kg-air} + , !- Zone Heating Sizing Factor + , !- Zone Cooling Sizing Factor + DesignDay, !- Cooling Design Air Flow Method + , !- Cooling Design Air Flow Rate {m3/s} + , !- Cooling Minimum Air Flow per Zone Floor Area {m3/s-m2} + , !- Cooling Minimum Air Flow {m3/s} + , !- Cooling Minimum Air Flow Fraction + DesignDay, !- Heating Design Air Flow Method + , !- Heating Design Air Flow Rate {m3/s} + , !- Heating Maximum Air Flow per Zone Floor Area {m3/s-m2} + , !- Heating Maximum Air Flow {m3/s} + , !- Heating Maximum Air Flow Fraction + , !- Design Zone Air Distribution Effectiveness in Cooling Mode + , !- Design Zone Air Distribution Effectiveness in Heating Mode + No, !- Account for Dedicated Outdoor Air System + NeutralSupplyAir, !- Dedicated Outdoor Air System Control Strategy + autosize, !- Dedicated Outdoor Air Low Setpoint Temperature for Design {C} + autosize; !- Dedicated Outdoor Air High Setpoint Temperature for Design {C} + +OS:ZoneHVAC:EquipmentList, + {0169f565-d125-496a-a2e2-65429e50c0f9}, !- Handle + Zone2 Fine Storage ZN Zone HVAC Equipment List, !- Name + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- Thermal Zone + , !- Load Distribution Scheme + {0ceee5cf-bef0-4d06-8728-dc0a2d572945}, !- Zone Equipment 1 + 1, !- Zone Equipment Cooling Sequence 1 + 1, !- Zone Equipment Heating or No-Load Sequence 1 + , !- Zone Equipment Sequential Cooling Fraction Schedule Name 1 + ; !- Zone Equipment Sequential Heating Fraction Schedule Name 1 + +OS:ThermostatSetpoint:DualSetpoint, + {694aa827-664c-4582-9180-84ad7fed88cc}, !- Handle + Warehouse Fine Thermostat 1, !- Name + {40c3c1fd-bef2-48f5-807c-11dc1230fe44}, !- Heating Setpoint Temperature Schedule Name + {7f2d99f8-e224-475c-9e77-c3acd1e33004}; !- Cooling Setpoint Temperature Schedule Name + +OS:ThermalZone, + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- Handle + Zone3 Bulk Storage ZN, !- Name + , !- Multiplier + , !- Ceiling Height {m} + , !- Volume {m3} + , !- Floor Area {m2} + , !- Zone Inside Convection Algorithm + , !- Zone Outside Convection Algorithm + , !- Zone Conditioning Equipment List Name + {29e5c2ed-c4ce-48bd-88f1-61fe01328f4d}, !- Zone Air Inlet Port List + {9bea9730-4b2b-4b44-a10e-49028e4329dc}, !- Zone Air Exhaust Port List + {348ff4c4-de0e-4bbb-81c5-2c4b13636658}, !- Zone Air Node Name + {e19379ac-837d-4430-b255-360083fa644d}, !- Zone Return Air Port List + , !- Primary Daylighting Control Name + , !- Fraction of Zone Controlled by Primary Daylighting Control + , !- Secondary Daylighting Control Name + , !- Fraction of Zone Controlled by Secondary Daylighting Control + , !- Illuminance Map Name + , !- Group Rendering Name + {63507396-93e8-4124-865d-fbe37a00cb39}, !- Thermostat Name + No; !- Use Ideal Air Loads + +OS:Node, + {c661337e-0a7e-4b31-9e38-52cd43237a34}, !- Handle + Zone3 Bulk Storage ZN Zone Air Node, !- Name + {348ff4c4-de0e-4bbb-81c5-2c4b13636658}, !- Inlet Port + ; !- Outlet Port + +OS:Connection, + {348ff4c4-de0e-4bbb-81c5-2c4b13636658}, !- Handle + {de090677-65c6-4ab5-a02d-5e038d123457}, !- Name + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- Source Object + 11, !- Outlet Port + {c661337e-0a7e-4b31-9e38-52cd43237a34}, !- Target Object + 2; !- Inlet Port + +OS:PortList, + {29e5c2ed-c4ce-48bd-88f1-61fe01328f4d}, !- Handle + {a33994ab-a734-4dd0-9ada-4f49144a8a42}, !- Name + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- HVAC Component + {a15b8b7e-af57-48b9-9467-285ba87378df}; !- Port 1 + +OS:PortList, + {9bea9730-4b2b-4b44-a10e-49028e4329dc}, !- Handle + {5874ec1c-094d-4819-8b9d-3abbd9cca777}, !- Name + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- HVAC Component + {a10c6987-179b-46fc-b645-e97347c35bf6}; !- Port 1 + +OS:PortList, + {e19379ac-837d-4430-b255-360083fa644d}, !- Handle + {afdd8875-a2ff-4a0a-905b-fe3907e1e9c6}, !- Name + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}; !- HVAC Component + +OS:Sizing:Zone, + {158aa880-6c4a-4f5c-b1bf-5cb0351769ad}, !- Handle + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- Zone or ZoneList Name + SupplyAirTemperature, !- Zone Cooling Design Supply Air Temperature Input Method + 14, !- Zone Cooling Design Supply Air Temperature {C} + 11.11, !- Zone Cooling Design Supply Air Temperature Difference {deltaC} + SupplyAirTemperature, !- Zone Heating Design Supply Air Temperature Input Method + 50.0000000000001, !- Zone Heating Design Supply Air Temperature {C} + 11.11, !- Zone Heating Design Supply Air Temperature Difference {deltaC} + 0.0085, !- Zone Cooling Design Supply Air Humidity Ratio {kg-H2O/kg-air} + 0.008, !- Zone Heating Design Supply Air Humidity Ratio {kg-H2O/kg-air} + , !- Zone Heating Sizing Factor + , !- Zone Cooling Sizing Factor + DesignDay, !- Cooling Design Air Flow Method + , !- Cooling Design Air Flow Rate {m3/s} + , !- Cooling Minimum Air Flow per Zone Floor Area {m3/s-m2} + , !- Cooling Minimum Air Flow {m3/s} + , !- Cooling Minimum Air Flow Fraction + DesignDay, !- Heating Design Air Flow Method + , !- Heating Design Air Flow Rate {m3/s} + , !- Heating Maximum Air Flow per Zone Floor Area {m3/s-m2} + , !- Heating Maximum Air Flow {m3/s} + , !- Heating Maximum Air Flow Fraction + , !- Design Zone Air Distribution Effectiveness in Cooling Mode + , !- Design Zone Air Distribution Effectiveness in Heating Mode + No, !- Account for Dedicated Outdoor Air System + NeutralSupplyAir, !- Dedicated Outdoor Air System Control Strategy + autosize, !- Dedicated Outdoor Air Low Setpoint Temperature for Design {C} + autosize; !- Dedicated Outdoor Air High Setpoint Temperature for Design {C} + +OS:ZoneHVAC:EquipmentList, + {70dfd0fd-678d-4eb8-a64f-9a1cc646487f}, !- Handle + Zone3 Bulk Storage ZN Zone HVAC Equipment List, !- Name + {be29d86a-a4ff-43ee-9aef-2e65b408bb9f}, !- Thermal Zone + , !- Load Distribution Scheme + {28e5f181-b6d7-4b69-93c7-588713ef093b}, !- Zone Equipment 1 + 1, !- Zone Equipment Cooling Sequence 1 + 1, !- Zone Equipment Heating or No-Load Sequence 1 + , !- Zone Equipment Sequential Cooling Fraction Schedule Name 1 + , !- Zone Equipment Sequential Heating Fraction Schedule Name 1 + {644dc883-1242-4948-bde2-cdff3d0bebb4}, !- Zone Equipment 2 + 2, !- Zone Equipment Cooling Sequence 2 + 2, !- Zone Equipment Heating or No-Load Sequence 2 + , !- Zone Equipment Sequential Cooling Fraction Schedule Name 2 + , !- Zone Equipment Sequential Heating Fraction Schedule Name 2 + {f8b0ec9f-3184-4fec-a660-dd830e5e4736}, !- Zone Equipment 3 + 3, !- Zone Equipment Cooling Sequence 3 + 3, !- Zone Equipment Heating or No-Load Sequence 3 + , !- Zone Equipment Sequential Cooling Fraction Schedule Name 3 + ; !- Zone Equipment Sequential Heating Fraction Schedule Name 3 + +OS:ThermostatSetpoint:DualSetpoint, + {63507396-93e8-4124-865d-fbe37a00cb39}, !- Handle + Warehouse Bulk Thermostat 1, !- Name + {481cbfc4-91f3-4cee-b788-6270a8fa877a}, !- Heating Setpoint Temperature Schedule Name + {eabcb337-3952-4a02-ab7e-24bdce96b93c}; !- Cooling Setpoint Temperature Schedule Name + +OS:Schedule:Ruleset, + {f575a3cd-5131-4672-9b4b-4077d4ecf8dd}, !- Handle + Warehouse FanSched, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + {bc7b073e-dfeb-4736-835e-ad9dec59c450}, !- Default Day Schedule Name + {41d37e1d-f630-4382-b203-648bff7bf41d}, !- Summer Design Day Schedule Name + {029b0582-a196-4e8d-a4a6-666cdcfa1dd3}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {bc7b073e-dfeb-4736-835e-ad9dec59c450}, !- Handle + Warehouse FanSched Default, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Rule, + {58189c31-817a-4319-8d28-9ebcfc2f81f2}, !- Handle + Schedule Rule 17, !- Name + {f575a3cd-5131-4672-9b4b-4077d4ecf8dd}, !- Schedule Ruleset Name + 0, !- Rule Order + {3e505de5-240e-4727-ac56-e7c914ec1746}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {3e505de5-240e-4727-ac56-e7c914ec1746}, !- Handle + Warehouse FanSched Sat|Wkdy Day, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 7, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 18, !- Hour 2 + 0, !- Minute 2 + 1, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 0; !- Value Until Time 3 + +OS:Schedule:Day, + {029b0582-a196-4e8d-a4a6-666cdcfa1dd3}, !- Handle + Warehouse FanSched Winter Design Day, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Day, + {41d37e1d-f630-4382-b203-648bff7bf41d}, !- Handle + Warehouse FanSched Summer Design Day, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {4f9c674b-7b27-453a-8602-a8158bb91eb7}, !- Handle + Warehouse MinOA_MotorizedDamper_Sched, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {74cdd887-89f3-4979-b3bc-929299b65ab4}, !- Default Day Schedule Name + {dd97b6ec-add3-4552-b5cc-bb09b38dd5cc}, !- Summer Design Day Schedule Name + {d94e717f-3a18-4f82-af76-0ad5d49d9cd3}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {74cdd887-89f3-4979-b3bc-929299b65ab4}, !- Handle + Warehouse MinOA_MotorizedDamper_Sched Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0; !- Value Until Time 1 + +OS:Schedule:Rule, + {77888bf0-bda0-443e-ba54-f5a7343182db}, !- Handle + Schedule Rule 18, !- Name + {4f9c674b-7b27-453a-8602-a8158bb91eb7}, !- Schedule Ruleset Name + 0, !- Rule Order + {b9c955e8-3f9b-4700-9034-3320de529fab}, !- Day Schedule Name + , !- Apply Sunday + Yes, !- Apply Monday + Yes, !- Apply Tuesday + Yes, !- Apply Wednesday + Yes, !- Apply Thursday + Yes, !- Apply Friday + Yes, !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {b9c955e8-3f9b-4700-9034-3320de529fab}, !- Handle + Warehouse MinOA_MotorizedDamper_Sched Sat|Wkdy Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 8, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 18, !- Hour 2 + 0, !- Minute 2 + 1, !- Value Until Time 2 + 24, !- Hour 3 + 0, !- Minute 3 + 0; !- Value Until Time 3 + +OS:Schedule:Day, + {d94e717f-3a18-4f82-af76-0ad5d49d9cd3}, !- Handle + Warehouse MinOA_MotorizedDamper_Sched Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Schedule:Day, + {dd97b6ec-add3-4552-b5cc-bb09b38dd5cc}, !- Handle + Warehouse MinOA_MotorizedDamper_Sched Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:AirLoopHVAC, + {f3981a67-c229-4be0-8cb3-1334c3dd3f8a}, !- Handle + Zone1 Office ZN HVAC_1, !- Name + , !- Controller List Name + {f575a3cd-5131-4672-9b4b-4077d4ecf8dd}, !- Availability Schedule + {d798028d-1d03-44f2-96fe-fda68af946ea}, !- Availability Manager List Name + AutoSize, !- Design Supply Air Flow Rate {m3/s} + , !- Branch List Name + , !- Connector List Name + {69c2a76c-e6dd-4f77-ac96-36fa0e7c3426}, !- Supply Side Inlet Node Name + {806b2c70-e7bf-4204-8cf9-791f9332bb8f}, !- Demand Side Outlet Node Name + {134b100d-67dc-4aaa-9f36-68d17689b61f}, !- Demand Side Inlet Node A + {f9552ab8-2573-4a4e-83fd-66f7b47981af}, !- Supply Side Outlet Node A + , !- Demand Side Inlet Node B + , !- Supply Side Outlet Node B + , !- Return Air Bypass Flow Temperature Setpoint Schedule Name + {b3b817e9-0640-4b2e-9a4f-dc7c33378011}, !- Demand Mixer Name + {f2222084-d326-4e2e-b955-3376b578a301}, !- Demand Splitter A Name + , !- Demand Splitter B Name + ; !- Supply Splitter Name + +OS:Node, + {2abda90c-010e-4677-9b1f-a225e6426414}, !- Handle + Zone1 Office ZN HVAC_1 Supply Inlet Node, !- Name + {69c2a76c-e6dd-4f77-ac96-36fa0e7c3426}, !- Inlet Port + {1028be7d-2b0d-418b-8916-ce736974a2f7}; !- Outlet Port + +OS:Node, + {710b155c-82a9-40b1-9aba-917c938057a7}, !- Handle + Zone1 Office ZN HVAC_1 Supply Outlet Node, !- Name + {17456497-0601-4040-a3d1-74842477baf8}, !- Inlet Port + {f9552ab8-2573-4a4e-83fd-66f7b47981af}; !- Outlet Port + +OS:Connection, + {69c2a76c-e6dd-4f77-ac96-36fa0e7c3426}, !- Handle + {e2127646-02ea-4a7d-9988-4cd6fc08dc37}, !- Name + {f3981a67-c229-4be0-8cb3-1334c3dd3f8a}, !- Source Object + 8, !- Outlet Port + {2abda90c-010e-4677-9b1f-a225e6426414}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {f9552ab8-2573-4a4e-83fd-66f7b47981af}, !- Handle + {953ac64d-f2fe-496e-ab29-6936f355230b}, !- Name + {710b155c-82a9-40b1-9aba-917c938057a7}, !- Source Object + 3, !- Outlet Port + {f3981a67-c229-4be0-8cb3-1334c3dd3f8a}, !- Target Object + 11; !- Inlet Port + +OS:Node, + {fc260fc6-1a15-4826-a14a-adff19413ddf}, !- Handle + Zone1 Office ZN HVAC_1 Demand Inlet Node, !- Name + {134b100d-67dc-4aaa-9f36-68d17689b61f}, !- Inlet Port + {9d0f74fb-caec-449c-88ca-430faad28c48}; !- Outlet Port + +OS:Node, + {7210affb-4622-4609-8f26-4b032692c778}, !- Handle + Zone1 Office ZN HVAC_1 Demand Outlet Node, !- Name + {e898db2a-f4c2-450d-af9d-f0453f8451aa}, !- Inlet Port + {806b2c70-e7bf-4204-8cf9-791f9332bb8f}; !- Outlet Port + +OS:Node, + {4c91eee9-e190-4de5-9809-cc83552e2ccc}, !- Handle + Zone1 Office ZN HVAC_1 Diffuser Outlet Air Node, !- Name + {29fb13d7-7413-498d-8992-2034eeffca34}, !- Inlet Port + {0148dad5-4a85-482f-92fc-7eca6b3ec49b}; !- Outlet Port + +OS:Connection, + {134b100d-67dc-4aaa-9f36-68d17689b61f}, !- Handle + {d03c8c28-4875-4073-b5bf-43872f432f2a}, !- Name + {f3981a67-c229-4be0-8cb3-1334c3dd3f8a}, !- Source Object + 10, !- Outlet Port + {fc260fc6-1a15-4826-a14a-adff19413ddf}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {806b2c70-e7bf-4204-8cf9-791f9332bb8f}, !- Handle + {2dfc3cef-3627-4bd7-9000-83e37350eb93}, !- Name + {7210affb-4622-4609-8f26-4b032692c778}, !- Source Object + 3, !- Outlet Port + {f3981a67-c229-4be0-8cb3-1334c3dd3f8a}, !- Target Object + 9; !- Inlet Port + +OS:AirLoopHVAC:ZoneSplitter, + {f2222084-d326-4e2e-b955-3376b578a301}, !- Handle + Air Loop HVAC Zone Splitter 1, !- Name + {9d0f74fb-caec-449c-88ca-430faad28c48}, !- Inlet Node Name + {4bbc892d-d6b0-41b5-87d3-2bf339b7ae7c}; !- Outlet Node Name 1 + +OS:AirLoopHVAC:ZoneMixer, + {b3b817e9-0640-4b2e-9a4f-dc7c33378011}, !- Handle + Air Loop HVAC Zone Mixer 1, !- Name + {e898db2a-f4c2-450d-af9d-f0453f8451aa}, !- Outlet Node Name + {117c87fa-8e9a-4b92-8c7e-fd0cdcc2afed}; !- Inlet Node Name 1 + +OS:Connection, + {9d0f74fb-caec-449c-88ca-430faad28c48}, !- Handle + {868eba6b-36eb-492f-ad94-da81e3a5b39f}, !- Name + {fc260fc6-1a15-4826-a14a-adff19413ddf}, !- Source Object + 3, !- Outlet Port + {f2222084-d326-4e2e-b955-3376b578a301}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {e898db2a-f4c2-450d-af9d-f0453f8451aa}, !- Handle + {78c9aeea-55eb-4141-bd65-2d050548b70a}, !- Name + {b3b817e9-0640-4b2e-9a4f-dc7c33378011}, !- Source Object + 2, !- Outlet Port + {7210affb-4622-4609-8f26-4b032692c778}, !- Target Object + 2; !- Inlet Port + +OS:Sizing:System, + {0851f77b-b6d5-4c0b-9c88-a50370894866}, !- Handle + {f3981a67-c229-4be0-8cb3-1334c3dd3f8a}, !- AirLoop Name + Sensible, !- Type of Load to Size On + Autosize, !- Design Outdoor Air Flow Rate {m3/s} + 1, !- Central Heating Maximum System Air Flow Ratio + 7.22222222222229, !- Preheat Design Temperature {C} + 0.008, !- Preheat Design Humidity Ratio {kg-H2O/kg-Air} + 12.7777777777778, !- Precool Design Temperature {C} + 0.008, !- Precool Design Humidity Ratio {kg-H2O/kg-Air} + 12.7777777777778, !- Central Cooling Design Supply Air Temperature {C} + 50.0000000000001, !- Central Heating Design Supply Air Temperature {C} + Coincident, !- Sizing Option + No, !- 100% Outdoor Air in Cooling + No, !- 100% Outdoor Air in Heating + 0.0085, !- Central Cooling Design Supply Air Humidity Ratio {kg-H2O/kg-Air} + 0.008, !- Central Heating Design Supply Air Humidity Ratio {kg-H2O/kg-Air} + DesignDay, !- Cooling Design Air Flow Method + 0, !- Cooling Design Air Flow Rate {m3/s} + DesignDay, !- Heating Design Air Flow Method + 0, !- Heating Design Air Flow Rate {m3/s} + ZoneSum, !- System Outdoor Air Method + 1, !- Zone Maximum Outdoor Air Fraction {dimensionless} + 0.0099676501, !- Cooling Supply Air Flow Rate Per Floor Area {m3/s-m2} + 1, !- Cooling Fraction of Autosized Cooling Supply Air Flow Rate + 3.9475456e-05, !- Cooling Supply Air Flow Rate Per Unit Cooling Capacity {m3/s-W} + 0.0099676501, !- Heating Supply Air Flow Rate Per Floor Area {m3/s-m2} + 1, !- Heating Fraction of Autosized Heating Supply Air Flow Rate + 1, !- Heating Fraction of Autosized Cooling Supply Air Flow Rate + 3.1588213e-05, !- Heating Supply Air Flow Rate Per Unit Heating Capacity {m3/s-W} + CoolingDesignCapacity, !- Cooling Design Capacity Method + autosize, !- Cooling Design Capacity {W} + 234.7, !- Cooling Design Capacity Per Floor Area {W/m2} + 1, !- Fraction of Autosized Cooling Design Capacity + HeatingDesignCapacity, !- Heating Design Capacity Method + autosize, !- Heating Design Capacity {W} + 157, !- Heating Design Capacity Per Floor Area {W/m2} + 1, !- Fraction of Autosized Heating Design Capacity + OnOff; !- Central Cooling Capacity Control Method + +OS:AvailabilityManagerAssignmentList, + {d798028d-1d03-44f2-96fe-fda68af946ea}, !- Handle + Air Loop HVAC 1 AvailabilityManagerAssignmentList, !- Name + {60e6f146-66c5-4950-9875-ff171c9c6ec6}; !- Availability Manager Name 1 + +OS:SetpointManager:SingleZone:Reheat, + {99ee7f33-0d2a-42a4-9bad-dadb4f2415f7}, !- Handle + Zone1 Office ZN Setpoint Manager SZ Reheat, !- Name + 12.7777777777778, !- Minimum Supply Air Temperature {C} + 50.0000000000001, !- Maximum Supply Air Temperature {C} + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Control Zone Name + {710b155c-82a9-40b1-9aba-917c938057a7}; !- Setpoint Node or NodeList Name + +OS:Fan:ConstantVolume, + {e96be975-108b-4384-a08c-3ce47da48ea7}, !- Handle + Zone1 Office ZN HVAC_1 Fan, !- Name + {f575a3cd-5131-4672-9b4b-4077d4ecf8dd}, !- Availability Schedule Name + 0.56225, !- Fan Total Efficiency + 622.722275, !- Pressure Rise {Pa} + AutoSize, !- Maximum Flow Rate {m3/s} + 0.865, !- Motor Efficiency + 1, !- Motor In Airstream Fraction + {ec929029-b8f1-421e-97a6-786ffaea560f}, !- Air Inlet Node Name + {17456497-0601-4040-a3d1-74842477baf8}, !- Air Outlet Node Name + ; !- End-Use Subcategory + +OS:ScheduleTypeLimits, + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Handle + OnOff, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + Discrete, !- Numeric Type + Availability; !- Unit Type + +OS:Coil:Heating:Gas, + {d4bf2c27-92ed-4177-a706-1a645da33153}, !- Handle + Zone1 Office ZN HVAC_1 Gas Htg Coil, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule Name + 0.8, !- Gas Burner Efficiency + AutoSize, !- Nominal Capacity {W} + {c54e1da2-0fe3-4554-9ea2-7da9a64a8100}, !- Air Inlet Node Name + {b3980069-d6c1-442a-b3b6-847a9163bb2f}, !- Air Outlet Node Name + , !- Temperature Setpoint Node Name + 0, !- Parasitic Electric Load {W} + , !- Part Load Fraction Correlation Curve Name + 0; !- Parasitic Gas Load {W} + +OS:Schedule:Constant, + {aa41ca63-59df-4ec8-b707-e32f1eb8b2e6}, !- Handle + Always Off Discrete, !- Name + {8a2a3c3c-912d-455e-9493-6f4b8574b667}, !- Schedule Type Limits Name + 0; !- Value + +OS:ScheduleTypeLimits, + {8a2a3c3c-912d-455e-9493-6f4b8574b667}, !- Handle + OnOff 1, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + Discrete, !- Numeric Type + Availability; !- Unit Type + +OS:Coil:Heating:Electric, + {95a3bf7a-a275-43d6-9333-e96dbe47cebd}, !- Handle + Zone1 Office ZN HVAC_1 No Heat, !- Name + {aa41ca63-59df-4ec8-b707-e32f1eb8b2e6}, !- Availability Schedule Name + 1, !- Efficiency + 0, !- Nominal Capacity {W} + {19bdae02-45dd-4b11-b94b-e6da631ee4de}, !- Air Inlet Node Name + {5ee42591-cc87-4696-b418-83d289fe665d}; !- Air Outlet Node Name + +OS:Coil:Cooling:DX:TwoSpeed, + {810cb75c-1aac-45a7-8ab0-17f5d665460c}, !- Handle + Zone1 Office ZN HVAC_1 2spd DX AC Clg Coil 65kBtu/hr 13.0SEER, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule Name + Autosize, !- Rated High Speed Total Cooling Capacity {W} + Autosize, !- Rated High Speed Sensible Heat Ratio + 3.91156806861466, !- Rated High Speed COP {W/W} + Autosize, !- Rated High Speed Air Flow Rate {m3/s} + {0f0709ba-a1de-4238-8832-3a40551f0c52}, !- Air Inlet Node Name + {084bc586-6541-4280-aa15-06e785e78476}, !- Air Outlet Node Name + {f6b2da2f-90a9-484f-a68c-96feb6cc67fe}, !- Total Cooling Capacity Function of Temperature Curve Name + {1e5c201f-f719-4044-8b97-bef2bb885144}, !- Total Cooling Capacity Function of Flow Fraction Curve Name + {1688d136-800f-45b7-b2f5-fb8eefcb8ef6}, !- Energy Input Ratio Function of Temperature Curve Name + {3127684e-06d9-4ed3-b5b8-66a752c1ef42}, !- Energy Input Ratio Function of Flow Fraction Curve Name + {1f4c9e8a-7656-4302-8ca8-4c6a4907347c}, !- Part Load Fraction Correlation Curve Name + Autosize, !- Rated Low Speed Total Cooling Capacity {W} + 0.69, !- Rated Low Speed Sensible Heat Ratio + 3.91156806861466, !- Rated Low Speed COP {W/W} + , !- Rated Low Speed Air Flow Rate {m3/s} + {f6b2da2f-90a9-484f-a68c-96feb6cc67fe}, !- Low Speed Total Cooling Capacity Function of Temperature Curve Name + {1688d136-800f-45b7-b2f5-fb8eefcb8ef6}, !- Low Speed Energy Input Ratio Function of Temperature Curve Name + , !- Condenser Air Inlet Node Name + AirCooled, !- Condenser Type + 0, !- High Speed Evaporative Condenser Effectiveness {dimensionless} + Autosize, !- High Speed Evaporative Condenser Air Flow Rate {m3/s} + Autosize, !- High Speed Evaporative Condenser Pump Rated Power Consumption {W} + 0, !- Low Speed Evaporative Condenser Effectiveness {dimensionless} + Autosize, !- Low Speed Evaporative Condenser Air Flow Rate {m3/s} + Autosize, !- Low Speed Evaporative Condenser Pump Rated Power Consumption {W} + , !- Supply Water Storage Tank Name + , !- Condensate Collection Water Storage Tank Name + 10, !- Basin Heater Capacity {W/K} + 2, !- Basin Heater Setpoint Temperature {C} + ; !- Basin Heater Operating Schedule Name + +OS:Connection, + {17456497-0601-4040-a3d1-74842477baf8}, !- Handle + {9e4a7f89-2dcd-4ce0-a82a-2b7f4e13ba54}, !- Name + {e96be975-108b-4384-a08c-3ce47da48ea7}, !- Source Object + 9, !- Outlet Port + {710b155c-82a9-40b1-9aba-917c938057a7}, !- Target Object + 2; !- Inlet Port + +OS:Node, + {f224ed4f-f4e8-42c8-810a-254c10230c6d}, !- Handle + Zone1 Office ZN HVAC_1 No Heat Outlet Air Node, !- Name + {5ee42591-cc87-4696-b418-83d289fe665d}, !- Inlet Port + {ec929029-b8f1-421e-97a6-786ffaea560f}; !- Outlet Port + +OS:Connection, + {5ee42591-cc87-4696-b418-83d289fe665d}, !- Handle + {8cd9cf84-e18e-4196-8c31-58e08c7e9043}, !- Name + {95a3bf7a-a275-43d6-9333-e96dbe47cebd}, !- Source Object + 6, !- Outlet Port + {f224ed4f-f4e8-42c8-810a-254c10230c6d}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {ec929029-b8f1-421e-97a6-786ffaea560f}, !- Handle + {ab021739-9f8f-4f8d-b721-d3f3ba363cc4}, !- Name + {f224ed4f-f4e8-42c8-810a-254c10230c6d}, !- Source Object + 3, !- Outlet Port + {e96be975-108b-4384-a08c-3ce47da48ea7}, !- Target Object + 8; !- Inlet Port + +OS:Node, + {af0da572-2b8b-4f35-96dc-4e5dd8e476ab}, !- Handle + Zone1 Office ZN HVAC_1 Gas Htg Coil Outlet Air Node, !- Name + {b3980069-d6c1-442a-b3b6-847a9163bb2f}, !- Inlet Port + {19bdae02-45dd-4b11-b94b-e6da631ee4de}; !- Outlet Port + +OS:Connection, + {b3980069-d6c1-442a-b3b6-847a9163bb2f}, !- Handle + {2cf19f57-d045-4735-8291-1e966e6f048c}, !- Name + {d4bf2c27-92ed-4177-a706-1a645da33153}, !- Source Object + 6, !- Outlet Port + {af0da572-2b8b-4f35-96dc-4e5dd8e476ab}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {19bdae02-45dd-4b11-b94b-e6da631ee4de}, !- Handle + {ae4f7c21-1dc1-49c9-9525-3ba73bcb9d25}, !- Name + {af0da572-2b8b-4f35-96dc-4e5dd8e476ab}, !- Source Object + 3, !- Outlet Port + {95a3bf7a-a275-43d6-9333-e96dbe47cebd}, !- Target Object + 5; !- Inlet Port + +OS:Node, + {202e0150-9efb-4711-b623-ddafa96d1f00}, !- Handle + Zone1 Office ZN HVAC_1 2spd DX AC Clg Coil 65kBtu/hr 13.0SEER Outlet Air Node, !- Name + {084bc586-6541-4280-aa15-06e785e78476}, !- Inlet Port + {c54e1da2-0fe3-4554-9ea2-7da9a64a8100}; !- Outlet Port + +OS:Connection, + {084bc586-6541-4280-aa15-06e785e78476}, !- Handle + {0ed6049f-68e7-4dbf-a05e-cfab48a5a7c5}, !- Name + {810cb75c-1aac-45a7-8ab0-17f5d665460c}, !- Source Object + 8, !- Outlet Port + {202e0150-9efb-4711-b623-ddafa96d1f00}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {c54e1da2-0fe3-4554-9ea2-7da9a64a8100}, !- Handle + {26902223-4ded-4d7e-91d9-dfc8dd3d3b07}, !- Name + {202e0150-9efb-4711-b623-ddafa96d1f00}, !- Source Object + 3, !- Outlet Port + {d4bf2c27-92ed-4177-a706-1a645da33153}, !- Target Object + 5; !- Inlet Port + +OS:Controller:OutdoorAir, + {718b2381-d244-4dee-9dc9-c8aae4fb9c87}, !- Handle + Zone1 Office ZN HVAC_1 OA System Controller, !- Name + , !- Relief Air Outlet Node Name + , !- Return Air Node Name + , !- Mixed Air Node Name + , !- Actuator Node Name + autosize, !- Minimum Outdoor Air Flow Rate {m3/s} + Autosize, !- Maximum Outdoor Air Flow Rate {m3/s} + DifferentialDryBulb, !- Economizer Control Type + ModulateFlow, !- Economizer Control Action Type + , !- Economizer Maximum Limit Dry-Bulb Temperature {C} + , !- Economizer Maximum Limit Enthalpy {J/kg} + , !- Economizer Maximum Limit Dewpoint Temperature {C} + , !- Electronic Enthalpy Limit Curve Name + , !- Economizer Minimum Limit Dry-Bulb Temperature {C} + NoLockout, !- Lockout Type + FixedMinimum, !- Minimum Limit Type + {4f9c674b-7b27-453a-8602-a8158bb91eb7}, !- Minimum Outdoor Air Schedule Name + , !- Minimum Fraction of Outdoor Air Schedule Name + , !- Maximum Fraction of Outdoor Air Schedule Name + {7ae1e7b3-a2d4-4969-b668-7dc118d4dab6}, !- Controller Mechanical Ventilation + , !- Time of Day Economizer Control Schedule Name + No, !- High Humidity Control + , !- Humidistat Control Zone Name + , !- High Humidity Outdoor Air Flow Ratio + , !- Control High Indoor Humidity Based on Outdoor Humidity Ratio + BypassWhenWithinEconomizerLimits; !- Heat Recovery Bypass Control Type + +OS:Controller:MechanicalVentilation, + {7ae1e7b3-a2d4-4969-b668-7dc118d4dab6}, !- Handle + Controller Mechanical Ventilation 1, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule + , !- Demand Controlled Ventilation + ZoneSum; !- System Outdoor Air Method + +OS:AirLoopHVAC:OutdoorAirSystem, + {079bd7db-7d8a-49fb-b321-c28736d69447}, !- Handle + Zone1 Office ZN HVAC_1 OA System, !- Name + {718b2381-d244-4dee-9dc9-c8aae4fb9c87}, !- Controller Name + , !- Outdoor Air Equipment List Name + , !- Availability Manager List Name + {8104ce83-d85a-48fd-9089-56d0c98e4c31}, !- Mixed Air Node Name + {36510d31-615f-405f-862c-fe876d1163dd}, !- Outdoor Air Stream Node Name + {eb54ff48-1609-463f-b574-33d940db76d7}, !- Relief Air Stream Node Name + {1028be7d-2b0d-418b-8916-ce736974a2f7}; !- Return Air Stream Node Name + +OS:Node, + {b292a1dd-e338-41c8-8cbd-25e352ad8def}, !- Handle + Zone1 Office ZN HVAC_1 Outdoor Air Node, !- Name + , !- Inlet Port + {36510d31-615f-405f-862c-fe876d1163dd}; !- Outlet Port + +OS:Connection, + {36510d31-615f-405f-862c-fe876d1163dd}, !- Handle + {b748ed57-f061-4d69-93ce-3e07f3975fee}, !- Name + {b292a1dd-e338-41c8-8cbd-25e352ad8def}, !- Source Object + 3, !- Outlet Port + {079bd7db-7d8a-49fb-b321-c28736d69447}, !- Target Object + 6; !- Inlet Port + +OS:Node, + {810b927e-2578-4e71-a362-bcdf0f14298d}, !- Handle + Zone1 Office ZN HVAC_1 Relief Air Node, !- Name + {eb54ff48-1609-463f-b574-33d940db76d7}, !- Inlet Port + ; !- Outlet Port + +OS:Connection, + {eb54ff48-1609-463f-b574-33d940db76d7}, !- Handle + {fc434722-065e-45ac-8058-a0497ecbfbb6}, !- Name + {079bd7db-7d8a-49fb-b321-c28736d69447}, !- Source Object + 7, !- Outlet Port + {810b927e-2578-4e71-a362-bcdf0f14298d}, !- Target Object + 2; !- Inlet Port + +OS:Node, + {445fad7f-df23-45a6-b581-e2b6b1d854f4}, !- Handle + Zone1 Office ZN HVAC_1 Mixed Air Node, !- Name + {8104ce83-d85a-48fd-9089-56d0c98e4c31}, !- Inlet Port + {0f0709ba-a1de-4238-8832-3a40551f0c52}; !- Outlet Port + +OS:Connection, + {1028be7d-2b0d-418b-8916-ce736974a2f7}, !- Handle + {f87058ee-d816-4b27-8e8e-3aaeb66db52e}, !- Name + {2abda90c-010e-4677-9b1f-a225e6426414}, !- Source Object + 3, !- Outlet Port + {079bd7db-7d8a-49fb-b321-c28736d69447}, !- Target Object + 8; !- Inlet Port + +OS:Connection, + {8104ce83-d85a-48fd-9089-56d0c98e4c31}, !- Handle + {e620600b-2a0d-4341-90c0-cecc08efba2e}, !- Name + {079bd7db-7d8a-49fb-b321-c28736d69447}, !- Source Object + 5, !- Outlet Port + {445fad7f-df23-45a6-b581-e2b6b1d854f4}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {0f0709ba-a1de-4238-8832-3a40551f0c52}, !- Handle + {ec74e912-c0cd-465c-b32c-aaff2190c487}, !- Name + {445fad7f-df23-45a6-b581-e2b6b1d854f4}, !- Source Object + 3, !- Outlet Port + {810cb75c-1aac-45a7-8ab0-17f5d665460c}, !- Target Object + 7; !- Inlet Port + +OS:AvailabilityManager:NightCycle, + {60e6f146-66c5-4950-9875-ff171c9c6ec6}, !- Handle + Availability Manager Night Cycle 1, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Applicability Schedule + , !- Fan Schedule + CycleOnAny, !- Control Type + 1, !- Thermostat Tolerance {deltaC} + , !- Cycling Run Time Control Type + 1800, !- Cycling Run Time {s} + {9ab4d3a6-a05e-4923-b842-2ebad590f812}, !- Control Zone or Zone List Name + {e0230166-2d1b-46fe-a39e-34371eaa6a0d}, !- Cooling Control Zone or Zone List Name + {5df1a57c-1510-46b8-9a7e-7190b786d4f3}, !- Heating Control Zone or Zone List Name + {13f00919-33ed-4a6d-9042-52ed7be2e982}; !- Heating Zone Fans Only Zone or Zone List Name + +OS:ModelObjectList, + {9ab4d3a6-a05e-4923-b842-2ebad590f812}, !- Handle + Availability Manager Night Cycle 1 Control Zone List; !- Name + +OS:ModelObjectList, + {e0230166-2d1b-46fe-a39e-34371eaa6a0d}, !- Handle + Availability Manager Night Cycle 1 Cooling Control Zone List; !- Name + +OS:ModelObjectList, + {5df1a57c-1510-46b8-9a7e-7190b786d4f3}, !- Handle + Availability Manager Night Cycle 1 Heating Control Zone List; !- Name + +OS:ModelObjectList, + {13f00919-33ed-4a6d-9042-52ed7be2e982}, !- Handle + Availability Manager Night Cycle 1 Heating Zone Fans Only Zone List; !- Name + +OS:AirTerminal:SingleDuct:ConstantVolume:NoReheat, + {7919ed84-1780-45df-aa2c-df7dc40765e4}, !- Handle + Zone1 Office ZN HVAC_1 Diffuser, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule Name + {85123251-51ed-4926-82d6-c9f66d6a7f29}, !- Air Inlet Node Name + {29fb13d7-7413-498d-8992-2034eeffca34}, !- Air Outlet Node Name + AutoSize; !- Maximum Air Flow Rate {m3/s} + +OS:Node, + {c9eab791-9595-43bf-b9e9-9e752b734b12}, !- Handle + Zone1 Office ZN Return Air Node, !- Name + {909d945c-8464-4456-9973-c93b78ddb182}, !- Inlet Port + {117c87fa-8e9a-4b92-8c7e-fd0cdcc2afed}; !- Outlet Port + +OS:Connection, + {0148dad5-4a85-482f-92fc-7eca6b3ec49b}, !- Handle + {c4f9ca3a-ff4b-424c-bf1f-1a0bbb46e25b}, !- Name + {4c91eee9-e190-4de5-9809-cc83552e2ccc}, !- Source Object + 3, !- Outlet Port + {14fdfff4-47f8-49a3-aa88-85135633f226}, !- Target Object + 3; !- Inlet Port + +OS:Connection, + {909d945c-8464-4456-9973-c93b78ddb182}, !- Handle + {95d394af-e763-480c-8222-8b689fa83963}, !- Name + {faeaa9b9-5a41-4020-b1de-80ba96ba34b1}, !- Source Object + 3, !- Outlet Port + {c9eab791-9595-43bf-b9e9-9e752b734b12}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {117c87fa-8e9a-4b92-8c7e-fd0cdcc2afed}, !- Handle + {4577c090-99a4-4daf-8890-7891a3195bdb}, !- Name + {c9eab791-9595-43bf-b9e9-9e752b734b12}, !- Source Object + 3, !- Outlet Port + {b3b817e9-0640-4b2e-9a4f-dc7c33378011}, !- Target Object + 3; !- Inlet Port + +OS:Node, + {cb2f3999-653a-4f4d-b2fb-ab178542d435}, !- Handle + Zone1 Office ZN HVAC_1 Diffuser Inlet Air Node, !- Name + {4bbc892d-d6b0-41b5-87d3-2bf339b7ae7c}, !- Inlet Port + {85123251-51ed-4926-82d6-c9f66d6a7f29}; !- Outlet Port + +OS:Connection, + {4bbc892d-d6b0-41b5-87d3-2bf339b7ae7c}, !- Handle + {c6940341-4529-4155-9312-9b50daf75e3b}, !- Name + {f2222084-d326-4e2e-b955-3376b578a301}, !- Source Object + 3, !- Outlet Port + {cb2f3999-653a-4f4d-b2fb-ab178542d435}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {85123251-51ed-4926-82d6-c9f66d6a7f29}, !- Handle + {0a52c5ae-fafe-407a-990b-6b18ec59bf63}, !- Name + {cb2f3999-653a-4f4d-b2fb-ab178542d435}, !- Source Object + 3, !- Outlet Port + {7919ed84-1780-45df-aa2c-df7dc40765e4}, !- Target Object + 3; !- Inlet Port + +OS:Connection, + {29fb13d7-7413-498d-8992-2034eeffca34}, !- Handle + {f312c616-c356-4724-ada4-bfadc00d6e59}, !- Name + {7919ed84-1780-45df-aa2c-df7dc40765e4}, !- Source Object + 4, !- Outlet Port + {4c91eee9-e190-4de5-9809-cc83552e2ccc}, !- Target Object + 2; !- Inlet Port + +OS:AirLoopHVAC, + {fe14157f-7077-4875-ba0c-ada6bd698667}, !- Handle + Zone2 Fine Storage ZN HVAC_2, !- Name + , !- Controller List Name + {f575a3cd-5131-4672-9b4b-4077d4ecf8dd}, !- Availability Schedule + {ad7d79f2-6d17-461d-9292-16ec63475547}, !- Availability Manager List Name + AutoSize, !- Design Supply Air Flow Rate {m3/s} + , !- Branch List Name + , !- Connector List Name + {c2f36e7b-64fe-460c-96a7-5ddf9862745f}, !- Supply Side Inlet Node Name + {7d9110d4-baa0-4328-9491-c4fb7d89ce84}, !- Demand Side Outlet Node Name + {bed3d74f-611f-41c7-ac6d-b53b8b152932}, !- Demand Side Inlet Node A + {ccecd6cc-a110-4847-bbac-d795dab7bae0}, !- Supply Side Outlet Node A + , !- Demand Side Inlet Node B + , !- Supply Side Outlet Node B + , !- Return Air Bypass Flow Temperature Setpoint Schedule Name + {f48a7f02-1332-4542-9403-41e75a34fbe5}, !- Demand Mixer Name + {64458484-184f-4ef3-99b6-ef4bb5dbd97f}, !- Demand Splitter A Name + , !- Demand Splitter B Name + ; !- Supply Splitter Name + +OS:Node, + {7e3ccadc-4338-4015-a3a0-28fc411334be}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Supply Inlet Node, !- Name + {c2f36e7b-64fe-460c-96a7-5ddf9862745f}, !- Inlet Port + {dbc7d206-61b2-4a24-9b89-69ea3805285f}; !- Outlet Port + +OS:Node, + {8352cad3-e01b-42f7-a77f-f86873486ea7}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Supply Outlet Node, !- Name + {5bfdee91-ae2f-4775-bf91-75e365230ba3}, !- Inlet Port + {ccecd6cc-a110-4847-bbac-d795dab7bae0}; !- Outlet Port + +OS:Connection, + {c2f36e7b-64fe-460c-96a7-5ddf9862745f}, !- Handle + {556afa7d-b437-424e-bccb-b180ea57a0f0}, !- Name + {fe14157f-7077-4875-ba0c-ada6bd698667}, !- Source Object + 8, !- Outlet Port + {7e3ccadc-4338-4015-a3a0-28fc411334be}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {ccecd6cc-a110-4847-bbac-d795dab7bae0}, !- Handle + {ab637794-145d-4f6b-be85-a7f636eb969b}, !- Name + {8352cad3-e01b-42f7-a77f-f86873486ea7}, !- Source Object + 3, !- Outlet Port + {fe14157f-7077-4875-ba0c-ada6bd698667}, !- Target Object + 11; !- Inlet Port + +OS:Node, + {6014d880-3f12-441b-b775-d9a69c0a9a09}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Demand Inlet Node, !- Name + {bed3d74f-611f-41c7-ac6d-b53b8b152932}, !- Inlet Port + {8dfa4124-efe1-464c-a120-cc8d74355662}; !- Outlet Port + +OS:Node, + {29948318-560b-4829-8836-b2ec4b70e1e0}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Demand Outlet Node, !- Name + {25eb4a0b-f449-46e4-a4ab-3afdd8ff7dd7}, !- Inlet Port + {7d9110d4-baa0-4328-9491-c4fb7d89ce84}; !- Outlet Port + +OS:Node, + {3b4bf328-a1d0-4092-a90f-aba6df6b23bc}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Diffuser Outlet Air Node, !- Name + {43c2324b-d5ce-4e3e-ac25-81095ae4ccc3}, !- Inlet Port + {caf7517e-d99a-4bcb-b6c3-7fbbf19a73eb}; !- Outlet Port + +OS:Connection, + {bed3d74f-611f-41c7-ac6d-b53b8b152932}, !- Handle + {7578db8d-c2d5-4d85-9608-dbcdb6463bfd}, !- Name + {fe14157f-7077-4875-ba0c-ada6bd698667}, !- Source Object + 10, !- Outlet Port + {6014d880-3f12-441b-b775-d9a69c0a9a09}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {7d9110d4-baa0-4328-9491-c4fb7d89ce84}, !- Handle + {2058a633-1d52-4645-8ba4-b488369959b8}, !- Name + {29948318-560b-4829-8836-b2ec4b70e1e0}, !- Source Object + 3, !- Outlet Port + {fe14157f-7077-4875-ba0c-ada6bd698667}, !- Target Object + 9; !- Inlet Port + +OS:AirLoopHVAC:ZoneSplitter, + {64458484-184f-4ef3-99b6-ef4bb5dbd97f}, !- Handle + Air Loop HVAC Zone Splitter 2, !- Name + {8dfa4124-efe1-464c-a120-cc8d74355662}, !- Inlet Node Name + {745703e7-d67a-48f2-901f-51d4d6d1d773}; !- Outlet Node Name 1 + +OS:AirLoopHVAC:ZoneMixer, + {f48a7f02-1332-4542-9403-41e75a34fbe5}, !- Handle + Air Loop HVAC Zone Mixer 2, !- Name + {25eb4a0b-f449-46e4-a4ab-3afdd8ff7dd7}, !- Outlet Node Name + {2c540f08-2233-42e7-a3e2-8b89c8ee177e}; !- Inlet Node Name 1 + +OS:Connection, + {8dfa4124-efe1-464c-a120-cc8d74355662}, !- Handle + {dae8b76e-7a7e-4b5e-8e70-0e4d2c6040f0}, !- Name + {6014d880-3f12-441b-b775-d9a69c0a9a09}, !- Source Object + 3, !- Outlet Port + {64458484-184f-4ef3-99b6-ef4bb5dbd97f}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {25eb4a0b-f449-46e4-a4ab-3afdd8ff7dd7}, !- Handle + {3c506674-0aed-4a1d-bc89-2fbfbc02cc7c}, !- Name + {f48a7f02-1332-4542-9403-41e75a34fbe5}, !- Source Object + 2, !- Outlet Port + {29948318-560b-4829-8836-b2ec4b70e1e0}, !- Target Object + 2; !- Inlet Port + +OS:Sizing:System, + {5f738e4a-0e31-4b47-995d-d3bb6b9d3054}, !- Handle + {fe14157f-7077-4875-ba0c-ada6bd698667}, !- AirLoop Name + Sensible, !- Type of Load to Size On + Autosize, !- Design Outdoor Air Flow Rate {m3/s} + 1, !- Central Heating Maximum System Air Flow Ratio + 7.22222222222229, !- Preheat Design Temperature {C} + 0.008, !- Preheat Design Humidity Ratio {kg-H2O/kg-Air} + 12.7777777777778, !- Precool Design Temperature {C} + 0.008, !- Precool Design Humidity Ratio {kg-H2O/kg-Air} + 12.7777777777778, !- Central Cooling Design Supply Air Temperature {C} + 50.0000000000001, !- Central Heating Design Supply Air Temperature {C} + Coincident, !- Sizing Option + No, !- 100% Outdoor Air in Cooling + No, !- 100% Outdoor Air in Heating + 0.0085, !- Central Cooling Design Supply Air Humidity Ratio {kg-H2O/kg-Air} + 0.008, !- Central Heating Design Supply Air Humidity Ratio {kg-H2O/kg-Air} + DesignDay, !- Cooling Design Air Flow Method + 0, !- Cooling Design Air Flow Rate {m3/s} + DesignDay, !- Heating Design Air Flow Method + 0, !- Heating Design Air Flow Rate {m3/s} + ZoneSum, !- System Outdoor Air Method + 1, !- Zone Maximum Outdoor Air Fraction {dimensionless} + 0.0099676501, !- Cooling Supply Air Flow Rate Per Floor Area {m3/s-m2} + 1, !- Cooling Fraction of Autosized Cooling Supply Air Flow Rate + 3.9475456e-05, !- Cooling Supply Air Flow Rate Per Unit Cooling Capacity {m3/s-W} + 0.0099676501, !- Heating Supply Air Flow Rate Per Floor Area {m3/s-m2} + 1, !- Heating Fraction of Autosized Heating Supply Air Flow Rate + 1, !- Heating Fraction of Autosized Cooling Supply Air Flow Rate + 3.1588213e-05, !- Heating Supply Air Flow Rate Per Unit Heating Capacity {m3/s-W} + CoolingDesignCapacity, !- Cooling Design Capacity Method + autosize, !- Cooling Design Capacity {W} + 234.7, !- Cooling Design Capacity Per Floor Area {W/m2} + 1, !- Fraction of Autosized Cooling Design Capacity + HeatingDesignCapacity, !- Heating Design Capacity Method + autosize, !- Heating Design Capacity {W} + 157, !- Heating Design Capacity Per Floor Area {W/m2} + 1, !- Fraction of Autosized Heating Design Capacity + OnOff; !- Central Cooling Capacity Control Method + +OS:AvailabilityManagerAssignmentList, + {ad7d79f2-6d17-461d-9292-16ec63475547}, !- Handle + Air Loop HVAC 1 AvailabilityManagerAssignmentList 1, !- Name + {0ea17e00-f3cb-4236-98f2-100704c0fd99}; !- Availability Manager Name 1 + +OS:SetpointManager:SingleZone:Reheat, + {24071933-688d-49c1-a23d-4b74ee74a96e}, !- Handle + Zone2 Fine Storage ZN Setpoint Manager SZ Reheat, !- Name + 12.7777777777778, !- Minimum Supply Air Temperature {C} + 50.0000000000001, !- Maximum Supply Air Temperature {C} + {3230a61a-488a-477c-bc81-f7fed1dfe7b0}, !- Control Zone Name + {8352cad3-e01b-42f7-a77f-f86873486ea7}; !- Setpoint Node or NodeList Name + +OS:Fan:ConstantVolume, + {abcc5e9e-5e0a-4e86-b925-151791967b3e}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Fan, !- Name + {f575a3cd-5131-4672-9b4b-4077d4ecf8dd}, !- Availability Schedule Name + 0.58175, !- Fan Total Efficiency + 622.722275, !- Pressure Rise {Pa} + AutoSize, !- Maximum Flow Rate {m3/s} + 0.895, !- Motor Efficiency + 1, !- Motor In Airstream Fraction + {b327ddbd-f33c-4a57-b80e-14e2ab608ea9}, !- Air Inlet Node Name + {5bfdee91-ae2f-4775-bf91-75e365230ba3}, !- Air Outlet Node Name + ; !- End-Use Subcategory + +OS:Coil:Heating:Gas, + {7087f4db-1922-4f22-92eb-2cf4d73148d4}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Gas Htg Coil, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule Name + 0.8, !- Gas Burner Efficiency + AutoSize, !- Nominal Capacity {W} + {f9ec964b-f318-4618-997d-b74ed0a97660}, !- Air Inlet Node Name + {a387d3c6-1599-4ba5-a55d-a1aa27c4025c}, !- Air Outlet Node Name + , !- Temperature Setpoint Node Name + 0, !- Parasitic Electric Load {W} + , !- Part Load Fraction Correlation Curve Name + 0; !- Parasitic Gas Load {W} + +OS:Coil:Heating:Electric, + {fd6603e7-156e-411c-bc56-a507d64b5d5f}, !- Handle + Zone2 Fine Storage ZN HVAC_2 No Heat, !- Name + {aa41ca63-59df-4ec8-b707-e32f1eb8b2e6}, !- Availability Schedule Name + 1, !- Efficiency + 0, !- Nominal Capacity {W} + {de97d5a0-851d-4166-8b84-1b3cdf099e4d}, !- Air Inlet Node Name + {4dbd0e5b-7a28-4c77-94dd-0e1e7a93b59d}; !- Air Outlet Node Name + +OS:Coil:Cooling:DX:TwoSpeed, + {93e9f4eb-ef76-4dad-abfc-1f564986ae78}, !- Handle + Zone2 Fine Storage ZN HVAC_2 2spd DX AC Clg Coil 175kBtu/hr 10.8EER, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule Name + Autosize, !- Rated High Speed Total Cooling Capacity {W} + Autosize, !- Rated High Speed Sensible Heat Ratio + 3.73224036438218, !- Rated High Speed COP {W/W} + Autosize, !- Rated High Speed Air Flow Rate {m3/s} + {025b4265-8b96-4054-a0f6-9c5b810fd7fd}, !- Air Inlet Node Name + {3c100989-a69c-4d7c-ac19-b01e4c5c0b4e}, !- Air Outlet Node Name + {146906a8-1178-404b-90a5-96b5b70f836e}, !- Total Cooling Capacity Function of Temperature Curve Name + {cccea7a4-0bef-410f-bb50-0501b2de86eb}, !- Total Cooling Capacity Function of Flow Fraction Curve Name + {329133a8-11d6-48fe-b1e3-dd58f7893802}, !- Energy Input Ratio Function of Temperature Curve Name + {315c9c06-7982-45ad-a003-85c2101a0384}, !- Energy Input Ratio Function of Flow Fraction Curve Name + {d186e2df-d98a-4407-872e-53069b0f0d90}, !- Part Load Fraction Correlation Curve Name + Autosize, !- Rated Low Speed Total Cooling Capacity {W} + 0.69, !- Rated Low Speed Sensible Heat Ratio + 3.73224036438218, !- Rated Low Speed COP {W/W} + , !- Rated Low Speed Air Flow Rate {m3/s} + {146906a8-1178-404b-90a5-96b5b70f836e}, !- Low Speed Total Cooling Capacity Function of Temperature Curve Name + {329133a8-11d6-48fe-b1e3-dd58f7893802}, !- Low Speed Energy Input Ratio Function of Temperature Curve Name + , !- Condenser Air Inlet Node Name + AirCooled, !- Condenser Type + 0, !- High Speed Evaporative Condenser Effectiveness {dimensionless} + Autosize, !- High Speed Evaporative Condenser Air Flow Rate {m3/s} + Autosize, !- High Speed Evaporative Condenser Pump Rated Power Consumption {W} + 0, !- Low Speed Evaporative Condenser Effectiveness {dimensionless} + Autosize, !- Low Speed Evaporative Condenser Air Flow Rate {m3/s} + Autosize, !- Low Speed Evaporative Condenser Pump Rated Power Consumption {W} + , !- Supply Water Storage Tank Name + , !- Condensate Collection Water Storage Tank Name + 10, !- Basin Heater Capacity {W/K} + 2, !- Basin Heater Setpoint Temperature {C} + ; !- Basin Heater Operating Schedule Name + +OS:Connection, + {5bfdee91-ae2f-4775-bf91-75e365230ba3}, !- Handle + {8d9971f6-1acd-4209-b5f8-0378b9591726}, !- Name + {abcc5e9e-5e0a-4e86-b925-151791967b3e}, !- Source Object + 9, !- Outlet Port + {8352cad3-e01b-42f7-a77f-f86873486ea7}, !- Target Object + 2; !- Inlet Port + +OS:Node, + {e6ef2488-b1bd-45de-b16d-c65c62360c8d}, !- Handle + Zone2 Fine Storage ZN HVAC_2 No Heat Outlet Air Node, !- Name + {4dbd0e5b-7a28-4c77-94dd-0e1e7a93b59d}, !- Inlet Port + {b327ddbd-f33c-4a57-b80e-14e2ab608ea9}; !- Outlet Port + +OS:Connection, + {4dbd0e5b-7a28-4c77-94dd-0e1e7a93b59d}, !- Handle + {6d66ffd9-f951-49df-96db-3268dc2a7553}, !- Name + {fd6603e7-156e-411c-bc56-a507d64b5d5f}, !- Source Object + 6, !- Outlet Port + {e6ef2488-b1bd-45de-b16d-c65c62360c8d}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {b327ddbd-f33c-4a57-b80e-14e2ab608ea9}, !- Handle + {7908012f-3f30-4d2e-812d-a3be87b8072a}, !- Name + {e6ef2488-b1bd-45de-b16d-c65c62360c8d}, !- Source Object + 3, !- Outlet Port + {abcc5e9e-5e0a-4e86-b925-151791967b3e}, !- Target Object + 8; !- Inlet Port + +OS:Node, + {8b1640b4-6712-4ed5-8e4e-4f9fd109021e}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Gas Htg Coil Outlet Air Node, !- Name + {a387d3c6-1599-4ba5-a55d-a1aa27c4025c}, !- Inlet Port + {de97d5a0-851d-4166-8b84-1b3cdf099e4d}; !- Outlet Port + +OS:Connection, + {a387d3c6-1599-4ba5-a55d-a1aa27c4025c}, !- Handle + {a04ede1e-f372-426e-907b-0429242df701}, !- Name + {7087f4db-1922-4f22-92eb-2cf4d73148d4}, !- Source Object + 6, !- Outlet Port + {8b1640b4-6712-4ed5-8e4e-4f9fd109021e}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {de97d5a0-851d-4166-8b84-1b3cdf099e4d}, !- Handle + {1bbd6f7c-0b9b-4bc3-a2d3-a9fb380cc5ba}, !- Name + {8b1640b4-6712-4ed5-8e4e-4f9fd109021e}, !- Source Object + 3, !- Outlet Port + {fd6603e7-156e-411c-bc56-a507d64b5d5f}, !- Target Object + 5; !- Inlet Port + +OS:Node, + {05b80a3d-e871-46f8-9bf0-654874c7c72e}, !- Handle + Zone2 Fine Storage ZN HVAC_2 2spd DX AC Clg Coil 175kBtu/hr 10.8EER Outlet Air Node, !- Name + {3c100989-a69c-4d7c-ac19-b01e4c5c0b4e}, !- Inlet Port + {f9ec964b-f318-4618-997d-b74ed0a97660}; !- Outlet Port + +OS:Connection, + {3c100989-a69c-4d7c-ac19-b01e4c5c0b4e}, !- Handle + {768b04ee-e9ad-46e5-969f-ef1f73af7957}, !- Name + {93e9f4eb-ef76-4dad-abfc-1f564986ae78}, !- Source Object + 8, !- Outlet Port + {05b80a3d-e871-46f8-9bf0-654874c7c72e}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {f9ec964b-f318-4618-997d-b74ed0a97660}, !- Handle + {a575d800-3e58-481c-8d35-a65f86d89b0c}, !- Name + {05b80a3d-e871-46f8-9bf0-654874c7c72e}, !- Source Object + 3, !- Outlet Port + {7087f4db-1922-4f22-92eb-2cf4d73148d4}, !- Target Object + 5; !- Inlet Port + +OS:Controller:OutdoorAir, + {cf5e9cad-314f-4e00-9e8e-268f965fd5c1}, !- Handle + Zone2 Fine Storage ZN HVAC_2 OA System Controller, !- Name + , !- Relief Air Outlet Node Name + , !- Return Air Node Name + , !- Mixed Air Node Name + , !- Actuator Node Name + autosize, !- Minimum Outdoor Air Flow Rate {m3/s} + Autosize, !- Maximum Outdoor Air Flow Rate {m3/s} + DifferentialDryBulb, !- Economizer Control Type + ModulateFlow, !- Economizer Control Action Type + , !- Economizer Maximum Limit Dry-Bulb Temperature {C} + , !- Economizer Maximum Limit Enthalpy {J/kg} + , !- Economizer Maximum Limit Dewpoint Temperature {C} + , !- Electronic Enthalpy Limit Curve Name + , !- Economizer Minimum Limit Dry-Bulb Temperature {C} + NoLockout, !- Lockout Type + FixedMinimum, !- Minimum Limit Type + {4f9c674b-7b27-453a-8602-a8158bb91eb7}, !- Minimum Outdoor Air Schedule Name + , !- Minimum Fraction of Outdoor Air Schedule Name + , !- Maximum Fraction of Outdoor Air Schedule Name + {eeed6fc6-c23c-474d-a57c-42fefd7d5356}, !- Controller Mechanical Ventilation + , !- Time of Day Economizer Control Schedule Name + No, !- High Humidity Control + , !- Humidistat Control Zone Name + , !- High Humidity Outdoor Air Flow Ratio + , !- Control High Indoor Humidity Based on Outdoor Humidity Ratio + BypassWhenWithinEconomizerLimits; !- Heat Recovery Bypass Control Type + +OS:Controller:MechanicalVentilation, + {eeed6fc6-c23c-474d-a57c-42fefd7d5356}, !- Handle + Controller Mechanical Ventilation 2, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule + , !- Demand Controlled Ventilation + ZoneSum; !- System Outdoor Air Method + +OS:AirLoopHVAC:OutdoorAirSystem, + {6616acb5-4b04-44be-96c8-75b2c07b2851}, !- Handle + Zone2 Fine Storage ZN HVAC_2 OA System, !- Name + {cf5e9cad-314f-4e00-9e8e-268f965fd5c1}, !- Controller Name + , !- Outdoor Air Equipment List Name + , !- Availability Manager List Name + {0917a552-a01b-42dc-8fe8-6cf444504f06}, !- Mixed Air Node Name + {5f2624dc-bb9f-4631-8cdc-c71e55db933c}, !- Outdoor Air Stream Node Name + {2bef5139-f9af-412a-a49b-84ea0725e8fa}, !- Relief Air Stream Node Name + {dbc7d206-61b2-4a24-9b89-69ea3805285f}; !- Return Air Stream Node Name + +OS:Node, + {be3d474c-d518-4902-9c61-67d4bf09b637}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Outdoor Air Node, !- Name + , !- Inlet Port + {5f2624dc-bb9f-4631-8cdc-c71e55db933c}; !- Outlet Port + +OS:Connection, + {5f2624dc-bb9f-4631-8cdc-c71e55db933c}, !- Handle + {dbda1c3c-f215-44ed-b824-68e4738202ae}, !- Name + {be3d474c-d518-4902-9c61-67d4bf09b637}, !- Source Object + 3, !- Outlet Port + {6616acb5-4b04-44be-96c8-75b2c07b2851}, !- Target Object + 6; !- Inlet Port + +OS:Node, + {e8661939-ef7a-4b0c-b592-f9d7928e9178}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Relief Air Node, !- Name + {2bef5139-f9af-412a-a49b-84ea0725e8fa}, !- Inlet Port + ; !- Outlet Port + +OS:Connection, + {2bef5139-f9af-412a-a49b-84ea0725e8fa}, !- Handle + {99f61b55-ad40-466d-b203-9f601e77c3e9}, !- Name + {6616acb5-4b04-44be-96c8-75b2c07b2851}, !- Source Object + 7, !- Outlet Port + {e8661939-ef7a-4b0c-b592-f9d7928e9178}, !- Target Object + 2; !- Inlet Port + +OS:Node, + {1c1d6577-5037-4c19-bc8b-96104be31250}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Mixed Air Node, !- Name + {0917a552-a01b-42dc-8fe8-6cf444504f06}, !- Inlet Port + {025b4265-8b96-4054-a0f6-9c5b810fd7fd}; !- Outlet Port + +OS:Connection, + {dbc7d206-61b2-4a24-9b89-69ea3805285f}, !- Handle + {acda51d0-4b4a-428f-a502-8638e0dc3bcf}, !- Name + {7e3ccadc-4338-4015-a3a0-28fc411334be}, !- Source Object + 3, !- Outlet Port + {6616acb5-4b04-44be-96c8-75b2c07b2851}, !- Target Object + 8; !- Inlet Port + +OS:Connection, + {0917a552-a01b-42dc-8fe8-6cf444504f06}, !- Handle + {1922fbd5-ed02-40fd-9ec1-4c7e1501f3e3}, !- Name + {6616acb5-4b04-44be-96c8-75b2c07b2851}, !- Source Object + 5, !- Outlet Port + {1c1d6577-5037-4c19-bc8b-96104be31250}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {025b4265-8b96-4054-a0f6-9c5b810fd7fd}, !- Handle + {e2de789f-1a34-47b5-aba0-2ac1bc55ae60}, !- Name + {1c1d6577-5037-4c19-bc8b-96104be31250}, !- Source Object + 3, !- Outlet Port + {93e9f4eb-ef76-4dad-abfc-1f564986ae78}, !- Target Object + 7; !- Inlet Port + +OS:AvailabilityManager:NightCycle, + {0ea17e00-f3cb-4236-98f2-100704c0fd99}, !- Handle + Availability Manager Night Cycle 2, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Applicability Schedule + , !- Fan Schedule + CycleOnAny, !- Control Type + 1, !- Thermostat Tolerance {deltaC} + , !- Cycling Run Time Control Type + 1800, !- Cycling Run Time {s} + {11af6438-3c02-4b09-aecd-5dd4043e9927}, !- Control Zone or Zone List Name + {1b9598c8-d521-43e8-ab0d-4895b7c24d56}, !- Cooling Control Zone or Zone List Name + {67082667-f78f-4ef1-875c-3918441cac5c}, !- Heating Control Zone or Zone List Name + {9f27daad-e4e2-4108-a1bb-26c71b255798}; !- Heating Zone Fans Only Zone or Zone List Name + +OS:ModelObjectList, + {11af6438-3c02-4b09-aecd-5dd4043e9927}, !- Handle + Availability Manager Night Cycle 2 Control Zone List; !- Name + +OS:ModelObjectList, + {1b9598c8-d521-43e8-ab0d-4895b7c24d56}, !- Handle + Availability Manager Night Cycle 2 Cooling Control Zone List; !- Name + +OS:ModelObjectList, + {67082667-f78f-4ef1-875c-3918441cac5c}, !- Handle + Availability Manager Night Cycle 2 Heating Control Zone List; !- Name + +OS:ModelObjectList, + {9f27daad-e4e2-4108-a1bb-26c71b255798}, !- Handle + Availability Manager Night Cycle 2 Heating Zone Fans Only Zone List; !- Name + +OS:AirTerminal:SingleDuct:ConstantVolume:NoReheat, + {0ceee5cf-bef0-4d06-8728-dc0a2d572945}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Diffuser, !- Name + {b3794e0e-1450-423f-aa4b-5046b9d06672}, !- Availability Schedule Name + {0bf7572d-20ee-4f53-9ad8-83be4c06f657}, !- Air Inlet Node Name + {43c2324b-d5ce-4e3e-ac25-81095ae4ccc3}, !- Air Outlet Node Name + AutoSize; !- Maximum Air Flow Rate {m3/s} + +OS:Node, + {d5a048d9-87ad-40a7-a0c5-2c97f23e0b3f}, !- Handle + Zone2 Fine Storage ZN Return Air Node, !- Name + {58e42913-adcf-41a6-80cf-f2a51bb4da83}, !- Inlet Port + {2c540f08-2233-42e7-a3e2-8b89c8ee177e}; !- Outlet Port + +OS:Connection, + {caf7517e-d99a-4bcb-b6c3-7fbbf19a73eb}, !- Handle + {f1a26449-54ab-4de2-a090-29f87159b248}, !- Name + {3b4bf328-a1d0-4092-a90f-aba6df6b23bc}, !- Source Object + 3, !- Outlet Port + {770bfe67-c97d-4c2a-a623-abc337ee287b}, !- Target Object + 3; !- Inlet Port + +OS:Connection, + {58e42913-adcf-41a6-80cf-f2a51bb4da83}, !- Handle + {2412f034-a851-4a07-9221-be842c2bb5c0}, !- Name + {d00784c7-878c-48bd-bb56-db5008cd76f8}, !- Source Object + 3, !- Outlet Port + {d5a048d9-87ad-40a7-a0c5-2c97f23e0b3f}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {2c540f08-2233-42e7-a3e2-8b89c8ee177e}, !- Handle + {f0c35b20-d8f0-4538-a12d-9d83adae7076}, !- Name + {d5a048d9-87ad-40a7-a0c5-2c97f23e0b3f}, !- Source Object + 3, !- Outlet Port + {f48a7f02-1332-4542-9403-41e75a34fbe5}, !- Target Object + 3; !- Inlet Port + +OS:Node, + {45e07fb5-35e9-44eb-871a-9db4894f8656}, !- Handle + Zone2 Fine Storage ZN HVAC_2 Diffuser Inlet Air Node, !- Name + {745703e7-d67a-48f2-901f-51d4d6d1d773}, !- Inlet Port + {0bf7572d-20ee-4f53-9ad8-83be4c06f657}; !- Outlet Port + +OS:Connection, + {745703e7-d67a-48f2-901f-51d4d6d1d773}, !- Handle + {2276239d-9fd7-433b-9c12-798092e13fbc}, !- Name + {64458484-184f-4ef3-99b6-ef4bb5dbd97f}, !- Source Object + 3, !- Outlet Port + {45e07fb5-35e9-44eb-871a-9db4894f8656}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {0bf7572d-20ee-4f53-9ad8-83be4c06f657}, !- Handle + {b37e6dd4-7fac-4d56-bda3-d767196914ba}, !- Name + {45e07fb5-35e9-44eb-871a-9db4894f8656}, !- Source Object + 3, !- Outlet Port + {0ceee5cf-bef0-4d06-8728-dc0a2d572945}, !- Target Object + 3; !- Inlet Port + +OS:Connection, + {43c2324b-d5ce-4e3e-ac25-81095ae4ccc3}, !- Handle + {6187b1a1-d960-4c17-bfde-183b06726e36}, !- Name + {0ceee5cf-bef0-4d06-8728-dc0a2d572945}, !- Source Object + 4, !- Outlet Port + {3b4bf328-a1d0-4092-a90f-aba6df6b23bc}, !- Target Object + 2; !- Inlet Port + +OS:Schedule:Ruleset, + {06c70b81-2930-46ed-a9e0-addf2a0ffb74}, !- Handle + Always On, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + {9eefa1e9-afec-4885-9a21-d3c17c0ff11b}; !- Default Day Schedule Name + +OS:Schedule:Day, + {9eefa1e9-afec-4885-9a21-d3c17c0ff11b}, !- Handle + Always On Default, !- Name + {e695cf0d-8c38-41d9-b07d-c8b5a7fd6cea}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 1; !- Value Until Time 1 + +OS:Fan:ConstantVolume, + {d149a11c-aa96-49da-91b0-e418edc631ab}, !- Handle + Zone3 Bulk Storage ZN UnitHeater Fan, !- Name + {06c70b81-2930-46ed-a9e0-addf2a0ffb74}, !- Availability Schedule Name + 0.55575, !- Fan Total Efficiency + 49.817782, !- Pressure Rise {Pa} + AutoSize, !- Maximum Flow Rate {m3/s} + 0.855, !- Motor Efficiency + 1, !- Motor In Airstream Fraction + , !- Air Inlet Node Name + , !- Air Outlet Node Name + ; !- End-Use Subcategory + +OS:Coil:Heating:Gas, + {2cc5cdb1-25bc-48bc-a4bf-510e26ff9116}, !- Handle + Zone3 Bulk Storage ZN UnitHeater Gas Htg Coil, !- Name + {06c70b81-2930-46ed-a9e0-addf2a0ffb74}, !- Availability Schedule Name + 0.8, !- Gas Burner Efficiency + AutoSize, !- Nominal Capacity {W} + , !- Air Inlet Node Name + , !- Air Outlet Node Name + , !- Temperature Setpoint Node Name + 0, !- Parasitic Electric Load {W} + , !- Part Load Fraction Correlation Curve Name + 0; !- Parasitic Gas Load {W} + +OS:ZoneHVAC:UnitHeater, + {28e5f181-b6d7-4b69-93c7-588713ef093b}, !- Handle + Zone3 Bulk Storage ZN Unit Heater, !- Name + {06c70b81-2930-46ed-a9e0-addf2a0ffb74}, !- Availability Schedule Name + {f092697d-742e-4dce-a439-f731cb41dc0b}, !- Air Inlet Node Name + {cfde54a3-da23-442d-a1f7-3a98bb66d9da}, !- Air Outlet Node Name + {d149a11c-aa96-49da-91b0-e418edc631ab}, !- Supply Air Fan Name + autosize, !- Maximum Supply Air Flow Rate {m3/s} + OnOff, !- Fan Control Type + {2cc5cdb1-25bc-48bc-a4bf-510e26ff9116}, !- Heating Coil Name + , !- Maximum Hot Water Flow Rate {m3/s} + 0, !- Minimum Hot Water Flow Rate {m3/s} + 0.001, !- Heating Convergence Tolerance + ; !- Availability Manager List Name + +OS:Node, + {a3d76a6d-8b20-4126-8b89-586eb706d13f}, !- Handle + Zone3 Bulk Storage ZN Unit Heater Inlet Air Node, !- Name + {a10c6987-179b-46fc-b645-e97347c35bf6}, !- Inlet Port + {f092697d-742e-4dce-a439-f731cb41dc0b}; !- Outlet Port + +OS:Connection, + {a10c6987-179b-46fc-b645-e97347c35bf6}, !- Handle + {3a5b3ac2-dadb-49b4-a623-9e47f7e9d794}, !- Name + {9bea9730-4b2b-4b44-a10e-49028e4329dc}, !- Source Object + 3, !- Outlet Port + {a3d76a6d-8b20-4126-8b89-586eb706d13f}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {f092697d-742e-4dce-a439-f731cb41dc0b}, !- Handle + {76501224-84af-41a7-a755-1b375adfe07b}, !- Name + {a3d76a6d-8b20-4126-8b89-586eb706d13f}, !- Source Object + 3, !- Outlet Port + {28e5f181-b6d7-4b69-93c7-588713ef093b}, !- Target Object + 3; !- Inlet Port + +OS:Node, + {b1e2b582-0a41-44d5-a232-5024602de67f}, !- Handle + Zone3 Bulk Storage ZN Unit Heater Outlet Air Node, !- Name + {cfde54a3-da23-442d-a1f7-3a98bb66d9da}, !- Inlet Port + {a15b8b7e-af57-48b9-9467-285ba87378df}; !- Outlet Port + +OS:Connection, + {a15b8b7e-af57-48b9-9467-285ba87378df}, !- Handle + {f44ca070-96f8-4381-8d95-f8427586758e}, !- Name + {b1e2b582-0a41-44d5-a232-5024602de67f}, !- Source Object + 3, !- Outlet Port + {29e5c2ed-c4ce-48bd-88f1-61fe01328f4d}, !- Target Object + 3; !- Inlet Port + +OS:Connection, + {cfde54a3-da23-442d-a1f7-3a98bb66d9da}, !- Handle + {89393105-4fa5-4d33-9769-5345c1e4f699}, !- Name + {28e5f181-b6d7-4b69-93c7-588713ef093b}, !- Source Object + 4, !- Outlet Port + {b1e2b582-0a41-44d5-a232-5024602de67f}, !- Target Object + 2; !- Inlet Port + +OS:ZoneVentilation:DesignFlowRate, + {644dc883-1242-4948-bde2-cdff3d0bebb4}, !- Handle + Zone3 Bulk Storage ZN Ventilation, !- Name + {06c70b81-2930-46ed-a9e0-addf2a0ffb74}, !- Schedule Name + Flow/Zone, !- Design Flow Rate Calculation Method + 37.7600048024407, !- Design Flow Rate {m3/s} + 0, !- Flow Rate per Zone Floor Area {m3/s-m2} + 0, !- Flow Rate per Person {m3/s-person} + 0, !- Air Changes per Hour {1/hr} + Exhaust, !- Ventilation Type + 31.1361206455786, !- Fan Pressure Rise {Pa} + 0.51, !- Fan Total Efficiency + 1, !- Constant Term Coefficient + 0, !- Temperature Term Coefficient + 0, !- Velocity Term Coefficient + 0, !- Velocity Squared Term Coefficient + 29.4444452244559, !- Minimum Indoor Temperature {C} + , !- Minimum Indoor Temperature Schedule Name + 100, !- Maximum Indoor Temperature {C} + , !- Maximum Indoor Temperature Schedule Name + -100, !- Delta Temperature {deltaC} + , !- Delta Temperature Schedule Name + -100, !- Minimum Outdoor Temperature {C} + , !- Minimum Outdoor Temperature Schedule Name + 100, !- Maximum Outdoor Temperature {C} + , !- Maximum Outdoor Temperature Schedule Name + 40; !- Maximum Wind Speed {m/s} + +OS:ZoneVentilation:DesignFlowRate, + {f8b0ec9f-3184-4fec-a660-dd830e5e4736}, !- Handle + Zone3 Bulk Storage ZN Ventilation 1, !- Name + {06c70b81-2930-46ed-a9e0-addf2a0ffb74}, !- Schedule Name + Flow/Zone, !- Design Flow Rate Calculation Method + 0.9438948864, !- Design Flow Rate {m3/s} + 0, !- Flow Rate per Zone Floor Area {m3/s-m2} + 0, !- Flow Rate per Person {m3/s-person} + 0, !- Air Changes per Hour {1/hr} + Natural, !- Ventilation Type + 0, !- Fan Pressure Rise {Pa} + 1, !- Fan Total Efficiency + 0, !- Constant Term Coefficient + 0, !- Temperature Term Coefficient + 0.224, !- Velocity Term Coefficient + 0, !- Velocity Squared Term Coefficient + -73.3333352760033, !- Minimum Indoor Temperature {C} + , !- Minimum Indoor Temperature Schedule Name + 29.4444452244559, !- Maximum Indoor Temperature {C} + , !- Maximum Indoor Temperature Schedule Name + -100, !- Delta Temperature {deltaC} + , !- Delta Temperature Schedule Name + -100, !- Minimum Outdoor Temperature {C} + , !- Minimum Outdoor Temperature Schedule Name + 100, !- Maximum Outdoor Temperature {C} + , !- Maximum Outdoor Temperature Schedule Name + 40; !- Maximum Wind Speed {m/s} + +OS:Material:NoMass, + {8e60f7a5-826b-4960-90da-86349518b4c0}, !- Handle + CP02 CARPET PAD, !- Name + VeryRough, !- Roughness + 0.21648, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.8; !- Visible Absorptance + +OS:Material, + {d00920e3-91a5-4b41-a7bd-2c26aef66c8d}, !- Handle + 100mm Normalweight concrete floor, !- Name + MediumSmooth, !- Roughness + 0.1016, !- Thickness {m} + 2.31, !- Conductivity {W/m-K} + 2322, !- Density {kg/m3} + 832; !- Specific Heat {J/kg-K} + +OS:Material:NoMass, + {d5bf31f5-441e-424f-9c81-cd8f6f9449c7}, !- Handle + Nonres_Floor_Insulation, !- Name + MediumSmooth, !- Roughness + 2.88291975297193, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material, + {94aa1c6d-4087-4920-84e5-cf6254f9e573}, !- Handle + G01 13mm gypsum board, !- Name + Smooth, !- Roughness + 0.0127, !- Thickness {m} + 0.16, !- Conductivity {W/m-K} + 800, !- Density {kg/m3} + 1090, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.5; !- Visible Absorptance + +OS:Material, + {f0522b18-1de9-47f5-b613-b4b54d5f6f1d}, !- Handle + M10 200mm concrete block basement wall, !- Name + MediumRough, !- Roughness + 0.2032, !- Thickness {m} + 1.326, !- Conductivity {W/m-K} + 1842, !- Density {kg/m3} + 912; !- Specific Heat {J/kg-K} + +OS:DefaultConstructionSet, + {c99232a4-00e1-48ec-aa23-aecb05855d37}, !- Handle + 90.1-2010 - Warehouse - ASHRAE 169-2013-3B, !- Name + {21656f89-4b57-4220-875b-2429d56c3e63}, !- Default Exterior Surface Constructions Name + {14d67030-aa78-4692-9d01-0ed24d7fc197}, !- Default Interior Surface Constructions Name + {ec6c5e52-b0d1-4384-a927-8fbaa1fad1de}, !- Default Ground Contact Surface Constructions Name + {6241899b-7d09-4127-8c1a-da0b44b7d1be}, !- Default Exterior SubSurface Constructions Name + {154bb149-211f-4bfe-bd5b-1b87a5e6c560}, !- Default Interior SubSurface Constructions Name + {538f879a-2d2d-437a-8ea0-f0a357be7167}, !- Interior Partition Construction Name + , !- Space Shading Construction Name + , !- Building Shading Construction Name + , !- Site Shading Construction Name + ; !- Adiabatic Surface Construction Name + +OS:DefaultSurfaceConstructions, + {21656f89-4b57-4220-875b-2429d56c3e63}, !- Handle + Default Surface Constructions 1, !- Name + {ae980f0c-e675-4b71-ab1b-a1e5fae42d67}, !- Floor Construction Name + {2cac27dd-92ec-471d-ab16-a05683b871a0}, !- Wall Construction Name + {987e6236-b488-446d-9e61-877d6fb4f469}; !- Roof Ceiling Construction Name + +OS:Construction, + {ae980f0c-e675-4b71-ab1b-a1e5fae42d67}, !- Handle + Typical Insulated Exterior Mass Floor R-9.35, !- Name + , !- Surface Rendering Name + {1358e683-15ad-48bc-a7aa-11b15666d54a}, !- Layer 1 + {a35896e8-e047-40c1-be2e-49f72fb5d4c8}, !- Layer 2 + {12ba7a16-a66c-4c66-a342-32f13797cacb}; !- Layer 3 + +OS:StandardsInformation:Construction, + {2c07a260-a22c-4d8a-bf9c-88024a2c362a}, !- Handle + {ae980f0c-e675-4b71-ab1b-a1e5fae42d67}, !- Construction Name + ExteriorFloor, !- Intended Surface Type + Mass; !- Standards Construction Type + +OS:Material:NoMass, + {1358e683-15ad-48bc-a7aa-11b15666d54a}, !- Handle + Typical Insulation R-6.78, !- Name + Smooth, !- Roughness + 1.19346679593268, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material, + {a35896e8-e047-40c1-be2e-49f72fb5d4c8}, !- Handle + 4 in. Normalweight Concrete Floor, !- Name + MediumRough, !- Roughness + 0.1016, !- Thickness {m} + 2.31, !- Conductivity {W/m-K} + 2321.99999999999, !- Density {kg/m3} + 831.999999999997, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material:NoMass, + {12ba7a16-a66c-4c66-a342-32f13797cacb}, !- Handle + Typical Carpet Pad, !- Name + Smooth, !- Roughness + 0.216479986995276, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.8; !- Visible Absorptance + +OS:Construction, + {2cac27dd-92ec-471d-ab16-a05683b871a0}, !- Handle + Typical Insulated Metal Building Wall R-8.85, !- Name + , !- Surface Rendering Name + {f217eba0-4d73-4b33-bdb2-c6731abd2047}, !- Layer 1 + {0d72b87d-ebba-4cb9-a66f-47b3a61f7cb4}, !- Layer 2 + {c0c15930-2857-4b94-8539-ce991ed28ae4}; !- Layer 3 + +OS:StandardsInformation:Construction, + {1e095dc8-48fa-435d-b51d-2778784c5665}, !- Handle + {2cac27dd-92ec-471d-ab16-a05683b871a0}, !- Construction Name + ExteriorWall, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material, + {f217eba0-4d73-4b33-bdb2-c6731abd2047}, !- Handle + Metal Siding, !- Name + Smooth, !- Roughness + 0.0015, !- Thickness {m} + 44.9599999999999, !- Conductivity {W/m-K} + 7688.86, !- Density {kg/m3} + 410, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material:NoMass, + {0d72b87d-ebba-4cb9-a66f-47b3a61f7cb4}, !- Handle + Typical Insulation R-7.55, !- Name + Smooth, !- Roughness + 1.32939518160909, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material, + {c0c15930-2857-4b94-8539-ce991ed28ae4}, !- Handle + 1/2IN Gypsum, !- Name + Smooth, !- Roughness + 0.0127, !- Thickness {m} + 0.16, !- Conductivity {W/m-K} + 784.9, !- Density {kg/m3} + 830.000000000001, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.4, !- Solar Absorptance + 0.4; !- Visible Absorptance + +OS:Construction, + {987e6236-b488-446d-9e61-877d6fb4f469}, !- Handle + Typical Insulated Metal Building Roof R-10.31, !- Name + , !- Surface Rendering Name + {1dbb61d0-d0bd-433a-89f8-e5f77f5ea82b}, !- Layer 1 + {ead9251d-9dce-462f-a87f-096834815522}; !- Layer 2 + +OS:StandardsInformation:Construction, + {8036439b-32d1-4c62-8a87-4f079f1221b9}, !- Handle + {987e6236-b488-446d-9e61-877d6fb4f469}, !- Construction Name + ExteriorRoof, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material, + {1dbb61d0-d0bd-433a-89f8-e5f77f5ea82b}, !- Handle + Metal Roof Surface, !- Name + Smooth, !- Roughness + 0.000799999999999998, !- Thickness {m} + 45.2799999999999, !- Conductivity {W/m-K} + 7823.99999999999, !- Density {kg/m3} + 499.999999999996, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material:NoMass, + {ead9251d-9dce-462f-a87f-096834815522}, !- Handle + Typical Insulation R-9.53, !- Name + Smooth, !- Roughness + 1.67818529282458, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:DefaultSurfaceConstructions, + {14d67030-aa78-4692-9d01-0ed24d7fc197}, !- Handle + Default Surface Constructions 2, !- Name + {5424c879-1e23-4f3c-a777-f34e545c37d8}, !- Floor Construction Name + {62668aa9-83f9-4fd1-ad49-c72633dd8b44}, !- Wall Construction Name + {a11127e9-b0c0-4eea-b5c0-ba3ed42940b3}; !- Roof Ceiling Construction Name + +OS:Construction, + {5424c879-1e23-4f3c-a777-f34e545c37d8}, !- Handle + Typical Interior Floor, !- Name + , !- Surface Rendering Name + {d00920e3-91a5-4b41-a7bd-2c26aef66c8d}, !- Layer 1 + {8e60f7a5-826b-4960-90da-86349518b4c0}; !- Layer 2 + +OS:StandardsInformation:Construction, + {e1119b8b-4ed7-46c3-a43c-d01b52ecabfb}, !- Handle + {5424c879-1e23-4f3c-a777-f34e545c37d8}, !- Construction Name + InteriorFloor, !- Intended Surface Type + ; !- Standards Construction Type + +OS:Construction, + {62668aa9-83f9-4fd1-ad49-c72633dd8b44}, !- Handle + Typical Interior Wall, !- Name + , !- Surface Rendering Name + {94aa1c6d-4087-4920-84e5-cf6254f9e573}, !- Layer 1 + {94aa1c6d-4087-4920-84e5-cf6254f9e573}; !- Layer 2 + +OS:StandardsInformation:Construction, + {7a5a8c1b-ad13-44d2-bca8-88632d87cb65}, !- Handle + {62668aa9-83f9-4fd1-ad49-c72633dd8b44}, !- Construction Name + InteriorWall, !- Intended Surface Type + ; !- Standards Construction Type + +OS:Construction, + {a11127e9-b0c0-4eea-b5c0-ba3ed42940b3}, !- Handle + Typical Interior Ceiling, !- Name + , !- Surface Rendering Name + {8e60f7a5-826b-4960-90da-86349518b4c0}, !- Layer 1 + {d00920e3-91a5-4b41-a7bd-2c26aef66c8d}; !- Layer 2 + +OS:StandardsInformation:Construction, + {3861b29a-0a58-46a1-b3cd-757fdd53ede4}, !- Handle + {a11127e9-b0c0-4eea-b5c0-ba3ed42940b3}, !- Construction Name + InteriorCeiling, !- Intended Surface Type + ; !- Standards Construction Type + +OS:DefaultSurfaceConstructions, + {ec6c5e52-b0d1-4384-a927-8fbaa1fad1de}, !- Handle + Default Surface Constructions 3, !- Name + {ab179a1b-edea-4bea-9692-79a257155fd0}, !- Floor Construction Name + {078b22fd-abf0-4db0-a340-00a310013d74}, !- Wall Construction Name + ; !- Roof Ceiling Construction Name + +OS:Construction, + {ab179a1b-edea-4bea-9692-79a257155fd0}, !- Handle + ext-slab, !- Name + , !- Surface Rendering Name + {b53775ff-6867-4133-9035-5942c2bb4023}; !- Layer 1 + +OS:StandardsInformation:Construction, + {86bfa189-4497-40c4-80af-a433f2abbfd6}, !- Handle + {ab179a1b-edea-4bea-9692-79a257155fd0}, !- Construction Name + GroundContactFloor, !- Intended Surface Type + Mass; !- Standards Construction Type + +OS:Material, + {b53775ff-6867-4133-9035-5942c2bb4023}, !- Handle + 6 in. Normalweight Concrete Floor, !- Name + MediumRough, !- Roughness + 0.1524, !- Thickness {m} + 2.31, !- Conductivity {W/m-K} + 2321.99999999999, !- Density {kg/m3} + 831.999999999997, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {078b22fd-abf0-4db0-a340-00a310013d74}, !- Handle + Typical Insulated Basement Mass Wall, !- Name + , !- Surface Rendering Name + {96cf6af3-b705-47ed-a16b-09041784be46}; !- Layer 1 + +OS:StandardsInformation:Construction, + {21e7a751-2047-4d04-b2d6-251ec731f71d}, !- Handle + {078b22fd-abf0-4db0-a340-00a310013d74}, !- Construction Name + GroundContactWall, !- Intended Surface Type + Mass; !- Standards Construction Type + +OS:Material:NoMass, + {5759fd9f-860f-46d9-a94b-4bf54b126b45}, !- Handle + Typical Insulation R-0.58, !- Name + Smooth, !- Roughness + 0.101892320559048, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Material, + {96cf6af3-b705-47ed-a16b-09041784be46}, !- Handle + 8 in. Concrete Block Basement Wall, !- Name + MediumRough, !- Roughness + 0.2032, !- Thickness {m} + 1.326, !- Conductivity {W/m-K} + 1841.99999999999, !- Density {kg/m3} + 911.999999999999, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:DefaultSubSurfaceConstructions, + {6241899b-7d09-4127-8c1a-da0b44b7d1be}, !- Handle + Default Sub Surface Constructions 1, !- Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Fixed Window Construction Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Operable Window Construction Name + {04710858-b6ca-41ce-9b4a-196e3c03794c}, !- Door Construction Name + , !- Glass Door Construction Name + {6168ce81-56ce-4c34-b0c5-dfcea37bac09}, !- Overhead Door Construction Name + {a4793330-85fd-494c-b058-b2395d50bd07}, !- Skylight Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Tubular Daylight Dome Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}; !- Tubular Daylight Diffuser Construction Name + +OS:Construction, + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Handle + U 0.62 SHGC 0.25 Dbl Ref-C-H Clr 6mm/6mm Air, !- Name + , !- Surface Rendering Name + {bb220900-4a29-4959-aaa5-03490d5ae01e}, !- Layer 1 + {1a11a815-dc7c-4215-bb55-f2def3272927}, !- Layer 2 + {b0919052-e4d0-45eb-9858-ec3c72ebd856}; !- Layer 3 + +OS:StandardsInformation:Construction, + {31a0bbd0-2438-4a6b-a525-f08edd71158b}, !- Handle + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Construction Name + ExteriorWindow, !- Intended Surface Type + Metal framing (all other), !- Standards Construction Type + , !- Perturbable Layer + , !- Perturbable Layer Type + , !- Other Perturbable Layer Type + , !- Construction Standard + , !- Construction Standard Source + , !- Fenestration Type + , !- Fenestration Assembly Context + , !- Fenestration Number of Panes + Metal Framing; !- Fenestration Frame Type + +OS:WindowMaterial:Glazing, + {bb220900-4a29-4959-aaa5-03490d5ae01e}, !- Handle + REF C CLEAR HI 6MM, !- Name + SpectralAverage, !- Optical Data Type + , !- Window Glass Spectral Data Set Name + 0.00599999999999998, !- Thickness {m} + 0.2, !- Solar Transmittance at Normal Incidence + 0.16, !- Front Side Solar Reflectance at Normal Incidence + 0.39, !- Back Side Solar Reflectance at Normal Incidence + 0.22, !- Visible Transmittance at Normal Incidence + 0.17, !- Front Side Visible Reflectance at Normal Incidence + 0.35, !- Back Side Visible Reflectance at Normal Incidence + 0, !- Infrared Transmittance at Normal Incidence + 0.84, !- Front Side Infrared Hemispherical Emissivity + 0.55, !- Back Side Infrared Hemispherical Emissivity + 0.9, !- Conductivity {W/m-K} + 1, !- Dirt Correction Factor for Solar and Visible Transmittance + No; !- Solar Diffusing + +OS:WindowMaterial:Gas, + {1a11a815-dc7c-4215-bb55-f2def3272927}, !- Handle + AIR 6MM, !- Name + Air, !- Gas Type + 0.0063; !- Thickness {m} + +OS:WindowMaterial:Glazing, + {b0919052-e4d0-45eb-9858-ec3c72ebd856}, !- Handle + CLEAR 6MM, !- Name + SpectralAverage, !- Optical Data Type + , !- Window Glass Spectral Data Set Name + 0.00599999999999998, !- Thickness {m} + 0.775, !- Solar Transmittance at Normal Incidence + 0.071, !- Front Side Solar Reflectance at Normal Incidence + 0.071, !- Back Side Solar Reflectance at Normal Incidence + 0.881, !- Visible Transmittance at Normal Incidence + 0.08, !- Front Side Visible Reflectance at Normal Incidence + 0.08, !- Back Side Visible Reflectance at Normal Incidence + 0, !- Infrared Transmittance at Normal Incidence + 0.84, !- Front Side Infrared Hemispherical Emissivity + 0.84, !- Back Side Infrared Hemispherical Emissivity + 0.9, !- Conductivity {W/m-K} + 1, !- Dirt Correction Factor for Solar and Visible Transmittance + No; !- Solar Diffusing + +OS:Construction, + {04710858-b6ca-41ce-9b4a-196e3c03794c}, !- Handle + Typical Uninsulated Swinging Door R-1.43, !- Name + , !- Surface Rendering Name + {5759fd9f-860f-46d9-a94b-4bf54b126b45}; !- Layer 1 + +OS:StandardsInformation:Construction, + {3ca04235-342d-4ea4-9fb3-8fbd12cc1b4e}, !- Handle + {04710858-b6ca-41ce-9b4a-196e3c03794c}, !- Construction Name + ExteriorDoor, !- Intended Surface Type + Swinging; !- Standards Construction Type + +OS:Construction, + {6168ce81-56ce-4c34-b0c5-dfcea37bac09}, !- Handle + Typical Overhead Door, !- Name + , !- Surface Rendering Name + {45b495c2-d5ae-43c2-aa47-b11ffe6fc3eb}; !- Layer 1 + +OS:StandardsInformation:Construction, + {73934110-c9c3-4fa8-961a-1e2bf467b6c9}, !- Handle + {6168ce81-56ce-4c34-b0c5-dfcea37bac09}, !- Construction Name + ExteriorDoor, !- Intended Surface Type + RollUp; !- Standards Construction Type + +OS:Material:NoMass, + {45b495c2-d5ae-43c2-aa47-b11ffe6fc3eb}, !- Handle + Typical Insulation R-6.78 1, !- Name + Smooth, !- Roughness + 1.19346679593268, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {a4793330-85fd-494c-b058-b2395d50bd07}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing Skylight U-1.17 SHGC 0.39, !- Name + , !- Surface Rendering Name + {a44bd12e-99b7-4136-8280-0c5fec392f09}; !- Layer 1 + +OS:StandardsInformation:Construction, + {ed8319bf-094f-418c-8438-aabc8e04d9ba}, !- Handle + {a4793330-85fd-494c-b058-b2395d50bd07}, !- Construction Name + , !- Intended Surface Type + ; !- Standards Construction Type + +OS:WindowMaterial:SimpleGlazingSystem, + {a44bd12e-99b7-4136-8280-0c5fec392f09}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing U-1.17 SHGC 0.39, !- Name + 6.64356810910278, !- U-Factor {W/m2-K} + 0.39, !- Solar Heat Gain Coefficient + 0.23; !- Visible Transmittance + +OS:Construction, + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Handle + Typical Interior Window, !- Name + , !- Surface Rendering Name + {45be67e1-7b97-420a-bcc0-1229ea2bd3c1}; !- Layer 1 + +OS:StandardsInformation:Construction, + {e9142943-d9c0-49b0-89dc-03f1705b9bb4}, !- Handle + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Construction Name + InteriorWindow, !- Intended Surface Type + ; !- Standards Construction Type + +OS:WindowMaterial:Glazing, + {45be67e1-7b97-420a-bcc0-1229ea2bd3c1}, !- Handle + Clear 3mm, !- Name + SpectralAverage, !- Optical Data Type + , !- Window Glass Spectral Data Set Name + 0.00299999999999999, !- Thickness {m} + 0.837, !- Solar Transmittance at Normal Incidence + 0.075, !- Front Side Solar Reflectance at Normal Incidence + 0.075, !- Back Side Solar Reflectance at Normal Incidence + 0.898, !- Visible Transmittance at Normal Incidence + 0.081, !- Front Side Visible Reflectance at Normal Incidence + 0.081, !- Back Side Visible Reflectance at Normal Incidence + 0, !- Infrared Transmittance at Normal Incidence + 0.84, !- Front Side Infrared Hemispherical Emissivity + 0.84, !- Back Side Infrared Hemispherical Emissivity + 0.9, !- Conductivity {W/m-K} + 1, !- Dirt Correction Factor for Solar and Visible Transmittance + No; !- Solar Diffusing + +OS:DefaultSubSurfaceConstructions, + {154bb149-211f-4bfe-bd5b-1b87a5e6c560}, !- Handle + Default Sub Surface Constructions 2, !- Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Fixed Window Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Operable Window Construction Name + {3c02b38e-6275-4ebb-9446-58b744dd661a}, !- Door Construction Name + , !- Glass Door Construction Name + , !- Overhead Door Construction Name + , !- Skylight Construction Name + , !- Tubular Daylight Dome Construction Name + ; !- Tubular Daylight Diffuser Construction Name + +OS:Construction, + {3c02b38e-6275-4ebb-9446-58b744dd661a}, !- Handle + Typical Interior Door, !- Name + , !- Surface Rendering Name + {0acc0848-3b2c-4a0a-a5b1-d09994a302c1}; !- Layer 1 + +OS:StandardsInformation:Construction, + {0f81b448-12e5-42ae-8347-3512ae24f878}, !- Handle + {3c02b38e-6275-4ebb-9446-58b744dd661a}, !- Construction Name + InteriorDoor, !- Intended Surface Type + ; !- Standards Construction Type + +OS:Material, + {0acc0848-3b2c-4a0a-a5b1-d09994a302c1}, !- Handle + G05 25mm wood, !- Name + MediumSmooth, !- Roughness + 0.0254, !- Thickness {m} + 0.15, !- Conductivity {W/m-K} + 608, !- Density {kg/m3} + 1630, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.5, !- Solar Absorptance + 0.5; !- Visible Absorptance + +OS:Construction, + {538f879a-2d2d-437a-8ea0-f0a357be7167}, !- Handle + Typical Interior Partition, !- Name + , !- Surface Rendering Name + {0acc0848-3b2c-4a0a-a5b1-d09994a302c1}; !- Layer 1 + +OS:StandardsInformation:Construction, + {3b4ec848-ed4e-4df2-a94a-07fb61252ae9}, !- Handle + {538f879a-2d2d-437a-8ea0-f0a357be7167}, !- Construction Name + InteriorPartition, !- Intended Surface Type + ; !- Standards Construction Type + +OS:DefaultConstructionSet, + {ed15bfb0-21d6-4e35-bd9f-898c0e2a0040}, !- Handle + 90.1-2010 - Warehouse - Bulk - ASHRAE 169-2013-3B, !- Name + {7728a4db-7f9e-4358-8986-eb210290c9de}, !- Default Exterior Surface Constructions Name + {a95bee57-405b-4e18-92cb-aaf2fcc28241}, !- Default Interior Surface Constructions Name + {c9193a87-8bb5-4c24-816e-2025a4232473}, !- Default Ground Contact Surface Constructions Name + {11d12ef3-2c18-439f-a405-99f958c75052}, !- Default Exterior SubSurface Constructions Name + {16dc9275-ee1c-4930-bf11-932e28803c9e}, !- Default Interior SubSurface Constructions Name + {538f879a-2d2d-437a-8ea0-f0a357be7167}, !- Interior Partition Construction Name + , !- Space Shading Construction Name + , !- Building Shading Construction Name + , !- Site Shading Construction Name + ; !- Adiabatic Surface Construction Name + +OS:DefaultSurfaceConstructions, + {7728a4db-7f9e-4358-8986-eb210290c9de}, !- Handle + Default Surface Constructions 4, !- Name + {e6c28602-b850-47f0-85da-685bee9c1a3a}, !- Floor Construction Name + {413f307e-0707-436d-aee5-bb0ce476d5af}, !- Wall Construction Name + {39e788c4-0ff3-424e-8326-b5bf9110fb87}; !- Roof Ceiling Construction Name + +OS:Construction, + {e6c28602-b850-47f0-85da-685bee9c1a3a}, !- Handle + Typical Insulated Exterior Mass Floor R-9.35 1, !- Name + , !- Surface Rendering Name + {45b495c2-d5ae-43c2-aa47-b11ffe6fc3eb}, !- Layer 1 + {a35896e8-e047-40c1-be2e-49f72fb5d4c8}, !- Layer 2 + {12ba7a16-a66c-4c66-a342-32f13797cacb}; !- Layer 3 + +OS:StandardsInformation:Construction, + {d5e853c5-d416-4032-87de-cf151ff2e658}, !- Handle + {e6c28602-b850-47f0-85da-685bee9c1a3a}, !- Construction Name + ExteriorFloor, !- Intended Surface Type + Mass; !- Standards Construction Type + +OS:Construction, + {413f307e-0707-436d-aee5-bb0ce476d5af}, !- Handle + Typical Insulated Metal Building Wall R-8.85 1, !- Name + , !- Surface Rendering Name + {f217eba0-4d73-4b33-bdb2-c6731abd2047}, !- Layer 1 + {47b9ca09-a014-4ec2-94de-4d04678ee6aa}, !- Layer 2 + {c0c15930-2857-4b94-8539-ce991ed28ae4}; !- Layer 3 + +OS:StandardsInformation:Construction, + {492cb072-120e-4a44-9b3b-d7bbe8dffe78}, !- Handle + {413f307e-0707-436d-aee5-bb0ce476d5af}, !- Construction Name + ExteriorWall, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material:NoMass, + {47b9ca09-a014-4ec2-94de-4d04678ee6aa}, !- Handle + Typical Insulation R-7.55 1, !- Name + Smooth, !- Roughness + 1.32939518160909, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {39e788c4-0ff3-424e-8326-b5bf9110fb87}, !- Handle + Typical Insulated Metal Building Roof R-10.31 1, !- Name + , !- Surface Rendering Name + {1dbb61d0-d0bd-433a-89f8-e5f77f5ea82b}, !- Layer 1 + {3eff3b8b-060b-4c81-8a64-73b9060b8853}; !- Layer 2 + +OS:StandardsInformation:Construction, + {c646f22f-9731-4b2c-bc41-0725915b1371}, !- Handle + {39e788c4-0ff3-424e-8326-b5bf9110fb87}, !- Construction Name + ExteriorRoof, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material:NoMass, + {3eff3b8b-060b-4c81-8a64-73b9060b8853}, !- Handle + Typical Insulation R-9.53 1, !- Name + Smooth, !- Roughness + 1.67818529282458, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:DefaultSurfaceConstructions, + {a95bee57-405b-4e18-92cb-aaf2fcc28241}, !- Handle + Default Surface Constructions 5, !- Name + {5424c879-1e23-4f3c-a777-f34e545c37d8}, !- Floor Construction Name + {62668aa9-83f9-4fd1-ad49-c72633dd8b44}, !- Wall Construction Name + {a11127e9-b0c0-4eea-b5c0-ba3ed42940b3}; !- Roof Ceiling Construction Name + +OS:DefaultSurfaceConstructions, + {c9193a87-8bb5-4c24-816e-2025a4232473}, !- Handle + Default Surface Constructions 6, !- Name + {ab179a1b-edea-4bea-9692-79a257155fd0}, !- Floor Construction Name + {078b22fd-abf0-4db0-a340-00a310013d74}, !- Wall Construction Name + ; !- Roof Ceiling Construction Name + +OS:DefaultSubSurfaceConstructions, + {11d12ef3-2c18-439f-a405-99f958c75052}, !- Handle + Default Sub Surface Constructions 3, !- Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Fixed Window Construction Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Operable Window Construction Name + {960e42c6-2d60-4046-be76-aadab607cada}, !- Door Construction Name + , !- Glass Door Construction Name + {6168ce81-56ce-4c34-b0c5-dfcea37bac09}, !- Overhead Door Construction Name + {fb2b72d8-aaf5-4714-81c7-6808eea49d3f}, !- Skylight Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Tubular Daylight Dome Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}; !- Tubular Daylight Diffuser Construction Name + +OS:Construction, + {960e42c6-2d60-4046-be76-aadab607cada}, !- Handle + Typical Uninsulated Swinging Door R-1.43 1, !- Name + , !- Surface Rendering Name + {256ec4b6-2a8d-4816-9c48-9118cc0422d7}; !- Layer 1 + +OS:StandardsInformation:Construction, + {2b1301cb-d83b-4da3-9c6f-37b23a795959}, !- Handle + {960e42c6-2d60-4046-be76-aadab607cada}, !- Construction Name + ExteriorDoor, !- Intended Surface Type + Swinging; !- Standards Construction Type + +OS:Material:NoMass, + {256ec4b6-2a8d-4816-9c48-9118cc0422d7}, !- Handle + Typical Insulation R-0.58 1, !- Name + Smooth, !- Roughness + 0.101892320559048, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {fb2b72d8-aaf5-4714-81c7-6808eea49d3f}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing Skylight U-1.17 SHGC 0.39 1, !- Name + , !- Surface Rendering Name + {dfa42a00-704c-43cd-b589-d806f8db8396}; !- Layer 1 + +OS:StandardsInformation:Construction, + {a3417730-de5f-4cd8-937e-978d979aca3c}, !- Handle + {fb2b72d8-aaf5-4714-81c7-6808eea49d3f}, !- Construction Name + , !- Intended Surface Type + ; !- Standards Construction Type + +OS:WindowMaterial:SimpleGlazingSystem, + {dfa42a00-704c-43cd-b589-d806f8db8396}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing U-1.17 SHGC 0.39 1, !- Name + 6.64356810910278, !- U-Factor {W/m2-K} + 0.39, !- Solar Heat Gain Coefficient + 0.23; !- Visible Transmittance + +OS:DefaultSubSurfaceConstructions, + {16dc9275-ee1c-4930-bf11-932e28803c9e}, !- Handle + Default Sub Surface Constructions 4, !- Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Fixed Window Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Operable Window Construction Name + {3c02b38e-6275-4ebb-9446-58b744dd661a}, !- Door Construction Name + , !- Glass Door Construction Name + , !- Overhead Door Construction Name + , !- Skylight Construction Name + , !- Tubular Daylight Dome Construction Name + ; !- Tubular Daylight Diffuser Construction Name + +OS:DefaultConstructionSet, + {d62c5b92-0d22-4e3e-ab39-1e07e2a927e9}, !- Handle + 90.1-2010 - Warehouse - Fine - ASHRAE 169-2013-3B, !- Name + {02ea2426-765f-439a-b8e2-932f77a367c6}, !- Default Exterior Surface Constructions Name + {ad95eb9c-e57e-41c8-af1c-a4a04c5d33f6}, !- Default Interior Surface Constructions Name + {3938a9d4-f33e-4d20-9c0a-fc103f3ef319}, !- Default Ground Contact Surface Constructions Name + {fcdf56d3-3fe7-4978-9225-6095f280dc75}, !- Default Exterior SubSurface Constructions Name + {09f3c301-22c9-42fd-a6f5-293676428460}, !- Default Interior SubSurface Constructions Name + {538f879a-2d2d-437a-8ea0-f0a357be7167}, !- Interior Partition Construction Name + , !- Space Shading Construction Name + , !- Building Shading Construction Name + , !- Site Shading Construction Name + ; !- Adiabatic Surface Construction Name + +OS:DefaultSurfaceConstructions, + {02ea2426-765f-439a-b8e2-932f77a367c6}, !- Handle + Default Surface Constructions 7, !- Name + {183717ca-db69-43f2-b402-577dece90c23}, !- Floor Construction Name + {af54a9ba-4f88-44ed-808a-be6043db34c6}, !- Wall Construction Name + {9033e1bf-9347-4d07-8832-f7b44bdf3735}; !- Roof Ceiling Construction Name + +OS:Construction, + {183717ca-db69-43f2-b402-577dece90c23}, !- Handle + Typical Insulated Exterior Mass Floor R-9.35 2, !- Name + , !- Surface Rendering Name + {b5e71fe7-cb96-4724-ad19-14518afce5d4}, !- Layer 1 + {a35896e8-e047-40c1-be2e-49f72fb5d4c8}, !- Layer 2 + {12ba7a16-a66c-4c66-a342-32f13797cacb}; !- Layer 3 + +OS:StandardsInformation:Construction, + {e028b016-2408-4f02-abf4-cd09d6715a0d}, !- Handle + {183717ca-db69-43f2-b402-577dece90c23}, !- Construction Name + ExteriorFloor, !- Intended Surface Type + Mass; !- Standards Construction Type + +OS:Material:NoMass, + {b5e71fe7-cb96-4724-ad19-14518afce5d4}, !- Handle + Typical Insulation R-6.78 2, !- Name + Smooth, !- Roughness + 1.19346679593268, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {af54a9ba-4f88-44ed-808a-be6043db34c6}, !- Handle + Typical Insulated Metal Building Wall R-11.9, !- Name + , !- Surface Rendering Name + {f217eba0-4d73-4b33-bdb2-c6731abd2047}, !- Layer 1 + {9bb90ad6-e9f0-4395-8366-1b70def464eb}, !- Layer 2 + {c0c15930-2857-4b94-8539-ce991ed28ae4}; !- Layer 3 + +OS:StandardsInformation:Construction, + {4f4d480b-efdc-4942-a098-6957eb848418}, !- Handle + {af54a9ba-4f88-44ed-808a-be6043db34c6}, !- Construction Name + ExteriorWall, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material:NoMass, + {9bb90ad6-e9f0-4395-8366-1b70def464eb}, !- Handle + Typical Insulation R-10.6, !- Name + Smooth, !- Roughness + 1.86744778662245, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {9033e1bf-9347-4d07-8832-f7b44bdf3735}, !- Handle + Typical Insulated Metal Building Roof R-18.18, !- Name + , !- Surface Rendering Name + {1dbb61d0-d0bd-433a-89f8-e5f77f5ea82b}, !- Layer 1 + {e9b0563f-a2f7-45b0-93f8-289a3c75fd77}; !- Layer 2 + +OS:StandardsInformation:Construction, + {19934b9e-ed21-4c35-a7a8-0d72d2c5a40d}, !- Handle + {9033e1bf-9347-4d07-8832-f7b44bdf3735}, !- Construction Name + ExteriorRoof, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material:NoMass, + {e9b0563f-a2f7-45b0-93f8-289a3c75fd77}, !- Handle + Typical Insulation R-17.4, !- Name + Smooth, !- Roughness + 3.06461972856157, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:DefaultSurfaceConstructions, + {ad95eb9c-e57e-41c8-af1c-a4a04c5d33f6}, !- Handle + Default Surface Constructions 8, !- Name + {5424c879-1e23-4f3c-a777-f34e545c37d8}, !- Floor Construction Name + {62668aa9-83f9-4fd1-ad49-c72633dd8b44}, !- Wall Construction Name + {a11127e9-b0c0-4eea-b5c0-ba3ed42940b3}; !- Roof Ceiling Construction Name + +OS:DefaultSurfaceConstructions, + {3938a9d4-f33e-4d20-9c0a-fc103f3ef319}, !- Handle + Default Surface Constructions 9, !- Name + {ab179a1b-edea-4bea-9692-79a257155fd0}, !- Floor Construction Name + {078b22fd-abf0-4db0-a340-00a310013d74}, !- Wall Construction Name + ; !- Roof Ceiling Construction Name + +OS:DefaultSubSurfaceConstructions, + {fcdf56d3-3fe7-4978-9225-6095f280dc75}, !- Handle + Default Sub Surface Constructions 5, !- Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Fixed Window Construction Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Operable Window Construction Name + {1c24399c-a88b-4eab-bc62-a8a71432d67d}, !- Door Construction Name + , !- Glass Door Construction Name + {6168ce81-56ce-4c34-b0c5-dfcea37bac09}, !- Overhead Door Construction Name + {32be725e-e5c1-4f6e-ab74-770a1bc4ac89}, !- Skylight Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Tubular Daylight Dome Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}; !- Tubular Daylight Diffuser Construction Name + +OS:Construction, + {1c24399c-a88b-4eab-bc62-a8a71432d67d}, !- Handle + Typical Uninsulated Swinging Door R-1.43 2, !- Name + , !- Surface Rendering Name + {0f877007-fcf1-49e1-9952-ea9433498511}; !- Layer 1 + +OS:StandardsInformation:Construction, + {8ee8e6d5-220c-4f11-81ba-add3a83a0e86}, !- Handle + {1c24399c-a88b-4eab-bc62-a8a71432d67d}, !- Construction Name + ExteriorDoor, !- Intended Surface Type + Swinging; !- Standards Construction Type + +OS:Material:NoMass, + {0f877007-fcf1-49e1-9952-ea9433498511}, !- Handle + Typical Insulation R-0.58 2, !- Name + Smooth, !- Roughness + 0.101892320559048, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {32be725e-e5c1-4f6e-ab74-770a1bc4ac89}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing Skylight U-1.17 SHGC 0.39 2, !- Name + , !- Surface Rendering Name + {26fafd6f-1dde-4ac0-b39b-dcd71728ecec}; !- Layer 1 + +OS:StandardsInformation:Construction, + {adfec68c-588c-4c61-8a6c-519015646e4f}, !- Handle + {32be725e-e5c1-4f6e-ab74-770a1bc4ac89}, !- Construction Name + , !- Intended Surface Type + ; !- Standards Construction Type + +OS:WindowMaterial:SimpleGlazingSystem, + {26fafd6f-1dde-4ac0-b39b-dcd71728ecec}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing U-1.17 SHGC 0.39 2, !- Name + 6.64356810910278, !- U-Factor {W/m2-K} + 0.39, !- Solar Heat Gain Coefficient + 0.23; !- Visible Transmittance + +OS:DefaultSubSurfaceConstructions, + {09f3c301-22c9-42fd-a6f5-293676428460}, !- Handle + Default Sub Surface Constructions 6, !- Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Fixed Window Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Operable Window Construction Name + {3c02b38e-6275-4ebb-9446-58b744dd661a}, !- Door Construction Name + , !- Glass Door Construction Name + , !- Overhead Door Construction Name + , !- Skylight Construction Name + , !- Tubular Daylight Dome Construction Name + ; !- Tubular Daylight Diffuser Construction Name + +OS:DefaultConstructionSet, + {1d26ed1d-dcb3-4e00-8739-759394e87a9c}, !- Handle + 90.1-2010 - Warehouse - Office - ASHRAE 169-2013-3B, !- Name + {a99550a0-f517-49a4-b347-34fec961d3b7}, !- Default Exterior Surface Constructions Name + {c6d7e1b6-a3cb-4408-b200-9b12fa24cba8}, !- Default Interior Surface Constructions Name + {cc590b44-fe74-4414-bc29-e37a0acc37c5}, !- Default Ground Contact Surface Constructions Name + {3bef5f51-aebb-412c-a156-1ef1470a7264}, !- Default Exterior SubSurface Constructions Name + {a2087a1d-edd6-41fb-b09d-3dbdaf3ff4c0}, !- Default Interior SubSurface Constructions Name + {538f879a-2d2d-437a-8ea0-f0a357be7167}, !- Interior Partition Construction Name + , !- Space Shading Construction Name + , !- Building Shading Construction Name + , !- Site Shading Construction Name + ; !- Adiabatic Surface Construction Name + +OS:DefaultSurfaceConstructions, + {a99550a0-f517-49a4-b347-34fec961d3b7}, !- Handle + Default Surface Constructions 10, !- Name + {595aa136-c217-4042-9466-c2f11b3a6ea9}, !- Floor Construction Name + {1e702b7c-1003-4657-9b8b-93d9d01f4fe7}, !- Wall Construction Name + {8ad7cddd-f9ed-4a63-8423-2a67ca81d87b}; !- Roof Ceiling Construction Name + +OS:Construction, + {595aa136-c217-4042-9466-c2f11b3a6ea9}, !- Handle + Typical Insulated Exterior Mass Floor R-9.35 3, !- Name + , !- Surface Rendering Name + {7031d6bd-2836-45b3-8f81-22d1558d09b4}, !- Layer 1 + {a35896e8-e047-40c1-be2e-49f72fb5d4c8}, !- Layer 2 + {12ba7a16-a66c-4c66-a342-32f13797cacb}; !- Layer 3 + +OS:StandardsInformation:Construction, + {8b7e52d8-4bcf-44b2-ba5f-e407caca1870}, !- Handle + {595aa136-c217-4042-9466-c2f11b3a6ea9}, !- Construction Name + ExteriorFloor, !- Intended Surface Type + Mass; !- Standards Construction Type + +OS:Material:NoMass, + {7031d6bd-2836-45b3-8f81-22d1558d09b4}, !- Handle + Typical Insulation R-6.78 3, !- Name + Smooth, !- Roughness + 1.19346679593268, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {1e702b7c-1003-4657-9b8b-93d9d01f4fe7}, !- Handle + Typical Insulated Metal Building Wall R-11.9 1, !- Name + , !- Surface Rendering Name + {f217eba0-4d73-4b33-bdb2-c6731abd2047}, !- Layer 1 + {c8850a1c-a06f-44a9-b2a1-02b838d82303}, !- Layer 2 + {c0c15930-2857-4b94-8539-ce991ed28ae4}; !- Layer 3 + +OS:StandardsInformation:Construction, + {a773d52b-aee4-4a33-b82e-54242f1c7873}, !- Handle + {1e702b7c-1003-4657-9b8b-93d9d01f4fe7}, !- Construction Name + ExteriorWall, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material:NoMass, + {c8850a1c-a06f-44a9-b2a1-02b838d82303}, !- Handle + Typical Insulation R-10.6 1, !- Name + Smooth, !- Roughness + 1.86744778662245, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {8ad7cddd-f9ed-4a63-8423-2a67ca81d87b}, !- Handle + Typical Insulated Metal Building Roof R-18.18 1, !- Name + , !- Surface Rendering Name + {1dbb61d0-d0bd-433a-89f8-e5f77f5ea82b}, !- Layer 1 + {6faaffb6-ac15-430a-9c42-1bad39cf7fd7}; !- Layer 2 + +OS:StandardsInformation:Construction, + {bccb59ec-585c-4aa8-9b23-1b4c911e9476}, !- Handle + {8ad7cddd-f9ed-4a63-8423-2a67ca81d87b}, !- Construction Name + ExteriorRoof, !- Intended Surface Type + Metal; !- Standards Construction Type + +OS:Material:NoMass, + {6faaffb6-ac15-430a-9c42-1bad39cf7fd7}, !- Handle + Typical Insulation R-17.4 1, !- Name + Smooth, !- Roughness + 3.06461972856157, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:DefaultSurfaceConstructions, + {c6d7e1b6-a3cb-4408-b200-9b12fa24cba8}, !- Handle + Default Surface Constructions 11, !- Name + {5424c879-1e23-4f3c-a777-f34e545c37d8}, !- Floor Construction Name + {62668aa9-83f9-4fd1-ad49-c72633dd8b44}, !- Wall Construction Name + {a11127e9-b0c0-4eea-b5c0-ba3ed42940b3}; !- Roof Ceiling Construction Name + +OS:DefaultSurfaceConstructions, + {cc590b44-fe74-4414-bc29-e37a0acc37c5}, !- Handle + Default Surface Constructions 12, !- Name + {ab179a1b-edea-4bea-9692-79a257155fd0}, !- Floor Construction Name + {078b22fd-abf0-4db0-a340-00a310013d74}, !- Wall Construction Name + ; !- Roof Ceiling Construction Name + +OS:DefaultSubSurfaceConstructions, + {3bef5f51-aebb-412c-a156-1ef1470a7264}, !- Handle + Default Sub Surface Constructions 7, !- Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Fixed Window Construction Name + {8c839b61-aa34-4fac-9e92-c1dbb76c13a3}, !- Operable Window Construction Name + {1dc308a2-c344-42fc-851e-cc1fa3375918}, !- Door Construction Name + , !- Glass Door Construction Name + {6168ce81-56ce-4c34-b0c5-dfcea37bac09}, !- Overhead Door Construction Name + {2779543d-c376-4d28-8e3d-fda7361bbb8e}, !- Skylight Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Tubular Daylight Dome Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}; !- Tubular Daylight Diffuser Construction Name + +OS:Construction, + {1dc308a2-c344-42fc-851e-cc1fa3375918}, !- Handle + Typical Uninsulated Swinging Door R-1.43 3, !- Name + , !- Surface Rendering Name + {7dce4c9d-9866-4d84-9895-b291ba4babf5}; !- Layer 1 + +OS:StandardsInformation:Construction, + {9625d1ca-b0d5-4617-a9ef-cd5c2bad254c}, !- Handle + {1dc308a2-c344-42fc-851e-cc1fa3375918}, !- Construction Name + ExteriorDoor, !- Intended Surface Type + Swinging; !- Standards Construction Type + +OS:Material:NoMass, + {7dce4c9d-9866-4d84-9895-b291ba4babf5}, !- Handle + Typical Insulation R-0.58 3, !- Name + Smooth, !- Roughness + 0.101892320559048, !- Thermal Resistance {m2-K/W} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {2779543d-c376-4d28-8e3d-fda7361bbb8e}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing Skylight U-1.17 SHGC 0.39 3, !- Name + , !- Surface Rendering Name + {3c472d02-1622-4090-9cad-225555bd7e03}; !- Layer 1 + +OS:StandardsInformation:Construction, + {ca5c49bd-ca46-47ed-b62e-7e4fee8f00d9}, !- Handle + {2779543d-c376-4d28-8e3d-fda7361bbb8e}, !- Construction Name + , !- Intended Surface Type + ; !- Standards Construction Type + +OS:WindowMaterial:SimpleGlazingSystem, + {3c472d02-1622-4090-9cad-225555bd7e03}, !- Handle + U 1.17 SHGC 0.39 Simple Glazing U-1.17 SHGC 0.39 3, !- Name + 6.64356810910278, !- U-Factor {W/m2-K} + 0.39, !- Solar Heat Gain Coefficient + 0.23; !- Visible Transmittance + +OS:DefaultSubSurfaceConstructions, + {a2087a1d-edd6-41fb-b09d-3dbdaf3ff4c0}, !- Handle + Default Sub Surface Constructions 8, !- Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Fixed Window Construction Name + {829fb767-053b-4cfd-9889-0ef24544bed7}, !- Operable Window Construction Name + {3c02b38e-6275-4ebb-9446-58b744dd661a}, !- Door Construction Name + , !- Glass Door Construction Name + , !- Overhead Door Construction Name + , !- Skylight Construction Name + , !- Tubular Daylight Dome Construction Name + ; !- Tubular Daylight Diffuser Construction Name + +OS:Material, + {08d08665-c9bf-4e44-a7e6-48e0d8f0e75b}, !- Handle + Std Wood 6inch, !- Name + MediumSmooth, !- Roughness + 0.15, !- Thickness {m} + 0.12, !- Conductivity {W/m-K} + 540, !- Density {kg/m3} + 1210, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.7, !- Solar Absorptance + 0.7; !- Visible Absorptance + +OS:Construction, + {d52ab39f-176d-4b02-b1b1-3b8fad86bc08}, !- Handle + InteriorFurnishings, !- Name + , !- Surface Rendering Name + {08d08665-c9bf-4e44-a7e6-48e0d8f0e75b}; !- Layer 1 + +OS:InternalMass:Definition, + {8a673a1d-b36f-48a7-90de-e1d4cf8d5bf8}, !- Handle + Internal Mass Definition 1, !- Name + {d52ab39f-176d-4b02-b1b1-3b8fad86bc08}, !- Construction Name + SurfaceArea/Area, !- Design Level Calculation Method + , !- Surface Area {m2} + 2, !- Surface Area per Space Floor Area {dimensionless} + ; !- Surface Area per Person {m2/person} + +OS:InternalMass, + {6818efa5-0444-4f36-96a1-02b7d24aa2f7}, !- Handle + Zone1 Office Mass, !- Name + {8a673a1d-b36f-48a7-90de-e1d4cf8d5bf8}, !- Internal Mass Definition Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space or SpaceType Name + 1; !- Multiplier + +OS:InternalMass, + {391acd60-85ae-44bd-90c2-3fdc7149914b}, !- Handle + Zone2 Fine Storage Mass, !- Name + {8a673a1d-b36f-48a7-90de-e1d4cf8d5bf8}, !- Internal Mass Definition Name + {cdd6a95f-f907-4c0e-817d-962525d358d0}, !- Space or SpaceType Name + 1; !- Multiplier + +OS:PlantLoop, + {58879e18-ffe9-4981-9ae6-b05ce16f3d51}, !- Handle + Main Service Water Loop, !- Name + , !- Fluid Type + 0, !- Glycol Concentration + , !- User Defined Fluid Type + , !- Plant Equipment Operation Heating Load + , !- Plant Equipment Operation Cooling Load + , !- Primary Plant Equipment Operation Scheme + {41a06294-c325-4d6a-b588-fc0c10922937}, !- Loop Temperature Setpoint Node Name + 60, !- Maximum Loop Temperature {C} + 10, !- Minimum Loop Temperature {C} + , !- Maximum Loop Flow Rate {m3/s} + , !- Minimum Loop Flow Rate {m3/s} + Autocalculate, !- Plant Loop Volume {m3} + {91094632-d336-45a9-9de6-1d9e2a9ad7b3}, !- Plant Side Inlet Node Name + {4c53a3cd-2322-4501-8226-ac76e9b879a4}, !- Plant Side Outlet Node Name + , !- Plant Side Branch List Name + {3633cafb-102c-4131-9413-66e62fed0e61}, !- Demand Side Inlet Node Name + {495cd6a1-2b93-4d04-bb34-951449601949}, !- Demand Side Outlet Node Name + , !- Demand Side Branch List Name + , !- Demand Side Connector List Name + Optimal, !- Load Distribution Scheme + {ec301281-ab6a-4d49-843d-7db795465d19}, !- Availability Manager List Name + , !- Plant Loop Demand Calculation Scheme + , !- Common Pipe Simulation + , !- Pressure Simulation Type + , !- Plant Equipment Operation Heating Load Schedule + , !- Plant Equipment Operation Cooling Load Schedule + , !- Primary Plant Equipment Operation Scheme Schedule + , !- Component Setpoint Operation Scheme Schedule + {657e7de9-ea9a-4885-87e1-9b1d78642416}, !- Demand Mixer Name + {95ef4e3c-c7f7-4de1-846a-c8e1b0daf9b4}, !- Demand Splitter Name + {295e7611-96c0-4c51-9314-42853b31488f}, !- Supply Mixer Name + {98674b7e-9d66-44cd-a18b-39574e97c8d6}; !- Supply Splitter Name + +OS:Node, + {14649ca7-301e-4ac2-b135-1d755d909f70}, !- Handle + Main Service Water Loop Supply Inlet Node, !- Name + {91094632-d336-45a9-9de6-1d9e2a9ad7b3}, !- Inlet Port + {90ecef1b-db8b-4674-9706-e2656eba40a1}; !- Outlet Port + +OS:Node, + {41a06294-c325-4d6a-b588-fc0c10922937}, !- Handle + Main Service Water Loop Supply Outlet Node, !- Name + {025ea488-83fb-4f50-b9c0-a939e520884c}, !- Inlet Port + {4c53a3cd-2322-4501-8226-ac76e9b879a4}; !- Outlet Port + +OS:Node, + {3a674774-a8c9-4e19-9287-2796ffae235f}, !- Handle + 20gal Electricity Water Heater - 21kBtu/hr 1.0 Therm Eff Supply Inlet Water Node, !- Name + {cce3fb4e-198e-4521-825b-049a71ee42f4}, !- Inlet Port + {8d94043d-044c-4e8a-9db3-5832929d271c}; !- Outlet Port + +OS:Connector:Mixer, + {295e7611-96c0-4c51-9314-42853b31488f}, !- Handle + Connector Mixer 1, !- Name + {ff56355e-d202-40ad-9dcd-3f5f95ac20ed}, !- Outlet Branch Name + {936577bb-166c-444e-9c4d-32c4ef007299}, !- Inlet Branch Name 1 + {79d960d9-c736-40f6-bd1b-a25f2b544863}; !- Inlet Branch Name 2 + +OS:Connector:Splitter, + {98674b7e-9d66-44cd-a18b-39574e97c8d6}, !- Handle + Connector Splitter 1, !- Name + {5cb2abb0-71a6-4d46-b7c4-a78aa2f94496}, !- Inlet Branch Name + {cce3fb4e-198e-4521-825b-049a71ee42f4}, !- Outlet Branch Name 1 + {f90a498c-0af3-40f5-a4a0-52a685766c7e}; !- Outlet Branch Name 2 + +OS:Connection, + {91094632-d336-45a9-9de6-1d9e2a9ad7b3}, !- Handle + {287c9b61-9b00-464d-9d99-26caca73df54}, !- Name + {58879e18-ffe9-4981-9ae6-b05ce16f3d51}, !- Source Object + 14, !- Outlet Port + {14649ca7-301e-4ac2-b135-1d755d909f70}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {cce3fb4e-198e-4521-825b-049a71ee42f4}, !- Handle + {ded1b9bf-5a8d-4350-9742-09e246738c99}, !- Name + {98674b7e-9d66-44cd-a18b-39574e97c8d6}, !- Source Object + 3, !- Outlet Port + {3a674774-a8c9-4e19-9287-2796ffae235f}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {4c53a3cd-2322-4501-8226-ac76e9b879a4}, !- Handle + {c823f753-8243-4a9f-b1d1-41b3b914d9b6}, !- Name + {41a06294-c325-4d6a-b588-fc0c10922937}, !- Source Object + 3, !- Outlet Port + {58879e18-ffe9-4981-9ae6-b05ce16f3d51}, !- Target Object + 15; !- Inlet Port + +OS:Node, + {8377404c-0c72-4fdf-98e8-6a099fe45a06}, !- Handle + Main Service Water Loop Demand Inlet Node, !- Name + {3633cafb-102c-4131-9413-66e62fed0e61}, !- Inlet Port + {366c6867-e10c-4190-a241-3d82a309ac70}; !- Outlet Port + +OS:Node, + {399ec1ba-7854-4a38-93c2-befa82d5e98a}, !- Handle + Main Service Water Loop Demand Outlet Node, !- Name + {6f277bf2-c315-4b1a-b021-2423bfb03edf}, !- Inlet Port + {495cd6a1-2b93-4d04-bb34-951449601949}; !- Outlet Port + +OS:Node, + {f8e2c26c-4d47-4cd7-b2d8-225361f0fe5c}, !- Handle + Pipe Adiabatic 2 Inlet Water Node, !- Name + {e4790b0d-21bf-4fa0-8677-57dc0abf5a46}, !- Inlet Port + {05d139bb-c696-4cf4-98bd-d6bf8501f4dc}; !- Outlet Port + +OS:Connector:Mixer, + {657e7de9-ea9a-4885-87e1-9b1d78642416}, !- Handle + Connector Mixer 2, !- Name + {4970a4ee-8f19-4e35-83ab-673ce50808c1}, !- Outlet Branch Name + {1a3cb812-77bc-4226-9646-a78bb30bab50}, !- Inlet Branch Name 1 + {ee625681-d9bf-4258-869a-fef7f0f89103}; !- Inlet Branch Name 2 + +OS:Connector:Splitter, + {95ef4e3c-c7f7-4de1-846a-c8e1b0daf9b4}, !- Handle + Connector Splitter 2, !- Name + {366c6867-e10c-4190-a241-3d82a309ac70}, !- Inlet Branch Name + {e4790b0d-21bf-4fa0-8677-57dc0abf5a46}, !- Outlet Branch Name 1 + {477eb579-9070-47af-b9f8-a60e60e602ab}; !- Outlet Branch Name 2 + +OS:Connection, + {3633cafb-102c-4131-9413-66e62fed0e61}, !- Handle + {541a371f-42f1-4966-893b-254573858025}, !- Name + {58879e18-ffe9-4981-9ae6-b05ce16f3d51}, !- Source Object + 17, !- Outlet Port + {8377404c-0c72-4fdf-98e8-6a099fe45a06}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {366c6867-e10c-4190-a241-3d82a309ac70}, !- Handle + {1d1b562f-c18b-48d2-8cb6-31fcd9de9cef}, !- Name + {8377404c-0c72-4fdf-98e8-6a099fe45a06}, !- Source Object + 3, !- Outlet Port + {95ef4e3c-c7f7-4de1-846a-c8e1b0daf9b4}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {e4790b0d-21bf-4fa0-8677-57dc0abf5a46}, !- Handle + {3008b3ca-c63f-41dd-8d46-c118fd92ffca}, !- Name + {95ef4e3c-c7f7-4de1-846a-c8e1b0daf9b4}, !- Source Object + 3, !- Outlet Port + {f8e2c26c-4d47-4cd7-b2d8-225361f0fe5c}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {495cd6a1-2b93-4d04-bb34-951449601949}, !- Handle + {37908b08-047b-49ce-96cd-31ce280f6e34}, !- Name + {399ec1ba-7854-4a38-93c2-befa82d5e98a}, !- Source Object + 3, !- Outlet Port + {58879e18-ffe9-4981-9ae6-b05ce16f3d51}, !- Target Object + 18; !- Inlet Port + +OS:Sizing:Plant, + {66e2f1bc-da51-4801-99bf-cb3e499e86e8}, !- Handle + {58879e18-ffe9-4981-9ae6-b05ce16f3d51}, !- Plant or Condenser Loop Name + Heating, !- Loop Type + 60.0000000000001, !- Design Loop Exit Temperature {C} + 5, !- Loop Design Temperature Difference {deltaC} + NonCoincident, !- Sizing Option + 1, !- Zone Timesteps in Averaging Window + None; !- Coincident Sizing Factor Mode + +OS:AvailabilityManagerAssignmentList, + {ec301281-ab6a-4d49-843d-7db795465d19}, !- Handle + Plant Loop 1 AvailabilityManagerAssignmentList; !- Name + +OS:ScheduleTypeLimits, + {1cf30669-09eb-4506-8120-22fa3c6d15a3}, !- Handle + Temperature Schedule Type Limits, !- Name + 0, !- Lower Limit Value + 100, !- Upper Limit Value + Continuous, !- Numeric Type + Temperature; !- Unit Type + +OS:Schedule:Ruleset, + {d2ef4e3a-537e-4d99-b0cf-501ae1232d55}, !- Handle + Service Water Loop Temp - 140F, !- Name + {1cf30669-09eb-4506-8120-22fa3c6d15a3}, !- Schedule Type Limits Name + {f91da3b3-158e-4905-9cbf-c42f4f043d51}; !- Default Day Schedule Name + +OS:Schedule:Day, + {f91da3b3-158e-4905-9cbf-c42f4f043d51}, !- Handle + Service Water Loop Temp - 140F Default, !- Name + {1cf30669-09eb-4506-8120-22fa3c6d15a3}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 60.0000000000001; !- Value Until Time 1 + +OS:SetpointManager:Scheduled, + {da996b90-97f8-4101-b4d4-a7587365dead}, !- Handle + Service hot water setpoint manager, !- Name + Temperature, !- Control Variable + {d2ef4e3a-537e-4d99-b0cf-501ae1232d55}, !- Schedule Name + {41a06294-c325-4d6a-b588-fc0c10922937}; !- Setpoint Node or NodeList Name + +OS:Pump:VariableSpeed, + {428a4486-4771-42af-b834-fba986bddaa1}, !- Handle + Main Service Water Loop Water Mains Pressure Driven, !- Name + {90ecef1b-db8b-4674-9706-e2656eba40a1}, !- Inlet Node Name + {023650b5-82b5-4de0-bd69-b3e101d760f8}, !- Outlet Node Name + , !- Rated Flow Rate {m3/s} + 0.001, !- Rated Pump Head {Pa} + , !- Rated Power Consumption {W} + 0.855, !- Motor Efficiency + , !- Fraction of Motor Inefficiencies to Fluid Stream + , !- Coefficient 1 of the Part Load Performance Curve + , !- Coefficient 2 of the Part Load Performance Curve + , !- Coefficient 3 of the Part Load Performance Curve + , !- Coefficient 4 of the Part Load Performance Curve + , !- Minimum Flow Rate {m3/s} + Continuous, !- Pump Control Type + , !- Pump Flow Rate Schedule Name + , !- Pump Curve Name + , !- Impeller Diameter {m} + , !- VFD Control Type + , !- Pump RPM Schedule Name + , !- Minimum Pressure Schedule {Pa} + , !- Maximum Pressure Schedule {Pa} + , !- Minimum RPM Schedule {rev/min} + , !- Maximum RPM Schedule {rev/min} + , !- Zone Name + 0.5, !- Skin Loss Radiative Fraction + PowerPerFlowPerPressure, !- Design Power Sizing Method + 348701.1, !- Design Electric Power per Unit Flow Rate {W/(m3/s)} + 1.282051282, !- Design Shaft Power per Unit Flow Rate per Unit Head {W-s/m3-Pa} + 0, !- Design Minimum Flow Rate Fraction + General; !- End-Use Subcategory + +OS:Node, + {cac8caea-1687-4dfc-9739-07db2ebaf62b}, !- Handle + Main Service Water Loop Water Mains Pressure Driven Outlet Water Node, !- Name + {023650b5-82b5-4de0-bd69-b3e101d760f8}, !- Inlet Port + {5cb2abb0-71a6-4d46-b7c4-a78aa2f94496}; !- Outlet Port + +OS:Connection, + {90ecef1b-db8b-4674-9706-e2656eba40a1}, !- Handle + {ed1600e5-9da4-4731-8f41-1e31b2f0a658}, !- Name + {14649ca7-301e-4ac2-b135-1d755d909f70}, !- Source Object + 3, !- Outlet Port + {428a4486-4771-42af-b834-fba986bddaa1}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {023650b5-82b5-4de0-bd69-b3e101d760f8}, !- Handle + {6b76ce37-899e-427d-8b1d-94b6c333c1fa}, !- Name + {428a4486-4771-42af-b834-fba986bddaa1}, !- Source Object + 3, !- Outlet Port + {cac8caea-1687-4dfc-9739-07db2ebaf62b}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {5cb2abb0-71a6-4d46-b7c4-a78aa2f94496}, !- Handle + {197def48-adc5-4dfa-9366-f080118f497b}, !- Name + {cac8caea-1687-4dfc-9739-07db2ebaf62b}, !- Source Object + 3, !- Outlet Port + {98674b7e-9d66-44cd-a18b-39574e97c8d6}, !- Target Object + 2; !- Inlet Port + +OS:WaterHeater:Mixed, + {6ce31c07-c985-40c6-ba1d-ee5762b91d96}, !- Handle + 20gal Electricity Water Heater - 21kBtu/hr 1.0 Therm Eff, !- Name + 0.075708235679397, !- Tank Volume {m3} + {d2ef4e3a-537e-4d99-b0cf-501ae1232d55}, !- Setpoint Temperature Schedule Name + 2, !- Deadband Temperature Difference {deltaC} + 60.0000000000001, !- Maximum Temperature Limit {C} + Cycle, !- Heater Control Type + 6154.74805474247, !- Heater Maximum Capacity {W} + , !- Heater Minimum Capacity {W} + , !- Heater Ignition Minimum Flow Rate {m3/s} + , !- Heater Ignition Delay {s} + Electricity, !- Heater Fuel Type + 1, !- Heater Thermal Efficiency + , !- Part Load Factor Curve Name + 481.000051716921, !- Off Cycle Parasitic Fuel Consumption Rate {W} + Electricity, !- Off Cycle Parasitic Fuel Type + 0, !- Off Cycle Parasitic Heat Fraction to Tank + 481.000051716921, !- On Cycle Parasitic Fuel Consumption Rate {W} + Electricity, !- On Cycle Parasitic Fuel Type + 0, !- On Cycle Parasitic Heat Fraction to Tank + ThermalZone, !- Ambient Temperature Indicator + , !- Ambient Temperature Schedule Name + {6f94c337-8aec-4d71-9b6a-4fb238973ec2}, !- Ambient Temperature Thermal Zone Name + , !- Ambient Temperature Outdoor Air Node Name + 0.79983430861932, !- Off Cycle Loss Coefficient to Ambient Temperature {W/K} + , !- Off Cycle Loss Fraction to Thermal Zone + 0.79983430861932, !- On Cycle Loss Coefficient to Ambient Temperature {W/K} + , !- On Cycle Loss Fraction to Thermal Zone + , !- Peak Use Flow Rate {m3/s} + , !- Use Flow Rate Fraction Schedule Name + , !- Cold Water Supply Temperature Schedule Name + {8d94043d-044c-4e8a-9db3-5832929d271c}, !- Use Side Inlet Node Name + {b9e20f41-ec6c-4753-a889-7f913a9751f1}, !- Use Side Outlet Node Name + 1, !- Use Side Effectiveness + , !- Source Side Inlet Node Name + , !- Source Side Outlet Node Name + 1, !- Source Side Effectiveness + autosize, !- Use Side Design Flow Rate {m3/s} + autosize, !- Source Side Design Flow Rate {m3/s} + 1.5, !- Indirect Water Heating Recovery Time {hr} + IndirectHeatPrimarySetpoint, !- Source Side Flow Control Mode + , !- Indirect Alternate Setpoint Temperature Schedule Name + General; !- End-Use Subcategory + +OS:Schedule:Day, + {39934160-8994-4e9d-b7fe-413bad906d31}, !- Handle + Schedule Day 32, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 22; !- Value Until Time 1 + +OS:Schedule:Day, + {3e78b8b3-b830-4924-b24e-c6d3272fa16c}, !- Handle + Schedule Day 33, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 60; !- Value Until Time 1 + +OS:Node, + {e4f7abe1-9b25-453c-813f-0f5720ee38b0}, !- Handle + 20gal Electricity Water Heater - 21kBtu/hr 1.0 Therm Eff Supply Outlet Water Node, !- Name + {b9e20f41-ec6c-4753-a889-7f913a9751f1}, !- Inlet Port + {936577bb-166c-444e-9c4d-32c4ef007299}; !- Outlet Port + +OS:Connection, + {8d94043d-044c-4e8a-9db3-5832929d271c}, !- Handle + {e3550110-3658-425c-a002-f720edefb9f2}, !- Name + {3a674774-a8c9-4e19-9287-2796ffae235f}, !- Source Object + 3, !- Outlet Port + {6ce31c07-c985-40c6-ba1d-ee5762b91d96}, !- Target Object + 31; !- Inlet Port + +OS:Connection, + {b9e20f41-ec6c-4753-a889-7f913a9751f1}, !- Handle + {9b343005-bddd-4c09-bc55-7495ad97bce5}, !- Name + {6ce31c07-c985-40c6-ba1d-ee5762b91d96}, !- Source Object + 32, !- Outlet Port + {e4f7abe1-9b25-453c-813f-0f5720ee38b0}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {936577bb-166c-444e-9c4d-32c4ef007299}, !- Handle + {f6841989-08f4-4136-8f77-bdc3d6652b0c}, !- Name + {e4f7abe1-9b25-453c-813f-0f5720ee38b0}, !- Source Object + 3, !- Outlet Port + {295e7611-96c0-4c51-9314-42853b31488f}, !- Target Object + 3; !- Inlet Port + +OS:Pipe:Adiabatic, + {8acac652-598e-469e-a4a9-ee757fa7c979}, !- Handle + Pipe Adiabatic 1, !- Name + {fa44b0ea-14e9-402c-8104-6034e8e8a108}, !- Inlet Node Name + {81b58c27-1edc-4feb-bace-a91cbbd05375}; !- Outlet Node Name + +OS:Node, + {5d5a18e1-1493-4112-9994-a00b3b16e899}, !- Handle + Pipe Adiabatic 1 Inlet Water Node, !- Name + {f90a498c-0af3-40f5-a4a0-52a685766c7e}, !- Inlet Port + {fa44b0ea-14e9-402c-8104-6034e8e8a108}; !- Outlet Port + +OS:Connection, + {f90a498c-0af3-40f5-a4a0-52a685766c7e}, !- Handle + {2782bed0-cc88-4706-90fa-b96648c5ee9d}, !- Name + {98674b7e-9d66-44cd-a18b-39574e97c8d6}, !- Source Object + 4, !- Outlet Port + {5d5a18e1-1493-4112-9994-a00b3b16e899}, !- Target Object + 2; !- Inlet Port + +OS:Node, + {b667995a-774b-44e7-b27e-30b72fa7039c}, !- Handle + Pipe Adiabatic 1 Outlet Water Node, !- Name + {81b58c27-1edc-4feb-bace-a91cbbd05375}, !- Inlet Port + {79d960d9-c736-40f6-bd1b-a25f2b544863}; !- Outlet Port + +OS:Connection, + {fa44b0ea-14e9-402c-8104-6034e8e8a108}, !- Handle + {06795f20-48e2-49bc-beae-0e9bb2a3430a}, !- Name + {5d5a18e1-1493-4112-9994-a00b3b16e899}, !- Source Object + 3, !- Outlet Port + {8acac652-598e-469e-a4a9-ee757fa7c979}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {81b58c27-1edc-4feb-bace-a91cbbd05375}, !- Handle + {ab1335b9-087b-40e2-80fa-9eda58974857}, !- Name + {8acac652-598e-469e-a4a9-ee757fa7c979}, !- Source Object + 3, !- Outlet Port + {b667995a-774b-44e7-b27e-30b72fa7039c}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {79d960d9-c736-40f6-bd1b-a25f2b544863}, !- Handle + {b8bafefb-d224-4708-8227-191d10719b73}, !- Name + {b667995a-774b-44e7-b27e-30b72fa7039c}, !- Source Object + 3, !- Outlet Port + {295e7611-96c0-4c51-9314-42853b31488f}, !- Target Object + 4; !- Inlet Port + +OS:Pipe:Adiabatic, + {3edb689b-9515-4abd-bb3a-4aa6d073950e}, !- Handle + Pipe Adiabatic 2, !- Name + {05d139bb-c696-4cf4-98bd-d6bf8501f4dc}, !- Inlet Node Name + {5114bc0f-fd8b-4aad-8d24-7e2df85c2032}; !- Outlet Node Name + +OS:Node, + {9e0d0677-fd43-4a0c-9b20-a6e993c97aae}, !- Handle + Pipe Adiabatic 2 Outlet Water Node, !- Name + {5114bc0f-fd8b-4aad-8d24-7e2df85c2032}, !- Inlet Port + {1a3cb812-77bc-4226-9646-a78bb30bab50}; !- Outlet Port + +OS:Connection, + {05d139bb-c696-4cf4-98bd-d6bf8501f4dc}, !- Handle + {50662c14-a23c-4c08-b1bb-334e27392bd8}, !- Name + {f8e2c26c-4d47-4cd7-b2d8-225361f0fe5c}, !- Source Object + 3, !- Outlet Port + {3edb689b-9515-4abd-bb3a-4aa6d073950e}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {5114bc0f-fd8b-4aad-8d24-7e2df85c2032}, !- Handle + {1634cad3-a233-4339-a225-26aa716eed2f}, !- Name + {3edb689b-9515-4abd-bb3a-4aa6d073950e}, !- Source Object + 3, !- Outlet Port + {9e0d0677-fd43-4a0c-9b20-a6e993c97aae}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {1a3cb812-77bc-4226-9646-a78bb30bab50}, !- Handle + {a36a565c-f5d7-450c-8712-0d067459cad7}, !- Name + {9e0d0677-fd43-4a0c-9b20-a6e993c97aae}, !- Source Object + 3, !- Outlet Port + {657e7de9-ea9a-4885-87e1-9b1d78642416}, !- Target Object + 3; !- Inlet Port + +OS:Pipe:Adiabatic, + {aef811a8-b321-47bb-a015-03797c267a28}, !- Handle + Pipe Adiabatic 3, !- Name + {1e0244a7-4a1f-4cc8-a3b6-c5513cdb8403}, !- Inlet Node Name + {025ea488-83fb-4f50-b9c0-a939e520884c}; !- Outlet Node Name + +OS:Node, + {11b8240f-d17b-4868-9056-e784a23a1dff}, !- Handle + Pipe Adiabatic 3 Inlet Water Node, !- Name + {ff56355e-d202-40ad-9dcd-3f5f95ac20ed}, !- Inlet Port + {1e0244a7-4a1f-4cc8-a3b6-c5513cdb8403}; !- Outlet Port + +OS:Connection, + {ff56355e-d202-40ad-9dcd-3f5f95ac20ed}, !- Handle + {1c3c513a-4615-4fba-9336-dfd30588de7a}, !- Name + {295e7611-96c0-4c51-9314-42853b31488f}, !- Source Object + 2, !- Outlet Port + {11b8240f-d17b-4868-9056-e784a23a1dff}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {1e0244a7-4a1f-4cc8-a3b6-c5513cdb8403}, !- Handle + {b26b028f-1bee-46bb-b27d-fa510d24bccc}, !- Name + {11b8240f-d17b-4868-9056-e784a23a1dff}, !- Source Object + 3, !- Outlet Port + {aef811a8-b321-47bb-a015-03797c267a28}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {025ea488-83fb-4f50-b9c0-a939e520884c}, !- Handle + {ceffff15-b63f-4375-8d6e-9f09b5379c47}, !- Name + {aef811a8-b321-47bb-a015-03797c267a28}, !- Source Object + 3, !- Outlet Port + {41a06294-c325-4d6a-b588-fc0c10922937}, !- Target Object + 2; !- Inlet Port + +OS:Pipe:Adiabatic, + {3198999a-e44c-400a-9649-05480137b8c8}, !- Handle + Pipe Adiabatic 4, !- Name + {8e42fce8-8804-4acc-84aa-65588571dbde}, !- Inlet Node Name + {6f277bf2-c315-4b1a-b021-2423bfb03edf}; !- Outlet Node Name + +OS:Node, + {de7bcce0-4bf4-4e13-8548-56d68e03dd72}, !- Handle + Pipe Adiabatic 4 Inlet Water Node, !- Name + {4970a4ee-8f19-4e35-83ab-673ce50808c1}, !- Inlet Port + {8e42fce8-8804-4acc-84aa-65588571dbde}; !- Outlet Port + +OS:Connection, + {4970a4ee-8f19-4e35-83ab-673ce50808c1}, !- Handle + {c1fcb16a-701b-4e21-88a9-1152006c28c6}, !- Name + {657e7de9-ea9a-4885-87e1-9b1d78642416}, !- Source Object + 2, !- Outlet Port + {de7bcce0-4bf4-4e13-8548-56d68e03dd72}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {8e42fce8-8804-4acc-84aa-65588571dbde}, !- Handle + {e80728f3-cdb3-4650-8bd6-e92d3cf8a424}, !- Name + {de7bcce0-4bf4-4e13-8548-56d68e03dd72}, !- Source Object + 3, !- Outlet Port + {3198999a-e44c-400a-9649-05480137b8c8}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {6f277bf2-c315-4b1a-b021-2423bfb03edf}, !- Handle + {b1f61639-9dc8-43c2-b637-6d4c63005bf2}, !- Name + {3198999a-e44c-400a-9649-05480137b8c8}, !- Source Object + 3, !- Outlet Port + {399ec1ba-7854-4a38-93c2-befa82d5e98a}, !- Target Object + 2; !- Inlet Port + +OS:WaterUse:Connections, + {31be98ee-4f84-4360-a7e2-0b0caa80a98c}, !- Handle + Main WUC 0.13gpm at 140F, !- Name + {3a5fabc6-6da4-41cf-8b61-24fced62b7da}, !- Inlet Node Name + {46a45e20-7342-44b9-a93b-52efb65d4975}, !- Outlet Node Name + , !- Supply Water Storage Tank Name + , !- Reclamation Water Storage Tank Name + , !- Hot Water Supply Temperature Schedule Name + , !- Cold Water Supply Temperature Schedule Name + , !- Drain Water Heat Exchanger Type + , !- Drain Water Heat Exchanger Destination + , !- Drain Water Heat Exchanger U-Factor Times Area {W/K} + {989d58d6-fb8e-4595-96d7-a18b16365a72}; !- Water Use Equipment Name 1 + +OS:WaterUse:Equipment:Definition, + {d66bf912-ed79-49d7-a30d-297c5f64d88c}, !- Handle + Main Service Water Use Def 0.13gpm, !- Name + , !- End-Use Subcategory + 8.074690890675e-06, !- Peak Flow Rate {m3/s} + {9dcb69d1-20e0-435a-9f20-f15852c58aa5}, !- Target Temperature Schedule Name + {6cabb111-d57f-4e3b-b760-e78c7a62f0d6}, !- Sensible Fraction Schedule Name + {9e9577fd-f638-4c7d-8202-e214b17e41f9}; !- Latent Fraction Schedule Name + +OS:Schedule:Ruleset, + {6cabb111-d57f-4e3b-b760-e78c7a62f0d6}, !- Handle + Fraction Sensible - 0.2, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {86040019-90d6-426e-af98-bf10ad408a4a}; !- Default Day Schedule Name + +OS:Schedule:Day, + {86040019-90d6-426e-af98-bf10ad408a4a}, !- Handle + Fraction Sensible - 0.2 Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.2; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {9e9577fd-f638-4c7d-8202-e214b17e41f9}, !- Handle + Fraction Latent - 0.05, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {04b96ad3-7fa0-438d-9665-ae8edcdae8dc}; !- Default Day Schedule Name + +OS:Schedule:Day, + {04b96ad3-7fa0-438d-9665-ae8edcdae8dc}, !- Handle + Fraction Latent - 0.05 Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.05; !- Value Until Time 1 + +OS:Schedule:Ruleset, + {9dcb69d1-20e0-435a-9f20-f15852c58aa5}, !- Handle + Mixed Water At Faucet Temp - 140F, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + {3aac3c8f-e4f4-4469-ba9c-b4ebe1fec152}; !- Default Day Schedule Name + +OS:Schedule:Day, + {3aac3c8f-e4f4-4469-ba9c-b4ebe1fec152}, !- Handle + Mixed Water At Faucet Temp - 140F Default, !- Name + {475f8de9-87ac-4a10-a11e-62c8694c4121}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 60.0000000000001; !- Value Until Time 1 + +OS:WaterUse:Equipment, + {989d58d6-fb8e-4595-96d7-a18b16365a72}, !- Handle + Main Service Water Use 0.13gpm at 140F, !- Name + {d66bf912-ed79-49d7-a30d-297c5f64d88c}, !- Water Use Equipment Definition Name + , !- Space Name + {7b7e8351-9c68-467a-9923-7b4ea62ed36b}; !- Flow Rate Fraction Schedule Name + +OS:Schedule:Ruleset, + {7b7e8351-9c68-467a-9923-7b4ea62ed36b}, !- Handle + Warehouse Office DHW Schedule, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {aa6ad624-eb65-416a-b6e1-ff4631668300}, !- Default Day Schedule Name + {781c3519-e279-453a-9442-c7b63771d804}, !- Summer Design Day Schedule Name + {43ba4bde-a115-4567-93ff-1c209a7cc143}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {aa6ad624-eb65-416a-b6e1-ff4631668300}, !- Handle + Warehouse Office DHW Schedule Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.05, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.101, !- Value Until Time 3 + 9, !- Hour 4 + 0, !- Minute 4 + 0.399, !- Value Until Time 4 + 11, !- Hour 5 + 0, !- Minute 5 + 0.501, !- Value Until Time 5 + 12, !- Hour 6 + 0, !- Minute 6 + 0.696, !- Value Until Time 6 + 13, !- Hour 7 + 0, !- Minute 7 + 0.9, !- Value Until Time 7 + 14, !- Hour 8 + 0, !- Minute 8 + 0.798, !- Value Until Time 8 + 15, !- Hour 9 + 0, !- Minute 9 + 0.696, !- Value Until Time 9 + 16, !- Hour 10 + 0, !- Minute 10 + 0.798, !- Value Until Time 10 + 17, !- Hour 11 + 0, !- Minute 11 + 0.297, !- Value Until Time 11 + 18, !- Hour 12 + 0, !- Minute 12 + 0.05, !- Value Until Time 12 + 24, !- Hour 13 + 0, !- Minute 13 + 0; !- Value Until Time 13 + +OS:Schedule:Day, + {43ba4bde-a115-4567-93ff-1c209a7cc143}, !- Handle + Warehouse Office DHW Schedule Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.05, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.101, !- Value Until Time 3 + 9, !- Hour 4 + 0, !- Minute 4 + 0.399, !- Value Until Time 4 + 11, !- Hour 5 + 0, !- Minute 5 + 0.501, !- Value Until Time 5 + 12, !- Hour 6 + 0, !- Minute 6 + 0.696, !- Value Until Time 6 + 13, !- Hour 7 + 0, !- Minute 7 + 0.9, !- Value Until Time 7 + 14, !- Hour 8 + 0, !- Minute 8 + 0.798, !- Value Until Time 8 + 15, !- Hour 9 + 0, !- Minute 9 + 0.696, !- Value Until Time 9 + 16, !- Hour 10 + 0, !- Minute 10 + 0.798, !- Value Until Time 10 + 17, !- Hour 11 + 0, !- Minute 11 + 0.297, !- Value Until Time 11 + 18, !- Hour 12 + 0, !- Minute 12 + 0.05, !- Value Until Time 12 + 24, !- Hour 13 + 0, !- Minute 13 + 0; !- Value Until Time 13 + +OS:Schedule:Day, + {781c3519-e279-453a-9442-c7b63771d804}, !- Handle + Warehouse Office DHW Schedule Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 7, !- Hour 2 + 0, !- Minute 2 + 0.05, !- Value Until Time 2 + 8, !- Hour 3 + 0, !- Minute 3 + 0.101, !- Value Until Time 3 + 9, !- Hour 4 + 0, !- Minute 4 + 0.399, !- Value Until Time 4 + 11, !- Hour 5 + 0, !- Minute 5 + 0.501, !- Value Until Time 5 + 12, !- Hour 6 + 0, !- Minute 6 + 0.696, !- Value Until Time 6 + 13, !- Hour 7 + 0, !- Minute 7 + 0.9, !- Value Until Time 7 + 14, !- Hour 8 + 0, !- Minute 8 + 0.798, !- Value Until Time 8 + 15, !- Hour 9 + 0, !- Minute 9 + 0.696, !- Value Until Time 9 + 16, !- Hour 10 + 0, !- Minute 10 + 0.798, !- Value Until Time 10 + 17, !- Hour 11 + 0, !- Minute 11 + 0.297, !- Value Until Time 11 + 18, !- Hour 12 + 0, !- Minute 12 + 0.05, !- Value Until Time 12 + 24, !- Hour 13 + 0, !- Minute 13 + 0; !- Value Until Time 13 + +OS:Schedule:Rule, + {01ded20d-24a6-4586-a7c2-c2d031963756}, !- Handle + Schedule Rule 19, !- Name + {7b7e8351-9c68-467a-9923-7b4ea62ed36b}, !- Schedule Ruleset Name + 0, !- Rule Order + {26642045-063d-49e7-82f1-fce60b0a292d}, !- Day Schedule Name + Yes, !- Apply Sunday + , !- Apply Monday + , !- Apply Tuesday + , !- Apply Wednesday + , !- Apply Thursday + , !- Apply Friday + , !- Apply Saturday + , !- Apply Holiday + DateRange, !- Date Specification Type + 1, !- Start Month + 1, !- Start Day + 12, !- End Month + 31; !- End Day + +OS:Schedule:Day, + {26642045-063d-49e7-82f1-fce60b0a292d}, !- Handle + Warehouse Office DHW Schedule Sun Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.015; !- Value Until Time 1 + +OS:Node, + {6f803b10-1a72-4a96-bf9a-28617e8a9e4f}, !- Handle + Main WUC 0.13gpm at 140F Inlet Water Node, !- Name + {477eb579-9070-47af-b9f8-a60e60e602ab}, !- Inlet Port + {3a5fabc6-6da4-41cf-8b61-24fced62b7da}; !- Outlet Port + +OS:Connection, + {477eb579-9070-47af-b9f8-a60e60e602ab}, !- Handle + {2bd8846a-f773-49c4-9b18-88edec84584b}, !- Name + {95ef4e3c-c7f7-4de1-846a-c8e1b0daf9b4}, !- Source Object + 4, !- Outlet Port + {6f803b10-1a72-4a96-bf9a-28617e8a9e4f}, !- Target Object + 2; !- Inlet Port + +OS:Node, + {2e535b23-0e82-4870-9ea3-28db296ec393}, !- Handle + Main WUC 0.13gpm at 140F Outlet Water Node, !- Name + {46a45e20-7342-44b9-a93b-52efb65d4975}, !- Inlet Port + {ee625681-d9bf-4258-869a-fef7f0f89103}; !- Outlet Port + +OS:Connection, + {3a5fabc6-6da4-41cf-8b61-24fced62b7da}, !- Handle + {5404d9d3-69ae-4978-944e-8f719627d4c9}, !- Name + {6f803b10-1a72-4a96-bf9a-28617e8a9e4f}, !- Source Object + 3, !- Outlet Port + {31be98ee-4f84-4360-a7e2-0b0caa80a98c}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {46a45e20-7342-44b9-a93b-52efb65d4975}, !- Handle + {e4a75f8f-99f6-4069-98cb-e07f38694181}, !- Name + {31be98ee-4f84-4360-a7e2-0b0caa80a98c}, !- Source Object + 3, !- Outlet Port + {2e535b23-0e82-4870-9ea3-28db296ec393}, !- Target Object + 2; !- Inlet Port + +OS:Connection, + {ee625681-d9bf-4258-869a-fef7f0f89103}, !- Handle + {611869af-82b9-4eeb-9680-65e3b0fc6b4e}, !- Name + {2e535b23-0e82-4870-9ea3-28db296ec393}, !- Source Object + 3, !- Outlet Port + {657e7de9-ea9a-4885-87e1-9b1d78642416}, !- Target Object + 4; !- Inlet Port + +OS:Exterior:Lights:Definition, + {10e1d4ce-e5b0-48eb-b1a1-91522e4d886a}, !- Handle + Occ Sensing Exterior Lights Def, !- Name + 6197.4; !- Design Level {W} + +OS:Schedule:Ruleset, + {9a285e68-b3c6-40d9-9501-8ec8ab951f73}, !- Handle + Warehouse Exterior_lighting_schedule_b, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {6cfc4de2-6ff7-499b-b1c2-a7f5e85eacb5}, !- Default Day Schedule Name + {12d6116b-16dc-4d14-a914-912f635382c8}, !- Summer Design Day Schedule Name + {43b99d42-429c-4bc9-a793-401380a0fd4b}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {6cfc4de2-6ff7-499b-b1c2-a7f5e85eacb5}, !- Handle + Warehouse Exterior_lighting_schedule_b Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0.7, !- Value Until Time 1 + 24, !- Hour 2 + 0, !- Minute 2 + 1; !- Value Until Time 2 + +OS:Schedule:Day, + {43b99d42-429c-4bc9-a793-401380a0fd4b}, !- Handle + Warehouse Exterior_lighting_schedule_b Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0.7, !- Value Until Time 1 + 24, !- Hour 2 + 0, !- Minute 2 + 1; !- Value Until Time 2 + +OS:Schedule:Day, + {12d6116b-16dc-4d14-a914-912f635382c8}, !- Handle + Warehouse Exterior_lighting_schedule_b Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0.7, !- Value Until Time 1 + 24, !- Hour 2 + 0, !- Minute 2 + 1; !- Value Until Time 2 + +OS:Exterior:Lights, + {67948007-7ea3-4d1a-8545-12e1df8a8a51}, !- Handle + Occ Sensing Exterior Lights Def, !- Name + {10e1d4ce-e5b0-48eb-b1a1-91522e4d886a}, !- Exterior Lights Definition Name + {9a285e68-b3c6-40d9-9501-8ec8ab951f73}, !- Schedule Name + AstronomicalClock, !- Control Option + , !- Multiplier + General; !- End-Use Subcategory + +OS:Exterior:Lights:Definition, + {0a541888-6eb7-4f5e-8e29-d507fd4fef8f}, !- Handle + NonDimming Exterior Lights Def, !- Name + 113.8; !- Design Level {W} + +OS:Schedule:Ruleset, + {5ac1b025-cc2d-4e67-b927-414409caf15f}, !- Handle + Warehouse Exterior_lighting_schedule_a, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + {45afeb18-9ced-43d9-8c2b-3cfa9de2759c}, !- Default Day Schedule Name + {8f053677-add6-4be5-b733-7b3f975c99e9}, !- Summer Design Day Schedule Name + {26a08c5f-c1a4-461a-a95f-3917f649837e}; !- Winter Design Day Schedule Name + +OS:Schedule:Day, + {45afeb18-9ced-43d9-8c2b-3cfa9de2759c}, !- Handle + Warehouse Exterior_lighting_schedule_a Default, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 24, !- Hour 2 + 0, !- Minute 2 + 1; !- Value Until Time 2 + +OS:Schedule:Day, + {26a08c5f-c1a4-461a-a95f-3917f649837e}, !- Handle + Warehouse Exterior_lighting_schedule_a Winter Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 24, !- Hour 2 + 0, !- Minute 2 + 1; !- Value Until Time 2 + +OS:Schedule:Day, + {8f053677-add6-4be5-b733-7b3f975c99e9}, !- Handle + Warehouse Exterior_lighting_schedule_a Summer Design Day, !- Name + {8c6f1e05-66dc-497c-b9fc-f36850bddc84}, !- Schedule Type Limits Name + , !- Interpolate to Timestep + 6, !- Hour 1 + 0, !- Minute 1 + 0, !- Value Until Time 1 + 24, !- Hour 2 + 0, !- Minute 2 + 1; !- Value Until Time 2 + +OS:Exterior:Lights, + {892f37a6-0772-40c3-ac80-3c3c79247a93}, !- Handle + NonDimming Exterior Lights Def, !- Name + {0a541888-6eb7-4f5e-8e29-d507fd4fef8f}, !- Exterior Lights Definition Name + {5ac1b025-cc2d-4e67-b927-414409caf15f}, !- Schedule Name + AstronomicalClock, !- Control Option + , !- Multiplier + General; !- End-Use Subcategory + +OS:WeatherFile, + {52950113-279c-43ea-8ca5-eaa3e259f719}, !- Handle + El Paso International Ap Ut, !- City + TX, !- State Province Region + USA, !- Country + TMY3, !- Data Source + 722700, !- WMO Number + 31, !- Latitude {deg} + -106, !- Longitude {deg} + -7, !- Time Zone {hr} + 1186, !- Elevation {m} + file:///Users/denisbourgeois/Documents/projets/tbd/spec/test_suite_runs/Warehouse_skip/000_Create DOE Prototype Building/extracted_files/weather/USA_TX_El.Paso.Intl.AP.722700_TMY3.epw, !- Url + CC705637; !- Checksum + +OS:Site:WaterMainsTemperature, + {f339eb8f-5ea9-4997-92d7-b17696e0fc51}, !- Handle + Correlation, !- Calculation Method + , !- Temperature Schedule Name + 17.9416666666667, !- Annual Average Outdoor Air Temperature {C} + 21.1; !- Maximum Difference In Monthly Average Outdoor Air Temperatures {deltaC} + +OS:SizingPeriod:DesignDay, + {d0608239-5eac-4d11-94e6-658797748afc}, !- Handle + El Paso International Ap Ut Ann Clg .4% Condns DB=>MWB, !- Name + 38.1, !- Maximum Dry-Bulb Temperature {C} + 13, !- Daily Dry-Bulb Temperature Range {deltaC} + 18.1, !- Humidity Indicating Conditions at Maximum Dry-Bulb + 87865, !- Barometric Pressure {Pa} + 3.9, !- Wind Speed {m/s} + 280, !- Wind Direction {deg} + 0, !- Sky Clearness + 0, !- Rain Indicator + 0, !- Snow Indicator + 21, !- Day of Month + 7, !- Month + SummerDesignDay, !- Day Type + 0, !- Daylight Saving Time Indicator + Wetbulb, !- Humidity Indicating Type + , !- Humidity Indicating Day Schedule Name + DefaultMultipliers, !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Schedule Name + ASHRAETau, !- Solar Model Indicator + , !- Beam Solar Day Schedule Name + , !- Diffuse Solar Day Schedule Name + 0.605, !- ASHRAE Taub {dimensionless} + 1.547; !- ASHRAE Taud {dimensionless} + +OS:SizingPeriod:DesignDay, + {61c36e59-a138-49be-a38f-52ec6af9ee84}, !- Handle + El Paso International Ap Ut Ann Htg 99.6% Condns DB, !- Name + -5.2, !- Maximum Dry-Bulb Temperature {C} + 0, !- Daily Dry-Bulb Temperature Range {deltaC} + -5.2, !- Humidity Indicating Conditions at Maximum Dry-Bulb + 87865, !- Barometric Pressure {Pa} + 2.2, !- Wind Speed {m/s} + 20, !- Wind Direction {deg} + 0, !- Sky Clearness + 0, !- Rain Indicator + 0, !- Snow Indicator + 21, !- Day of Month + 12, !- Month + WinterDesignDay, !- Day Type + 0, !- Daylight Saving Time Indicator + Wetbulb, !- Humidity Indicating Type + , !- Humidity Indicating Day Schedule Name + DefaultMultipliers, !- Dry-Bulb Temperature Range Modifier Type + , !- Dry-Bulb Temperature Range Modifier Schedule Name + ASHRAEClearSky; !- Solar Model Indicator + +OS:RunPeriodControl:DaylightSavingTime, + {15951d3b-e7b8-43d9-bb96-7104eecd8262}, !- Handle + 2nd Sunday in March, !- Start Date + 1st Sunday in November; !- End Date + +OS:Site:GroundTemperature:BuildingSurface, + {f904478f-7d4c-4665-bc6f-f6798a5ea52f}, !- Handle + 13.02, !- January Ground Temperature {C} + 12.94, !- February Ground Temperature {C} + 12.92, !- March Ground Temperature {C} + 18.03, !- April Ground Temperature {C} + 22.41, !- May Ground Temperature {C} + 26.33, !- June Ground Temperature {C} + 26.64, !- July Ground Temperature {C} + 25.84, !- August Ground Temperature {C} + 23.47, !- September Ground Temperature {C} + 18.98, !- October Ground Temperature {C} + 13.86, !- November Ground Temperature {C} + 13.19; !- December Ground Temperature {C} + +OS:SimulationControl, + {877f732a-d1d5-4a18-ac74-5d4b152d1e86}, !- Handle + , !- Do Zone Sizing Calculation + , !- Do System Sizing Calculation + , !- Do Plant Sizing Calculation + No, !- Run Simulation for Sizing Periods + Yes; !- Run Simulation for Weather File Run Periods + +OS:Schedule:Day, + {a29c3a34-406e-4739-b679-73d218d746e5}, !- Handle + Economizer Max OA Fraction 70 pct Default, !- Name + , !- Schedule Type Limits Name + , !- Interpolate to Timestep + 24, !- Hour 1 + 0, !- Minute 1 + 0.7; !- Value Until Time 1 + +OS:Curve:Biquadratic, + {f6b2da2f-90a9-484f-a68c-96feb6cc67fe}, !- Handle + PSZ-Fine Storage DX Coil Cap-FT, !- Name + 0.942587793, !- Coefficient1 Constant + 0.009543347, !- Coefficient2 x + 0.00068377, !- Coefficient3 x**2 + -0.011042676, !- Coefficient4 y + 5.249e-06, !- Coefficient5 y**2 + -9.72e-06, !- Coefficient6 x*y + 12.77778, !- Minimum Value of x + 23.88889, !- Maximum Value of x + 23.88889, !- Minimum Value of y + 46.11111; !- Maximum Value of y + +OS:Curve:Quadratic, + {1e5c201f-f719-4044-8b97-bef2bb885144}, !- Handle + DX Coil Cap-FF, !- Name + 0.8, !- Coefficient1 Constant + 0.2, !- Coefficient2 x + 0, !- Coefficient3 x**2 + 0.5, !- Minimum Value of x + 1.5; !- Maximum Value of x + +OS:Curve:Biquadratic, + {1688d136-800f-45b7-b2f5-fb8eefcb8ef6}, !- Handle + PSZ-AC DX Coil EIR-FT, !- Name + 0.342414409, !- Coefficient1 Constant + 0.034885008, !- Coefficient2 x + -0.0006237, !- Coefficient3 x**2 + 0.004977216, !- Coefficient4 y + 0.000437951, !- Coefficient5 y**2 + -0.000728028, !- Coefficient6 x*y + 12.77778, !- Minimum Value of x + 23.88889, !- Maximum Value of x + 23.88889, !- Minimum Value of y + 46.11111; !- Maximum Value of y + +OS:Curve:Quadratic, + {3127684e-06d9-4ed3-b5b8-66a752c1ef42}, !- Handle + Split DX Coil EIR-FF, !- Name + 1.1552, !- Coefficient1 Constant + -0.1808, !- Coefficient2 x + 0.0256, !- Coefficient3 x**2 + 0.5, !- Minimum Value of x + 1.5; !- Maximum Value of x + +OS:Curve:Quadratic, + {1f4c9e8a-7656-4302-8ca8-4c6a4907347c}, !- Handle + HPACCOOLPLFFPLR, !- Name + 0.85, !- Coefficient1 Constant + 0.15, !- Coefficient2 x + 0, !- Coefficient3 x**2 + 0, !- Minimum Value of x + 1; !- Maximum Value of x + +OS:Curve:Biquadratic, + {146906a8-1178-404b-90a5-96b5b70f836e}, !- Handle + PSZ-AC_CoolCLennoxStandard10Ton_TGA120S2B_CapFT, !- Name + 0.42415, !- Coefficient1 Constant + 0.04426, !- Coefficient2 x + -0.00042, !- Coefficient3 x**2 + 0.00333, !- Coefficient4 y + -8e-05, !- Coefficient5 y**2 + -0.00021, !- Coefficient6 x*y + 17, !- Minimum Value of x + 22, !- Maximum Value of x + 29, !- Minimum Value of y + 46; !- Maximum Value of y + +OS:Curve:Quadratic, + {cccea7a4-0bef-410f-bb50-0501b2de86eb}, !- Handle + AHU-1_CoolCStandard10Ton_CapFF, !- Name + 0.77136, !- Coefficient1 Constant + 0.34053, !- Coefficient2 x + -0.11088, !- Coefficient3 x**2 + 0.75918, !- Minimum Value of x + 1.13877; !- Maximum Value of x + +OS:Curve:Biquadratic, + {329133a8-11d6-48fe-b1e3-dd58f7893802}, !- Handle + PSZ-AC_CoolCStandard10Ton_EIRFT, !- Name + 1.23649, !- Coefficient1 Constant + -0.02431, !- Coefficient2 x + 0.00057, !- Coefficient3 x**2 + -0.01434, !- Coefficient4 y + 0.00063, !- Coefficient5 y**2 + -0.00038, !- Coefficient6 x*y + 17, !- Minimum Value of x + 22, !- Maximum Value of x + 29, !- Minimum Value of y + 46; !- Maximum Value of y + +OS:Curve:Quadratic, + {315c9c06-7982-45ad-a003-85c2101a0384}, !- Handle + CoolCLennoxStandard10Ton_TGA120S2B_EIRFFF, !- Name + 1.2055, !- Coefficient1 Constant + -0.32953, !- Coefficient2 x + 0.12308, !- Coefficient3 x**2 + 0.75918, !- Minimum Value of x + 1.13877; !- Maximum Value of x + +OS:Curve:Quadratic, + {d186e2df-d98a-4407-872e-53069b0f0d90}, !- Handle + PSZ-AC_CoolCLennoxStandard10Ton_TGA120S2B_PLR, !- Name + 0.771, !- Coefficient1 Constant + 0.229, !- Coefficient2 x + 0, !- Coefficient3 x**2 + 0, !- Minimum Value of x + 1; !- Maximum Value of x + +OS:AdditionalProperties, + {d1af12ab-bd30-4fbb-845d-7afdba121c42}, !- Handle + {6ce31c07-c985-40c6-ba1d-ee5762b91d96}; !- Object Name + +OS:Daylighting:Control, + {810ebe1b-d9eb-428d-b046-e2dc0d3fe399}, !- Handle + Zone1 Office Daylt Sensor 1, !- Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + 5.48613244577575, !- Position X-Coordinate {m} + 1.60993537419919, !- Position Y-Coordinate {m} + 0.762, !- Position Z-Coordinate {m} + , !- Psi Rotation Around X-Axis {deg} + , !- Theta Rotation Around Y-Axis {deg} + , !- Phi Rotation Around Z-Axis {deg} + 500, !- Illuminance Setpoint {lux} + Stepped, !- Lighting Control Type + 0.3, !- Minimum Input Power Fraction for Continuous Dimming Control + 0.2, !- Minimum Light Output Fraction for Continuous Dimming Control + 3, !- Number of Stepped Control Steps + 1, !- Probability Lighting will be Reset When Needed in Manual Stepped Control + , !- Number of Daylighting Views + 22; !- Maximum Allowable Discomfort Glare Index + +OS:Daylighting:Control, + {ef04a2f5-10cb-4990-900e-5910323b3e66}, !- Handle + Zone1 Office Daylt Sensor 2, !- Name + {12c3d4a0-20ec-4e1f-8040-e70cdfba4a69}, !- Space Name + 20.4206041037209, !- Position X-Coordinate {m} + 3.24426370861351, !- Position Y-Coordinate {m} + 0.762, !- Position Z-Coordinate {m} + , !- Psi Rotation Around X-Axis {deg} + , !- Theta Rotation Around Y-Axis {deg} + , !- Phi Rotation Around Z-Axis {deg} + 500, !- Illuminance Setpoint {lux} + Stepped, !- Lighting Control Type + 0.3, !- Minimum Input Power Fraction for Continuous Dimming Control + 0.2, !- Minimum Light Output Fraction for Continuous Dimming Control + 3, !- Number of Stepped Control Steps + 1, !- Probability Lighting will be Reset When Needed in Manual Stepped Control + , !- Number of Daylighting Views + 22; !- Maximum Allowable Discomfort Glare Index diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index b3613bf..3334f8a 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -2,6 +2,7 @@ RSpec.describe OSut do TOL = OSut::TOL + TOL2 = TOL * TOL DBG = OSut::DEBUG INF = OSut::INFO WRN = OSut::WARN @@ -1597,7 +1598,6 @@ module M expect(mod1.clean!).to eq(DBG) # Successful test. - mdl = OpenStudio::Model::Model.new file = File.join(__dir__, "files/osms/in/seb.osm") path = OpenStudio::Path.new(file) model = translator.loadModel(path) @@ -1625,6 +1625,356 @@ module M expect(mod1.debug?).to be true expect(mod1.logs.size).to eq(1) expect(mod1.logs.first[:message]).to eq(m1) + expect(mod1.clean!).to eq(DBG) + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Realignment of flat surfaces. + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new( 1, 4, 0) + vtx << OpenStudio::Point3d.new( 2, 2, 0) + vtx << OpenStudio::Point3d.new( 6, 4, 0) + vtx << OpenStudio::Point3d.new( 5, 6, 0) + + origin = vtx[1] + hyp = (origin - vtx[0]).length + hyp2 = (origin - vtx[2]).length + right = OpenStudio::Point3d.new(origin.x + 10, origin.y, origin.z ) + zenith = OpenStudio::Point3d.new(origin.x, origin.y, origin.z + 10) + seg = vtx[2] - origin + axis = zenith - origin + droite = right - origin + radians = OpenStudio::getAngle(droite, seg) + degrees = OpenStudio::radToDeg(radians) + expect(degrees).to be_within(TOL).of(26.565) + + r = OpenStudio::Transformation.rotation(origin, axis, radians) + a = r.inverse * vtx + + expect(mod1.same?(a[1], vtx[1])).to be true + expect(a[0].x - a[1].x).to be_within(TOL).of(0) + expect(a[2].x - a[1].x).to be_within(TOL).of(hyp2) + expect(a[3].x - a[2].x).to be_within(TOL).of(0) + expect(a[0].y - a[1].y).to be_within(TOL).of(hyp) + expect(a[2].y - a[1].y).to be_within(TOL).of(0) + expect(a[3].y - a[1].y).to be_within(TOL).of(hyp) + + pts = r * a + + pts.each_with_index { |pt, i| expect(mod1.same?(pt, vtx[i])).to be true } + + output1 = mod1.getRealignedFace(vtx) + expect(mod1.status).to be_zero + expect(output1).to be_a Hash + expect(output1).to have_key(:set) + expect(output1).to have_key(:box) + expect(output1).to have_key(:bbox) + expect(output1).to have_key(:t) + expect(output1).to have_key(:r) + expect(output1).to have_key(:o) + + # Realign a previously realigned surface? + output2 = mod1.getRealignedFace(output1[:box]) + expect(mod1.status).to be_zero + + # Realigning a previously realigned polygon has no effect (== safe). + expect(mod1.same?(output1[:box ], output2[:box ])).to be true + expect(mod1.same?(output1[:bbox], output2[:bbox])).to be true + + bounded_area = OpenStudio.getArea(output1[:box ]) + bounding_area = OpenStudio.getArea(output1[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + bounded_area = OpenStudio.getArea(output2[:box ]) + bounding_area = OpenStudio.getArea(output2[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Repeat with slight change in orientation. + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new( 2, 6, 0) + vtx << OpenStudio::Point3d.new( 1, 4, 0) + vtx << OpenStudio::Point3d.new( 5, 2, 0) + vtx << OpenStudio::Point3d.new( 6, 4, 0) + + output3 = mod1.getRealignedFace(vtx) + expect(mod1.status).to be_zero + + # Realign a previously realigned surface? + output4 = mod1.getRealignedFace(output3[:box]) + expect(mod1.status).to be_zero + + # Realigning a previously realigned polygon has no effect (== safe). + expect(mod1.same?(output1[:box ], output2[:box ])).to be true + expect(mod1.same?(output1[:bbox], output2[:bbox])).to be true + expect(mod1.same?(output1[:box ], output3[:box ])).to be true + expect(mod1.same?(output1[:bbox], output3[:bbox])).to be true + expect(mod1.same?(output1[:box ], output4[:box ])).to be true + expect(mod1.same?(output1[:bbox], output4[:bbox])).to be true + + bounded_area = OpenStudio.getArea(output3[:box ]) + bounding_area = OpenStudio.getArea(output3[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + bounded_area = OpenStudio.getArea(output4[:box ]) + bounding_area = OpenStudio.getArea(output4[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Repeat with changes in vertex sequence. + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new( 6, 4, 0) + vtx << OpenStudio::Point3d.new( 5, 6, 0) + vtx << OpenStudio::Point3d.new( 1, 4, 0) + vtx << OpenStudio::Point3d.new( 2, 2, 0) + + output5 = mod1.getRealignedFace(vtx) + expect(mod1.status).to be_zero + + # Realign a previously realigned surface? + output6 = mod1.getRealignedFace(output5[:box]) + expect(mod1.status).to be_zero + + # Realigning a previously realigned polygon has no effect (== safe). + expect(mod1.same?(output1[:box ], output5[:box ])).to be true + expect(mod1.same?(output1[:box ], output6[:box ])).to be true + expect(mod1.same?(output1[:bbox], output5[:bbox])).to be true + expect(mod1.same?(output1[:bbox], output6[:bbox])).to be true + expect(mod1.same?(output5[:box ], output6[:box ])).to be true + expect(mod1.same?(output5[:box ], output5[:box ])).to be true + expect(mod1.same?(output5[:bbox], output6[:bbox])).to be true + expect(mod1.same?(output6[:bbox], output6[:bbox])).to be true + + bounded_area = OpenStudio.getArea(output5[:box ]) + bounding_area = OpenStudio.getArea(output5[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + bounded_area = OpenStudio.getArea(output6[:box ]) + bounding_area = OpenStudio.getArea(output6[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Repeat with slight change in orientation (vertices resequenced). + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new( 5, 2, 0) + vtx << OpenStudio::Point3d.new( 6, 4, 0) + vtx << OpenStudio::Point3d.new( 2, 6, 0) + vtx << OpenStudio::Point3d.new( 1, 4, 0) + + output7 = mod1.getRealignedFace(vtx) + expect(mod1.status).to be_zero + + # Realign a previously realigned surface? + output8 = mod1.getRealignedFace(output7[:box]) + expect(mod1.status).to be_zero + + # Realigning a previously realigned polygon has no effect (== safe). + expect(mod1.same?(output1[:box ], output7[:box ])).to be true + expect(mod1.same?(output1[:box ], output8[:box ])).to be true + expect(mod1.same?(output1[:bbox], output7[:bbox])).to be true + expect(mod1.same?(output1[:bbox], output8[:bbox])).to be true + expect(mod1.same?(output5[:box ], output7[:box ])).to be true + expect(mod1.same?(output5[:bbox], output7[:bbox])).to be true + expect(mod1.same?(output5[:box ], output8[:box ])).to be true + expect(mod1.same?(output5[:bbox], output8[:bbox])).to be true + + bounded_area = OpenStudio.getArea(output7[:box ]) + bounding_area = OpenStudio.getArea(output7[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + bounded_area = OpenStudio.getArea(output8[:box ]) + bounding_area = OpenStudio.getArea(output8[:bbox]) + expect(bounded_area).to_not be_empty + expect(bounding_area).to_not be_empty + expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # SEB case. + file = File.join(__dir__, "files/osms/in/seb.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get + + # Roof Surfaces. + r1 = "Level 0 Open area 1 Ceiling Plenum RoofCeiling" + r2 = "Level 0 Utility 1 Ceiling Plenum RoofCeiling" + r3 = "Level 0 Entry way Ceiling Plenum RoofCeiling" + r4 = "Level 0 Small office 1 Ceiling Plenum RoofCeiling" + + r1 = model.getSurfaceByName(r1) + r2 = model.getSurfaceByName(r2) + r3 = model.getSurfaceByName(r3) + r4 = model.getSurfaceByName(r4) + + expect(r1).to_not be_empty + expect(r2).to_not be_empty + expect(r3).to_not be_empty + expect(r4).to_not be_empty + + r1 = r1.get + r2 = r2.get + r3 = r3.get + r4 = r4.get + + # Detailed tests for r2. + r2v = r2.vertices + t = OpenStudio::Transformation.alignFace(r2v) + r2a = mod1.poly(r2v, false, false, false, t) + output = mod1.getRealignedFace(r2a) + expect(mod1.status).to be_zero + expect(output).to be_a Hash + expect(output).to have_key(:set) + expect(output).to have_key(:box) + expect(output).to have_key(:bbox) + expect(output).to have_key(:t) + expect(output).to have_key(:r) + expect(output).to have_key(:o) + expect(mod1.rectangular?(output[:box])).to be true + + # Reapply translation transformation. + tset = output[:t] * output[:set] + expect(tset.size).to eq(r2v.size) + + tset.each_with_index do |pt, i| + expect(pt.x - output[:set][i].x).to be_within(TOL).of(0.765) # delta-X + expect(pt.y - output[:set][i].y).to be_within(TOL).of(-0.142) # delta-Y + end + + # Reapply rotation transformation. + rset = output[:r] * tset + expect(rset.size).to eq(r2v.size) + rset.each_with_index { |pt, i| expect(mod1.same?(pt, r2a[i])).to be true } + + # Full circle, back to original vertices. + vtx = t * rset + expect(vtx.size).to eq(r2v.size) + vtx.each_with_index { |pt, i| expect(mod1.same?(pt, r2v[i])).to be true } + + # More concise call for generated bounded and bounding boxes. + box = t * (output[:r] * (output[:t] * output[:box])) + bbox = t * (output[:r] * (output[:t] * output[:bbox])) + + # Both bounded and bounding boxes are rectangular. + expect(mod1.rectangular?(box)).to be true + expect(mod1.rectangular?(bbox)).to be true + + # The bounded box fits within the original roof surface limits, while the + # latter fits within its bounding box. Bounded and bounding box sides are + # parallel to each other. + expect(mod1.fits?(box, bbox)).to be true + expect(mod1.fits?(box, vtx)).to be true + expect(mod1.fits?(vtx, bbox)).to be true + + r2_area = OpenStudio.getArea(vtx) + box_area = OpenStudio.getArea(box) + bbox_area = OpenStudio.getArea(bbox) + expect(r2_area).to_not be_empty + expect(box_area).to_not be_empty + expect(bbox_area).to_not be_empty + expect(box_area.get).to be_within(TOL).of(10.96) + expect(r2_area.get).to be_within(TOL).of(16.44) + expect(bbox_area.get).to be_within(TOL).of(19.78) + + # Repeat for r1, more concisely. + r1v = r1.vertices + t = OpenStudio::Transformation.alignFace(r1v) + r1a = mod1.poly(r1v, false, false, false, t) + o = mod1.getRealignedFace(r1a) + expect(o).to be_a Hash + expect(o).to have_key(:set) + expect(o).to have_key(:box) + expect(o).to have_key(:bbox) + expect(o).to have_key(:t) + expect(o).to have_key(:r) + expect(o).to have_key(:o) + width = mod1.width(o[:box]) + height = mod1.height(o[:box]) + area = OpenStudio.getArea(o[:box]) + expect(area).to_not be_empty + area = area.get + expect(area).to be_within(TOL).of(30.81) + expect(area).to be_within(TOL).of(width * height) + vtx = t * (o[:r] * (o[:t] * o[:box])) + expect(mod1.rectangular?(vtx)).to be true + expect(mod1.fits?(vtx, r1)).to be true + + # Realign bounded box. + o2 = mod1.getRealignedFace(o[:box]) + expect(o2).to be_a Hash + expect(o2).to have_key(:set) + expect(o2).to have_key(:box) + expect(o2).to have_key(:t) + expect(o2).to have_key(:r) + expect(o2).to have_key(:o) + # puts o2[:box] + # [8.169, 0.000, 0] + # [8.169, 3.772, 0] + # [0.000, 3.772, 0] + # [0.000, 0.000, 0] + width2 = mod1.width(o2[:box]) + height2 = mod1.height(o2[:box]) + expect(width2).to be_within(TOL).of(width) + expect(height2).to be_within(TOL).of(height) + area2 = OpenStudio.getArea(o2[:box]) + expect(area2).to_not be_empty + area2 = area2.get + expect(area2).to be_within(TOL).of(area) + expect(area2).to be_within(TOL).of(width2 * height2) + vtx = o2[:r] * (o2[:t] * o2[:box]) + expect(mod1.rectangular?(vtx)).to be true + expect(mod1.fits?(vtx, o[:set])).to be true + vtx2 = o[:r] * (o[:t] * vtx) + expect(mod1.fits?(vtx2, r1a)).to be true + vtx3 = t * vtx2 + expect(mod1.fits?(vtx3, r1)).to be true + expect(mod1.status).to be_zero + + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Test pre-triangulated case. + max = 0 + r1tri = mod1.poly(r1v, false, true, true, true, :cw) + holes = OpenStudio::Point3dVectorVector.new + + OpenStudio.computeTriangulation(r1tri, holes).each do |triangle| + o = mod1.getRealignedFace(triangle) + expect(o).to be_a Hash + expect(o).to have_key(:set) + expect(o).to have_key(:box) + expect(o).to have_key(:t) + expect(o).to have_key(:r) + expect(o).to have_key(:o) + width = mod1.width(o[:box]) + height = mod1.height(o[:box]) + area = OpenStudio.getArea(o[:box]) + expect(area).to_not be_empty + area = area.get + expect(area).to be_within(TOL).of(width * height) + max = [max, area].max + vtx = t * (o[:r] * (o[:t] * o[:box])) + expect(mod1.rectangular?(vtx)).to be true + expect(mod1.fits?(vtx, r1)).to be true + expect(mod1.status).to be_zero + end + + expect(max).to be < 4 # m2 ... significantly less than 30.8m2 end it "checks flattened 3D points" do @@ -1652,7 +2002,8 @@ module M flat = mod1.flatten(s) expect(flat).to be_a(cl2) - expect(mod1.xyz?(flat, :z, 0)).to be true + expect(mod1.xyz?(flat, :z)).to be true + expect(mod1.facingUp?(flat)).to be true expect(s.vertices.first.x).to be_within(TOL).of(flat.first.x) expect(s.vertices.first.y).to be_within(TOL).of(flat.first.y) end @@ -1707,15 +2058,11 @@ module M vec << OpenStudio::Point3d.new( 1, 0, 2) door1 = OpenStudio::Model::SubSurface.new(vec, model) + # Order of arguments matter. expect(mod1.fits?(door1, wall)).to be true - expect(mod1.status).to be_zero expect(mod1.overlaps?(door1, wall)).to be true - expect(mod1.status).to be_zero - - # Order of arguments matter. expect(mod1.fits?(wall, door1)).to be false expect(mod1.overlaps?(wall, door1)).to be true - expect(mod1.status).to be_zero # Another 1m x 2m corner door, yet entirely beyond the wall surface. vec = OpenStudio::Point3dVector.new @@ -1725,15 +2072,11 @@ module M vec << OpenStudio::Point3d.new( 17, 0, 2) door2 = OpenStudio::Model::SubSurface.new(vec, model) - # Door2 fits?, overlaps? + # Door2 fits?, overlaps? Order of arguments doesn't matter. expect(mod1.fits?(door2, wall)).to be false expect(mod1.overlaps?(door2, wall)).to be false - expect(mod1.status).to be_zero - - # Order of arguments doesn't matter. expect(mod1.fits?(wall, door2)).to be false expect(mod1.overlaps?(wall, door2)).to be false - expect(mod1.status).to be_zero # Top-right corner 2m x 2m window, overlapping top-right corner of wall. vec = OpenStudio::Point3dVector.new @@ -1746,11 +2089,8 @@ module M # Window fits?, overlaps? expect(mod1.fits?(window, wall)).to be false expect(mod1.overlaps?(window, wall)).to be true - expect(mod1.status).to be_zero - expect(mod1.fits?(wall, window)).to be false expect(mod1.overlaps?(wall, window)).to be true - expect(mod1.status).to be_zero # A glazed surface, entirely encompassing the wall. vec = OpenStudio::Point3dVector.new @@ -1760,13 +2100,166 @@ module M vec << OpenStudio::Point3d.new( 10, 0, 10) glazing = OpenStudio::Model::SubSurface.new(vec, model) - # Glazing fits?, overlaps? + # Glazing fits?, overlaps? parallel? + expect(mod1.parallel?(glazing, wall)).to be(true) expect(mod1.fits?(glazing, wall)).to be true expect(mod1.overlaps?(glazing, wall)).to be true - expect(mod1.status).to be_zero - + expect(mod1.parallel?(wall, glazing)).to be(true) expect(mod1.fits?(wall, glazing)).to be true expect(mod1.overlaps?(wall, glazing)).to be true + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Checks overlaps when 2 surfaces don't share the same plane equation. + translator = OpenStudio::OSVersion::VersionTranslator.new + expect(mod1.reset(DBG)).to eq(DBG) + expect(mod1.level).to eq(DBG) + expect(mod1.clean!).to eq(DBG) + + v = OpenStudio.openStudioVersion.split(".").join.to_i + file = File.join(__dir__, "files/osms/in/smalloffice.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get + + ceiling = model.getSurfaceByName("Core_ZN_ceiling") + floor = model.getSurfaceByName("Attic_floor_core") + roof = model.getSurfaceByName("Attic_roof_east") + soffit = model.getSurfaceByName("Attic_soffit_east") + south = model.getSurfaceByName("Attic_roof_south") + expect(ceiling).to_not be_empty + expect(floor).to_not be_empty + expect(roof).to_not be_empty + expect(soffit).to_not be_empty + expect(south).to_not be_empty + ceiling = ceiling.get + floor = floor.get + roof = roof.get + soffit = soffit.get + south = south.get + + # For parallel surfaces, OSut's 'overlap' output is consistent regardless + # of the sequence of arguments. Here, floor and ceiling are mirrored - the + # former counterclockwise, the latter clockwise. The returned overlap + # conserves the vertex winding of the first surface. + olap1 = mod1.overlap(floor, ceiling) + olap2 = mod1.overlap(ceiling, floor) + expect(mod1.same?(floor.vertices, olap1)).to be true + expect(mod1.same?(ceiling.vertices, olap2)).to be true + + # When surfaces aren't parallel, 'overlap' remains somewhat consistent if + # both share a common edge. Here, the flat soffit shares an edge with the + # sloped roof. The projection of the soffit neatly fits onto the roof, yet + # the generated overlap will obviously be distorted with respect to the + # original soffit vertices. Nonetheless, the shared vertices/edge(s) would + # be preserved. + olap1 = mod1.overlap(soffit, roof) + olap2 = mod1.overlap(roof, soffit) + expect(mod1.parallel?(olap1, soffit)).to be true + expect(mod1.parallel?(olap1, roof)).to be false + expect(mod1.parallel?(olap2, roof)).to be true + expect(mod1.parallel?(olap2, soffit)).to be false + expect(olap1.size).to eq(4) + expect(olap2.size).to eq(4) + area1 = OpenStudio.getArea(olap1) + area2 = OpenStudio.getArea(olap2) + expect(area1).to_not be_empty + expect(area2).to_not be_empty + area1 = area1.get + area2 = area2.get + expect(area1 - area2).to be > TOL + pl1 = OpenStudio::Plane.new(mod1.getNonCollinears(olap1, 3)) + pl2 = OpenStudio::Plane.new(mod1.getNonCollinears(olap2, 3)) + n1 = pl1.outwardNormal + n2 = pl2.outwardNormal + expect(soffit.plane.outwardNormal.dot(n1)).to be_within(TOL).of(1) + expect( roof.plane.outwardNormal.dot(n2)).to be_within(TOL).of(1) + + # When surfaces are neither parallel nor share any edges (e.g. sloped roof + # vs horizontal floor), the generated overlap is more likely to hold extra + # vertices, depending on which surface it is cast onto. + olap1 = mod1.overlap(floor, roof) + olap2 = mod1.overlap(roof, floor) + expect(mod1.parallel?(olap1, floor)).to be true + expect(mod1.parallel?(olap1, roof)).to be false + expect(mod1.parallel?(olap2, roof)).to be true + expect(mod1.parallel?(olap2, floor)).to be false + expect(olap1.size).to eq(3) + expect(olap2.size).to eq(5) + area1 = OpenStudio.getArea(olap1) + area2 = OpenStudio.getArea(olap2) + expect(area1).to_not be_empty + expect(area2).to_not be_empty + area1 = area1.get + area2 = area2.get + expect(area2 - area1).to be > TOL + pl1 = OpenStudio::Plane.new(mod1.getNonCollinears(olap1, 3)) + pl2 = OpenStudio::Plane.new(mod1.getNonCollinears(olap2, 3)) + n1 = pl1.outwardNormal + n2 = pl2.outwardNormal + expect(floor.plane.outwardNormal.dot(n1)).to be_within(TOL).of(1) + expect( roof.plane.outwardNormal.dot(n2)).to be_within(TOL).of(1) + + # Alternative: first 'cast' vertically one polygon onto the other. + pl1 = OpenStudio::Plane.new(mod1.getNonCollinears(ceiling, 3)) + pl2 = OpenStudio::Plane.new(mod1.getNonCollinears(roof, 3)) + up = OpenStudio::Point3d.new(0, 0, 1) - OpenStudio::Point3d.new(0, 0, 0) + down = OpenStudio::Point3d.new(0, 0,-1) - OpenStudio::Point3d.new(0, 0, 0) + cast00 = mod1.cast(roof, ceiling, down) + cast01 = mod1.cast(roof, ceiling, up) + cast02 = mod1.cast(ceiling, roof, up) + expect(mod1.parallel?(cast00, ceiling)).to be true + expect(mod1.parallel?(cast01, ceiling)).to be true + expect(mod1.parallel?(cast02, roof)).to be true + expect(mod1.parallel?(cast00, roof)).to be false + expect(mod1.parallel?(cast01, roof)).to be false + expect(mod1.parallel?(cast02, ceiling)).to be false + + # As the cast ray is vertical, only the Z-axis coordinate changes. + cast00.each_with_index do |pt, i| + expect(pl1.pointOnPlane(pt)) + expect(pt.x).to be_within(TOL).of(roof.vertices[i].x) + expect(pt.y).to be_within(TOL).of(roof.vertices[i].y) + end + + # The direction of the cast ray doesn't matter (e.g. up or down). + cast01.each_with_index do |pt, i| + expect(pl1.pointOnPlane(pt)) + expect(pt.x).to be_within(TOL).of(cast00[i].x) + expect(pt.y).to be_within(TOL).of(cast00[i].y) + end + + # The sequence of arguments matters: the 1st polygon is cast onto the 2nd. + cast02.each_with_index do |pt, i| + expect(pl2.pointOnPlane(pt)) + expect(pt.x).to be_within(TOL).of(ceiling.vertices[i].x) + expect(pt.y).to be_within(TOL).of(ceiling.vertices[i].y) + end + + # Overlap between roof and vertically-cast ceiling onto roof plane. + olap02 = mod1.overlap(roof, cast02, false) + expect(olap02.size).to eq(3) # not 5 + expect(mod1.fits?(olap02, roof)).to be(true) + + olap02.each { |pt| expect(pl2.pointOnPlane(pt)) } + + # Bounded box test. + cast03 = mod1.cast(ceiling, south, down) + expect(mod1.rectangular?(cast03)).to be true + olap03 = mod1.overlap(south, cast03, false) + expect(mod1.rectangular?(olap03)).to be false + box = mod1.boundedBox(olap03) + expect(mod1.rectangular?(box)).to be true + expect(mod1.parallel?(olap03, box)).to be true + + area1 = OpenStudio.getArea(olap03) + area2 = OpenStudio.getArea(box) + expect(area1).to_not be_empty + expect(area2).to_not be_empty + area1 = area1.get + area2 = area2.get + expect((100 * area2 / area1).to_i).to eq(68) # % + expect(mod1.status).to be_zero end @@ -1813,52 +2306,158 @@ module M expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) - # Regular polygon, counterclockwise yet not UpperLeftCorner (ULC). + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Basic OpenStudio intersection methods. + + # Enclosed polygon. + p0 = OpenStudio::Point3d.new(-5, -5, -5) + p1 = OpenStudio::Point3d.new( 5, 5, -5) + p2 = OpenStudio::Point3d.new(15, 15, -5) + p3 = OpenStudio::Point3d.new(15, 25, -5) + + # Independent line segment. + p4 = OpenStudio::Point3d.new(10,-30, -5) + p5 = OpenStudio::Point3d.new(10, 10, -5) + p6 = OpenStudio::Point3d.new(10, 40, -5) + + # Independent point. + p7 = OpenStudio::Point3d.new(14, 20, -5) + + collinears = mod1.getCollinears( [p0, p1, p2, p3] ) + expect(collinears.size).to eq(1) + expect(mod1.same?(collinears[0], p1)).to be true + + # CASE a1: 2x end-to-end line segments (returns matching endpoints). + expect(mod1.lineIntersects?( [p0, p1], [p1, p2] )).to be true + pt = mod1.getLineIntersection( [p0, p1], [p1, p2] ) + expect(mod1.same?(pt, p1)).to be true + + # CASE a2: as a1, sequence of line segment endpoints doesn't matter. + expect(mod1.lineIntersects?( [p1, p0], [p1, p2] )).to be true + pt = mod1.getLineIntersection( [p1, p0], [p1, p2] ) + expect(mod1.same?(pt, p1)).to be true + + # CASE b1: 2x right-angle line segments, with 1x matching at corner. + expect(mod1.lineIntersects?( [p1, p2], [p1, p3] )).to be true + pt = mod1.getLineIntersection( [p1, p2], [p2, p3] ) + expect(mod1.same?(pt, p2)).to be true + + # CASE b2: as b1, sequence of segments doesn't matter. + expect(mod1.lineIntersects?( [p2, p3], [p1, p2] )).to be true + pt = mod1.getLineIntersection( [p2, p3], [p1, p2] ) + expect(mod1.same?(pt, p2)).to be true + + # CASE c: 2x right-angle line segments, yet disconnected. + expect(mod1.lineIntersects?( [p0, p1], [p2, p3] )).to be false + expect(mod1.getLineIntersection( [p0, p1], [p2, p3] )).to be_nil + + # CASE d: 2x connected line segments, acute angle. + expect(mod1.lineIntersects?( [p0, p2], [p3, p0] )).to be true + pt = mod1.getLineIntersection( [p0, p2], [p3, p0] ) + expect(mod1.same?(pt, p0)).to be true + + # CASE e1: 2x disconnected line segments, right angle. + expect(mod1.lineIntersects?( [p0, p2], [p4, p6] )).to be true + pt = mod1.getLineIntersection( [p0, p2], [p4, p6] ) + expect(mod1.same?(pt, p5)).to be true + + # CASE e2: as e1, sequence of line segment endpoints doesn't matter. + expect(mod1.lineIntersects?( [p0, p2], [p6, p4] )).to be true + pt = mod1.getLineIntersection( [p0, p2], [p6, p4] ) + expect(mod1.same?(pt, p5)).to be true + + # Point ENTIRELY within (vs outside) a polygon. + expect(mod1.pointWithinPolygon?(p0, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p1, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p2, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p3, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p4, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p5, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p6, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p7, [p0, p1, p2, p3])).to be true + expect(mod1.status).to be_zero + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Test invalid plane. vtx = OpenStudio::Point3dVector.new vtx << OpenStudio::Point3d.new(20, 0, 10) vtx << OpenStudio::Point3d.new( 0, 0, 10) vtx << OpenStudio::Point3d.new( 0, 0, 0) + vtx << OpenStudio::Point3d.new(20, 1, 0) + + expect(mod1.poly(vtx)).to be_empty + expect(mod1.status).to eq(ERR) + expect(mod1.logs.size).to eq(1) + expect(mod1.logs.first[:message]).to include("Empty 'plane'") + expect(mod1.clean!).to eq(DBG) + + # Test self-intersecting polygon. If reactivated, OpenStudio logs to stdout: + # [utilities.Transformation] <1> Cannot compute outward normal for vertices + # vtx = OpenStudio::Point3dVector.new + # vtx << OpenStudio::Point3d.new(20, 0, 10) + # vtx << OpenStudio::Point3d.new( 0, 0, 10) + # vtx << OpenStudio::Point3d.new(20, 0, 0) + # vtx << OpenStudio::Point3d.new( 0, 0, 0) + # + # expect(mod1.poly(vtx)).to be_empty + # expect(mod1.status).to eq(ERR) + # expect(mod1.logs.size).to eq(1) + # expect(mod1.logs.first[:message]).to include("'(unaligned) points'") + # expect(mod1.clean!).to eq(DBG) + + # Regular polygon, counterclockwise yet not UpperLeftCorner (ULC). + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new(20, 0, 10) + vtx << OpenStudio::Point3d.new( 0, 0, 10) + vtx << OpenStudio::Point3d.new( 0, 0, 0) segments = mod1.getSegments(vtx) expect(segments).to be_a(OpenStudio::Point3dVectorVector) expect(segments.size).to eq(3) segments.each_with_index do |segment, i| - expect(mod1.yy?(segment.first, segment.last)).to be false - expect(mod1.yy?(segment.first, segment.last, false)).to be false - - case i - when 0 - expect(mod1.xx?(segment.first, segment.last)).to be true - expect(mod1.xx?(segment.first, segment.last, false)).to be true - expect(mod1.zz?(segment.first, segment.last)).to be false - expect(mod1.zz?(segment.first, segment.last, false)).to be false - when 1 - expect(mod1.zz?(segment.first, segment.last)).to be true - expect(mod1.zz?(segment.first, segment.last, false)).to be true - expect(mod1.xx?(segment.first, segment.last)).to be false - expect(mod1.xx?(segment.first, segment.last, false)).to be false - else - expect(mod1.xx?(segment.first, segment.last)).to be false - expect(mod1.xx?(segment.first, segment.last, false)).to be true - expect(mod1.zz?(segment.first, segment.last)).to be false - expect(mod1.zz?(segment.first, segment.last, false)).to be true + unless mod1.xyz?(segment, :x, segment.first.x) + vplane = mod1.verticalPlane(segment.first, segment.last) + expect(vplane).to be_a(OpenStudio::Plane) end end - # Retrieve polygon 'triads' (3x consecutive points) and qualify each as - # describing an acute, right or obtuse angle. As the considered polygons - # are either triangles or convex quadrilaterals, the number of returned - # unique triads should be limited to either 3x or 4x, as confirmed by the - # poly method (convex = true, uniqueness = true, collinearity = false). - expect(vtx.size).to eq(3) - expect(mod1.poly(vtx, true, true, false).size).to eq(3) - expect(mod1.status).to be_zero - triads = mod1.getTriads(vtx) - expect(mod1.status).to be_zero - expect(triads.size).to eq(3) + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Test when alignFace switches solution when surfaces are nearly flat, + # i.e. when dot product of surface normal vs zenith > 0.99. + # (see OpenStudio::Transformation.alignFace) + origin = OpenStudio::Point3d.new(0,0,0) + originZ = OpenStudio::Point3d.new(0,0,1) + zenith = originZ - origin + + # 1st surface, nearly horizontal. + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new( 2,10, 0.0) + vtx << OpenStudio::Point3d.new( 6, 4, 0.0) + vtx << OpenStudio::Point3d.new( 8, 8, 0.5) + normal = OpenStudio.getOutwardNormal(vtx).get + expect(zenith.dot(normal).abs).to be > 0.99 + expect(mod1.facingUp?(vtx)).to be true + + aligned = mod1.poly(vtx, false, false, false, true, :ulc).to_a + matches = aligned.select { |pt| mod1.same?(pt, origin) } + expect(matches).to be_empty + + # 2nd surface (nearly identical, yet too slanted to be flat. + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new( 2,10, 0.0) + vtx << OpenStudio::Point3d.new( 6, 4, 0.0) + vtx << OpenStudio::Point3d.new( 8, 8, 0.6) + normal = OpenStudio.getOutwardNormal(vtx).get + expect(zenith.dot(normal).abs).to be < 0.99 + expect(mod1.facingUp?(vtx)).to be false + + aligned = mod1.poly(vtx, false, false, false, true, :ulc).to_a + matches = aligned.select { |pt| mod1.same?(pt, origin) } + expect(matches).to_not be_empty + expect(matches.size).to eq(1) - # TO DO ... in progress. + expect(mod1.status).to be_zero end it "checks ULC" do @@ -1973,6 +2572,21 @@ module M # [18, 0, 0] # [20, 0, 5] # [18, 0, 10] + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + vtx = OpenStudio::Point3dVector.new + vtx << OpenStudio::Point3d.new(70, 45, 0) + vtx << OpenStudio::Point3d.new( 0, 45, 0) + vtx << OpenStudio::Point3d.new( 0, 0, 0) + vtx << OpenStudio::Point3d.new(70, 0, 0) + + ulc_vtx = mod1.ulc(vtx) + expect(mod1.status).to be_zero + # puts ulc_vtx + # [ 0, 45, 0] + # [ 0, 0, 0] + # [70, 0, 0] + # [70, 45, 0] end it "checks polygon attributes" do @@ -2062,7 +2676,7 @@ module M vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(10, 0, 0) - v = mod1.poly(vtx, false, true, true, false, :ulc) + v = mod1.poly(vtx, false, true, false, false, :ulc) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(3) expect(mod1.same?(vtx[0], v[0])).to be true @@ -2078,7 +2692,7 @@ module M vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(20, 0, 0) - v = mod1.poly(vtx, false, false, false, false, :ulc) + v = mod1.poly(vtx, false, false, true, false, :ulc) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(3) expect(mod1.same?(vtx[0], v[0])).to be true @@ -2095,7 +2709,7 @@ module M vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(20, 0, 0) - v = mod1.poly(vtx, false, false, false, false, :ulc) + v = mod1.poly(vtx, false, false, true, false, :ulc) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(3) expect(mod1.same?(vtx[0], v[0])).to be true @@ -2132,7 +2746,7 @@ module M vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(20, 0, 0) - v = mod1.poly(vtx, true, false, true, false, :ulc) + v = mod1.poly(vtx, true, false, false, false, :ulc) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(4) expect(mod1.same?(vtx[0], v[0])).to be true @@ -2143,7 +2757,7 @@ module M # 2nd check for (invalid) convexity (with collinear points). vtx << OpenStudio::Point3d.new(1, 0, 1) - v = mod1.poly(vtx, true, false, true, false, :ulc) + v = mod1.poly(vtx, true, false, false, false, :ulc) expect(v).to be_a(OpenStudio::Point3dVector) expect(v).to be_empty expect(mod1.error?).to be true @@ -2152,14 +2766,14 @@ module M expect(mod1.clean!).to eq(INF) # 3rd check for (valid) convexity (with collinear points), yet returned - # 3D points vector remains 'aligned' & clockwise. + # 3D points vector become 'aligned' & clockwise. vtx = OpenStudio::Point3dVector.new vtx << OpenStudio::Point3d.new( 0, 0,10) vtx << OpenStudio::Point3d.new( 0, 0, 0) vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(20, 0, 0) - v = mod1.poly(vtx, true, false, true, true, :cw) + v = mod1.poly(vtx, true, false, false, true, :cw) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(4) expect(mod1.xyz?(v, :z, 0)).to be true @@ -2173,13 +2787,14 @@ module M vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(20, 0, 0) - v = mod1.poly(vtx, true, false, true, false, :no) + v = mod1.poly(vtx, true, false, false, false, :no) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(4) expect(mod1.same?(vtx[0], v[0])).to be true expect(mod1.same?(vtx[1], v[1])).to be true expect(mod1.same?(vtx[2], v[2])).to be true expect(mod1.same?(vtx[3], v[3])).to be true + expect(mod1.clockwise?(v)).to be false expect(mod1.status).to be_zero # Sequence of returned vector if altered (avoid collinearity). @@ -2189,12 +2804,13 @@ module M vtx << OpenStudio::Point3d.new(10, 0, 0) vtx << OpenStudio::Point3d.new(20, 0, 0) - v = mod1.poly(vtx, true, false, false, false, :no) + v = mod1.poly(vtx, true, false, true, false, :no) expect(v).to be_a(OpenStudio::Point3dVector) expect(v.size).to eq(3) expect(mod1.same?(vtx[0], v[0])).to be true expect(mod1.same?(vtx[1], v[1])).to be true expect(mod1.same?(vtx[3], v[2])).to be true + expect(mod1.clockwise?(v)).to be false expect(mod1.status).to be_zero end @@ -2514,7 +3130,6 @@ module M # sub[:type ] = "FixedWindow" # defaulted if not specified. expect(mod1.addSubs(tilt_wall, [sub])).to be true expect(mod1.status).to be_zero - expect(mod1.logs.size).to be_zero tilted = model.getSubSurfaceByName("Tilted window|0") expect(tilted).to_not be_empty @@ -2547,38 +3162,73 @@ module M expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) + # Modeified NREL SEB model' file = File.join(__dir__, "files/osms/out/seb_ext2.osm") path = OpenStudio::Path.new(file) model = translator.loadModel(path) expect(model).to_not be_empty model = model.get + # Extension holds: + # - 2x vertical side walls + # - tilted (cantilevered) wall + # - sloped roof tilted = model.getSurfaceByName("Openarea tilted wall") + left = model.getSurfaceByName("Openarea left side wall") + right = model.getSurfaceByName("Openarea right side wall") expect(tilted).to_not be_empty + expect(left).to_not be_empty + expect(right).to_not be_empty tilted = tilted.get + left = left.get + right = right.get + expect(mod1.facingUp?(tilted)).to be false + expect(mod1.xyz?(tilted)).to be false + + # Neither wall has coordinates which align with the model grid. Without some + # transformation (eg alignFace), OSut's 'width' of a given surface is of + # some utility. A vertical surface's 'height' remains valid/useful. w1 = mod1.width(tilted) h1 = mod1.height(tilted) - expect(w1).to be_within(TOL).of(5.89) - expect(h1).to be_within(TOL).of(3.09) - - left = model.getSurfaceByName("Openarea left side wall") - expect(left).to_not be_empty - left = left.get + expect(w1).to be_within(TOL).of(5.69) + expect(h1).to be_within(TOL).of(2.35) + + # Aligned, a vertical or sloped (or tilted) surface's 'width' and 'height' + # correctly report what a tape measurement would reveal (from left to right, + # when looking at the surface perpendicularly). + t = OpenStudio::Transformation.alignFace(tilted.vertices) + tilted_aligned = t.inverse * tilted.vertices + w01 = mod1.width(tilted_aligned) + h01 = mod1.height(tilted_aligned) + expect(mod1.facingUp?(tilted_aligned)).to be true + expect(mod1.xyz?(tilted_aligned)).to be true + expect(w01).to be_within(TOL).of(5.89) + expect(h01).to be_within(TOL).of(3.09) w2 = mod1.width(left) h2 = mod1.height(left) - expect(w2).to be_within(TOL).of(2.24) + expect(w2).to be_within(TOL).of(0.45) expect(h2).to be_within(TOL).of(3.35) - - right = model.getSurfaceByName("Openarea right side wall") - expect(right).to_not be_empty - right = right.get + t = OpenStudio::Transformation.alignFace(left.vertices) + left_aligned = t.inverse * left.vertices + w02 = mod1.width(left_aligned) + h02 = mod1.height(left_aligned) + expect(w02).to be_within(TOL).of(2.24) + expect(h02).to be_within(TOL).of(h2) # 'height' based on Y-axis (vs Z-axis) w3 = mod1.width(right) h3 = mod1.height(right) - expect(w3).to be_within(TOL).of(w2) - expect(h3).to be_within(TOL).of(h2) + expect(w3).to be_within(TOL).of(1.49) + expect(h3).to be_within(TOL).of(h2) # same as left + t = OpenStudio::Transformation.alignFace(right.vertices) + right_aligned = t.inverse * right.vertices + w03 = mod1.width(right_aligned) + h03 = mod1.height(right_aligned) + expect(w03).to be_within(TOL).of(w02) # same as aligned left + expect(h03).to be_within(TOL).of(h02) # same as aligned left + + expect(mod1.status).to be_zero # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # What if wall vertex sequences were no longer ULC (e.g. URC)? @@ -2735,6 +3385,7 @@ module M expect(mod1.status).to be_zero + # Add another 5x (frame÷r-enabled) fixed windows, from either # left- or right-corner of base surfaces. Fetch "Openarea Wall 6". wall6 = model.getSurfaceByName("Openarea 1 Wall 6") @@ -2784,6 +3435,25 @@ module M file = File.join(__dir__, "files/osms/out/seb_ext3.osm") model.save(file, true) + + # Fetch a (flat) plenum roof surface. + id = "Level 0 Open area 1 ceiling Plenum RoofCeiling" + ruf1 = model.getSurfaceByName(id) + expect(ruf1).to_not be_empty + ruf1 = ruf1.get + + a8 = {} + a8[:id ] = "ruf skylight" + a8[:type ] = "Skylight" + a8[:count ] = 1 + a8[:width ] = 1.2 + a8[:height ] = 1.2 + + expect(mod1.addSubs(ruf1, [a8])).to be true + expect(mod1.status).to be_zero + + file = File.join(__dir__, "files/osms/out/seb_ext3a.osm") + model.save(file, true) end it "checks for space/surface convexity" do @@ -2812,9 +3482,9 @@ module M attic = space if id == "Attic" next if id == "Attic" - expect(space.partofTotalFloorArea).to be true # Isolate core as being part of the total floor area (occupied zone) and # not having sidelighting. + expect(space.partofTotalFloorArea).to be true next unless space.exteriorWallArea < TOL core = space @@ -2842,8 +3512,8 @@ module M core_volume = core.floorArea * 3.05 expect(core_volume).to be_within(TOL).of(core.volume) - # OpenStudio versions prior to v351 report erroneous volume calculation - # results for the attic (798.41 m3). + # OpenStudio versions prior to v351 overestimate attic volume (798.41 m3), + # as they resort to floor area x height. expect(attic.volume).to be_within(TOL).of(720.19) unless v < 350 expect(attic.volume).to be_within(TOL).of(798.41) if v < 350 expect(attic.floorArea).to be_within(TOL).of(567.98) # includes overhangs @@ -2941,14 +3611,25 @@ module M vtx << vtx[3] expect(attic_floor.setVertices(vtx)).to be true + # Generate (temporary) OSM & IDF: + file = File.join(__dir__, "files/osms/out/miniX.osm") + model.save(file, true) + + # file = File.join(__dir__, "files/osms/out/miniX.idf") + # ft = OpenStudio::EnergyPlus::ForwardTranslator.new + # idf = ft.translateModel(model) + # idf.save(file, true) + # Add 2x skylights to attic. attic_south = model.getSurfaceByName("Attic_roof_south") expect(attic_south).to_not be_empty attic_south = attic_south.get + aligned = mod1.poly(attic_south, false, false, true, true, :ulc) + side = 1.2 offset = side + 1 - head = mod1.height(attic_south.vertices) - 0.2 + head = mod1.height(aligned) - 0.2 expect(head).to be_within(TOL).of(10.16) sub = {} @@ -2962,11 +3643,31 @@ module M expect(mod1.addSubs(attic_south, [sub])).to be true expect(mod1.status).to be_zero + file = File.join(__dir__, "files/osms/out/mini_test.osm") + model.save(file, true) + + # file = File.join(__dir__, "files/osms/out/mini_test.idf") + # ft = OpenStudio::EnergyPlus::ForwardTranslator.new + # idf = ft.translateModel(model) + # idf.save(file, true) + # Running OS 3.7.0-rc: Both Attic ZN and Core_ZN ZN (in IDF) have + # autocalculated volumes: + # - Attic ZN : 720.19 m3 + # - CORE_ZN ZN : 456.46 m3 (easy-peasy : 149.66 m2 x 3.05 m) + # + # NO ISSUES UP TO HERE! + # [utilities.Polyhedron] <0> Polyhedron is not enclosed in original testing. Trying to add missing colinear points. + # [utilities.Polyhedron] <0> Polyhedron is not enclosed. + # [openstudio.model.Space] <0> Object of type 'OS:Space' and named 'Core_ZN' is not enclosed, there are 2 edges that aren't used exactly twice. Falling back to ceilingHeight * floorArea. Volume calculation will be potentially inaccurate. + # [utilities.Polyhedron] <0> Polyhedron is not enclosed in original testing. Trying to add missing colinear points. + # [utilities.Polyhedron] <0> Polyhedron is not enclosed. + # [openstudio.model.Space] <0> Object of type 'OS:Space' and named 'Attic' is not enclosed, there are 1 edges that aren't used exactly twice. Falling back to ceilingHeight * floorArea. Volume calculation will be potentially inaccurate. + # Re-validating pre-tested areas + volumes, as well as convexity. expect(core.floorArea).to be_within(TOL).of(149.66) core_volume = core.floorArea * 3.05 expect(core_volume).to be_within(TOL).of(core.volume) - expect(attic.volume).to be_within(TOL).of(720.19) unless v < 350 + # expect(attic.volume).to be_within(TOL).of(720.19) unless v < 350 expect(attic.volume).to be_within(TOL).of(798.41) if v < 350 expect(attic.floorArea).to be_within(TOL).of(567.98) # includes overhangs @@ -3005,13 +3706,13 @@ module M file = File.join(__dir__, "files/osms/out/mini.osm") model.save(file, true) - # Simulation results E+ 22.2 (SutherlandHodgman, PolygonClipping) + # Simulation results E+ 23.1 (SutherlandHodgman, PolygonClipping) # ... no E+ errors/warnings. # - # office.osm FullInteriorAndExterior 247 GJ 3.8 UMH cool - # mini.osm A FullInteriorAndExterior 247 GJ 4.0 UMH cool - # mini.osm B FullExteriorWithReflections 248 GJ 3.8 UMH cool - # mini.osm C FullInteriorAndExteriorWithReflections 247 GJ 3.8 UMH cool + # office.osm FullInteriorAndExterior 248 GJ 3.7 UMH cool + # mini.osm A FullInteriorAndExterior 248 GJ 3.7 UMH cool + # mini.osm B FullExteriorWithReflections 249 GJ 3.7 UMH cool + # mini.osm C FullInteriorAndExteriorWithReflections 248 GJ 3.7 UMH cool # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Mini as an interior courtyard: @@ -3066,7 +3767,7 @@ module M expect(core.floorArea).to be_within(TOL).of(149.66 - 4) # -mini m2 core_volume = core.floorArea * 3.05 expect(core_volume).to be_within(TOL).of(core.volume) - expect(attic.volume).to be_within(TOL).of(720.19) unless v < 350 + # expect(attic.volume).to be_within(TOL).of(720.19) unless v < 350 expect(attic.volume).to be_within(TOL).of(798.41) if v < 350 expect(attic.floorArea).to be_within(TOL).of(567.98) # includes overhangs @@ -3083,9 +3784,9 @@ module M # Simulation results E+ 22.2 (SutherlandHodgman, PolygonClipping) # ... no E+ errors/warnings. # - # mini2.osm A FullInteriorAndExterior 248 GJ 3.8 UMH cool - # mini2.osm B FullExteriorWithReflections 249 GJ 3.7 UMH cool - # mini2.osm C FullInteriorAndExteriorWithReflections 249 GJ 3.7 UMH cool + # mini2.osm A FullInteriorAndExterior 249 GJ 3.7 UMH cool + # mini2.osm B FullExteriorWithReflections 250 GJ 3.5 UMH cool + # mini2.osm C FullInteriorAndExteriorWithReflections 250 GJ 3.7 UMH cool # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Mini as an attic "well": @@ -3173,7 +3874,7 @@ module M # OpenStudio volume calculations are fixed as of v351. May need to take a # deeper dive. expect(attic.volume).to be_within(TOL).of(949.05) if v < 350 - expect(attic.volume).to be_within(TOL).of(720.19 + 4 * 3.05) unless v < 350 + # expect(attic.volume).to be_within(TOL).of(720.19 + 4 * 3.05) unless v < 350 expect(attic.floorArea).to be_within(TOL).of(567.98) # includes overhangs expect(mod1.poly(core_floor, true)).to be_empty # now concave @@ -3190,8 +3891,8 @@ module M # ... no E+ errors/warnings. # # mini2.osm A FullInteriorAndExterior 252 GJ 3.5 UMH cool - # mini2.osm B FullExteriorWithReflections 253 GJ 3.5 UMH cool - # mini2.osm C FullInteriorAndExteriorWithReflections 253 GJ 3.5 UMH cool + # mini2.osm B FullExteriorWithReflections 254 GJ 3.3 UMH cool + # mini2.osm C FullInteriorAndExteriorWithReflections 254 GJ 3.3 UMH cool end it "checks for outdoor-facing roofs" do @@ -3222,7 +3923,6 @@ module M end expect(spaces.size).to eq(5) - # puts spaces # "Story 1 East Perimeter Space" : "Surface 18" # "Story 1 North Perimeter Space" : "Surface 12" # "Story 1 Core Space" : "Surface 30" @@ -3238,13 +3938,6 @@ module M end expect(roofs.size).to eq(spaces.size) - expect(mod1.status).to be_zero - # puts roofs - # "Story 1 East Perimeter Space" : "Surface 18" - # "Story 1 North Perimeter Space" : "Surface 12" - # "Story 1 Core Space" : "Surface 30" - # "Story 1 South Perimeter Space" : "Surface 24" - # "Story 1 West Perimeter Space" : "Surface 6" spaces.each do |id, surface| expect(roofs.keys).to include(id) @@ -3299,7 +3992,6 @@ module M expect(roofs[o].downcase).to include("plenum") end - # puts roofs # "Utility 1" : "Level 0 Utility 1 Ceiling Plenum RoofCeiling" # "Open area 1" : "Level 0 Open area 1 Ceiling Plenum RoofCeiling" # "Small office 1" : "Level 0 Small office 1 Ceiling Plenum RoofCeiling" @@ -3357,16 +4049,19 @@ module M south = south.get west = west.get + # Tracking copy of the core ceiling for later. + cor = core + expect(mod1.fits?(p4, core)).to be false expect(mod1.fits?(p4, north)).to be false expect(mod1.fits?(p4, east)).to be false - expect(mod1.fits?(p4, west)).to be true + expect(mod1.fits?(p4, west)).to be true # ... it fits expect(mod1.fits?(p4, south)).to be false expect(mod1.overlaps?(p4, core)).to be false expect(mod1.overlaps?(p4, north)).to be false expect(mod1.overlaps?(p4, east)).to be false - expect(mod1.overlaps?(p4, west)).to be true # ... it fits + expect(mod1.overlaps?(p4, west)).to be true # ... since it fits expect(mod1.overlaps?(p4, south)).to be false expect(mod1.fits?(core, p4)).to be false @@ -3387,42 +4082,219 @@ module M rufs = mod1.getRoofs(space) + rufs.each { |rf| expect(mod1.slopedRoof?(rf)).to be true } + if id.include?("Perimeter") expect(rufs.size).to eq(1) ruf = rufs.first expect(ruf).to be_a(OpenStudio::Model::Surface) perimeters[id] = ruf.nameString + + # No horizontal ridges. + ridges = mod1.getHorizontalRidges(rufs) + expect(ridges).to be_a(Array) + expect(ridges).to be_empty else expect(id).to include("Core") expect(rufs.size).to eq(4) core = rufs + + # 1x horizontal ridge. + ridges = mod1.getHorizontalRidges(core) + expect(ridges).to be_a(Array) + expect(ridges).to_not be_empty + expect(ridges.size).to eq(1) + ridge = ridges.first + + expect(ridge).to have_key(:edge) + expect(ridge).to have_key(:length) + expect(ridge).to have_key(:roofs) + + expect(ridge[:length]).to be_within(TOL).of(9.23) + expect(ridge[:roofs].size).to eq(2) + expect(ridge[:roofs].include?(north)).to be true + expect(ridge[:roofs].include?(south)).to be true + + # Check overlaps with core ceiling. + core.each { |s| expect(mod1.overlap(s, cor)).to_not be_empty } end end - expect(mod1.status).to be_zero expect(perimeters.size).to eq(4) expect(core.size).to eq(4) expect(perimeters.values.all? { |s| s.include?("Attic") }).to be true expect(core.all? { |s| s.nameString.include?("Attic") }).to be true core.each { |s| expect(s).to be_a(OpenStudio::Model::Surface) } - expect(perimeters.keys.all? { |s| occupied.include?(s) }).to be true + + expect(mod1.status).to be_zero end - it "checks for candidate toplit spaces" do + it "checks leader line anchors and polygon inserts" do translator = OpenStudio::OSVersion::VersionTranslator.new expect(mod1.reset(DBG)).to eq(DBG) expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) - v = OpenStudio.openStudioVersion.split(".").join.to_i - file = File.join(__dir__, "files/osms/in/smalloffice.osm") - path = OpenStudio::Path.new(file) - model = translator.loadModel(path) - expect(model).to_not be_empty - model = model.get + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # A larger polygon (s0), defined UpperLeftCorner (ULC). Polygon s0 entirely + # encompasses 4x smaller rectangular polygons (s1 to s4), The vertex + # sequence of the 4x smaller polygons is based on their aspect ratios, as + # well as their placement vis-à-vis grid origin. + s0 = OpenStudio::Point3dVector.new + s0 << OpenStudio::Point3d.new( 2, 16, 20) + s0 << OpenStudio::Point3d.new( 2, 2, 20) + s0 << OpenStudio::Point3d.new( 8, 2, 20) + s0 << OpenStudio::Point3d.new( 8, 10, 20) + s0 << OpenStudio::Point3d.new(16, 10, 20) + s0 << OpenStudio::Point3d.new(16, 2, 20) + s0 << OpenStudio::Point3d.new(20, 2, 20) + s0 << OpenStudio::Point3d.new(20, 16, 20) + + s1 = OpenStudio::Point3dVector.new + s1 << OpenStudio::Point3d.new( 7, 3, 20) + s1 << OpenStudio::Point3d.new( 7, 7, 20) + s1 << OpenStudio::Point3d.new( 5, 7, 20) + s1 << OpenStudio::Point3d.new( 5, 3, 20) + + s2 = OpenStudio::Point3dVector.new + s2 << OpenStudio::Point3d.new( 3, 11, 20) + s2 << OpenStudio::Point3d.new(10, 11, 20) + s2 << OpenStudio::Point3d.new(10, 15, 20) + s2 << OpenStudio::Point3d.new( 3, 15, 20) + + s3 = OpenStudio::Point3dVector.new + s3 << OpenStudio::Point3d.new(12, 13, 20) + s3 << OpenStudio::Point3d.new(16, 11, 20) + s3 << OpenStudio::Point3d.new(17, 13, 20) + s3 << OpenStudio::Point3d.new(13, 15, 20) + + s4 = OpenStudio::Point3dVector.new + s4 << OpenStudio::Point3d.new(19, 3, 20) + s4 << OpenStudio::Point3d.new(19, 6, 20) + s4 << OpenStudio::Point3d.new(17, 6, 20) + s4 << OpenStudio::Point3d.new(17, 3, 20) + + area0 = OpenStudio.getArea(s0) + area1 = OpenStudio.getArea(s1) + area2 = OpenStudio.getArea(s2) + area3 = OpenStudio.getArea(s3) + area4 = OpenStudio.getArea(s4) + expect(area0).to_not be_empty + expect(area1).to_not be_empty + expect(area2).to_not be_empty + expect(area3).to_not be_empty + expect(area4).to_not be_empty + area0 = area0.get + area1 = area1.get + area2 = area2.get + area3 = area3.get + area4 = area4.get + expect(area0).to be_within(TOL).of(188) + expect(area1).to be_within(TOL).of( 8) + expect(area2).to be_within(TOL).of( 28) + expect(area3).to be_within(TOL).of( 10) + expect(area4).to be_within(TOL).of( 6) + + # Side tests: index of nearest/farthest box coordinate to grid origin. + expect(mod1.nearest(s1)).to eq(3) + expect(mod1.nearest(s2)).to eq(0) + expect(mod1.nearest(s3)).to eq(0) + expect(mod1.nearest(s4)).to eq(3) + expect(mod1.farthest(s1)).to eq(1) + expect(mod1.farthest(s2)).to eq(2) + expect(mod1.farthest(s3)).to eq(2) + expect(mod1.farthest(s4)).to eq(1) + + # Box-specific grid instructions. + set = [] + set << { box: s1, rows: 1, cols: 2, w0: 1.4, d0: 1.4, dX: 0.2, dY: 0.2 } + set << { box: s2, rows: 2, cols: 3, w0: 1.4, d0: 1.4, dX: 0.2, dY: 0.2 } + set << { box: s3, rows: 1, cols: 1, w0: 2.6, d0: 1.4, dX: 0.2, dY: 0.2 } + set << { box: s4, rows: 1, cols: 1, w0: 2.6, d0: 1.4, dX: 0.2, dY: 0.2 } + + area_s1 = set[0][:rows] * set[0][:cols] * set[0][:w0] * set[0][:d0] + area_s2 = set[1][:rows] * set[1][:cols] * set[1][:w0] * set[1][:d0] + area_s3 = set[2][:rows] * set[2][:cols] * set[2][:w0] * set[2][:d0] + area_s4 = set[3][:rows] * set[3][:cols] * set[3][:w0] * set[3][:d0] + expect(area_s1).to be_within(TOL).of( 3.92) + expect(area_s2).to be_within(TOL).of(11.76) + expect(area_s3).to be_within(TOL).of( 3.64) + expect(area_s4).to be_within(TOL).of( 3.64) + area_s = area_s1 + area_s2 + area_s3 + area_s4 + expect(area_s).to be_within(TOL).of(22.96) + + # Generate leader line anchors, linking set boxes to s0 vertices. + n = mod1.genAnchors(s0, set, :box) + expect(n).to eq(4) + + # These tests are successful, given the initial vertex sequencing. A simple + # resequencing of set box vertices could easily prevent genAnchors from + # identifying valid leader line anchor points. + set.each_with_index do |st, i| + expect(st).to have_key(:ld) + expect(st[:ld]).to be_a(Hash) + expect(st[:ld]).to have_key(s0) + expect(st[:ld][s0]).to be_a(OpenStudio::Point3d) + expect(mod1.same?(st[:ld][s0], s0[1])).to be true if i == 0 # 2, 2, 20 + expect(mod1.same?(st[:ld][s0], s0[0])).to be true if i == 1 # 2, 16, 20 + expect(mod1.same?(st[:ld][s0], s0[4])).to be true if i == 2 # 16, 10, 20 + expect(mod1.same?(st[:ld][s0], s0[5])).to be true if i == 3 # 16, 2, 20 + end + + # Add array of polygon inserts to s0. + s00 = mod1.genInserts(s0, set) + puts mod1.logs + expect(s00).to be_a(OpenStudio::Point3dVector) + expect(s00.size).to eq(68) + + area00 = OpenStudio.getArea(s00) + expect(area00).to_not be_empty + area00 = area00.get + expect(area00).to be_within(TOL).of(165.04) + sX_area = 0 + + # Detailed checks of sets. + set.each_with_index do |st, i| + expect(st).to have_key(:out) + expect(st).to have_key(:box) + expect(st).to have_key(:vts) + expect(st).to have_key(:vtx) + + st_area = 0 + + st[:vts].each do |id, sX| + area = OpenStudio.getArea(sX) + expect(area).to_not be_empty + st_area += area.get + end + + expect(st_area).to be_within(TOL).of(area_s1) if i == 0 + expect(st_area).to be_within(TOL).of(area_s2) if i == 1 + expect(st_area).to be_within(TOL).of(area_s3) if i == 2 + expect(st_area).to be_within(TOL).of(area_s4) if i == 3 + + sX_area += st_area - # TO DO ... + # As mentioned earlier, box vertex sequencing is key in successfully + # identifying leader line anchors for each set. Boxes remain unchanged. + st[:box].each do |pt| + expect(mod1.same?(st[:box], s1)).to be true if i == 0 + expect(mod1.same?(st[:box], s2)).to be true if i == 1 + expect(mod1.same?(st[:box], s3)).to be true if i == 2 + expect(mod1.same?(st[:box], s4)).to be true if i == 3 + end + + expect(st[:out]).to have_key(:set) + expect(st[:out]).to have_key(:box) + expect(st[:out]).to have_key(:bbox) + expect(st[:out]).to have_key(:t) + expect(st[:out]).to have_key(:o) + end + + expect(sX_area).to be_within(TOL).of(area_s) + expect(area00 + sX_area).to be_within(TOL).of(area0) + expect(mod1.status).to be_zero end it "checks generated skylight wells" do @@ -3431,311 +4303,242 @@ module M expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) - if OpenStudio.openStudioVersion.split(".").join.to_i > 300 - file = File.join(__dir__, "files/osms/in/smalloffice.osm") - path = OpenStudio::Path.new(file) - model = translator.loadModel(path) - expect(model).to_not be_empty - model = model.get - - # The following tests are a step-by-step, proof of concept demo towards an - # eventual general solution to autogenerate skylight wells, roof monitors, - # dormers, etc., in particular when spanning unoccupied spaces like attics - # and plenums. Once the final set of methods are completed and validated, - # these current tests may or may not be maintained in the long run (in - # favour of more compact, to-the-point tests). - - # Test case: Add 2x skylights to the sloped "Attic_roof_north", with a - # single individual wels leading down to "Core_ZN". Each (sloped) skylight - # is a standard 4'x4' model (1.2m x 1.2m), with a 1m gap between skylights, - # and 200mm from the roof ridge. - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 1. Fetch attic space and north-facing roof surface. - attic = model.getSpaceByName("Attic") - expect(attic).to_not be_empty - attic = attic.get - - roof = model.getSurfaceByName("Attic_roof_north") - expect(roof).to_not be_empty - roof = roof.get - - core = model.getSpaceByName("Core_ZN") - expect(core).to_not be_empty - core = core.get - - plafond = model.getSurfaceByName("Perimeter_ZN_1_ceiling") - expect(plafond).to_not be_empty - plafond = plafond.get - - ceiling = model.getSurfaceByName("Core_ZN_ceiling") - expect(ceiling).to_not be_empty - ceiling = ceiling.get - - minZ = ceiling.vertices.map(&:z).min - maxZ = ceiling.vertices.map(&:z).max - expect(minZ).to be_within(TOL).of(maxZ) - expect(plafond.vertices.map(&:z).min).to be_within(TOL).of(minZ) - expect(plafond.vertices.map(&:z).max).to be_within(TOL).of(maxZ) - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 2. Generate an array of 2x skylights to North Roof. - # __________ - # / || || \ - # / \ - # / \ - # / \ - # /__________________\ - - side = 1.2 - offset = side + 1 - head = mod1.height(roof.vertices) - 0.2 - expect(head).to be_within(TOL).of(10.16) - - sub = {} - sub[:id ] = "North Skylight" - sub[:type ] = "Skylight" - sub[:height] = side - sub[:width ] = side - sub[:head ] = head - sub[:count ] = 2 - sub[:offset] = offset - expect(mod1.addSubs(roof, [sub])).to be true - expect(mod1.status).to be_zero - expect(mod1.logs.size).to be_zero - expect(roof.subSurfaces.size).to eq(2) - plane = roof.plane - subs = roof.subSurfaces - expect(subs.size).to eq(2) - - subs.each { |sub| expect(sub.plane.equal(plane)).to be true } - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 3. Generate a 'buffered' outline around both skylights. The upper edge - # of the outline coincides with the roof ridge. The outline is to define a - # new Core_ZN ceiling holding the 2x new skylights. - # __________ - # / |___| \ - # / \ - # / \ - # / \ - # /__________________\ - - perimetre = mod1.outline(subs, 0.200) - expect(perimetre).to be_a(OpenStudio::Point3dVector) - expect(mod1.fits?(perimetre, roof)).to be true - - # Generate a projected 'perimetre' unto the original horizontal core - # ceiling below. Ensure 'opening' "fits" - a "sine qua non" condition for - # an eventual general method: a generated 'outline' must neatly fit within - # a receiving surface (below). - expect(mod1.fits?(perimetre, ceiling)).to be true - expect(mod1.status).to be_zero - opening = mod1.flatten(perimetre, :z, minZ) - expect(mod1.fits?(opening, ceiling)).to be true - expect(mod1.status).to be_zero + file = File.join(__dir__, "files/osms/in/smalloffice.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get - # Polygons below appended with an 'a' designate 'aligned' (or flattened) - # polygons relying on OpenStudio::Transformation class. - t = OpenStudio::Transformation.alignFace(roof.vertices) - aroof = mod1.poly(roof, false, true, false, true, :cw) - aperi = mod1.poly(perimetre, true, true, false, t, :cw) - expect(mod1.clockwise?(aroof)).to be true - expect(mod1.clockwise?(aperi)).to be true - expect(mod1.status).to be_zero - expect(mod1.fits?(aperi, aroof)).to be true - expect(mod1.status).to be_zero + srr = 0.05 + core = [] + attic = [] - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 4. Create a new, clockwise bounding box around aroof. - # ___ __________ ___ - # | / \ | - # | / \ | - # | / \ | - # |/ \| - # /__________________\ + model.getSpaces.each do |space| + id = space.nameString - abox = mod1.outline([aroof]) - expect(mod1.status).to be_zero - expect(mod1.width(aperi)).to be < mod1.width(abox) - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 5. Create a new, clockwise bounding box 'strip' around aperi (i.e. the - # new, flattened sub surface base surface), yet stretched left & right as - # to align with the roof bounding 'box' X-axis coordinates. - # ___ __________ ___ - # |___/__ ___ ___\___| - # | / \ | - # | / \ | - # |/ \| - # /__________________\ - - xMIN = abox.min_by(&:x).x - xMAX = abox.max_by(&:x).x - yMIN = aperi.min_by(&:y).y - yMAX = aperi.max_by(&:y).y - - astrip = OpenStudio::Point3dVector.new - astrip << OpenStudio::Point3d.new(xMAX, yMAX, 0) - astrip << OpenStudio::Point3d.new(xMAX, yMIN, 0) - astrip << OpenStudio::Point3d.new(xMIN, yMIN, 0) - astrip << OpenStudio::Point3d.new(xMIN, yMAX, 0) - # puts astrip - # [28.89, 10.36, 0] - # [28.89, 8.76, 0] - # [ 0.00, 8.76, 0] - # [ 0.00, 10.36, 0] - - # Split box by intersecting with strip. - res1 = OpenStudio.intersect(astrip, abox, TOL) - expect(res1).to_not be_empty - res1 = res1.get - # puts res1.polygon1 # ... == res1.polygon2 (strip) - # [28.89, 8.76, 0] - # [ 0.00, 8.76, 0] - # [ 0.00, 10.36, 0] - # [28.89, 10.36, 0] - - # The 'strip' isn't chopped up, so no residual polygons. The initial 'box' - # is however split into 2x (possibly 3x in other cases): - # 1. the intersecting strip itself - # 2. a residual, non-intersecting 'box' (smaller than the initial one) - expect(res1.newPolygons1).to be_empty - expect(res1.newPolygons2.size).to eq(1) - # res1.newPolygons2.each { |poly, i| puts poly } - # [28.89, 8.76, 0] - # [28.89, 0.00, 0] - # [ 0.00, 0.00, 0] - # [ 0.00, 8.76, 0] - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 6. Generate a new array to hold (here 3x, possibly 4x in other cases) - # new, flattened roof polygons that will replace the initial, single aroof - # polygon. - aroofs = [] - - # The first of these new aroofs is the intersection between the previous - # residual box and the initial aroof. - # ___ __________ ___ - # |___/____o_ ___\___| - # | / \ | - # | / x \ | - # |/ \| - # /__________________\ - - res2 = OpenStudio.intersect(res1.newPolygons2.first, aroof, TOL) - expect(res2).to_not be_empty - res2 = res2.get - # puts res2.polygon1 # ... res2.polygon2 ('x' marks the spot) - # [28.89, 0.00, 0] - # [ 0.00, 0.00, 0] - # [ 8.31, 8.76, 0] - # [20.58, 8.76, 0] - aroofs << mod1.to_p3Dv(res2.polygon1) - - expect(res2.newPolygons1.size).to eq(2) # 2x triangles left/right of 'x' - expect(res2.newPolygons2.size).to eq(1) # previous residual 'o' - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # 7. Repeat similar intersection exercice between aperi ('a') vs 'o'. - # __________ - # /_o|_a_|o__\ - # / \ - # / \ - # / \ - # /__________________\ - - # puts aperi - # [16.35, 10.36, 0] - # [16.35, 8.76, 0] - # [12.55, 8.76, 0] - # [12.55, 10.36, 0] - res3 = OpenStudio.intersect(res2.newPolygons2.first, aperi, TOL) - expect(res3).to_not be_empty - res3 = res3.get - # puts res3.polygon1 # ... res3.polygon2 (i.e. aperi) - # [16.35, 8.76, 0] - # [12.55, 8.76, 0] - # [12.55, 10.36, 0] - # [16.35, 10.36, 0] - - expect(res3.newPolygons1.size).to eq(2) # 2x polygons, left/right of 'o' - expect(res3.newPolygons2).to be_empty # aperi remains intact - # res3.newPolygons1.each { |poly| puts poly } - # [12.55, 8.76, 0] - # [ 8.31, 8.76, 0] - # [ 9.83, 10.36, 0] - # [12.55, 10.36, 0] - # - # [16.35, 8.76, 0] - # [16.35, 10.36, 0] - # [19.06, 10.36, 0] - # [20.58, 8.76, 0] - res3.newPolygons1.each { |poly| aroofs << mod1.to_p3Dv(poly) } - expect(aroofs.size).to eq(3) - - # Area check. - areas = 0 - - aroofs.each do |poly| - area = OpenStudio.getArea(poly) - expect(area).to_not be_empty - areas += area.get + unless space.partofTotalFloorArea + attic << space + next end - area = OpenStudio.getArea(aperi) - expect(area).to_not be_empty - areas += area.get - expect((roof.grossArea - areas).abs).to be < TOL - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Temporary setup for testing: - # 1. Generate new 'skybase' for the skylights, with 'perimetre' vertices. - # Apply non-defaulted roof parameters. Transfer skylights to skybase. - skybase = OpenStudio::Model::Surface.new(perimetre, model) - skybase.setName("#{roof.nameString} | skybase") - expect(skybase.setSpace(attic)).to be true - - unless roof.isConstructionDefaulted - construction = roof.construction - expect(construction).to_not be_empty - construction = construction.get.to_LayeredConstruction - expect(construction).to_not be_empty - construction = construction.get - expect(skybase.setConstruction(construction)).to be true - end + sidelit = mod1.daylit?(space, true, false) + toplit = mod1.daylit?(space, false) + expect(sidelit).to be true if id.include?("Perimeter") + expect(sidelit).to be false if id.include?("Core") + expect(toplit ).to be false + core << space if id.include?("Core") + end - expect(roof.subSurfaces.size).to eq(2) - expect(skybase.subSurfaces).to be_empty - subs.each { |sub| expect(sub.setSurface(skybase)).to be true } - expect(roof.subSurfaces).to be_empty - expect(skybase.subSurfaces.size).to eq(2) - - # 2. Modify initial roof vertices with the 1° new polygon (redressed). - poly1 = mod1.to_p3Dv(t * mod1.ulc(aroofs.first)) - expect(poly1).to be_a(OpenStudio::Point3dVector) - # puts poly1 - # [19.98, 10.75, 5.82] - # [28.29, 19.06, 3.05] - # [-0.60, 19.06, 3.05] - # [ 7.71, 10.75, 5.82] - expect(roof.setVertices(poly1)).to be true - - # 3. Add subsequent generated roof polygons (also redressed). - aroofs.each_with_index do |poly, i| - next if i == 0 - - vtx = mod1.to_p3Dv(t * mod1.ulc(poly)) - surface = roof.clone - surface = surface.to_Surface - expect(surface).to_not be_empty - surface = surface.get - expect(surface.setVertices(vtx)).to be true - end + expect(core.size).to eq(1) + expect(attic.size).to eq(1) + core = core.first + attic = attic.first + expect(mod1.plenum?(attic)).to be false + expect(mod1.unconditioned?(attic)).to be true + + # TOTAL attic roof area, including overhangs. + roofs = mod1.facets(attic, "Outdoors", "RoofCeiling") + total = roofs.sum(&:grossArea) + expect(total.round(2)).to eq(598.76) + + # "GROSS ROOF AREA" (GRA), as per 90.1/NECB - excludes roof overhangs (60m2) + gra1 = mod1.grossRoofArea(model.getSpaces) + expect(mod1.status).to be_zero + expect(gra1.round(2)).to eq(538.86) + + # Unless model geometry is too granular (e.g. finely tessellated), the + # method 'addSkyLights' generates skylight/wells achieving user-required + # skylight-to-roof ratios (SRR%). The distinction between TOTAL vs GRA is + # obviously key for SRR% calculations (i.e. denominators). + + # 2x test CASES: + # 1. UNCONDITIONED (attic, as is) + # 2. INDIRECTLY-CONDITIONED (e.g. plenum) + # + # For testing purposes, only the core zone is targeted for skylight wells. + # Context: NECBs and 90.1 require separate SRR% calculations for spaces + # conditioned differently (SEMI-CONDITIONED vs CONDITIONED). + # See 'addSkyLights' doc. + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # CASE 1: + # Retrieve core GRA. As with overhangs, only the attic roof sections + # directly-above the core are retained for SRR% calculations. Here, the + # GRA is substantially lower (than gra1). For now, calculated GRA is only + # valid BEFORE adding skylight wells. + gra_attic = mod1.grossRoofArea(core) + expect(mod1.status).to be_zero + expect(gra_attic.round(2)).to eq(157.77) + + # The method returns the GRA, calculated BEFORE adding skylights/wells. + rm2 = mod1.addSkyLights(core, {srr: srr }) + puts mod1.logs unless mod1.status.zero? + expect(mod1.status).to be_zero + expect(rm2.round(2)).to eq(gra_attic.round(2)) + + # New core skylight areas. Successfully achieved SRR%. + core_skies = mod1.facets(core, "Outdoors", "Skylight") + sky_area1 = core_skies.sum(&:grossArea) + expect(sky_area1.round(2)).to eq(7.89) + ratio = sky_area1 / rm2 + expect(ratio.round(2)).to eq(srr) + + file = File.join(__dir__, "files/osms/out/office_attic.osm") + model.save(file, true) + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # CASE 2: + file = File.join(__dir__, "files/osms/in/smalloffice.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get + + core = model.getSpaceByName("Core_ZN") + attic = model.getSpaceByName("Attic") + expect(core).to_not be_empty + expect(attic).to_not be_empty + core = core.get + attic = attic.get + + # Tag attic as an INDIRECTLY-CONDITIONED space. + key = "indirectlyconditioned" + val = core.nameString + expect(attic.additionalProperties.setFeature(key, val)).to be true + expect(mod1.plenum?(attic)).to be false + expect(mod1.unconditioned?(attic)).to be false + expect(mod1.setpoints(attic)[:heating]).to be_within(TOL).of(21.11) + expect(mod1.setpoints(attic)[:cooling]).to be_within(TOL).of(23.89) + + # Here, GRA includes ALL plenum roof surfaces (not just vertically-cast + # areas onto core ceiling). This will make meeting the SRR% of 5% much + # harder. + gra_plenum = mod1.grossRoofArea(core) + expect(mod1.status).to be_zero + expect(gra_plenum.round(2)).to eq(total.round(2)) + + rm2 = mod1.addSkyLights(core, {srr: srr}) + puts mod1.logs unless mod1.status.zero? + expect(mod1.status).to be_zero + expect(rm2.round(2)).to eq(total.round(2)) + + # New core skylight areas. Although the total skylight area is greater than + # in CASE 1, the method is unable to meet the requested SRR 5%. This is + # understandable given the constrained roof/core overlap vs the ~4x greater + # roof area. A plenum vastly larger than the room(s) it serves would is rare. + core_skies = mod1.facets(core, "Outdoors", "Skylight") + sky_area2 = core_skies.sum(&:grossArea) + expect(sky_area2.round(2)).to eq(8.93) + ratio2 = sky_area2 / rm2 + expect(ratio2.round(2)).to eq(0.01) # not 5% + expect(mod1.status).to be_zero + + file = File.join(__dir__, "files/osms/out/office_plenum.osm") + model.save(file, true) + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # CASE 2b: + file = File.join(__dir__, "files/osms/in/smalloffice.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get + + core = model.getSpaceByName("Core_ZN") + attic = model.getSpaceByName("Attic") + expect(core).to_not be_empty + expect(attic).to_not be_empty + core = core.get + attic = attic.get + + # Again, tag attic as an INDIRECTLY-CONDITIONED space. + key = "indirectlyconditioned" + val = core.nameString + expect(attic.additionalProperties.setFeature(key, val)).to be true + expect(mod1.plenum?(attic)).to be false + expect(mod1.unconditioned?(attic)).to be false + expect(mod1.setpoints(attic)[:heating]).to be_within(TOL).of(21.11) + expect(mod1.setpoints(attic)[:cooling]).to be_within(TOL).of(23.89) + expect(mod1.status).to be_zero + + gra_plenum = mod1.grossRoofArea(core) + expect(mod1.status).to be_zero + expect(gra_plenum.round(2)).to eq(total.round(2)) + + # Conflicting argument case: Here, the method can only add skylight wells + # through model "plenums" (in this context, :plenum is an all encompassing + # keyword for any INDIRECTLY-CONDITIONED, unoccupied space). Yet by passing + # option "plenum: false", the method is instructed to skip "plenum" + # skylight wells altogether. + rm2 = mod1.addSkyLights(core, {srr: srr, plenum: false}) + expect(mod1.status).to be_zero + expect(rm2.round(2)).to eq(total.round(2)) + + core_skies = mod1.facets(core, "Outdoors", "Skylight") + sky_area2 = core_skies.sum(&:grossArea) + expect(sky_area2.round(2)).to eq(0.00) - file = File.join(__dir__, "files/osms/out/office.osm") - model.save(file, true) + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + file = File.join(__dir__, "files/osms/in/warehouse.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get + + model.getSpaces.each do |space| + id = space.nameString + next unless space.partofTotalFloorArea + + sidelit = mod1.daylit?(space, true, false) + toplit = mod1.daylit?(space, false) + expect(sidelit).to be true if id.include?("Office") + expect(sidelit).to be false if id.include?("Storage") + expect(toplit ).to be false if id.include?("Office") + expect(toplit ).to be true if id.include?("Storage") end + + bulk = model.getSpaceByName("Zone3 Bulk Storage") + fine = model.getSpaceByName("Zone2 Fine Storage") + expect(bulk).to_not be_empty + expect(fine).to_not be_empty + bulk = bulk.get + fine = fine.get + + # No overhangs/attics. Calculation of roof area for SRR% is more intuitive. + gra_bulk = mod1.grossRoofArea(bulk) + gra_fine = mod1.grossRoofArea(fine) + + bulk_roof_m2 = mod1.getRoofs(bulk).sum(&:grossArea) + fine_roof_m2 = mod1.getRoofs(fine).sum(&:grossArea) + expect(mod1.status).to be_zero + expect(gra_bulk.round(2)).to eq(bulk_roof_m2.round(2)) + expect(gra_fine.round(2)).to eq(fine_roof_m2.round(2)) + + # Initial SSR%. + bulk_skies = mod1.facets(bulk, "Outdoors", "Skylight") + sky_area1 = bulk_skies.sum(&:grossArea) + ratio1 = sky_area1 / bulk_roof_m2 + expect(sky_area1.round(2)).to eq(47.57) + expect(ratio1.round(2)).to eq(0.01) + + srr = 0.04 + + opts = {} + opts[:srr ] = srr + opts[:size ] = 2.4 + opts[:clear] = true + rm2 = mod1.addSkyLights(bulk, opts) + puts mod1.logs unless mod1.status.zero? + expect(mod1.status).to be_zero + + bulk_skies = mod1.facets(bulk, "Outdoors", "Skylight") + sky_area2 = bulk_skies.sum(&:grossArea) + ratio2 = sky_area2 / rm2 + expect(sky_area2.round(2)).to eq(128.19) + expect(ratio2.round(2)).to eq(srr) + + file = File.join(__dir__, "files/osms/out/warehouse_sky.osm") + model.save(file, true) end it "checks facet retrieval" do From 396c338484e8694d862168e131f1d83da5f10b57 Mon Sep 17 00:00:00 2001 From: rd2 Date: Tue, 19 Mar 2024 13:42:41 -0400 Subject: [PATCH 02/13] Autogenerate skylight wells --- .github/workflows/pull_request.yml | 4 +- lib/osut/utils.rb | 1182 +++++++++++++++++----------- spec/osut_tests_spec.rb | 756 +++++++----------- 3 files changed, 997 insertions(+), 945 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3605bdb..a513a5a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -111,8 +111,8 @@ jobs: run: | echo $(pwd) echo $(ls) - docker pull nrel/openstudio:dev-3.7.0 - docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:dev-3.7.0 + docker pull nrel/openstudio:3.7.0 + docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:d3.7.0 docker exec -t test pwd docker exec -t test ls docker exec -t test bundle update diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index a265a17..9966c79 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -2334,17 +2334,24 @@ def scalar(v = OpenStudio::Vector3d.new, m = 0) # @return [OpenStudio::Point3dVector] 3D vector (see logs if empty) def to_p3Dv(pts = nil) mth = "OSut::#{__callee__}" - cl1 = Array + cl1 = OpenStudio::Point3d cl2 = OpenStudio::Point3dVector cl3 = OpenStudio::Model::PlanarSurface - cl4 = OpenStudio::Point3d + cl4 = Array v = OpenStudio::Point3dVector.new - 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?(cl1) + + if pts.is_a?(cl1) + v << pts + 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) pts.each do |pt| - return mismatch("point", pt, cl4, mth, DBG, v) unless pt.is_a?(cl4) + return mismatch("point", pt, cl4, mth, DBG, v) unless pt.is_a?(cl1) end pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, pt.z) } @@ -2357,23 +2364,49 @@ def to_p3Dv(pts = nil) # # @param s1 [Set] 1st set of 3D point(s) # @param s2 [Set] 2nd set of 3D point(s) + # @param indexed [Bool] whether to attempt to harmonize vertex sequence # # @return [Bool] whether sets are nearly equal (within TOL) # @return [false] if invalid input (see logs) - def same?(s1 = [], s2 = []) + def same?(s1 = nil, s2 = nil, indexed = true) mth = "OSut::#{__callee__}" - s1 = [s1] if s1.is_a?(OpenStudio::Point3d) - s2 = [s2] if s2.is_a?(OpenStudio::Point3d) - s1 = to_p3Dv(s1) - s2 = to_p3Dv(s2) + s1 = to_p3Dv(s1).to_a + s2 = to_p3Dv(s2).to_a return false if s1.empty? return false if s2.empty? + return false unless s1.size == s2.size + + indexed = true unless [true, false].include?(indexed) - size = s1.size - return invalid("set sizes", mth, 0, ERR, false) unless s2.size == size + if indexed + xOK = (s1[0].x - s2[0].x).abs < TOL + yOK = (s1[0].y - s2[0].y).abs < TOL + zOK = (s1[0].z - s2[0].z).abs < TOL + + if xOK && yOK && zOK && s1.size == 1 + return true + else + indx = nil + + s2.each_with_index do |pt, i| + break if indx + + xOK = (s1[0].x - s2[i].x).abs < TOL + yOK = (s1[0].y - s2[i].y).abs < TOL + zOK = (s1[0].z - s2[i].z).abs < TOL + + indx = i if xOK && yOK && zOK + end + + return false unless indx + + s2 = to_p3Dv(s2).to_a + s2.rotate!(indx) + end + end # OpenStudio.isAlmostEqual3dPt(p1, p2, TOL) # ... from v350 onwards. - size.times.each do |i| + s1.size.times.each do |i| xOK = (s1[i].x - s2[i].x).abs < TOL yOK = (s1[i].y - s2[i].y).abs < TOL zOK = (s1[i].z - s2[i].z).abs < TOL @@ -2403,29 +2436,55 @@ def holds?(pts = nil, p1 = nil) end ## - # Returns OpenStudio 3D point (of a provided set) nearest to e.g. grid origin. + # Returns OpenStudio 3D point (in a set) nearest to a point of reference, e.g. + # grid origin. If left unspecified, the method systematically returns the + # bottom-left corner (BLC) of any horizontal set. If more than one point fits + # the initial criteria, the method relies on deterministic sorting through + # triangulation. # # @param pts [Set] 3D points - # @param p0 [OpenStudio::Point3d] e.g. grid origin coordinates + # @param p01 [OpenStudio::Point3d] point of reference # - # @return [Integer] set index of nearest point to e.g. grid origin + # @return [Integer] set index of nearest point to point of reference # @return [nil] if invalid input (see logs) - def nearest(pts = nil, p0 = OpenStudio::Point3d.new(0,0,0)) + def nearest(pts = nil, p01 = nil) mth = "OSut::#{__callee__}" - l = 100000000000 + l = 100 + d01 = 10000 + d02 = 0 + d03 = 0 idx = nil pts = to_p3Dv(pts) - cl = OpenStudio::Point3d - return mismatch("point", p0, cl, mth) unless p0.is_a?(cl) + return idx if pts.empty? - pts.each_with_index { |pt, i| return i if same?(pt, p0) } + p03 = OpenStudio::Point3d.new( l,-l,-l) + p02 = OpenStudio::Point3d.new( l, l, l) + p01 = OpenStudio::Point3d.new(-l,-l,-l) unless p01 + return mismatch("point", p01, cl, mth) unless p01.is_a?(OpenStudio::Point3d) - pts.each_with_index do |pt, i| - length = (pt - p0).length + pts.each_with_index { |pt, i| return i if same?(pt, p01) } - if length < l - l = length + pts.each_with_index do |pt, i| + length01 = (pt - p01).length + length02 = (pt - p02).length + length03 = (pt - p03).length + + if length01.round(2) == d01.round(2) + if length02.round(2) == d02.round(2) + if length03.round(2) > d03.round(2) + idx = i + d03 = length03 + end + elsif length02.round(2) > d02.round(2) + idx = i + d03 = length03 + d02 = length02 + end + elsif length01.round(2) < d01.round(2) idx = i + d01 = length01 + d02 = length02 + d03 = length03 end end @@ -2433,29 +2492,55 @@ def nearest(pts = nil, p0 = OpenStudio::Point3d.new(0,0,0)) end ## - # Returns OpenStudio 3D point (of a provided set) farthest to e.g. grid origin. + # Returns OpenStudio 3D point (in a set) farthest from a point of reference, + # e.g. grid origin. If left unspecified, the method systematically returns the + # top-right corner (TRC) of any horizontal set. If more than one point fits + # the initial criteria, the method relies on deterministic sorting through + # triangulation. # # @param pts [Set] 3D points - # @param p0 [OpenStudio::Point3d] e.g. grid origin coordinates + # @param p01 [OpenStudio::Point3d] point of reference # - # @return [Integer] set index of farthest point to e.g. grid origin + # @return [Integer] set index of farthest point from point of reference # @return [nil] if invalid input (see logs) - def farthest(pts = nil, p0 = OpenStudio::Point3d.new(0,0,0)) + def farthest(pts = nil, p01 = nil) mth = "OSut::#{__callee__}" - l = 0 + l = 100 + d01 = 0 + d02 = 10000 + d03 = 10000 idx = nil pts = to_p3Dv(pts) - cl = OpenStudio::Point3d - return mismatch("point", p0, cl, mth) unless p0.is_a?(cl) + return idx if pts.empty? + + p03 = OpenStudio::Point3d.new( l,-l,-l) + p02 = OpenStudio::Point3d.new( l, l, l) + p01 = OpenStudio::Point3d.new(-l,-l,-l) unless p01 + return mismatch("point", p01, cl, mth) unless p01.is_a?(OpenStudio::Point3d) pts.each_with_index do |pt, i| - next if same?(pt, p0) + next if same?(pt, p01) - length = (pt - p0).length + length01 = (pt - p01).length + length02 = (pt - p02).length + length03 = (pt - p03).length - if length > l - l = length + if length01.round(2) == d01.round(2) + if length02.round(2) == d02.round(2) + if length03.round(2) < d03.round(2) + idx = i + d03 = length03 + end + elsif length02.round(2) < d02.round(2) + idx = i + d03 = length03 + d02 = length02 + end + elsif length01.round(2) > d01.round(2) idx = i + d01 = length01 + d02 = length02 + d03 = length03 end end @@ -2533,7 +2618,7 @@ def xyz?(pts = nil, axs = :z, val = 0) # # @return [OpenStudio::Point3d] the next sequential point # @return [nil] if invalid input (see logs) - def next(pts = nil, pt = nil) + def nextUp(pts = nil, pt = nil) mth = "OSut::#{__callee__}" pts = to_p3Dv(pts) cl = OpenStudio::Point3d @@ -2546,9 +2631,9 @@ def next(pts = nil, pt = nil) end ## - # Returns 'width' of a set of OpenStudio 3D points, once re/aligned. + # Returns 'width' of a set of OpenStudio 3D points. # - # @param pts [Set] 3D points, once re/aligned + # @param pts [Set] 3D points # # @return [Float] width along X-axis, once re/aligned # @return [0.0] if invalid inputs @@ -2656,13 +2741,12 @@ def getUniques(pts = nil, n = 0) # Returns paired sequential points as (non-zero length) line segments. If the # set strictly holds 2x unique points, a single segment is returned. # Otherwise, the returned number of segments equals the number of unique - # points. If non-collinearity is requested, then the number of returned - # segments equals the number of non-colliear points. + # points. # # @param pts [Set] 3D points - #^ + # # @return [OpenStudio::Point3dVectorVector] line segments (see logs if empty) - def getSegments(pts = nil, co = false) + def getSegments(pts = nil) mth = "OSut::#{__callee__}" vv = OpenStudio::Point3dVectorVector.new pts = getUniques(pts) @@ -2752,39 +2836,59 @@ def triad?(pts = nil) end ## - # Validates whether a 3D point lies along a set of 3D point segments. + # Validates whether a 3D point lies ~along a 3D point segment, i.e. less than + # 10mm from any segment. # # @param p0 [OpenStudio::Point3d] a 3D point - # @param s [Set ab.length + TOL + + ap0 = a + apd + return true if (p0 - ap0).length.round(2) <= TOL + + false + end + + ## + # Validates whether a 3D point lies anywhere ~along a set of 3D point + # segments, i.e. less than 10mm from any segment. + # + # @param p0 [OpenStudio::Point3d] a 3D point + # @param sgs [Set 0.001 - return true if pal < abl && pbl < abl - end + sgs.each { |sg| return true if pointAlongSegment?(p0, sg) } false end @@ -2798,7 +2902,6 @@ def pointAlongSegments?(p0 = nil, s = []) # @return [OpenStudio::Point3d] point of intersection of both lines # @return [nil] if no intersection, equal, or invalid input (see logs) def getLineIntersection(s1 = [], s2 = []) - mth = "OSut::#{__callee__}" s1 = getSegments(s1) s2 = getSegments(s2) return nil if s1.empty? @@ -2809,7 +2912,7 @@ def getLineIntersection(s1 = [], s2 = []) # Matching segments? return nil if same?(s1, s2) - return nil if same?(s1, s2.reverse) + return nil if same?(s1, s2.to_a.reverse) a1 = s1[0] a2 = s1[1] @@ -2828,42 +2931,47 @@ def getLineIntersection(s1 = [], s2 = []) return b1 if pointAlongSegments?(b1, s1) return b2 if pointAlongSegments?(b2, s1) - # Line segments as vectors. Skip if colinear + # Line segments as vectors. Skip if colinear. a = a2 - a1 b = b2 - b1 xab = a.cross(b) - return nil if xab.length < 0.001 + return nil if xab.length.round(4) < TOL2 - # Link segment endpoints as vectors. Must be coplanar (re cross products). + # Link 1st point to other segment endpoints as vectors. Must be coplanar. a1b1 = b1 - a1 a1b2 = b2 - a1 xa1b1 = a.cross(a1b1) xa1b2 = a.cross(a1b2) - return nil unless xab.cross(xa1b1).length < 0.001 - return nil unless xab.cross(xa1b2).length < 0.001 + return nil unless xab.cross(xa1b1).length.round(4) < TOL2 + return nil unless xab.cross(xa1b2).length.round(4) < TOL2 - lxa1b1 = xa1b1.length - lxa1b2 = xa1b2.length + # Both segment endpoints can't be 'behind' point. + return nil if a.dot(a1b1) < 0 && a.dot(a1b2) < 0 + + # Both in 'front' of point? Pick farthest from 'a'. + if a.dot(a1b1) > 0 && a.dot(a1b2) > 0 + lxa1b1 = xa1b1.length + lxa1b2 = xa1b2.length + + c1 = lxa1b1.round(4) < lxa1b2.round(4) ? b1 : b2 + else + c1 = a.dot(a1b1) > 0 ? b1 : b2 + end - # Pick 'b' endpoint farthest from 'a' (as 1x could be colinear). - c1 = lxa1b1 < lxa1b2 ? b2 : b1 - c2 = same?(c1, b1) ? b2 : b1 c1a1 = a1 - c1 xc1a1 = a.cross(c1a1) d1 = a1 + xc1a1 n = a.cross(xc1a1) dot = b.dot(n) + n = n.reverseVector if dot < 0 + f = c1a1.dot(n) / b.dot(n) + p0 = c1 + scalar(b, f) - # Precaution, shouldn't be necessary ... - return zero("dot product (n, xc1a1)", mth) if dot.abs < 0.001 + # Intersection can't be 'behind' point. + return nil if a.dot(p0 - a1) < 0 - n = n.reverseVector if dot < 0 - f = c1a1.dot(n) / b.dot(n) - p0 = c1 + scalar(b, f) - - # Ensure intersection is sandwiched between 1st line segment endpoints. - return nil unless pointAlongSegments?(p0, s1) - return nil unless pointAlongSegments?(p0, s2) + # Ensure intersection is sandwiched between endpoints. + return nil unless pointAlongSegments?(p0, s2) && pointAlongSegments?(p0, s1) p0 end @@ -2877,7 +2985,6 @@ def getLineIntersection(s1 = [], s2 = []) # @return [Bool] whether 3D line intersects 3D segments # @return [false] if invalid input (see logs) def lineIntersects?(l = [], s = []) - mth = "OSut::#{__callee__}" l = getSegments(l) s = getSegments(s) return nil if l.empty? @@ -2885,7 +2992,7 @@ def lineIntersects?(l = [], s = []) l = l.first - s.each { |segment| return true unless getLineIntersection(l, segment).nil? } + s.each { |segment| return true if getLineIntersection(l, segment) } false end @@ -2901,46 +3008,70 @@ def clockwise?(pts = nil) mth = "OSut::#{__callee__}" pts = to_p3Dv(pts) n = false - return invalid("3+ points" , mth, 1, DBG, n) if pts.size < 3 - return invalid("(unaligned) points", mth, 1, DBG, n) unless xyz?(pts, :z, 0) + return invalid("3+ points" , mth, 1, DBG, n) if pts.size < 3 + return invalid("flat points", mth, 1, DBG, n) unless xyz?(pts, :z) OpenStudio.pointInPolygon(pts.first, pts, TOL) end ## - # Returns 'aligned' OpenStudio 3D points conforming to Openstudio's - # counterclockwise UpperLeftCorner (ULC) convention. + # Returns OpenStudio 3D points (min 3x) conforming to an UpperLeftCorner (ULC) + # convention. Points Z-axis values must be ~= 0. Points are returned + # counterclockwise. # - # @param pts [Set] aligned 3D points + # @param pts [Set] 3D points # # @return [OpenStudio::Point3dVector] ULC points (see logs if empty) def ulc(pts = nil) mth = "OSut::#{__callee__}" - pts = to_p3Dv(pts) v = OpenStudio::Point3dVector.new - p0 = OpenStudio::Point3d.new(0,0,0) - i0 = nil - return v if pts.empty? + pts = to_p3Dv(pts).to_a + return invalid("points (3+)", mth, 1, DBG, v) if pts.size < 3 + return invalid("points (aligned)", mth, 1, DBG, v) unless xyz?(pts, :z) + + # Ensure counterclockwise sequence. + pts = pts.reverse if clockwise?(pts) + minX = pts.min_by(&:x).x + i0 = nearest(pts) + p0 = pts[i0] + + pts_x = pts.select { |pt| pt.x.round(2) == minX.round(2) }.reverse + p1 = pts_x.max_by { |pt| (pt - p0).length } + i1 = pts.index(p1) + + to_p3Dv(pts.rotate(i1)) + end + + ## + # Returns OpenStudio 3D points (min 3x) conforming to an BottomLeftCorner + # (BLC) convention. Points Z-axis values must be ~= 0. Points are returned + # counterclockwise. + # + # @param pts [Set] 3D points + # + # @return [OpenStudio::Point3dVector] BLC points (see logs if empty) + def blc(pts = nil) + mth = "OSut::#{__callee__}" + v = OpenStudio::Point3dVector.new + pts = to_p3Dv(pts).to_a return invalid("points (3+)", mth, 1, DBG, v) if pts.size < 3 return invalid("points (aligned)", mth, 1, DBG, v) unless xyz?(pts, :z) # Ensure counterclockwise sequence. - pts = pts.to_a - pts = pts.reverse if clockwise?(pts) + pts = pts.reverse if clockwise?(pts) + minX = pts.min_by(&:x).x + i0 = nearest(pts) + p0 = pts[i0] - # Fetch index of candidate (0,0,0) point (i == 1, in most cases). Resort - # to last X == 0 point. Leave as is if failed attempts. - i0 = pts.index { |pt| same?(pt, p0) } - i0 = pts.rindex { |pt| pt.x.abs < TOL } if i0.nil? + pts_x = pts.select { |pt| pt.x.round(2) == minX.round(2) }.reverse - unless i0.nil? - i = pts.size - 1 - i = i0 - 1 unless i0 == 0 - pts = pts.rotate(i) - end + return to_p3Dv(pts.rotate(i0)) if pts_x.include?(p0) + + p1 = pts_x.min_by { |pt| (pt - p0).length } + i1 = pts.index(p1) - to_p3Dv(pts) + to_p3Dv(pts.rotate(i1)) end ## @@ -2978,9 +3109,8 @@ def getNonCollinears(pts = nil, n = 0) end n = n.to_i - n = 0 unless n.abs < pts.size - a = a[0..n] if n > 0 - a = a[n..-1] if n < 0 + a = a[0..n-1] if n > 0 + a = a[n-1..-1] if n < 0 to_p3Dv(a) end @@ -3010,7 +3140,7 @@ def getCollinears(pts = nil, n = 0) # Returns an OpenStudio 3D point vector as basis for a valid OpenStudio 3D # polygon. In addition to basic OpenStudio polygon tests (e.g. all points # sharing the same 3D plane, non-self-intersecting), the method can - # optionally check for convexity, or ensure uniqueness and/or collinearity. + # optionally check for convexity, or ensure uniqueness and/or non-collinearity. # Returned vector can also be 'aligned', as well as in UpperLeftCorner (ULC) # counterclockwise sequence, or in clockwise sequence. # @@ -3019,7 +3149,7 @@ def getCollinears(pts = nil, n = 0) # @param uq [Bool] whether to ensure uniqueness # @param co [Bool] whether to ensure non-collinearity # @param tt [Bool, OpenStudio::Transformation] whether to 'align' - # @param sq [:no, :ulc, :cw] unaltered, ULC or clockwise sequence + # @param sq [:no, :ulc, :blc, :cw] unaltered, ULC, BLC or clockwise sequence # # @return [OpenStudio::Point3dVector] 3D points (see logs if empty) def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no) @@ -3034,7 +3164,7 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no) # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Exit if mismatched/invalid arguments. ok1 = tt == true || tt == false || tt.is_a?(cl) - ok2 = sq == :no || sq == :ulc || sq == :cw + ok2 = sq == :no || sq == :ulc || sq == :blc || sq == :cw return invalid("transformation", mth, 5, DBG, v) unless ok1 return invalid("sequence", mth, 6, DBG, v) unless ok2 @@ -3050,33 +3180,22 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no) return empty("plane", mth, ERR, v) unless pln.pointOnPlane(pt) end - # A self-intersecting polygon will log the following: - # [utilities.Transformation] <1> Cannot compute outward normal for vertices - t = tt.is_a?(cl) ? tt : OpenStudio::Transformation.alignFace(pts) - a = (t.inverse * pts).reverse - - # May reactivate the following in the future. For now, OpenStudio's - # selfIntersect will correctly catch a typical self-intersecting polygon - # (like a "bowtie"), yet unfortunately will also catch a valid polygon that - # may surround another (through the use of leader lines). - # - # acw = clockwise?(a) ? a : a.reverse - # - # if OpenStudio.selfIntersects(acw, TOL) - # return invalid("polygon", mth, 1, ERR, v) - # end + t = OpenStudio::Transformation.alignFace(pts) + at = (t.inverse * pts).reverse if tt.is_a?(cl) - # Using a transformation that is most likely not specific to pts. The - # most likely reason to retain this option is when testing for polygon - # intersections, unions, etc., operations that typically require that - # points remain nonetheless 'aligned'. If re-activated, this logs a - # warning if aligned points aren't @Z = 0, before 'flattening'. - # - # invalid("points (non-aligned)", mth, 1, WRN) unless xyz?(a, :z, 0) - a = flatten(a).to_a unless xyz?(a, :z) + att = (tt.inverse * pts).reverse + + if same?(at, att) + a = att + a = ulc(a).to_a if clockwise?(a) + t = nil + else + t = xyz?(att, :z) ? nil : OpenStudio::Transformation.alignFace(att) + a = t ? (t.inverse * att).reverse : att + end else - return invalid("(unaligned) points", mth, 1, ERR, v) unless xyz?(a, :z, 0) + a = at end # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # @@ -3089,43 +3208,43 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no) # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Check for convexity (optional). - if vx && p3.size > 3 + if vx && a.size > 3 zen = OpenStudio::Point3d.new(0, 0, 1000) - acw = clockwise?(a) ? a.clone : a.reverse - acw = getNonCollinears(acw) - getTriads(acw).each do |trio| + getTriads(a).each do |trio| p1 = trio[0] p2 = trio[1] p3 = trio[2] v12 = p2 - p1 v13 = p3 - p1 x = (zen - p1).cross(v12) - return invalid("points (non-convex)", mth, 1, ERR, v) if x.dot(v13) > 0 + return v if x.dot(v13).round(4) > 0 end end # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Alter sequence (optional). - unless tt + if tt.is_a?(cl) case sq when :ulc - a = to_p3Dv(t * ulc(a.reverse)) + a = t ? to_p3Dv(t * ulc(a.reverse)) : to_p3Dv(ulc(a.reverse)) + when :blc + a = t ? to_p3Dv(t * blc(a.reverse)) : to_p3Dv(blc(a.reverse)) when :cw - a = to_p3Dv(t * a) - a = OpenStudio.reverse(a) unless clockwise?(a) + a = t ? to_p3Dv(t * a) : to_p3Dv(a) else - a = to_p3Dv(t * a.reverse) + a = t ? to_p3Dv(t * a.reverse) : to_p3Dv(a.reverse) end else case sq when :ulc - a = ulc(a.reverse) + a = tt ? to_p3Dv(ulc(a.reverse)) : to_p3Dv(t * ulc(a.reverse)) + when :blc + a = tt ? to_p3Dv(blc(a.reverse)) : to_p3Dv(t * blc(a.reverse)) when :cw - a = to_p3Dv(a) - a = OpenStudio.reverse(a) unless clockwise?(a) + a = tt ? to_p3Dv(a) : to_p3Dv(t * a) else - a = to_p3Dv(a.reverse) + a = tt ? to_p3Dv(a.reverse) : to_p3Dv(t * a.reverse) end end @@ -3133,44 +3252,60 @@ def poly(pts = nil, vx = false, uq = false, co = false, tt = false, sq = :no) end ## - # Validates whether 3D point is entirely within a 3D polygon. False if point - # lies along any of the polygon edges, or is near any of its vertices. + # Validates whether 3D point is within a 3D polygon. If option 'entirely' is + # set to true, then the method returns false if point lies along any of the + # polygon edges, or is very near any of its vertices. # # @param p0 [OpenStudio::Point3d] a 3D point # @param s [Set] 3D points # # @return [Bool] whether polygon is rectangular # @return [false] if invalid input (see logs) def rectangular?(pts = nil) - mth = "OSut::#{__callee__}" pts = poly(pts, false, false, false) return false if pts.empty? return false unless pts.size == 4 @@ -3276,116 +3408,150 @@ def rectangular?(pts = nil) end ## - # Determines whether a 1st OpenStudio polygon fits in a 2nd polygon. If the - # optional 3rd argument is set to false, the 1st polygon may only fit if it - # shares the 3D plane equation of the 2nd one. If the 3rd argument instead - # remains true (default), then the 1st polygon is first cast onto the 3D plane - # of the 2nd one; the method therefore returns true if the projection of the - # 1st polygon fits in the 2nd one. + # Validates whether an OpenStudio polygon is a square (rectangular, 4x ~equal + # sides). + # + # @param pts [Set] 3D points + # + # @return [Bool] whether polygon is a square + # @return [false] if invalid input (see logs) + def square?(pts = nil) + d = nil + pts = poly(pts, false, false, false) + return false if pts.empty? + return false unless rectangular?(pts) + + getSegments(pts).each do |pt| + l = (pt[1] - pt[0]).length + d = l unless d + return false unless l.round(2) == d.round(2) + end + + true + end + + ## + # Determines whether a 1st OpenStudio polygon fits in a 2nd polygon. Vertex + # sequencing of both polygons must be counterclockwise. If option 'entirely' + # is set to true, then the method returns false if point lies along any of the + # polygon edges, or is very near any of its vertices. # # @param p1 [Set] 1st set of 3D points # @param p2 [Set] 2nd set of 3D points - # @param flat [Bool] whether to first cast the 1st set onto the 2nd set plane + # @param entirely [Bool] whether point should be neatly within polygon limits # # @return [Bool] whether 1st polygon fits within the 2nd polygon # @return [false] if invalid input (see logs) - def fits?(p1 = nil, p2 = nil, flat = true) - mth = "OSut::#{__callee__}" - flat = true unless [true, false].include?(flat) - p1 = poly(p1, false, true, false) - p2 = poly(p2, false, true, false) + def fits?(p1 = nil, p2 = nil, entirely = false) + pts = [] + p1 = poly(p1) + p2 = poly(p2) return false if p1.empty? return false if p2.empty? - # Aligned, clockwise points using transformation from 2nd polygon. - t = OpenStudio::Transformation.alignFace(p2) - p1 = poly(p1, false, false, true, t, :cw) - p2 = poly(p2, false, false, true, t, :cw) - return false if p1.empty? - return false if p2.empty? + p1.each { |p0| return false unless pointWithinPolygon?(p0, p2) } - # If flat == false, return false unless p1 is flat. - if flat - p1 = flatten(p1) - else - return false unless xyz?(p1, :z) - end - - area1 = OpenStudio.getArea(p1) - area2 = OpenStudio.getArea(p2) - return empty("points 1 area", mth, ERR, false) if area1.empty? - return empty("points 2 area", mth, ERR, false) if area2.empty? - - area1 = area1.get - area2 = area2.get - union = OpenStudio.join(p1, p2, TOL2) - return false if union.empty? + entirely = false unless [true, false].include?(entirely) + return true unless entirely - union = union.get - area = OpenStudio.getArea(union) - return false if area.empty? + p1.each { |p0| return false unless pointWithinPolygon?(p0, p2, entirely) } - area = area.get - - if area > TOL - return true if (area - area2).abs < TOL - end - - false + true end ## - # Returns largest intersection of overlapping polygons, empty if non - # intersecting. If the optional 3rd argument is set to false, the 2nd polygon - # may only overlap if it shares the 3D plane equation of the 1st one. If the - # 3rd argument instead remains true (default), then the 2nd polygon is first - # cast onto the 3D plane of the 1st one; the method therefore returns (as - # overlap) the intersection of a projection of the 2nd polygon onto the 1st - # one. The method returns the smallest of the 2 polygons if either fits within - # the larger one. + # Returns intersection of overlapping polygons, empty if non intersecting. If + # the optional 3rd argument is left as false, the 2nd polygon may only overlap + # if it shares the 3D plane equation of the 1st one. If the 3rd argument is + # instead set to true, then the 2nd polygon is first cast onto the 3D plane of + # the 1st one; the method therefore returns (as overlap) the intersection of a + # projection of the 2nd polygon onto the 1st one. The method returns the + # smallest of the 2 polygons if either fits within the larger one. # # @param p1 [Set] 1st set of 3D points # @param p2 [Set] 2nd set of 3D points # @param flat [Bool] whether to first align the 2nd set onto the 1st set plane # # @return [OpenStudio::Point3dVector] largest intersection (see logs if empty) - def overlap(p1 = nil, p2 = nil, flat = true) + def overlap(p1 = nil, p2 = nil, flat = false) mth = "OSut::#{__callee__}" - flat = true unless [true, false].include?(flat) + flat = false unless [true, false].include?(flat) face = OpenStudio::Point3dVector.new - p1 = poly(p1, false, true, false) - p2 = poly(p2, false, true, false) - return face if p1.empty? - return face if p2.empty? + p01 = poly(p1) + p02 = poly(p2) + return empty("points 1", mth, DBG, face) if p01.empty? + return empty("points 2", mth, DBG, face) if p02.empty? + return p01 if fits?(p01, p02) + return p02 if fits?(p02, p01) + + if xyz?(p01, :z) + t = nil + 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 - pl1 = OpenStudio::Plane.new(getNonCollinears(p1, 3)) - pl2 = OpenStudio::Plane.new(getNonCollinears(p2, 3)) - n1 = pl1.outwardNormal - n2 = pl2.outwardNormal - t = OpenStudio::Transformation.alignFace(p1) - a1 = poly(p1, false, false, true, t, :cw) - a2 = poly(p2, false, false, true, t, :cw) - - # If flat == false, return face unless a2 is flat. - if flat - a2 = flatten(a2) + # Return either (transformed) polygon if one fits into the other. + p1t = p01 + + if t + p2t = to_p3Dv(cw2 ? t * a2 : t * a2.reverse) else - return face unless xyz?(a2, :z) + if cw1 + p2t = to_p3Dv(cw2 ? a2.reverse : a2) + else + p2t = to_p3Dv(cw2 ? a2 : a2.reverse) + end end - if fits?(a1, a2) - return p1 - elsif fits?(a2, a1) - pl02 = OpenStudio::Plane.new(getNonCollinears(a2, 3)) - n02 = pl02.outwardNormal - a2 = to_p3Dv(a2.to_a.reverse) if n1.dot(n02) < 0 - return to_p3Dv(t * a2) + return p1t if fits?(a1, a2) + return p2t if fits?(a2, a1) + + area1 = OpenStudio.getArea(a1) + area2 = OpenStudio.getArea(a2) + return empty("points 1 area", mth, ERR, face) if area1.empty? + return empty("points 2 area", mth, ERR, face) if area2.empty? + + area1 = area1.get + area2 = area2.get + union = OpenStudio.join(a1.reverse, a2.reverse, TOL2) + return face if union.empty? + + union = union.get + area = OpenStudio.getArea(union) + return face if area.empty? + + area = area.get + delta = area1 + area2 - area + + if area > TOL + return face if area.round(2) == area1.round(2) + return face if area.round(2) == area2.round(2) + return face if delta.round(2) == 0 end - res = OpenStudio.intersect(a1, a2, TOL) + res = OpenStudio.intersect(a1.reverse, a2.reverse, TOL) return face if res.empty? - to_p3Dv(t * res.get.polygon1.reverse) + res = res.get + res1 = res.polygon1 + return face if res1.empty? + + to_p3Dv(t ? t * res1.reverse : res1.reverse) end ## @@ -3397,7 +3563,7 @@ def overlap(p1 = nil, p2 = nil, flat = true) # # @return [Bool] whether polygons overlap (or fit) # @return [false] if invalid input (see logs) - def overlaps?(p1 = nil, p2 = nil, flat = true) + def overlaps?(p1 = nil, p2 = nil, flat = false) overlap(p1, p2, flat).empty? ? false : true end @@ -3414,8 +3580,8 @@ def cast(p1 = nil, p2 = nil, ray = nil) mth = "OSut::#{__callee__}" cl = OpenStudio::Vector3d face = OpenStudio::Point3dVector.new - p1 = poly(p1, false, false, false) - p2 = poly(p2, false, false, false) + p1 = poly(p1) + p2 = poly(p2) return face if p1.empty? return face if p2.empty? return mismatch("ray", ray, cl, mth) unless ray.is_a?(cl) @@ -3746,31 +3912,30 @@ def outline(a = [], bfr = 0, flat = true) end ## - # Generates a box from a triad (3D points). Points must be unique and + # Generates a BLC box from a triad (3D points). Points must be unique and # non-collinear. # # @param [Set] a triad (3D points) # - # @return [Set] a generated rectangle (see logs if empty) + # @return [Set] a rectangular ULC box (see logs if empty) def triadBox(pts = nil) mth = "OSut::#{__callee__}" bkp = OpenStudio::Point3dVector.new - box = OpenStudio::Point3dVector.new + box = [] pts = getNonCollinears(pts) return bkp if pts.empty? t = xyz?(pts, :z) ? nil : OpenStudio::Transformation.alignFace(pts) - pts = poly(pts, false, false, false, t) if t + pts = poly(pts, false, true, true, t) if t return bkp if pts.empty? return invalid("triad", mth, 1, ERR, bkp) unless pts.size == 3 - cw = clockwise?(pts) + pts = to_p3Dv(pts.to_a.reverse) if clockwise?(pts) + p0 = pts[0] + p1 = pts[1] + p2 = pts[2] - p0 = pts[0] - p1 = pts[1] - p2 = pts[2] - - # Cast p1 unto vertical plane defined by p1/p2. + # Cast p0 unto vertical plane defined by p1/p2. pp0 = verticalPlane(p1, p2).project(p0) v00 = p0 - pp0 v11 = pp0 - p1 @@ -3795,23 +3960,16 @@ def triadBox(pts = nil) box << OpenStudio::Point3d.new(p2.x, p2.y, p2.z) box << OpenStudio::Point3d.new(p3.x, p3.y, p3.z) - return invalid("box", mth, 0, ERR, bkp) unless rectangular?(box) - - clockwise = clockwise?(box) + box = blc(box) + return bkp unless rectangular?(box) - if cw - box = OpenStudio.reverse(box) unless clockwise - else - box = OpenStudio.reverse(box) if clockwise - end - - box = ulc(box) unless cw box = to_p3Dv(t * box) if t + box end ## - # Generates a box bounded within a triangle (midpoint theorem). + # Generates a BLC box bounded within a triangle (midpoint theorem). # # pts [Set] triangular polygon # @@ -3819,7 +3977,7 @@ def triadBox(pts = nil) def medialBox(pts = nil) mth = "OSut::#{__callee__}" bkp = OpenStudio::Point3dVector.new - box = OpenStudio::Point3dVector.new + box = [] pts = poly(pts, true, true, true) return bkp if pts.empty? return invalid("triangle", mth, 1, ERR, bkp) unless pts.size == 3 @@ -3828,7 +3986,7 @@ def medialBox(pts = nil) pts = poly(pts, false, false, false, t) if t return bkp if pts.empty? - cw = clockwise?(pts) + pts = to_p3Dv(pts.to_a.reverse) if clockwise?(pts) # Generate vertical plane along longest segment. mpoints = [] @@ -3839,34 +3997,24 @@ def medialBox(pts = nil) # Fetch midpoints of other 2 segments. sgs.each { |s| mpoints << midpoint(s.first, s.last) unless s == longest } - return invalid("midpoints", mth, 0, ERR, bkp) unless mpoints.size == 2 + return bkp unless mpoints.size == 2 # Generate medial bounded box. box << plane.project(mpoints.first) box << mpoints.first box << mpoints.last box << plane.project(mpoints.last) + box = clockwise?(box) ? blc(box.reverse) : blc(box) + return bkp unless rectangular?(box) + return bkp unless fits?(box, pts) - return invalid("box", mth, 0, ERR, bkp) unless rectangular?(box) - - clockwise = clockwise?(box) - - if cw - box = OpenStudio.reverse(box) unless clockwise - else - box = OpenStudio.reverse(box) if clockwise - end - - return invalid("unfit medial", mth, 1, ERR, bkp) unless fits?(box, pts) - - box = ulc(box) unless cw box = to_p3Dv(t * box) if t + box end ## - # Generates a bounded box within a polygon. Returns a ULC sequence if original - # points are counterclockwise. + # Generates a BLC bounded box within a polygon. # # @param pts [Set] OpenStudio 3D points # @@ -3877,22 +4025,21 @@ def boundedBox(pts = nil) mth = "OSut::#{__callee__}" bkp = OpenStudio::Point3dVector.new - box = OpenStudio::Point3dVector.new - pts = poly(pts, false, true) + box = [] + pts = poly(pts, false, true, true) return bkp if pts.empty? t = xyz?(pts, :z) ? nil : OpenStudio::Transformation.alignFace(pts) - pts = poly(pts, false, false, false, t) if t + pts = t.inverse * pts if t return bkp if pts.empty? - cw = clockwise?(pts) + pts = to_p3Dv(pts.to_a.reverse) if clockwise?(pts) # PATH A : Return medial bounded box if polygon is a triangle. if pts.size == 3 box = medialBox(pts) unless box.empty? - box = ulc(box) unless cw box = to_p3Dv(t * box) if t return box end @@ -3900,14 +4047,13 @@ def boundedBox(pts = nil) # PATH B : Return polygon itself if already rectangular. if rectangular?(pts) - box = ulc(pts) unless cw box = t ? to_p3Dv(t * pts) : pts return box end - # PATH C : Right-angle, midpoint triad approach. aire = 0 + # PATH C : Right-angle, midpoint triad approach. getSegments(pts).each do |sg| m0 = midpoint(sg.first, sg.last) @@ -3921,7 +4067,7 @@ def boundedBox(pts = nil) out = triadBox(OpenStudio::Point3dVector.new([m0, p1, p2])) next if out.empty? - next unless fits?(out, pts, false) + next unless fits?(out, pts) area = OpenStudio.getArea(out) next if area.empty? @@ -3946,7 +4092,7 @@ def boundedBox(pts = nil) out = triadBox(OpenStudio::Point3dVector.new([p0, p1, p2])) next if out.empty? - next unless fits?(out, pts, false) + next unless fits?(out, pts) area = OpenStudio.getArea(out) next if area.empty? @@ -3961,7 +4107,6 @@ def boundedBox(pts = nil) end unless aire < TOL - box = ulc(box) unless cw box = to_p3Dv(t * box) if t return box end @@ -3977,9 +4122,9 @@ def boundedBox(pts = nil) next if same?(p2, p0) next if same?(p2, p1) - out = medialBox(OpenStudio::Point3dVector.new(p0, p1, p2)) + out = medialBox(OpenStudio::Point3dVector.new([p0, p1, p2])) next if out.empty? - next unless fits?(out, pts, false) + next unless fits?(out, pts) area = OpenStudio.getArea(box) next if area.empty? @@ -3994,7 +4139,6 @@ def boundedBox(pts = nil) end unless aire < TOL - box = ulc(box) unless cw box = to_p3Dv(t * box) if t return box end @@ -4007,9 +4151,9 @@ def boundedBox(pts = nil) p1 = sg[1] p2 = sg[2] - out = medialBox(OpenStudio::Point3dVector.new(p0, p1, p2)) + out = medialBox(OpenStudio::Point3dVector.new([p0, p1, p2])) next if out.empty? - next unless fits?(out, pts, false) + next unless fits?(out, pts) area = OpenStudio.getArea(box) next if area.empty? @@ -4023,14 +4167,13 @@ def boundedBox(pts = nil) end unless aire < TOL - box = ulc(box) unless cw box = to_p3Dv(t * box) if t return box end # PATH G : Medial box, triangulated approach. aire = 0 - outer = poly(pts, false, true, true, false, :cw) + outer = to_p3Dv(pts.to_a.reverse) holes = OpenStudio::Point3dVectorVector.new OpenStudio.computeTriangulation(outer, holes).each do |triangle| @@ -4042,9 +4185,9 @@ def boundedBox(pts = nil) next if same?(p2, p0) next if same?(p2, p1) - out = medialBox(OpenStudio::Point3dVector.new(p0, p1, p2)) + out = medialBox(OpenStudio::Point3dVector.new([p0, p1, p2])) next if out.empty? - next unless fits?(out, pts, false) + next unless fits?(out, pts) area = OpenStudio.getArea(out) next if area.empty? @@ -4061,16 +4204,8 @@ def boundedBox(pts = nil) return bkp if aire < TOL - clockwise = clockwise?(box) - - if cw - box = OpenStudio.reverse(box) unless clockwise - else - box = OpenStudio.reverse(box) if clockwise - end - - box = ulc(box) unless cw box = to_p3Dv(t * box) if t + box end @@ -4100,24 +4235,45 @@ def getRealignedFace(pts = nil) pts = poly(pts, false, true) return out if pts.empty? return invalid("aligned plane", mth, 1, DBG, out) unless xyz?(pts, :z) + return invalid("clockwise pts", mth, 1, DBG, out) if clockwise?(pts) + + o = OpenStudio::Point3d.new(0, 0, 0) + w = width(pts) + h = height(pts) + d = h > w ? h : w + sgs = {} + box = boundedBox(pts) + return invalid("bounded box", mth, 0, DBG, out) if box.empty? - cw = clockwise?(pts) - w = width(pts) - h = height(pts) - d = h > w ? h : w - box = boundedBox(pts) - return invalid("bounded box" , mth, 0, DBG, out) if box.empty? - - s = getSegments(box) - return invalid("bounded box segments", mth, 0, DBG, out) if s.empty? - - i = ((s[0][1] - s[0][0]).length < (s[1][1] - s[1][0]).length) ? 0 : 1 - k = i + 2 - mid0 = midpoint(s[i][0], s[i][1]) - mid1 = midpoint(s[k][0], s[k][1]) - o = OpenStudio::Point3d.new(0, 0, 0) - origin = ((o - mid0).length < (o - mid1).length) ? mid0 : mid1 - terminal = ((o - mid0).length < (o - mid1).length) ? mid1 : mid0 + segments = getSegments(box) + return invalid("bounded box segments", mth, 0, DBG, out) if segments.empty? + + # Deterministic ID of box rotation/translation 'origin'. + segments.each_with_index do |sg, idx| + sgs[sg] = {} + sgs[sg][:idx] = idx + sgs[sg][:mid] = midpoint(sg[0], sg[1]) + sgs[sg][:l ] = (sg[1] - sg[0]).length + sgs[sg][:mo ] = (sgs[sg][:mid] - o).length + end + + sgs = sgs.sort_by { |sg, s| s[:mo] }.first(2).to_h if square?(box) + sgs = sgs.sort_by { |sg, s| s[:l ] }.first(2).to_h unless square?(box) + sgs = sgs.sort_by { |sg, s| s[:mo] }.first(2).to_h unless square?(box) + + sg0 = sgs.values[0] + sg1 = sgs.values[1] + + if (sg0[:mo]).round(2) == (sg1[:mo]).round(2) + i = sg1[:mid].y.round(2) < sg0[:mid].y.round(2) ? sg1[:idx] : sg0[:idx] + else + i = sg0[:idx] + end + + k = i + 2 < segments.size ? i + 2 : i - 2 + + origin = midpoint(segments[i][0], segments[i][1]) + terminal = midpoint(segments[k][0], segments[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 @@ -4125,23 +4281,16 @@ def getRealignedFace(pts = nil) angle = OpenStudio::getAngle(right, seg) angle = -angle if north.dot(seg) < 0 r = OpenStudio.createRotation(origin, axis, angle) - pts = poly(r.inverse * pts) - box = poly(r.inverse * box) + pts = to_p3Dv(r.inverse * pts) + box = to_p3Dv(r.inverse * box) dX = pts.min_by(&:x).x dY = pts.min_by(&:y).y xy = OpenStudio::Point3d.new(origin.x + dX, origin.y + dY, 0) origin2 = xy - origin t = OpenStudio.createTranslation(origin2) - set = poly(t.inverse * pts) - box = poly(t.inverse * box, false, false, false, false, :ulc) + set = t.inverse * pts + box = t.inverse * box bbox = outline([set]) - clckwise = clockwise?(bbox) - - if cw - bbox = OpenStudio.reverse(bbox) unless clckwise - else - bbox = OpenStudio.reverse(bbox) if clckwise - end out[:set ] = set out[:box ] = box @@ -4191,12 +4340,12 @@ def alignedHeight(pts = nil) # Generates leader line anchors, linking polygon vertices to one or more sets # (Hashes) of sequenced vertices. By default, the method seeks to link set # :vtx (key) vertices (users can select another collection of vertices, e.g. - # tag == :box). The method does not validate individual sets of vertices (e.g. - # non-coplanarity, self-intersecting, inter-set conflicts). Potential leader - # lines cannot intersect each other, other 'tagged' set vertices or original - # polygon edges. For highly-articulated cases (e.g. a narrow polygon with - # multiple concavities, holding multiple sets), such leader line conflicts - # will undoubtedly occur. The method relies on a 'first-come-first-served' + # tag == :box). The method minimally validates individual sets of vertices + # (e.g. coplanarity, non-self-intersecting, no inter-set conflicts). Potential + # leader lines cannot intersect each other, other 'tagged' set vertices or + # original polygon edges. For highly-articulated cases (e.g. a narrow polygon + # with multiple concavities, holding multiple sets), such leader line + # conflicts will surely occur. The method relies on a 'first-come-first-served' # approach: sets without leader lines are ignored (check for set :void keys, # see error logs). It is recommended to sort sets prior to calling the method. # @@ -4207,27 +4356,31 @@ def alignedHeight(pts = nil) # @return [Integer] number of successfully-generated anchors (check logs) def genAnchors(s = nil, set = [], tag = :vtx) mth = "OSut::#{__callee__}" + dZ = nil + t = nil id = s.respond_to?(:nameString) ? "#{s.nameString}: " : "" - f = false - pts = poly(s, f, f, f) + pts = poly(s) n = 0 return n if pts.empty? return mismatch("set", set, Array, mth, DBG, n) unless set.respond_to?(:to_a) set = set.to_a - t = OpenStudio::Transformation.alignFace(pts) - pts = t.inverse * pts # Validate individual sets. Purge surface-specific leader line anchors. set.each_with_index do |st, i| - str = id + "set ##{i+1}" - return mismatch(str, st, Hash, mth, DBG, a) unless st.respond_to?(:key?) - return hashkey( str, st, tag, mth, DBG, a) unless st.key?(tag) - return empty("#{str} vertices", mth, DBG, a) if st[tag].empty? + str1 = id + "set ##{i+1}" + str2 = str1 + " #{tag.to_s}" + return mismatch(str1, st, Hash, mth, DBG, n) unless st.respond_to?(:key?) + return hashkey( str1, st, tag, mth, DBG, n) unless st.key?(tag) + return empty("#{str2} vertices", mth, DBG, n) if st[tag].empty? + + stt = poly(st[tag]) + return invalid("#{str2} polygon", mth, 0, DBG, n) if stt.empty? + return invalid("#{str2} gap", mth, 0, DBG, n) unless fits?(stt, pts, true) if st.key?(:ld) ld = st[:ld] - return invalid("#{id} leaders", mth, 0, DBG, a) unless ld.is_a?(Hash) + return invalid("#{str1} leaders", mth, 0, DBG, n) unless ld.is_a?(Hash) ld.reject! { |k, _| k == s } else @@ -4235,31 +4388,49 @@ def genAnchors(s = nil, set = [], tag = :vtx) end end - # Set leader lines anchors. - pts.each_with_index do |pt, k| - set.each_with_index do |st, i| - next if st[:ld].key?(s) + if facingUp?(pts) + if xyz?(pts, :z) + dZ = 0 + else + dZ = pts.first.z + pts = flatten(pts).to_a + end + else + t = OpenStudio::Transformation.alignFace(pts) + pts = t.inverse * pts + end + + # Set leader lines anchors. Gather candidate leader line anchors; select + # anchor with shortest distance to first vertex of 'tagged' set. + set.each_with_index do |st, i| + candidates = [] + break if st[:ld].key?(s) + + stt = dZ ? flatten(st[tag]).to_a : t.inverse * st[tag] + p1 = stt.first - p1 = (t.inverse * st[tag]).first - ld = [pt, p1] - nb = 0 + pts.each_with_index do |pt, k| + ld = [pt, p1] + nb = 0 # Check for intersections between leader line and polygon edges. getSegments(pts).each do |sg| - next unless nb.zero? + break unless nb.zero? next if holds?(sg, pt) - nb += 1 unless getLineIntersection(sg, ld).nil? + nb += 1 if lineIntersects?(sg, ld) end next unless nb.zero? # Check for intersections between candidate leader line and other sets. set.each_with_index do |other, j| - next unless nb.zero? + break unless nb.zero? next if i == j - sgj = getSegments(t.inverse * other[tag]) + ost = dZ ? flatten(other[tag]).to_a : t.inverse * other[tag] + sgj = getSegments(ost) + sgj.each { |sg| nb += 1 if lineIntersects?(ld, sg) } end @@ -4267,39 +4438,48 @@ def genAnchors(s = nil, set = [], tag = :vtx) # ... and previous leader lines (first come, first serve basis). set.each_with_index do |other, j| - next unless nb.zero? + break unless nb.zero? next if i == j next unless other[:ld].key?(s) - pj = other[tag].first - ldj = [ other[:ld][s], pj ] - nb += 1 if lineIntersects?(ld, t.inverse * ldj) + ost = other[tag] + pj = ost.first + old = other[:ld][s] + ldj = dZ ? flatten([ old, pj ]) : t.inverse * [ old, pj ] + + unless same?(old, pt) + nb += 1 if lineIntersects?(ld, ldj) + end end next unless nb.zero? # Finally, check for self-intersections. - getSegments(t.inverse * st[tag]).each do |sg| - next unless nb.zero? + getSegments(stt).each do |sg| + break unless nb.zero? next if holds?(sg, p1) - nb += 1 unless getLineIntersection(sg, ld).nil? + nb += 1 if lineIntersects?(sg, ld) + nb += 1 if (sg.first - sg.last).cross(ld.first - ld.last).length < TOL end - # Only consider sets with valid leader line anchor points. - if nb.zero? - st[:ld][s] = t * pt - n += 1 - end + candidates << pt if nb.zero? end - end - # Log unsuccessful attempts. - set.each_with_index do |st, i| - unless st[:ld].key?(s) + if candidates.empty? str = id + "set ##{i+1}" - log(ERR, "#{str}: unable to anchor leader line (#{mth})") + log(ERR, "#{str}: unable to anchor #{tag} leader line (#{mth})") st[:void] = true + else + p0 = candidates.sort_by! { |pt| (pt - p1).length }.first + + if dZ + st[:ld][s] = OpenStudio::Point3d.new(p0.x, p0.y, p0.z + dZ) + else + st[:ld][s] = t * p0 + end + + n += 1 end end @@ -4308,13 +4488,12 @@ def genAnchors(s = nil, set = [], tag = :vtx) ## # Generates extended polygon vertices to circumscribe one or more sets - # (Hashes) of sequenced vertices. The method neither alters the original - # polygon vertices, nor validates individual sets of vertices (e.g. - # non-coplanarity, self-intersecting, inter-set conflicts). Valid leader line - # anchors (set key :ld) need to be generated prior to calling the method (see - # genAnchors). By default, the method seeks to link leader line anchors to - # set :vtx (key) vertices (users can select another collection of vertices, - # e.g. tag == :box). + # (Hashes) of sequenced vertices. The method minimally validates individual + # sets of vertices (e.g. coplanarity, non-self-intersecting, no inter-set + # conflicts). Valid leader line anchors (set key :ld) need to be generated + # prior to calling the method (see genAnchors). By default, the method seeks + # to link leader line anchors to set :vtx (key) vertices (users can select + # another collection of vertices, e.g. tag == :box). # # @param s [Set] a larger (parent) set of points # @param [Array] set a collection of sequenced vertices @@ -4326,7 +4505,7 @@ def genExtendedVertices(s = nil, set = [], tag = :vtx) mth = "OSut::#{__callee__}" id = s.respond_to?(:nameString) ? "#{s.nameString}: " : "" f = false - pts = poly(s, f, f, f) + pts = poly(s) cl = OpenStudio::Point3d a = OpenStudio::Point3dVector.new v = [] @@ -4334,13 +4513,18 @@ def genExtendedVertices(s = nil, set = [], tag = :vtx) return mismatch("set", set, Array, mth, DBG, a) unless set.respond_to?(:to_a) set = set.to_a - t = OpenStudio::Transformation.alignFace(pts) # Validate individual sets. set.each_with_index do |st, i| - str = id + "set ##{i+1}" - return mismatch(str, st, Hash, mth, DBG, a) unless st.respond_to?(:key?) - return hashkey( str, st, :ld, mth, DBG, a) unless st.key?(:ld) + str1 = id + "set ##{i+1}" + str2 = str1 + " #{tag.to_s}" + return mismatch(str1, st, Hash, mth, DBG, a) unless st.respond_to?(:key?) + 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) + + stt = poly(st[tag]) + 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) @@ -4349,13 +4533,11 @@ def genExtendedVertices(s = nil, set = [], tag = :vtx) end # Re-sequence polygon vertices. - t.inverse * pts.each do |pt| + pts.each do |pt| v << pt # Loop through each valid set; concatenate circumscribing vertices. - set.each do |st| - next unless st.key?(:ld) - next unless st[:ld].key?(s) + set.each_with_index do |st, i| next unless same?(st[:ld][s], pt) next unless st.key?(tag) @@ -4390,7 +4572,6 @@ def genExtendedVertices(s = nil, set = [], tag = :vtx) def genInserts(s = nil, set = []) mth = "OSut::#{__callee__}" id = s.respond_to?(:nameString) ? "#{s.nameString}:" : "" - f = false pts = poly(s) cl = OpenStudio::Point3d a = OpenStudio::Point3dVector.new @@ -4414,19 +4595,15 @@ def genInserts(s = nil, set = []) 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) - bx = poly(st[:box], true, true) - return invalid("#{str1} box", mth, 0, DBG, a) if bx.empty? - return invalid("#{str1} rectangle", mth, 0, DBG, a) unless rectangular?(bx) - # Ensure each set bounding box is safely within larger polygon boundaries. # TO DO: In line with related addSkylights "TO DO", expand method to # safely handle 'side' cutouts (i.e. no need for leader lines). In # so doing, boxes could eventually align along surface edges. - bx.each do |pt| - unless pointWithinPolygon?(pt, s) - return invalid("#{id} point", mth, 0, DBG, a) - end - end + str3 = str1 + " box" + bx = poly(st[:box]) + return invalid(str3, mth, 0, DBG, a) if bx.empty? + return invalid("#{str3} rectangle", mth, 0, DBG, a) unless rectangular?(bx) + return invalid("#{str3} box", mth, 0, DBG, a) unless fits?(bx, pts, true) if st.key?(:rows) rws = st[:rows] @@ -4481,53 +4658,49 @@ def genInserts(s = nil, set = []) # Flag conflicts between set bounding boxes. TO DO: ease up for ridges. set.each_with_index do |st, i| - str = id + "set ##{i+1}" - bx = st[:box] + bx = st[:box] set.each_with_index do |other, j| next if i == j - bx2 = other[:box] - sgs = getSegments(bx2) - - bx.each do |pt| - if pointAlongSegments?(pt, getSegments(bx2)) - return invalid("#{str} collinear point", mth, 0, DBG, a) - end - - if pointWithinPolygon?(pt, bx2) - return invalid("#{str} overlapping point", mth, 0, DBG, a) - end - end + bx2 = other[:box] + str4 = id + "set boxes ##{i+1}:##{j+1}" + next unless overlaps?(bx, bx2) + return invalid("#{str4} (overlapping)", mth, 0, DBG, a) end end - t = OpenStudio::Transformation.alignFace(pts) - pts = poly(pts, f, f, f, t) - sgs = getSegments(pts) - - set.each do |st| - bx = t.inverse * poly(st[:box]) - - out = getRealignedFace(bx) - next unless out[:set] - - # Overwrite original box vertex sequence ... redundant? - st[:out] = out - st[:bx ] = out[:r] * (out[:t] * out[:set]) - st[:box] = t * st[:bx] - end - # Loop through each 'valid' set (i.e. linking a valid leader line anchor), # generate set vertex array based on user-provided specs. Reset BLC vertex # coordinates once completed. set.each_with_index do |st, i| str = id + "set ##{i+1}" - next unless st.key?(:out) + dZ = nil + t = nil + bx = st[:box] + + if facingUp?(bx) + if xyz?(bx, :z) + dZ = 0 + else + dZ = bx.first.z + bx = flatten(bx).to_a + end + else + t = OpenStudio::Transformation.alignFace(bx) + bx = t.inverse * bx + end + + o = getRealignedFace(bx) + next unless o[:set] + + st[:out] = o + st[:bx ] = blc(o[:r] * (o[:t] * o[:set])) + vts = {} # collection of individual (named) polygon insert vertices vtx = [] # sequence of circumscribing polygon vertices - o = st[:out] + bx = o[:set] w = width(bx) # overall sandbox width d = height(bx) # overall sandbox depth @@ -4614,8 +4787,15 @@ def genInserts(s = nil, set = []) vec << OpenStudio::Point3d.new(xC + x, yC , 0) # Store. - vertices = o[:r] * (o[:t] * vec) - vts[nom] = to_p3Dv(t * vertices) + vtz = ulc(o[:r] * (o[:t] * vec)) + + if dZ + vz = OpenStudio::Point3dVector.new + vtz.each { |v| vz << OpenStudio::Point3d.new(v.x, v.y, v.z + dZ) } + vts[nom] = vz + else + vts[nom] = to_p3Dv(t * vtz) + end # Add reverse vertices, circumscribing each insert. vec.reverse! @@ -4632,7 +4812,14 @@ def genInserts(s = nil, set = []) end vtx = o[:r] * (o[:t] * vtx) - vtx = to_p3Dv(t * vtx) + + if dZ + vz = OpenStudio::Point3dVector.new + vtx.each { |v| vz << OpenStudio::Point3d.new(v.x, v.y, v.z + dZ) } + vtx = vz + else + vtx = to_p3Dv(t * vtx) + end st[:vts] = vts st[:vtx] = vtx @@ -4999,7 +5186,7 @@ def addSubs(s = nil, subs = [], clear = false, bfr = 0.005) # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # t = OpenStudio::Transformation.alignFace(s.vertices) - s0 = poly(s, false, false, true, t, :ulc) + s0 = poly(s, false, false, false, t, :ulc) s00 = nil if facingUp?(s) || facingDown?(s) # TODO: redundant check? @@ -5458,6 +5645,7 @@ def addSubs(s = nil, subs = [], clear = false, bfr = 0.005) vc = vec vc = offset(vc, fr, 300) if fr > 0 ok = fits?(vc, s) + log(ERR, "Skip '#{name}': won't fit in '#{nom}' (#{mth})") unless ok break unless ok @@ -5603,11 +5791,13 @@ def grossRoofArea(spaces = []) facets(other, "Outdoors", "RoofCeiling").each do |roof| rvi = ti * roof.vertices cst = cast(cv0, rvi, up) + next if cst.empty? # The overlap calculation fails for roof and ceiling surfaces with # 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? @@ -5664,7 +5854,6 @@ def getHorizontalRidges(roofs = []) edg = ridge[:edge] match = same?(edge, edg) || same?(edge, edg.reverse) - # match = true if same?(edge, edg) || same?(edge, edg.reverse) end next if match @@ -6050,14 +6239,21 @@ def addSkyLights(spaces = [], opts = {}) # When unoccupied spaces are involved (e.g. plenums, attics), the occupied # space (to toplight) may not share the same local transformation as its # unoccupied space(s) above. Fetching local transformation. + h = 0 t0 = transforms(space) next unless t0[:t] + toitures = facets(space, "Outdoors", "RoofCeiling") + plafonds = facets(space, "Surface", "RoofCeiling") + + toitures.each { |surf| h = [h, surf.vertices.max_by(&:z).z].max } + plafonds.each { |surf| h = [h, surf.vertices.max_by(&:z).z].max } + rooms[space] = {} rooms[space][:t ] = t0[:t] rooms[space][:m ] = space.multiplier - rooms[space][:h ] = space.ceilingHeight - rooms[space][:roofs ] = facets(space, "Outdoors", "RoofCeiling") + rooms[space][:h ] = h + rooms[space][:roofs ] = toitures rooms[space][:sidelit] = daylit?(space, true, false, false) # Fetch and process room-specific outdoor-facing roof surfaces, the most @@ -6111,10 +6307,58 @@ def addSkyLights(spaces = [], opts = {}) ti = transforms(espace) next unless ti[:t] - ti = ti[:t] + ti = ti[:t] + vtx = ruf.vertices + + # Ensure BLC vertex sequence. + if facingUp?(vtx) + vtx = ti * vtx + + if xyz?(vtx, :z) + vtx = blc(vtx) + else + dZ = vtx.first.z + vtz = blc(flatten(vtx)).to_a + vtx = [] + + vtz.each { |v| vtx << OpenStudio::Point3d.new(v.x, v.y, v.z + dZ) } + end + + ruf.setVertices(ti.inverse * vtx) + else + tr = OpenStudio::Transformation.alignFace(vtx) + vtx = blc(tr.inverse * vtx) + ruf.setVertices(tr * vtx) + end + ri = ti * ruf.vertices facets(space, "Surface", "RoofCeiling").each do |tile| + vtx = tile.vertices + + # Ensure BLC vertex sequence. + if facingUp?(vtx) + vtx = t0 * vtx + + if xyz?(vtx, :z) + vtx = blc(vtx) + else + dZ = vtx.first.z + vtz = blc(flatten(vtx)).to_a + vtx = [] + + vtz.each { |v| vtx << OpenStudio::Point3d.new(v.x, v.y, v.z + dZ) } + end + + vtx = t0.inverse * vtx + else + tr = OpenStudio::Transformation.alignFace(vtx) + vtx = blc(tr.inverse * vtx) + vtx = tr * vtx + end + + tile.setVertices(vtx) + ci0 = cast(t0 * tile.vertices, ri, ray) next if ci0.empty? @@ -6132,6 +6376,7 @@ def addSkyLights(spaces = [], opts = {}) # cutouts can be supported (no need for leader lines), e.g. # skylight strips along roof ridges. box = offset(box, -gap, 340) + box = poly(box, false, false, false, false, :blc) next if box.empty? bm2 = OpenStudio.getArea(box) @@ -6164,6 +6409,10 @@ def addSkyLights(spaces = [], opts = {}) floor = floor.get + # Ensure BLC vertex sequence. + vtx = t0 * vtx + floor.setVertices(ti.inverse * vtx.reverse) + if floor.space.empty? log(ERR, "#{floor.nameString} space? (#{mth})") next @@ -6301,7 +6550,6 @@ def addSkyLights(spaces = [], opts = {}) next if stz.empty? - stz = stz.sort_by { |st| st[:cm2] } genAnchors(tile, stz, :cbox) end @@ -6886,7 +7134,6 @@ def addSkyLights(spaces = [], opts = {}) grenier[:roofs].each do |roof| sts = sets - sts = sts.select { |st| st.key?(k) } sts = sts.select { |st| st.key?(:pattern) } sts = sts.select { |st| st.key?(:clng) } @@ -6905,10 +7152,10 @@ def addSkyLights(spaces = [], opts = {}) # contingent to successfully inserting corresponding room ceiling # inserts (vis-à-vis attic/plenum floor below). The method also # generates new roof inserts. See key:value pair :vts. - vertices = genInserts(roof, sts) - next if vertices.empty? + vz = genInserts(roof, sts) + next if vz.empty? # TODO log error if empty - roof.setVertices(ti * vertices) + roof.setVertices(ti.inverse * vz) end end end @@ -6963,7 +7210,6 @@ def addSkyLights(spaces = [], opts = {}) end # Extended ceiling vertices. - stz = stz.sort_by { |st| st[:cm2] } vertices = genExtendedVertices(tile, stz, :cvtx) next if vertices.empty? diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index 3334f8a..6d7bb93 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -1614,8 +1614,8 @@ module M end # Invalid input test. - m1 = "Invalid 'group' arg #2 (OSut::transforms)" expect(mod1.status).to be_zero + m1 = "Invalid 'group' arg #2 (OSut::transforms)" tr = mod1.transforms(nil) expect(tr).to be_a(Hash) expect(tr).to have_key(:t) @@ -1660,8 +1660,7 @@ module M expect(a[3].y - a[1].y).to be_within(TOL).of(hyp) pts = r * a - - pts.each_with_index { |pt, i| expect(mod1.same?(pt, vtx[i])).to be true } + expect(mod1.same?(pts, vtx)).to be true output1 = mod1.getRealignedFace(vtx) expect(mod1.status).to be_zero @@ -1673,25 +1672,30 @@ module M expect(output1).to have_key(:r) expect(output1).to have_key(:o) + ubox1 = output1[ :box] + ubbox1 = output1[:bbox] + # Realign a previously realigned surface? output2 = mod1.getRealignedFace(output1[:box]) - expect(mod1.status).to be_zero + ubox2 = output1[ :box] + ubbox2 = output1[:bbox] # Realigning a previously realigned polygon has no effect (== safe). - expect(mod1.same?(output1[:box ], output2[:box ])).to be true - expect(mod1.same?(output1[:bbox], output2[:bbox])).to be true + expect(mod1.same?(ubox1, ubox2, false)).to be true + expect(mod1.same?(ubbox1, ubbox2, false)).to be true - bounded_area = OpenStudio.getArea(output1[:box ]) - bounding_area = OpenStudio.getArea(output1[:bbox]) + bounded_area = OpenStudio.getArea(ubox1) + bounding_area = OpenStudio.getArea(ubbox1) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) - bounded_area = OpenStudio.getArea(output2[:box ]) - bounding_area = OpenStudio.getArea(output2[:bbox]) + bounded_area = OpenStudio.getArea(ubox2) + bounding_area = OpenStudio.getArea(ubbox2) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + expect(mod1.status).to be_zero # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Repeat with slight change in orientation. @@ -1702,32 +1706,32 @@ module M vtx << OpenStudio::Point3d.new( 6, 4, 0) output3 = mod1.getRealignedFace(vtx) - expect(mod1.status).to be_zero + ubox3 = output3[ :box] + ubbox3 = output3[:bbox] # Realign a previously realigned surface? output4 = mod1.getRealignedFace(output3[:box]) - expect(mod1.status).to be_zero + ubox4 = output4[ :box] + ubbox4 = output4[:bbox] # Realigning a previously realigned polygon has no effect (== safe). - expect(mod1.same?(output1[:box ], output2[:box ])).to be true - expect(mod1.same?(output1[:bbox], output2[:bbox])).to be true - expect(mod1.same?(output1[:box ], output3[:box ])).to be true - expect(mod1.same?(output1[:bbox], output3[:bbox])).to be true - expect(mod1.same?(output1[:box ], output4[:box ])).to be true - expect(mod1.same?(output1[:bbox], output4[:bbox])).to be true - - bounded_area = OpenStudio.getArea(output3[:box ]) - bounding_area = OpenStudio.getArea(output3[:bbox]) + expect(mod1.same?(ubox1, ubox3, false)).to be true + expect(mod1.same?(ubbox1, ubbox3, false)).to be true + expect(mod1.same?(ubox1, ubox4, false)).to be true + expect(mod1.same?(ubbox1, ubbox4, false)).to be true + + bounded_area = OpenStudio.getArea(ubox3) + bounding_area = OpenStudio.getArea(ubbox3) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) - bounded_area = OpenStudio.getArea(output4[:box ]) - bounding_area = OpenStudio.getArea(output4[:bbox]) + bounded_area = OpenStudio.getArea(ubox4) + bounding_area = OpenStudio.getArea(ubbox4) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) - + expect(mod1.status).to be_zero # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Repeat with changes in vertex sequence. @@ -1738,33 +1742,36 @@ module M vtx << OpenStudio::Point3d.new( 2, 2, 0) output5 = mod1.getRealignedFace(vtx) - expect(mod1.status).to be_zero + ubox5 = output5[ :box] + ubbox5 = output5[:bbox] # Realign a previously realigned surface? output6 = mod1.getRealignedFace(output5[:box]) - expect(mod1.status).to be_zero + ubox6 = output6[ :box] + ubbox6 = output6[:bbox] # Realigning a previously realigned polygon has no effect (== safe). - expect(mod1.same?(output1[:box ], output5[:box ])).to be true - expect(mod1.same?(output1[:box ], output6[:box ])).to be true - expect(mod1.same?(output1[:bbox], output5[:bbox])).to be true - expect(mod1.same?(output1[:bbox], output6[:bbox])).to be true - expect(mod1.same?(output5[:box ], output6[:box ])).to be true - expect(mod1.same?(output5[:box ], output5[:box ])).to be true - expect(mod1.same?(output5[:bbox], output6[:bbox])).to be true - expect(mod1.same?(output6[:bbox], output6[:bbox])).to be true - - bounded_area = OpenStudio.getArea(output5[:box ]) - bounding_area = OpenStudio.getArea(output5[:bbox]) + expect(mod1.same?(ubox1, ubox5)).to be true + expect(mod1.same?(ubox1, ubox6)).to be true + expect(mod1.same?(ubbox1, ubbox5)).to be true + expect(mod1.same?(ubbox1, ubbox6)).to be true + expect(mod1.same?(ubox5, ubox6, false)).to be true + expect(mod1.same?(ubox5, ubbox5, false)).to be true + expect(mod1.same?(ubbox5, ubox6, false)).to be true + expect(mod1.same?(ubox6, ubbox6, false)).to be true + + bounded_area = OpenStudio.getArea(ubox5) + bounding_area = OpenStudio.getArea(ubbox5) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) - bounded_area = OpenStudio.getArea(output6[:box ]) - bounding_area = OpenStudio.getArea(output6[:bbox]) + bounded_area = OpenStudio.getArea(ubox6) + bounding_area = OpenStudio.getArea(ubbox6) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) + expect(mod1.status).to be_zero # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Repeat with slight change in orientation (vertices resequenced). @@ -1775,261 +1782,78 @@ module M vtx << OpenStudio::Point3d.new( 1, 4, 0) output7 = mod1.getRealignedFace(vtx) - expect(mod1.status).to be_zero + ubox7 = output7[ :box] + ubbox7 = output7[:bbox] # Realign a previously realigned surface? - output8 = mod1.getRealignedFace(output7[:box]) - expect(mod1.status).to be_zero + output8 = mod1.getRealignedFace(ubox7) + ubox8 = output8[ :box] + ubbox8 = output8[:bbox] # Realigning a previously realigned polygon has no effect (== safe). - expect(mod1.same?(output1[:box ], output7[:box ])).to be true - expect(mod1.same?(output1[:box ], output8[:box ])).to be true - expect(mod1.same?(output1[:bbox], output7[:bbox])).to be true - expect(mod1.same?(output1[:bbox], output8[:bbox])).to be true - expect(mod1.same?(output5[:box ], output7[:box ])).to be true - expect(mod1.same?(output5[:bbox], output7[:bbox])).to be true - expect(mod1.same?(output5[:box ], output8[:box ])).to be true - expect(mod1.same?(output5[:bbox], output8[:bbox])).to be true - - bounded_area = OpenStudio.getArea(output7[:box ]) - bounding_area = OpenStudio.getArea(output7[:bbox]) + expect(mod1.same?(ubox1, ubox7)).to be true + expect(mod1.same?(ubox1, ubox8)).to be true + expect(mod1.same?(ubbox1, ubbox7)).to be true + expect(mod1.same?(ubbox1, ubbox8)).to be true + expect(mod1.same?(ubox5, ubox7, false)).to be true + expect(mod1.same?(ubbox5, ubbox7, false)).to be true + expect(mod1.same?(ubox5, ubox5, false)).to be true + expect(mod1.same?(ubbox5, ubbox8, false)).to be true + + bounded_area = OpenStudio.getArea(ubox7) + bounding_area = OpenStudio.getArea(ubbox7) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) - bounded_area = OpenStudio.getArea(output8[:box ]) - bounding_area = OpenStudio.getArea(output8[:bbox]) + bounded_area = OpenStudio.getArea(ubox8) + bounding_area = OpenStudio.getArea(ubbox8) expect(bounded_area).to_not be_empty expect(bounding_area).to_not be_empty expect(bounded_area.get).to be_within(TOL).of(bounding_area.get) - - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # SEB case. - file = File.join(__dir__, "files/osms/in/seb.osm") - path = OpenStudio::Path.new(file) - model = translator.loadModel(path) - expect(model).to_not be_empty - model = model.get - - # Roof Surfaces. - r1 = "Level 0 Open area 1 Ceiling Plenum RoofCeiling" - r2 = "Level 0 Utility 1 Ceiling Plenum RoofCeiling" - r3 = "Level 0 Entry way Ceiling Plenum RoofCeiling" - r4 = "Level 0 Small office 1 Ceiling Plenum RoofCeiling" - - r1 = model.getSurfaceByName(r1) - r2 = model.getSurfaceByName(r2) - r3 = model.getSurfaceByName(r3) - r4 = model.getSurfaceByName(r4) - - expect(r1).to_not be_empty - expect(r2).to_not be_empty - expect(r3).to_not be_empty - expect(r4).to_not be_empty - - r1 = r1.get - r2 = r2.get - r3 = r3.get - r4 = r4.get - - # Detailed tests for r2. - r2v = r2.vertices - t = OpenStudio::Transformation.alignFace(r2v) - r2a = mod1.poly(r2v, false, false, false, t) - output = mod1.getRealignedFace(r2a) expect(mod1.status).to be_zero - expect(output).to be_a Hash - expect(output).to have_key(:set) - expect(output).to have_key(:box) - expect(output).to have_key(:bbox) - expect(output).to have_key(:t) - expect(output).to have_key(:r) - expect(output).to have_key(:o) - expect(mod1.rectangular?(output[:box])).to be true - - # Reapply translation transformation. - tset = output[:t] * output[:set] - expect(tset.size).to eq(r2v.size) - - tset.each_with_index do |pt, i| - expect(pt.x - output[:set][i].x).to be_within(TOL).of(0.765) # delta-X - expect(pt.y - output[:set][i].y).to be_within(TOL).of(-0.142) # delta-Y - end - - # Reapply rotation transformation. - rset = output[:r] * tset - expect(rset.size).to eq(r2v.size) - rset.each_with_index { |pt, i| expect(mod1.same?(pt, r2a[i])).to be true } - - # Full circle, back to original vertices. - vtx = t * rset - expect(vtx.size).to eq(r2v.size) - vtx.each_with_index { |pt, i| expect(mod1.same?(pt, r2v[i])).to be true } - - # More concise call for generated bounded and bounding boxes. - box = t * (output[:r] * (output[:t] * output[:box])) - bbox = t * (output[:r] * (output[:t] * output[:bbox])) - - # Both bounded and bounding boxes are rectangular. - expect(mod1.rectangular?(box)).to be true - expect(mod1.rectangular?(bbox)).to be true - - # The bounded box fits within the original roof surface limits, while the - # latter fits within its bounding box. Bounded and bounding box sides are - # parallel to each other. - expect(mod1.fits?(box, bbox)).to be true - expect(mod1.fits?(box, vtx)).to be true - expect(mod1.fits?(vtx, bbox)).to be true - - r2_area = OpenStudio.getArea(vtx) - box_area = OpenStudio.getArea(box) - bbox_area = OpenStudio.getArea(bbox) - expect(r2_area).to_not be_empty - expect(box_area).to_not be_empty - expect(bbox_area).to_not be_empty - expect(box_area.get).to be_within(TOL).of(10.96) - expect(r2_area.get).to be_within(TOL).of(16.44) - expect(bbox_area.get).to be_within(TOL).of(19.78) - - # Repeat for r1, more concisely. - r1v = r1.vertices - t = OpenStudio::Transformation.alignFace(r1v) - r1a = mod1.poly(r1v, false, false, false, t) - o = mod1.getRealignedFace(r1a) - expect(o).to be_a Hash - expect(o).to have_key(:set) - expect(o).to have_key(:box) - expect(o).to have_key(:bbox) - expect(o).to have_key(:t) - expect(o).to have_key(:r) - expect(o).to have_key(:o) - width = mod1.width(o[:box]) - height = mod1.height(o[:box]) - area = OpenStudio.getArea(o[:box]) - expect(area).to_not be_empty - area = area.get - expect(area).to be_within(TOL).of(30.81) - expect(area).to be_within(TOL).of(width * height) - vtx = t * (o[:r] * (o[:t] * o[:box])) - expect(mod1.rectangular?(vtx)).to be true - expect(mod1.fits?(vtx, r1)).to be true - - # Realign bounded box. - o2 = mod1.getRealignedFace(o[:box]) - expect(o2).to be_a Hash - expect(o2).to have_key(:set) - expect(o2).to have_key(:box) - expect(o2).to have_key(:t) - expect(o2).to have_key(:r) - expect(o2).to have_key(:o) - # puts o2[:box] - # [8.169, 0.000, 0] - # [8.169, 3.772, 0] - # [0.000, 3.772, 0] - # [0.000, 0.000, 0] - width2 = mod1.width(o2[:box]) - height2 = mod1.height(o2[:box]) - expect(width2).to be_within(TOL).of(width) - expect(height2).to be_within(TOL).of(height) - area2 = OpenStudio.getArea(o2[:box]) - expect(area2).to_not be_empty - area2 = area2.get - expect(area2).to be_within(TOL).of(area) - expect(area2).to be_within(TOL).of(width2 * height2) - vtx = o2[:r] * (o2[:t] * o2[:box]) - expect(mod1.rectangular?(vtx)).to be true - expect(mod1.fits?(vtx, o[:set])).to be true - vtx2 = o[:r] * (o[:t] * vtx) - expect(mod1.fits?(vtx2, r1a)).to be true - vtx3 = t * vtx2 - expect(mod1.fits?(vtx3, r1)).to be true - expect(mod1.status).to be_zero - - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # Test pre-triangulated case. - max = 0 - r1tri = mod1.poly(r1v, false, true, true, true, :cw) - holes = OpenStudio::Point3dVectorVector.new - - OpenStudio.computeTriangulation(r1tri, holes).each do |triangle| - o = mod1.getRealignedFace(triangle) - expect(o).to be_a Hash - expect(o).to have_key(:set) - expect(o).to have_key(:box) - expect(o).to have_key(:t) - expect(o).to have_key(:r) - expect(o).to have_key(:o) - width = mod1.width(o[:box]) - height = mod1.height(o[:box]) - area = OpenStudio.getArea(o[:box]) - expect(area).to_not be_empty - area = area.get - expect(area).to be_within(TOL).of(width * height) - max = [max, area].max - vtx = t * (o[:r] * (o[:t] * o[:box])) - expect(mod1.rectangular?(vtx)).to be true - expect(mod1.fits?(vtx, r1)).to be true - expect(mod1.status).to be_zero - end - - expect(max).to be < 4 # m2 ... significantly less than 30.8m2 end - it "checks flattened 3D points" do - translator = OpenStudio::OSVersion::VersionTranslator.new + it "checks surface fits? & overlaps?" do expect(mod1.reset(DBG)).to eq(DBG) expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) - file = File.join(__dir__, "files/osms/in/seb.osm") - path = OpenStudio::Path.new(file) - model = translator.loadModel(path) - model = model.get - - cl1 = OpenStudio::Model::Model - cl2 = OpenStudio::Point3dVector - cl3 = OpenStudio::Point3d - cl4 = NilClass - m = "OSut::flatten" - m1 = "'points' #{cl4}?" - m2 = "'points' #{cl1}?" - - model.getSurfaces.each do |s| - next unless s.isPartOfEnvelope - next unless s.surfaceType == "RoofCeiling" - - flat = mod1.flatten(s) - expect(flat).to be_a(cl2) - expect(mod1.xyz?(flat, :z)).to be true - expect(mod1.facingUp?(flat)).to be true - expect(s.vertices.first.x).to be_within(TOL).of(flat.first.x) - expect(s.vertices.first.y).to be_within(TOL).of(flat.first.y) + # Tests line intersecting line segments. + sg1 = OpenStudio::Point3dVector.new + sg1 << OpenStudio::Point3d.new(18, 0, 0) + sg1 << OpenStudio::Point3d.new( 8, 3, 0) + + sg2 = OpenStudio::Point3dVector.new + sg2 << OpenStudio::Point3d.new(12, 14, 0) + sg2 << OpenStudio::Point3d.new(12, 6, 0) + + expect(mod1.getLineIntersection(sg1, sg2)).to be_nil + + sg1 = OpenStudio::Point3dVector.new + sg1 << OpenStudio::Point3d.new(0.60,19.06, 0) + sg1 << OpenStudio::Point3d.new(0.60, 0.60, 0) + sg1 << OpenStudio::Point3d.new(0.00, 0.00, 0) + sg1 << OpenStudio::Point3d.new(0.00,19.66, 0) + sgs1 = mod1.getSegments(sg1) + + sg2 = OpenStudio::Point3dVector.new + sg2 << OpenStudio::Point3d.new(9.83, 9.83, 0) + sg2 << OpenStudio::Point3d.new(0.00, 0.00, 0) + sg2 << OpenStudio::Point3d.new(0.00,19.66, 0) + sgs2 = mod1.getSegments(sg2) + + expect(mod1.same?(sg1[2], sg2[1])).to be true + expect(mod1.same?(sg1[3], sg2[2])).to be true + expect(mod1.fits?(sg1, sg2)).to be true + expect(mod1.fits?(sg2, sg1)).to be false + expect( mod1.same?( mod1.overlap(sg1, sg2), sg1) ).to be true + expect( mod1.same?( mod1.overlap(sg2, sg1), sg1) ).to be true + + sg1.each_with_index do |pt, i| + expect(mod1.pointWithinPolygon?(pt, sg2)).to be true end - expect(mod1.status).to be_zero - flat = mod1.flatten(nil) - expect(flat).to be_a(cl2) - expect(flat).to be_empty - expect(mod1.debug?).to be true - expect(mod1.logs.size).to be(1) - expect(mod1.logs.first[:message]).to include(m1) - - expect(mod1.clean!).to eq(DBG) - flat = mod1.flatten(model) - expect(flat).to be_a(cl2) - expect(flat).to be_empty - expect(mod1.debug?).to be true - expect(mod1.logs.size).to be(1) - expect(mod1.logs.first[:message]).to include(m2) - end - - it "checks surface fits? & overlaps?" do - expect(mod1.reset(DBG)).to eq(DBG) - expect(mod1.level).to eq(DBG) - expect(mod1.clean!).to eq(DBG) - # Note: As of OpenStudio v340, the following method is available as an # all-in-one solution to check if a polygon fits within another polygon. # @@ -2039,7 +1863,6 @@ module M # (using OpenStudio Transformation' alignFace method), and set in a # clockwise sequence. OSut sticks to fits? as it executes these steps # behind the scenes, and remains consistent for pre-v340 implementations. - model = OpenStudio::Model::Model.new # 10m x 10m parent vertical (wall) surface. @@ -2050,20 +1873,49 @@ module M vec << OpenStudio::Point3d.new( 10, 0, 10) wall = OpenStudio::Model::Surface.new(vec, model) - # 1m x 2m corner door (with 2x edges along wall edges) + # Side test: point alignment detection, 'w12' == wall/floor edge (vector). + w1 = vec[1] + w2 = vec[2] + w12 = w2 - w1 + + # Side test: same? + vec2 = mod1.to_p3Dv(vec).to_a + expect(vec).to_not eq(vec2) + expect(mod1.same?(vec, vec2)).to be true + + vec2.rotate!(2) + expect(mod1.same?(vec, vec2)).to be true + expect(mod1.same?(vec, vec2, false)).to be false + + # 1m x 2m corner door (with 2x edges along wall edges), 4mm sill. vec = OpenStudio::Point3dVector.new - vec << OpenStudio::Point3d.new( 0, 0, 2) - vec << OpenStudio::Point3d.new( 0, 0, 0) - vec << OpenStudio::Point3d.new( 1, 0, 0) - vec << OpenStudio::Point3d.new( 1, 0, 2) + vec << OpenStudio::Point3d.new( 0.5, 0, 2.000) + vec << OpenStudio::Point3d.new( 0.5, 0, 0.004) + vec << OpenStudio::Point3d.new( 1.5, 0, 0.004) + vec << OpenStudio::Point3d.new( 1.5, 0, 2.000) door1 = OpenStudio::Model::SubSurface.new(vec, model) + # Side test: point alignment detection: + # 'd1_w1': vector from door sill to wall corner 1 ( 0,0,0) + # 'd1_w2': vector from door sill to wall corner 1 (10,0,0) + d1 = vec[1] + d2 = vec[2] + d1_w1 = w1 - d1 + d1_w2 = w2 - d1 + expect(mod1.pointAlongSegments?(d1, [w1, w2])).to be true + # Order of arguments matter. expect(mod1.fits?(door1, wall)).to be true expect(mod1.overlaps?(door1, wall)).to be true expect(mod1.fits?(wall, door1)).to be false expect(mod1.overlaps?(wall, door1)).to be true + # The method 'fits' offers an optional 3rd argument: whether a smaller + # polygon (e.g. door1) needs to 'entirely' fit within the larger polygon. + # Here, door1 shares its sill with the host wall (as its within 10mm of the + # wall bottom edge). + expect(mod1.fits?(door1, wall, true)).to be false + # Another 1m x 2m corner door, yet entirely beyond the wall surface. vec = OpenStudio::Point3dVector.new vec << OpenStudio::Point3d.new( 16, 0, 2) @@ -2088,6 +1940,9 @@ module M # Window fits?, overlaps? expect(mod1.fits?(window, wall)).to be false + olap = mod1.overlap(window, wall) + expect(olap.size).to eq(4) + expect(mod1.fits?(olap, wall)).to be true expect(mod1.overlaps?(window, wall)).to be true expect(mod1.fits?(wall, window)).to be false expect(mod1.overlaps?(wall, window)).to be true @@ -2138,10 +1993,28 @@ module M soffit = soffit.get south = south.get + # Side test: triad, medial and bounded boxes. + pts = mod1.getNonCollinears(ceiling.vertices, 3) + box01 = mod1.triadBox(pts) + box11 = mod1.boundedBox(ceiling) + expect(mod1.same?(box01, box11)).to be true + expect(mod1.fits?(box01, ceiling)).to be true + + pts = mod1.getNonCollinears(roof.vertices, 3) + box02 = mod1.medialBox(pts) + box12 = mod1.boundedBox(roof) + expect(mod1.same?(box02, box12)).to be true + expect(mod1.fits?(box02, roof)).to be true + + box03 = mod1.triadBox(pts) + expect(mod1.same?(box03, box12)).to be false + expect(mod1.status).to be_zero + # For parallel surfaces, OSut's 'overlap' output is consistent regardless # of the sequence of arguments. Here, floor and ceiling are mirrored - the # former counterclockwise, the latter clockwise. The returned overlap # conserves the vertex winding of the first surface. + expect(mod1.parallel?(floor, ceiling)).to be true olap1 = mod1.overlap(floor, ceiling) olap2 = mod1.overlap(ceiling, floor) expect(mod1.same?(floor.vertices, olap1)).to be true @@ -2153,8 +2026,8 @@ module M # the generated overlap will obviously be distorted with respect to the # original soffit vertices. Nonetheless, the shared vertices/edge(s) would # be preserved. - olap1 = mod1.overlap(soffit, roof) - olap2 = mod1.overlap(roof, soffit) + olap1 = mod1.overlap(soffit, roof, true) + olap2 = mod1.overlap(roof, soffit, true) expect(mod1.parallel?(olap1, soffit)).to be true expect(mod1.parallel?(olap1, roof)).to be false expect(mod1.parallel?(olap2, roof)).to be true @@ -2167,7 +2040,7 @@ module M expect(area2).to_not be_empty area1 = area1.get area2 = area2.get - expect(area1 - area2).to be > TOL + expect((area1 - area2).abs).to be > TOL pl1 = OpenStudio::Plane.new(mod1.getNonCollinears(olap1, 3)) pl2 = OpenStudio::Plane.new(mod1.getNonCollinears(olap2, 3)) n1 = pl1.outwardNormal @@ -2175,11 +2048,12 @@ module M expect(soffit.plane.outwardNormal.dot(n1)).to be_within(TOL).of(1) expect( roof.plane.outwardNormal.dot(n2)).to be_within(TOL).of(1) + # When surfaces are neither parallel nor share any edges (e.g. sloped roof # vs horizontal floor), the generated overlap is more likely to hold extra # vertices, depending on which surface it is cast onto. - olap1 = mod1.overlap(floor, roof) - olap2 = mod1.overlap(roof, floor) + olap1 = mod1.overlap(floor, roof, true) + olap2 = mod1.overlap(roof, floor, true) expect(mod1.parallel?(olap1, floor)).to be true expect(mod1.parallel?(olap1, roof)).to be false expect(mod1.parallel?(olap2, roof)).to be true @@ -2237,16 +2111,34 @@ module M end # Overlap between roof and vertically-cast ceiling onto roof plane. - olap02 = mod1.overlap(roof, cast02, false) + olap02 = mod1.overlap(roof, cast02) expect(olap02.size).to eq(3) # not 5 expect(mod1.fits?(olap02, roof)).to be(true) olap02.each { |pt| expect(pl2.pointOnPlane(pt)) } + vtx1 = OpenStudio::Point3dVector.new + vtx1 << OpenStudio::Point3d.new(17.69, 0.00, 0) + vtx1 << OpenStudio::Point3d.new(13.46, 4.46, 0) + vtx1 << OpenStudio::Point3d.new( 4.23, 4.46, 0) + vtx1 << OpenStudio::Point3d.new( 0.00, 0.00, 0) + + vtx2 = OpenStudio::Point3dVector.new + vtx2 << OpenStudio::Point3d.new( 8.85, 0.00, 0) + vtx2 << OpenStudio::Point3d.new( 8.85, 4.46, 0) + vtx2 << OpenStudio::Point3d.new( 4.23, 4.46, 0) + vtx2 << OpenStudio::Point3d.new( 4.23, 0.00, 0) + + expect(mod1.pointAlongSegment?(vtx2[1], [ vtx1[1], vtx1[2] ])).to be true + expect(mod1.pointAlongSegments?(vtx2[1], vtx1)).to be true + expect(mod1.pointWithinPolygon?(vtx2[1], vtx1)).to be true + expect(mod1.fits?(vtx2, vtx1)).to be true + # Bounded box test. cast03 = mod1.cast(ceiling, south, down) expect(mod1.rectangular?(cast03)).to be true - olap03 = mod1.overlap(south, cast03, false) + olap03 = mod1.overlap(south, cast03) + expect(mod1.parallel?(south, olap03)).to be true expect(mod1.rectangular?(olap03)).to be false box = mod1.boundedBox(olap03) expect(mod1.rectangular?(box)).to be true @@ -2367,12 +2259,12 @@ module M expect(mod1.same?(pt, p5)).to be true # Point ENTIRELY within (vs outside) a polygon. - expect(mod1.pointWithinPolygon?(p0, [p0, p1, p2, p3])).to be false - expect(mod1.pointWithinPolygon?(p1, [p0, p1, p2, p3])).to be false - expect(mod1.pointWithinPolygon?(p2, [p0, p1, p2, p3])).to be false - expect(mod1.pointWithinPolygon?(p3, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p0, [p0, p1, p2, p3], true)).to be false + expect(mod1.pointWithinPolygon?(p1, [p0, p1, p2, p3], true)).to be false + expect(mod1.pointWithinPolygon?(p2, [p0, p1, p2, p3], true)).to be false + expect(mod1.pointWithinPolygon?(p3, [p0, p1, p2, p3], true)).to be false expect(mod1.pointWithinPolygon?(p4, [p0, p1, p2, p3])).to be false - expect(mod1.pointWithinPolygon?(p5, [p0, p1, p2, p3])).to be false + expect(mod1.pointWithinPolygon?(p5, [p0, p1, p2, p3])).to be true expect(mod1.pointWithinPolygon?(p6, [p0, p1, p2, p3])).to be false expect(mod1.pointWithinPolygon?(p7, [p0, p1, p2, p3])).to be true expect(mod1.status).to be_zero @@ -2460,7 +2352,7 @@ module M expect(mod1.status).to be_zero end - it "checks ULC" do + it "checks ULC & BLC" do expect(mod1.reset(DBG)).to eq(DBG) expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) @@ -2490,16 +2382,25 @@ module M # 2. OSut ULC reordering. ulc_a_vtx = mod1.ulc(a_vtx) + blc_a_vtx = mod1.blc(a_vtx) ulc_vtx = t * ulc_a_vtx - expect(mod1.status).to be_zero + blc_vtx = t * blc_a_vtx expect(ulc_vtx[1].x.abs).to be < TOL expect(ulc_vtx[1].y.abs).to be < TOL expect(ulc_vtx[1].z.abs).to be < TOL + expect(blc_vtx[0].x.abs).to be < TOL + expect(blc_vtx[0].y.abs).to be < TOL + expect(blc_vtx[0].z.abs).to be < TOL # puts ulc_vtx # [ 0, 0, 10] # [ 0, 0, 0] # [20, 0, 0] # [20, 0, 10] + # puts blc_vtx + # [ 0, 0, 0] + # [20, 0, 0] + # [20, 0, 10] + # [ 0, 0, 10] # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Same, yet (0,0,0) is at index == 0. @@ -2523,26 +2424,37 @@ module M # 2. OSut ULC reordering. ulc_a_vtx = mod1.ulc(a_vtx) + blc_a_vtx = mod1.blc(a_vtx) ulc_vtx = t * ulc_a_vtx - expect(mod1.status).to be_zero + blc_vtx = t * blc_a_vtx expect(ulc_vtx[1].x.abs).to be < TOL expect(ulc_vtx[1].y.abs).to be < TOL expect(ulc_vtx[1].z.abs).to be < TOL + expect(blc_vtx[0].x.abs).to be < TOL + expect(blc_vtx[0].y.abs).to be < TOL + expect(blc_vtx[0].z.abs).to be < TOL # puts ulc_vtx # [ 0, 0, 10] # [ 0, 0, 0] # [20, 0, 0] # [20, 0, 10] + # puts blc_vtx + # [ 0, 0, 0] + # [20, 0, 0] + # [20, 0, 10] + # [ 0, 0, 10] # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Irregular polygon, no point at 0,0,0. vtx = OpenStudio::Point3dVector.new vtx << OpenStudio::Point3d.new(18, 0, 10) vtx << OpenStudio::Point3d.new( 2, 0, 10) - vtx << OpenStudio::Point3d.new( 0, 0, 5) + vtx << OpenStudio::Point3d.new( 0, 0, 6) + vtx << OpenStudio::Point3d.new( 0, 0, 4) vtx << OpenStudio::Point3d.new( 2, 0, 0) vtx << OpenStudio::Point3d.new(18, 0, 0) - vtx << OpenStudio::Point3d.new(20, 0, 5) + vtx << OpenStudio::Point3d.new(20, 0, 4) + vtx << OpenStudio::Point3d.new(20, 0, 6) t = OpenStudio::Transformation.alignFace(vtx) a_vtx = t.inverse * vtx @@ -2552,26 +2464,43 @@ module M ulc_vtx = t * ulc_a_vtx # puts ulc_vtx # [18, 0, 0] - # [20, 0, 5] + # [20, 0, 4] + # [20, 0, 6] # [18, 0, 10] # [ 2, 0, 10] - # [ 0, 0, 5] + # [ 0, 0, 6] + # [ 0, 0, 4] # [ 2, 0, 0] ... consistent pattern with previous cases, yet ULC? # 2. OSut ULC reordering. ulc_a_vtx = mod1.ulc(a_vtx) + blc_a_vtx = mod1.blc(a_vtx) + iN = mod1.nearest(ulc_a_vtx) + iF = mod1.farthest(ulc_a_vtx) + expect(iN).to eq(2) + expect(iF).to eq(6) ulc_vtx = t * ulc_a_vtx - expect(mod1.status).to be_zero - expect(ulc_vtx[1].x.abs).to be < TOL - expect(ulc_vtx[1].y.abs).to be < TOL - expect(ulc_vtx[1].z.abs).to be > TOL + blc_vtx = t * blc_a_vtx + expect(mod1.same?(ulc_vtx[2], ulc_vtx[iN])).to be true + expect(mod1.same?(blc_vtx[1], ulc_vtx[iN])).to be true # puts ulc_vtx + # [ 0, 0, 6] + # [ 0, 0, 4] + # [ 2, 0, 0] + # [18, 0, 0] + # [20, 0, 4] + # [20, 0, 6] + # [18, 0, 10] # [ 2, 0, 10] - # [ 0, 0, 5] + # puts blc_vtx + # [ 0, 0, 4] # [ 2, 0, 0] # [18, 0, 0] - # [20, 0, 5] + # [20, 0, 4] + # [20, 0, 6] # [18, 0, 10] + # [ 2, 0, 10] + # [ 0, 0, 6] # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # vtx = OpenStudio::Point3dVector.new @@ -2581,12 +2510,18 @@ module M vtx << OpenStudio::Point3d.new(70, 0, 0) ulc_vtx = mod1.ulc(vtx) + blc_vtx = mod1.blc(vtx) expect(mod1.status).to be_zero # puts ulc_vtx # [ 0, 45, 0] # [ 0, 0, 0] # [70, 0, 0] # [70, 45, 0] + # puts blc_vtx + # [ 0, 0, 0] + # [70, 0, 0] + # [70, 45, 0] + # [ 0, 45, 0] end it "checks polygon attributes" do @@ -2734,10 +2669,7 @@ module M v = mod1.poly(vtx, true) expect(v).to be_a(OpenStudio::Point3dVector) expect(v).to be_empty - expect(mod1.error?).to be true - expect(mod1.logs.size).to eq(1) - expect(mod1.logs.first[:message]).to include("non-convex") - expect(mod1.clean!).to eq(INF) + expect(mod1.status).to be_zero # 2nd check for (valid) convexity (with collinear points). vtx = OpenStudio::Point3dVector.new @@ -2760,10 +2692,7 @@ module M v = mod1.poly(vtx, true, false, false, false, :ulc) expect(v).to be_a(OpenStudio::Point3dVector) expect(v).to be_empty - expect(mod1.error?).to be true - expect(mod1.logs.size).to eq(1) - expect(mod1.logs.first[:message]).to include("non-convex") - expect(mod1.clean!).to eq(INF) + expect(mod1.status).to be_zero # 3rd check for (valid) convexity (with collinear points), yet returned # 3D points vector become 'aligned' & clockwise. @@ -3382,10 +3311,9 @@ module M sbz = [w1, w2, t1, t2] expect(mod1.addSubs(wall4, sbz)).to be true + puts mod1.logs unless mod1.status.zero? expect(mod1.status).to be_zero - - # Add another 5x (frame÷r-enabled) fixed windows, from either # left- or right-corner of base surfaces. Fetch "Openarea Wall 6". wall6 = model.getSurfaceByName("Openarea 1 Wall 6") @@ -3455,7 +3383,7 @@ module M file = File.join(__dir__, "files/osms/out/seb_ext3a.osm") model.save(file, true) end - + it "checks for space/surface convexity" do translator = OpenStudio::OSVersion::VersionTranslator.new expect(mod1.reset(DBG)).to eq(DBG) @@ -3641,6 +3569,7 @@ module M sub[:count ] = 2 sub[:offset] = offset expect(mod1.addSubs(attic_south, [sub])).to be true + puts mod1.logs unless mod1.status.zero? expect(mod1.status).to be_zero file = File.join(__dir__, "files/osms/out/mini_test.osm") @@ -3674,10 +3603,7 @@ module M expect(mod1.poly(core_floor, true)).to be_empty # now concave expect(mod1.poly(core_ceiling, true)).to be_empty # now concave expect(mod1.poly(attic_floor, true)).to be_empty # now concave - expect(mod1.logs.size).to eq(3) - expect(mod1.error?).to be true - mod1.logs.each { |l| expect(l[:message]).to include("non-convex") } - expect(mod1.clean!).to eq(DBG) + expect(mod1.status).to be_zero shd = model.getShadowCalculation # shadingCalculationMethodValues: @@ -3774,10 +3700,7 @@ module M expect(mod1.poly(core_floor, true)).to be_empty # now concave expect(mod1.poly(core_ceiling, true)).to be_empty # now concave expect(mod1.poly(attic_floor, true)).to be_empty # now concave - expect(mod1.logs.size).to eq(3) - expect(mod1.error?).to be true - mod1.logs.each { |l| expect(l[:message]).to include("non-convex") } - expect(mod1.clean!).to eq(DBG) + expect(mod1.status).to be_zero file = File.join(__dir__, "files/osms/out/mini2.osm") model.save(file, true) @@ -3871,8 +3794,7 @@ module M core_volume = core.floorArea * 3.05 expect(core_volume).to be_within(TOL).of(core.volume) - # OpenStudio volume calculations are fixed as of v351. May need to take a - # deeper dive. + # OpenStudio volume calculations are fixed as of v351. expect(attic.volume).to be_within(TOL).of(949.05) if v < 350 # expect(attic.volume).to be_within(TOL).of(720.19 + 4 * 3.05) unless v < 350 expect(attic.floorArea).to be_within(TOL).of(567.98) # includes overhangs @@ -3880,10 +3802,7 @@ module M expect(mod1.poly(core_floor, true)).to be_empty # now concave expect(mod1.poly(core_ceiling, true)).to be_empty # now concave expect(mod1.poly(attic_floor, true)).to be_empty # now concave - expect(mod1.logs.size).to eq(3) - expect(mod1.error?).to be true - mod1.logs.each { |l| expect(l[:message]).to include("non-convex") } - expect(mod1.clean!).to eq(DBG) + expect(mod1.status).to be_zero file = File.join(__dir__, "files/osms/out/mini3.osm") model.save(file, true) @@ -3991,143 +3910,6 @@ module M expect(roofs.keys).to include(o) expect(roofs[o].downcase).to include("plenum") end - - # "Utility 1" : "Level 0 Utility 1 Ceiling Plenum RoofCeiling" - # "Open area 1" : "Level 0 Open area 1 Ceiling Plenum RoofCeiling" - # "Small office 1" : "Level 0 Small office 1 Ceiling Plenum RoofCeiling" - # "Entry way 1" : "Level 0 Entry way Ceiling Plenum RoofCeiling" - - # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # - # CASE 3: 5x spaces under 4x attic roofs; none "fit", many "overlap". - file = File.join(__dir__, "files/osms/in/smalloffice.osm") - path = OpenStudio::Path.new(file) - model = translator.loadModel(path) - expect(model).to_not be_empty - model = model.get - - occupied = [] - spaces = {} - perimeters = {} - core = [] - - model.getSpaces.each do |space| - next unless space.partofTotalFloorArea - - occupied << space.nameString - - space.surfaces.each do |s| - next unless s.surfaceType.downcase == "roofceiling" - next unless s.outsideBoundaryCondition.downcase == "outdoors" - - expect(spaces).to_not have_key(space.nameString) - spaces[space.nameString] = s.nameString - end - end - - expect(spaces).to be_empty - expect(occupied.size).to eq(5) - - # In-depth testing, as v3.6.1 Boost-based runs differ from older versions. - p4 = model.getSurfaceByName("Perimeter_ZN_4_ceiling") - core = model.getSurfaceByName("Core_ZN_ceiling") - north = model.getSurfaceByName("Attic_roof_north") - east = model.getSurfaceByName("Attic_roof_east") - south = model.getSurfaceByName("Attic_roof_south") - west = model.getSurfaceByName("Attic_roof_west") - - expect(p4).to_not be_empty - expect(core).to_not be_empty - expect(north).to_not be_empty - expect(east).to_not be_empty - expect(south).to_not be_empty - expect(west).to_not be_empty - - p4 = p4.get - core = core.get - north = north.get - east = east.get - south = south.get - west = west.get - - # Tracking copy of the core ceiling for later. - cor = core - - expect(mod1.fits?(p4, core)).to be false - expect(mod1.fits?(p4, north)).to be false - expect(mod1.fits?(p4, east)).to be false - expect(mod1.fits?(p4, west)).to be true # ... it fits - expect(mod1.fits?(p4, south)).to be false - - expect(mod1.overlaps?(p4, core)).to be false - expect(mod1.overlaps?(p4, north)).to be false - expect(mod1.overlaps?(p4, east)).to be false - expect(mod1.overlaps?(p4, west)).to be true # ... since it fits - expect(mod1.overlaps?(p4, south)).to be false - - expect(mod1.fits?(core, p4)).to be false - expect(mod1.fits?(core, north)).to be false - expect(mod1.fits?(core, east)).to be false - expect(mod1.fits?(core, west)).to be false - expect(mod1.fits?(core, south)).to be false - - expect(mod1.overlaps?(core, p4)).to be false - expect(mod1.overlaps?(core, north)).to be true - expect(mod1.overlaps?(core, east)).to be true - expect(mod1.overlaps?(core, west)).to be true - expect(mod1.overlaps?(core, south)).to be true - - model.getSpaces.each do |space| - id = space.nameString - next unless space.partofTotalFloorArea - - rufs = mod1.getRoofs(space) - - rufs.each { |rf| expect(mod1.slopedRoof?(rf)).to be true } - - if id.include?("Perimeter") - expect(rufs.size).to eq(1) - ruf = rufs.first - expect(ruf).to be_a(OpenStudio::Model::Surface) - perimeters[id] = ruf.nameString - - # No horizontal ridges. - ridges = mod1.getHorizontalRidges(rufs) - expect(ridges).to be_a(Array) - expect(ridges).to be_empty - else - expect(id).to include("Core") - expect(rufs.size).to eq(4) - core = rufs - - # 1x horizontal ridge. - ridges = mod1.getHorizontalRidges(core) - expect(ridges).to be_a(Array) - expect(ridges).to_not be_empty - expect(ridges.size).to eq(1) - ridge = ridges.first - - expect(ridge).to have_key(:edge) - expect(ridge).to have_key(:length) - expect(ridge).to have_key(:roofs) - - expect(ridge[:length]).to be_within(TOL).of(9.23) - expect(ridge[:roofs].size).to eq(2) - expect(ridge[:roofs].include?(north)).to be true - expect(ridge[:roofs].include?(south)).to be true - - # Check overlaps with core ceiling. - core.each { |s| expect(mod1.overlap(s, cor)).to_not be_empty } - end - end - - expect(perimeters.size).to eq(4) - expect(core.size).to eq(4) - expect(perimeters.values.all? { |s| s.include?("Attic") }).to be true - expect(core.all? { |s| s.nameString.include?("Attic") }).to be true - core.each { |s| expect(s).to be_a(OpenStudio::Model::Surface) } - expect(perimeters.keys.all? { |s| occupied.include?(s) }).to be true - - expect(mod1.status).to be_zero end it "checks leader line anchors and polygon inserts" do @@ -4141,6 +3923,7 @@ module M # encompasses 4x smaller rectangular polygons (s1 to s4), The vertex # sequence of the 4x smaller polygons is based on their aspect ratios, as # well as their placement vis-à-vis grid origin. + o0 = OpenStudio::Point3d.new( 0, 0, 0) s0 = OpenStudio::Point3dVector.new s0 << OpenStudio::Point3d.new( 2, 16, 20) s0 << OpenStudio::Point3d.new( 2, 2, 20) @@ -4206,6 +3989,15 @@ module M expect(mod1.farthest(s3)).to eq(2) expect(mod1.farthest(s4)).to eq(1) + expect(mod1.nearest(s1, o0)).to eq(3) + expect(mod1.nearest(s2, o0)).to eq(0) + expect(mod1.nearest(s3, o0)).to eq(0) + expect(mod1.nearest(s4, o0)).to eq(3) + expect(mod1.farthest(s1, o0)).to eq(1) + expect(mod1.farthest(s2, o0)).to eq(2) + expect(mod1.farthest(s3, o0)).to eq(2) + expect(mod1.farthest(s4, o0)).to eq(1) + # Box-specific grid instructions. set = [] set << { box: s1, rows: 1, cols: 2, w0: 1.4, d0: 1.4, dX: 0.2, dY: 0.2 } @@ -4217,34 +4009,47 @@ module M area_s2 = set[1][:rows] * set[1][:cols] * set[1][:w0] * set[1][:d0] area_s3 = set[2][:rows] * set[2][:cols] * set[2][:w0] * set[2][:d0] area_s4 = set[3][:rows] * set[3][:cols] * set[3][:w0] * set[3][:d0] + area_s = area_s1 + area_s2 + area_s3 + area_s4 expect(area_s1).to be_within(TOL).of( 3.92) expect(area_s2).to be_within(TOL).of(11.76) expect(area_s3).to be_within(TOL).of( 3.64) expect(area_s4).to be_within(TOL).of( 3.64) - area_s = area_s1 + area_s2 + area_s3 + area_s4 expect(area_s).to be_within(TOL).of(22.96) + ld1 = OpenStudio::Point3d.new(18, 0, 0) + ld2 = OpenStudio::Point3d.new( 8, 3, 0) + sg1 = OpenStudio::Point3d.new(12, 14, 0) + sg2 = OpenStudio::Point3d.new(12, 6, 0) + expect(mod1.getLineIntersection([sg1, sg2], [ld1, ld2])).to be_nil + + t = OpenStudio::Transformation.alignFace(s0) + s00 = t.inverse * s0 + s01 = t.inverse * s1 + s01.each { |pt| expect(mod1.pointWithinPolygon?(pt, s00, true)).to be true } + # Generate leader line anchors, linking set boxes to s0 vertices. n = mod1.genAnchors(s0, set, :box) + puts mod1.logs unless mod1.status.zero? expect(n).to eq(4) + expect(mod1.status).to be_zero - # These tests are successful, given the initial vertex sequencing. A simple - # resequencing of set box vertices could easily prevent genAnchors from - # identifying valid leader line anchor points. + # These previous tests are successful, given the initial vertex sequencing. + # A simple resequencing of set box vertices could easily prevent genAnchors + # from identifying valid leader line anchor points. set.each_with_index do |st, i| expect(st).to have_key(:ld) expect(st[:ld]).to be_a(Hash) expect(st[:ld]).to have_key(s0) expect(st[:ld][s0]).to be_a(OpenStudio::Point3d) - expect(mod1.same?(st[:ld][s0], s0[1])).to be true if i == 0 # 2, 2, 20 + expect(mod1.same?(st[:ld][s0], s0[2])).to be true if i == 0 # 8, 2, 20 expect(mod1.same?(st[:ld][s0], s0[0])).to be true if i == 1 # 2, 16, 20 expect(mod1.same?(st[:ld][s0], s0[4])).to be true if i == 2 # 16, 10, 20 - expect(mod1.same?(st[:ld][s0], s0[5])).to be true if i == 3 # 16, 2, 20 + expect(mod1.same?(st[:ld][s0], s0[6])).to be true if i == 3 # 20, 2, 20 end # Add array of polygon inserts to s0. s00 = mod1.genInserts(s0, set) - puts mod1.logs + puts mod1.logs unless mod1.status.zero? expect(s00).to be_a(OpenStudio::Point3dVector) expect(s00.size).to eq(68) @@ -4276,7 +4081,7 @@ module M sX_area += st_area - # As mentioned earlier, box vertex sequencing is key in successfully + # As discussed earlier, box vertex sequencing is key in successfully # identifying leader line anchors for each set. Boxes remain unchanged. st[:box].each do |pt| expect(mod1.same?(st[:box], s1)).to be true if i == 0 @@ -4343,6 +4148,7 @@ module M # "GROSS ROOF AREA" (GRA), as per 90.1/NECB - excludes roof overhangs (60m2) gra1 = mod1.grossRoofArea(model.getSpaces) + puts mod1.logs unless mod1.status.zero? expect(mod1.status).to be_zero expect(gra1.round(2)).to eq(538.86) From 76c80d937214f98cbe0569e7f374192194788acd Mon Sep 17 00:00:00 2001 From: rd2 Date: Tue, 19 Mar 2024 14:53:06 -0400 Subject: [PATCH 03/13] GitHub Action fix + offset parameter fix --- .github/workflows/pull_request.yml | 2 +- lib/osut/utils.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a513a5a..f6682b1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -112,7 +112,7 @@ jobs: echo $(pwd) echo $(ls) docker pull nrel/openstudio:3.7.0 - docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:d3.7.0 + docker run --name test --rm -d -t -v $(pwd):/work -w /work nrel/openstudio:3.7.0 docker exec -t test pwd docker exec -t test ls docker exec -t test bundle update diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index 9966c79..dfcc573 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -6375,7 +6375,7 @@ def addSkyLights(spaces = [], opts = {}) # TO DO: expand the method to factor in cases where simple 'side' # cutouts can be supported (no need for leader lines), e.g. # skylight strips along roof ridges. - box = offset(box, -gap, 340) + box = offset(box, -gap, 300) box = poly(box, false, false, false, false, :blc) next if box.empty? From b8e4b668c9670daf169a861c53b13fc37f7cabdc Mon Sep 17 00:00:00 2001 From: rd2 Date: Wed, 20 Mar 2024 07:40:53 -0400 Subject: [PATCH 04/13] Updates copyright (2024) --- LICENSE | 2 +- lib/osut.rb | 2 +- lib/osut/utils.rb | 2 +- lib/osut/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index b24b2b1..f8d9959 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2022-2023, Denis Bourgeois +Copyright (c) 2022-2024, Denis Bourgeois All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/lib/osut.rb b/lib/osut.rb index 232a471..4772084 100644 --- a/lib/osut.rb +++ b/lib/osut.rb @@ -1,6 +1,6 @@ # BSD 3-Clause License # -# Copyright (c) 2022-2023, Denis Bourgeois +# Copyright (c) 2022-2024, Denis Bourgeois # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index dfcc573..6e93a02 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -1,6 +1,6 @@ # BSD 3-Clause License # -# Copyright (c) 2022-2023, Denis Bourgeois +# Copyright (c) 2022-2024, Denis Bourgeois # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/lib/osut/version.rb b/lib/osut/version.rb index 7baa8ae..622bb96 100644 --- a/lib/osut/version.rb +++ b/lib/osut/version.rb @@ -1,6 +1,6 @@ # BSD 3-Clause License # -# Copyright (c) 2022-2023, Denis Bourgeois +# Copyright (c) 2022-2024, Denis Bourgeois # All rights reserved. # # Redistribution and use in source and binary forms, with or without From a4d49b0cb6173acc28f007ee246e0d4a83a5a0e9 Mon Sep 17 00:00:00 2001 From: rd2 Date: Sat, 6 Apr 2024 17:20:45 -0400 Subject: [PATCH 05/13] Offset/fits/overlaps with F&D --- spec/osut_tests_spec.rb | 175 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 4 deletions(-) diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index 6d7bb93..a5cc13d 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -1847,8 +1847,8 @@ module M expect(mod1.same?(sg1[3], sg2[2])).to be true expect(mod1.fits?(sg1, sg2)).to be true expect(mod1.fits?(sg2, sg1)).to be false - expect( mod1.same?( mod1.overlap(sg1, sg2), sg1) ).to be true - expect( mod1.same?( mod1.overlap(sg2, sg1), sg1) ).to be true + expect(mod1.same?(mod1.overlap(sg1, sg2), sg1)).to be true + expect(mod1.same?(mod1.overlap(sg2, sg1), sg1)).to be true sg1.each_with_index do |pt, i| expect(mod1.pointWithinPolygon?(pt, sg2)).to be true @@ -2048,7 +2048,6 @@ module M expect(soffit.plane.outwardNormal.dot(n1)).to be_within(TOL).of(1) expect( roof.plane.outwardNormal.dot(n2)).to be_within(TOL).of(1) - # When surfaces are neither parallel nor share any edges (e.g. sloped roof # vs horizontal floor), the generated overlap is more likely to hold extra # vertices, depending on which surface it is cast onto. @@ -2151,6 +2150,174 @@ module M area1 = area1.get area2 = area2.get expect((100 * area2 / area1).to_i).to eq(68) # % + expect(mod1.status).to be_zero + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # Testing more complex cases, e.g. triangular windows, irregular 4-side + # windows, rough opening edges overlapping parent surface edges. These tests + # were initially part of the TBD Tests repository (github.com/rd2/tbd_tests), + # yet have been upgraded and are now tested here. + model = OpenStudio::Model::Model.new + space = OpenStudio::Model::Space.new(model) + space.setName("Space") + + # Windows are SimpleGlazing constructions. + fen = OpenStudio::Model::Construction.new(model) + glazing = OpenStudio::Model::SimpleGlazing.new(model) + layers = OpenStudio::Model::MaterialVector.new + fen.setName("FD fen") + glazing.setName("FD glazing") + expect(glazing.setUFactor(2.0)).to be true + layers << glazing + expect(fen.setLayers(layers)).to be true + + # Frame & Divider object. + w000 = 0.000 + w200 = 0.200 # 0mm to 200mm (wide!) around glazing + fd = OpenStudio::Model::WindowPropertyFrameAndDivider.new(model) + fd.setName("FD") + expect(fd.setFrameConductance(0.500)).to be true + expect(fd.isFrameWidthDefaulted).to be true + expect(fd.frameWidth).to be_within(TOL).of(w000) + + # A square base wall surface: + v0 = OpenStudio::Point3dVector.new + v0 << OpenStudio::Point3d.new( 0.00, 0.00, 10.00) + v0 << OpenStudio::Point3d.new( 0.00, 0.00, 0.00) + v0 << OpenStudio::Point3d.new(10.00, 0.00, 0.00) + v0 << OpenStudio::Point3d.new(10.00, 0.00, 10.00) + + # A first triangular window: + v1 = OpenStudio::Point3dVector.new + v1 << OpenStudio::Point3d.new( 2.00, 0.00, 8.00) + v1 << OpenStudio::Point3d.new( 1.00, 0.00, 6.00) + v1 << OpenStudio::Point3d.new( 4.00, 0.00, 9.00) + + # A larger, irregular window: + v2 = OpenStudio::Point3dVector.new + v2 << OpenStudio::Point3d.new( 7.00, 0.00, 4.00) + v2 << OpenStudio::Point3d.new( 4.00, 0.00, 1.00) + v2 << OpenStudio::Point3d.new( 8.00, 0.00, 2.00) + v2 << OpenStudio::Point3d.new( 9.00, 0.00, 3.00) + + # A final triangular window, near the wall's upper right corner: + v3 = OpenStudio::Point3dVector.new + v3 << OpenStudio::Point3d.new( 9.00, 0.00, 9.80) + v3 << OpenStudio::Point3d.new( 9.80, 0.00, 9.00) + v3 << OpenStudio::Point3d.new( 9.80, 0.00, 9.80) + + w0 = OpenStudio::Model::Surface.new(v0, model) + w1 = OpenStudio::Model::SubSurface.new(v1, model) + w2 = OpenStudio::Model::SubSurface.new(v2, model) + w3 = OpenStudio::Model::SubSurface.new(v3, model) + w0.setName("w0") + w1.setName("w1") + w2.setName("w2") + w3.setName("w3") + expect(w0.setSpace(space)).to be true + sub_gross = 0 + + [w1, w2, w3].each do |w| + expect(w.setSubSurfaceType("FixedWindow")).to be true + expect(w.setSurface(w0)).to be true + expect(w.setConstruction(fen)).to be true + expect(w.uFactor).to_not be_empty + expect(w.uFactor.get).to be_within(0.1).of(2.0) + expect(w.allowWindowPropertyFrameAndDivider).to be true + expect(w.setWindowPropertyFrameAndDivider(fd)).to be true + width = w.windowPropertyFrameAndDivider.get.frameWidth + expect(width).to be_within(TOL).of(w000) + + sub_gross += w.grossArea + end + + expect(w1.grossArea).to be_within(TOL).of(1.50) + expect(w2.grossArea).to be_within(TOL).of(6.00) + expect(w3.grossArea).to be_within(TOL).of(0.32) + expect(w0.grossArea).to be_within(TOL).of(100.00) + expect(w1.netArea).to be_within(TOL).of(w1.grossArea) + expect(w2.netArea).to be_within(TOL).of(w2.grossArea) + expect(w3.netArea).to be_within(TOL).of(w3.grossArea) + expect(w0.netArea).to be_within(TOL).of(w0.grossArea - sub_gross) + + # Applying 2 sets of alterations: + # - without, then with Frame & Dividers (F&D) + # - 3 successive 20° rotations around: + angle = Math::PI / 9 + origin = OpenStudio::Point3d.new(0, 0, 0) + east = OpenStudio::Point3d.new(1, 0, 0) - origin + up = OpenStudio::Point3d.new(0, 0, 1) - origin + north = OpenStudio::Point3d.new(0, 1, 0) - origin + + 4.times.each do |i| # successive rotations + unless i.zero? + r = OpenStudio.createRotation(origin, east, angle) if i == 1 + r = OpenStudio.createRotation(origin, up, angle) if i == 2 + r = OpenStudio.createRotation(origin, north, angle) if i == 3 + expect(w0.setVertices(r.inverse * w0.vertices)).to be true + expect(w1.setVertices(r.inverse * w1.vertices)).to be true + expect(w2.setVertices(r.inverse * w2.vertices)).to be true + expect(w3.setVertices(r.inverse * w3.vertices)).to be true + end + + 2.times.each do |j| # F&D + if j.zero? + wx = w000 + fd.resetFrameWidth unless i.zero? + else + wx = w200 + expect(fd.setFrameWidth(wx)).to be true + + [w1, w2, w3].each do |w| + width = w.windowPropertyFrameAndDivider.get.frameWidth + expect(width).to be_within(TOL).of(wx) + end + end + + # F&D widths offset window vertices. + w1o = mod1.offset(w1.vertices, wx, 300) + w2o = mod1.offset(w2.vertices, wx, 300) + w3o = mod1.offset(w3.vertices, wx, 300) + + w1o_m2 = OpenStudio.getArea(w1o) + w2o_m2 = OpenStudio.getArea(w2o) + w3o_m2 = OpenStudio.getArea(w3o) + expect(w1o_m2).to_not be_empty + expect(w2o_m2).to_not be_empty + expect(w3o_m2).to_not be_empty + w1o_m2 = w1o_m2.get + w2o_m2 = w2o_m2.get + w3o_m2 = w3o_m2.get + + if j.zero? + expect(w1o_m2).to be_within(TOL).of(w1.grossArea) # 1.50 m2 + expect(w2o_m2).to be_within(TOL).of(w2.grossArea) # 6.00 m2 + expect(w3o_m2).to be_within(TOL).of(w3.grossArea) # 0.32 m2 + else + expect(w1o_m2).to be_within(TOL).of(3.75) + expect(w2o_m2).to be_within(TOL).of(8.64) + expect(w3o_m2).to be_within(TOL).of(1.10) + end + + # All windows entirely fit within the wall (without F&D). + [w1, w2, w3].each { |w| expect(mod1.fits?(w, w0, true)).to be true } + + # All windows fit within the wall (with F&D). + [w1o, w2o].each { |w| expect(mod1.fits?(w, w0)).to be true } + + # If F&D frame width == 200mm, w3o aligns along the wall top & side, + # so not entirely within wall polygon. + expect(mod1.fits?(w3, w0, true)).to be true + expect(mod1.fits?(w3o, w0)).to be true + expect(mod1.fits?(w3o, w0, true)).to be true if j.zero? + expect(mod1.fits?(w3o, w0, true)).to be false unless j.zero? + + # None of the windows conflict with each other. + expect(mod1.overlaps?(w1o, w2o)).to be false + expect(mod1.overlaps?(w1o, w3o)).to be false + expect(mod1.overlaps?(w2o, w3o)).to be false + end + end expect(mod1.status).to be_zero end @@ -3383,7 +3550,7 @@ module M file = File.join(__dir__, "files/osms/out/seb_ext3a.osm") model.save(file, true) end - + it "checks for space/surface convexity" do translator = OpenStudio::OSVersion::VersionTranslator.new expect(mod1.reset(DBG)).to eq(DBG) From 97f328e8accc744e7e4ced4efd770652ccab35ef Mon Sep 17 00:00:00 2001 From: rd2 Date: Sun, 7 Apr 2024 10:21:46 -0400 Subject: [PATCH 06/13] Tests narrow overlaps/intersections --- spec/osut_tests_spec.rb | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index a5cc13d..d0514cd 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -1818,7 +1818,62 @@ module M expect(mod1.reset(DBG)).to eq(DBG) expect(mod1.level).to eq(DBG) expect(mod1.clean!).to eq(DBG) + v = OpenStudio.openStudioVersion.split(".").join.to_i + p1 = OpenStudio::Point3dVector.new + p2 = OpenStudio::Point3dVector.new + + p1 << OpenStudio::Point3d.new(3.63, 0, 4.03) + p1 << OpenStudio::Point3d.new(3.63, 0, 2.44) + p1 << OpenStudio::Point3d.new(7.34, 0, 2.44) + p1 << OpenStudio::Point3d.new(7.34, 0, 4.03) + + t = OpenStudio::Transformation.alignFace(p1) + + if v < 340 + p2 << OpenStudio::Point3d.new(3.63, 0, 2.49) + p2 << OpenStudio::Point3d.new(3.63, 0, 1.00) + p2 << OpenStudio::Point3d.new(7.34, 0, 1.00) + p2 << OpenStudio::Point3d.new(7.34, 0, 2.49) + else + p2 << OpenStudio::Point3d.new(3.63, 0, 2.47) + p2 << OpenStudio::Point3d.new(3.63, 0, 1.00) + p2 << OpenStudio::Point3d.new(7.34, 0, 1.00) + p2 << OpenStudio::Point3d.new(7.34, 0, 2.47) + end + + area1 = OpenStudio.getArea(p1) + area2 = OpenStudio.getArea(p2) + expect(area1).to_not be_empty + expect(area2).to_not be_empty + area1 = area1.get + area2 = area2.get + + p1a = t.inverse * p1 + p2a = t.inverse * p2 + + union = OpenStudio.join(p1a.reverse, p2a.reverse, TOL2) + expect(union).to_not be_empty + union = union.get + area = OpenStudio.getArea(union) + expect(area).to_not be_empty + area = area.get + delta = area1 + area2 - area + + res = OpenStudio.intersect(p1a.reverse, p2a.reverse, TOL) + expect(res).to_not be_empty + res = res.get + res1 = res.polygon1 + expect(res1).to_not be_empty + + res1_m2 = OpenStudio.getArea(res1) + expect(res1_m2).to_not be_empty + res1_m2 = res1_m2.get + expect(res1_m2).to be_within(TOL2).of(delta) + expect(mod1.overlaps?(p1a, p2a)).to be true + expect(mod1.status).to be_zero + + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # Tests line intersecting line segments. sg1 = OpenStudio::Point3dVector.new sg1 << OpenStudio::Point3d.new(18, 0, 0) From f45de6aa895948ed0d7659fd708d75b20447fea4 Mon Sep 17 00:00:00 2001 From: rd2 Date: Tue, 9 Apr 2024 08:37:47 -0400 Subject: [PATCH 07/13] Relaxes getNonCollinears check --- lib/osut/utils.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index 6e93a02..27260e5 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -3099,7 +3099,7 @@ def getNonCollinears(pts = nil, n = 0) p3 = pts[i3] v13 = p3 - p1 v12 = p2 - p1 - next if v12.cross(v13).length < TOL + next if v12.cross(v13).length < TOL2 a << p2 end From 130131911194c369786396a1038da5d474bde102 Mon Sep 17 00:00:00 2001 From: rd2 Date: Tue, 9 Apr 2024 17:27:46 -0400 Subject: [PATCH 08/13] Removes weather file entries --- spec/files/osms/in/smalloffice.osm | 14 -------------- spec/files/osms/in/warehouse.osm | 14 -------------- 2 files changed, 28 deletions(-) diff --git a/spec/files/osms/in/smalloffice.osm b/spec/files/osms/in/smalloffice.osm index c454951..c7be3bd 100644 --- a/spec/files/osms/in/smalloffice.osm +++ b/spec/files/osms/in/smalloffice.osm @@ -7647,20 +7647,6 @@ OS:Exterior:Lights, , !- Multiplier General; !- End-Use Subcategory -OS:WeatherFile, - {2a877a71-2768-41da-a77e-945540ea5fea}, !- Handle - El Paso International Ap Ut, !- City - TX, !- State Province Region - USA, !- Country - TMY3, !- Data Source - 722700, !- WMO Number - 31, !- Latitude {deg} - -106, !- Longitude {deg} - -7, !- Time Zone {hr} - 1186, !- Elevation {m} - file:///Users/denisbourgeois/Documents/projets/tbd/spec/test_suite_runs/SmallOffice_poor (BC Hydro)/000_Create DOE Prototype Building/extracted_files/weather/USA_TX_El.Paso.Intl.AP.722700_TMY3.epw, !- Url - CC705637; !- Checksum - OS:Site:WaterMainsTemperature, {a35e14d9-5d4c-4890-9782-5ba38e0f2485}, !- Handle Correlation, !- Calculation Method diff --git a/spec/files/osms/in/warehouse.osm b/spec/files/osms/in/warehouse.osm index 5cc3b49..f13cfaf 100644 --- a/spec/files/osms/in/warehouse.osm +++ b/spec/files/osms/in/warehouse.osm @@ -6862,20 +6862,6 @@ OS:Exterior:Lights, , !- Multiplier General; !- End-Use Subcategory -OS:WeatherFile, - {52950113-279c-43ea-8ca5-eaa3e259f719}, !- Handle - El Paso International Ap Ut, !- City - TX, !- State Province Region - USA, !- Country - TMY3, !- Data Source - 722700, !- WMO Number - 31, !- Latitude {deg} - -106, !- Longitude {deg} - -7, !- Time Zone {hr} - 1186, !- Elevation {m} - file:///Users/denisbourgeois/Documents/projets/tbd/spec/test_suite_runs/Warehouse_skip/000_Create DOE Prototype Building/extracted_files/weather/USA_TX_El.Paso.Intl.AP.722700_TMY3.epw, !- Url - CC705637; !- Checksum - OS:Site:WaterMainsTemperature, {f339eb8f-5ea9-4997-92d7-b17696e0fc51}, !- Handle Correlation, !- Calculation Method From 246f83b7433040e171e2ea4c80d229845904bd53 Mon Sep 17 00:00:00 2001 From: rd2 Date: Thu, 11 Apr 2024 09:57:23 -0400 Subject: [PATCH 09/13] Fixes nilled dX/dY (addSkylights) --- lib/osut/utils.rb | 7 ++-- spec/osut_tests_spec.rb | 76 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index 27260e5..ed365ae 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -7077,8 +7077,8 @@ def addSkyLights(spaces = [], opts = {}) set[:d ] = set[pattern][:wy ] set[:w0 ] = set[pattern][:wxl ] set[:d0 ] = set[pattern][:wyl ] - set[:dX ] = set[pattern][:dX ] - set[:dY ] = set[pattern][:dY ] + set[:dX ] = set[pattern][:dX ] if set[pattern][:dX] + set[:dY ] = set[pattern][:dY ] if set[pattern][:dY] end end end @@ -7264,6 +7264,7 @@ def addSkyLights(spaces = [], opts = {}) end end + # New direct roof loop. No overlaps, so no need for relative space # coordinate adjustments. rooms.each do |space, room| @@ -7276,8 +7277,6 @@ def addSkyLights(spaces = [], opts = {}) next unless set.key?(:rows) next unless set.key?(:d) next unless set.key?(:w) - next unless set.key?(:dX) - next unless set.key?(:dY) next unless set.key?(:tight) next unless set[:roof] == roof diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index d0514cd..7a43278 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -4392,14 +4392,14 @@ module M # CASE 1: # Retrieve core GRA. As with overhangs, only the attic roof sections # directly-above the core are retained for SRR% calculations. Here, the - # GRA is substantially lower (than gra1). For now, calculated GRA is only - # valid BEFORE adding skylight wells. + # GRA is substantially lower (than previously-calculated gra1). For now, + # calculated GRA is only valid BEFORE adding skylight wells. gra_attic = mod1.grossRoofArea(core) expect(mod1.status).to be_zero expect(gra_attic.round(2)).to eq(157.77) # The method returns the GRA, calculated BEFORE adding skylights/wells. - rm2 = mod1.addSkyLights(core, {srr: srr }) + rm2 = mod1.addSkyLights(core, {srr: srr}) puts mod1.logs unless mod1.status.zero? expect(mod1.status).to be_zero expect(rm2.round(2)).to eq(gra_attic.round(2)) @@ -4453,7 +4453,7 @@ module M # New core skylight areas. Although the total skylight area is greater than # in CASE 1, the method is unable to meet the requested SRR 5%. This is # understandable given the constrained roof/core overlap vs the ~4x greater - # roof area. A plenum vastly larger than the room(s) it serves would is rare. + # roof area. A plenum vastly larger than the room(s) it serves is rare. core_skies = mod1.facets(core, "Outdoors", "Skylight") sky_area2 = core_skies.sum(&:grossArea) expect(sky_area2.round(2)).to eq(8.93) @@ -4506,6 +4506,72 @@ module M sky_area2 = core_skies.sum(&:grossArea) expect(sky_area2.round(2)).to eq(0.00) + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # + # SEB case (flat ceiling plenum). + file = File.join(__dir__, "files/osms/in/seb.osm") + path = OpenStudio::Path.new(file) + model = translator.loadModel(path) + expect(model).to_not be_empty + model = model.get + + entry = model.getSpaceByName("Entry way 1") + office = model.getSpaceByName("Small office 1") + open = model.getSpaceByName("Open area 1") + utility = model.getSpaceByName("Utility 1") + plenum = model.getSpaceByName("Level 0 Ceiling Plenum") + expect(entry).to_not be_empty + expect(office).to_not be_empty + expect(open).to_not be_empty + expect(utility).to_not be_empty + expect(plenum).to_not be_empty + entry = entry.get + office = office.get + open = open.get + utility = utility.get + plenum = plenum.get + expect(plenum.partofTotalFloorArea).to be false + expect(mod1.unconditioned?(plenum)).to be false + + # TOTAL plenum roof area (4x surfaces), no overhangs. + roofs = mod1.facets(plenum, "Outdoors", "RoofCeiling") + total = roofs.sum(&:grossArea) + expect(total.round(2)).to eq(82.21) + + gra_seb = mod1.grossRoofArea(model.getSpaces) + expect(mod1.status).to be_zero + expect(gra_seb.round(2)).to eq(total.round(2)) + + srr = 0.04 + + # The method returns the GRA, calculated BEFORE adding skylights/wells. + rm2 = mod1.addSkyLights(model.getSpaces, {srr: srr}) + puts mod1.logs unless mod1.status.zero? + expect(mod1.status).to be_zero + expect(rm2.round(2)).to eq(gra_seb.round(2)) + + entry_skies = mod1.facets(entry, "Outdoors", "Skylight") + office_skies = mod1.facets(office, "Outdoors", "Skylight") + utility_skies = mod1.facets(utility, "Outdoors", "Skylight") + open_skies = mod1.facets(open, "Outdoors", "Skylight") + + expect(entry_skies).to be_empty + expect(office_skies).to be_empty + expect(utility_skies.size).to eq(1) + expect(open_skies.size).to eq(1) + utility_sky = utility_skies.first + open_sky = open_skies.first + + skm2 = utility_sky.grossArea + open_sky.grossArea + expect((skm2 / rm2).round(2)).to eq(srr) + + # Assign construction to skylights. + construction = mod1.genConstruction(model, {type: :skylight, uo: 2.8}) + expect(utility_sky.setConstruction(construction)).to be true + expect(open_sky.setConstruction(construction)).to be true + + file = File.join(__dir__, "files/osms/out/seb_sky.osm") + model.save(file, true) + # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # file = File.join(__dir__, "files/osms/in/warehouse.osm") path = OpenStudio::Path.new(file) @@ -4561,8 +4627,8 @@ module M bulk_skies = mod1.facets(bulk, "Outdoors", "Skylight") sky_area2 = bulk_skies.sum(&:grossArea) - ratio2 = sky_area2 / rm2 expect(sky_area2.round(2)).to eq(128.19) + ratio2 = sky_area2 / rm2 expect(ratio2.round(2)).to eq(srr) file = File.join(__dir__, "files/osms/out/warehouse_sky.osm") From f1011b9953a226bdd59b26405486054f992bec60 Mon Sep 17 00:00:00 2001 From: rd2 Date: Thu, 11 Apr 2024 13:06:19 -0400 Subject: [PATCH 10/13] Insulates skylight well wall constructions --- lib/osut/utils.rb | 1 + spec/osut_tests_spec.rb | 44 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index ed365ae..d57735e 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -264,6 +264,7 @@ def genConstruction(model = nil, specs = {}) a[:clad][:d ] = d a[:clad][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" + # TO DO: replace sheathing by mineral below a certain Uo factor. mt = :sheathing a[:compo][:mat] = @@mats[mt] a[:compo][:d ] = d diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index 7a43278..accd67d 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -4411,6 +4411,45 @@ module M ratio = sky_area1 / rm2 expect(ratio.round(2)).to eq(srr) + # Assign insulated constructions to new skylight well walls. + drywall = OpenStudio::Model::StandardOpaqueMaterial.new(model) + drywall.setName("drywall") + expect(drywall.setThickness(0.015)) + expect(drywall.setRoughness("MediumSmooth")) + expect(drywall.setConductivity(0.160)) + expect(drywall.setDensity(785)) + expect(drywall.setSpecificHeat(1090)) + expect(drywall.setThermalAbsorptance(0.9)) + expect(drywall.setSolarAbsorptance(0.7)) + expect(drywall.setVisibleAbsorptance(0.7)) + + composite = OpenStudio::Model::StandardOpaqueMaterial.new(model) + composite.setName("composite") + expect(composite.setThickness(0.100)) + expect(composite.setRoughness("MediumSmooth")) + expect(composite.setConductivity(0.030)) + expect(composite.setDensity(40)) + expect(composite.setSpecificHeat(960)) + expect(composite.setThermalAbsorptance(0.9)) + expect(composite.setSolarAbsorptance(0.7)) + expect(composite.setVisibleAbsorptance(0.7)) + + layers = OpenStudio::Model::OpaqueMaterialVector.new + layers << drywall + layers << composite + layers << drywall + construction = OpenStudio::Model::Construction.new(layers) + expect(mod1.rsi(construction, 0.240)).to be_within(TOL).of(3.76) + + mod1.facets(attic, "Surface", "Wall").each do |wall| + expect(wall.setConstruction(construction)).to be true + adj = wall.adjacentSurface + expect(adj).to_not be_empty + adj = adj.get + expect(wall.setConstruction(construction)).to be true + expect(adj.setConstruction(construction)).to be true + end + file = File.join(__dir__, "files/osms/out/office_attic.osm") model.save(file, true) @@ -4564,7 +4603,7 @@ module M skm2 = utility_sky.grossArea + open_sky.grossArea expect((skm2 / rm2).round(2)).to eq(srr) - # Assign construction to skylights. + # Assign construction to new skylights. construction = mod1.genConstruction(model, {type: :skylight, uo: 2.8}) expect(utility_sky.setConstruction(construction)).to be true expect(open_sky.setConstruction(construction)).to be true @@ -4615,8 +4654,7 @@ module M expect(sky_area1.round(2)).to eq(47.57) expect(ratio1.round(2)).to eq(0.01) - srr = 0.04 - + srr = 0.04 opts = {} opts[:srr ] = srr opts[:size ] = 2.4 From 158a702c1b03c8e080a4be1b77c3834e4b00972d Mon Sep 17 00:00:00 2001 From: rd2 Date: Mon, 15 Apr 2024 13:17:14 -0400 Subject: [PATCH 11/13] Revised design/tests for genConstruction --- lib/osut/utils.rb | 208 +++++++++++++------------ spec/osut_tests_spec.rb | 338 ++++++++++++++++++++++++++-------------- 2 files changed, 329 insertions(+), 217 deletions(-) diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index d57735e..96ae4e4 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -61,14 +61,14 @@ module OSut # - thickness : 0.1 m # - thermal conductivity (k ) : 0.1 W/m.K # - density (rho) : 0.1 kg/m3 - # - specific heat (cp ) : 1400.0 J/kg.K + # - specific heat (cp ) : 1400.0 J/kg•K # # https://s3.amazonaws.com/openstudio-sdk-documentation/cpp/ # OpenStudio-3.6.1-doc/model/html/ # classopenstudio_1_1model_1_1_standard_opaque_material.html # # ... apart from surface roughness, rarely would these material properties be - # suitable - and are therefore explicitely set below. On roughness: + # suitable - and are therefore explicitly set below. On roughness: # - "Very Rough" : stucco # - "Rough" : brick # - "Medium Rough" : concrete @@ -76,7 +76,7 @@ module OSut # - "Smooth" : smooth plaster # - "Very Smooth" : glass - # thermal mass categories (e.g. exterior cladding, interior finish, framing) + # Thermal mass categories (e.g. exterior cladding, interior finish, framing). @@mass = [ :none, # token for 'no user selection', resort to defaults :light, # e.g. 16mm drywall interior @@ -84,43 +84,42 @@ module OSut :heavy # e.g. 200mm poured concrete ].freeze - # basic materials (StandardOpaqueMaterials only) + # Basic materials (StandardOpaqueMaterials only). @@mats = { + material: {}, # generic, e.g. lightweight cladding over furring, fibreboard sand: {}, concrete: {}, brick: {}, - cladding: {}, # e.g. lightweight cladding over furring - sheathing: {}, # e.g. plywood + drywall: {}, # e.g. finished drywall, intermediate sheating + mineral: {}, # e.g. light, semi-rigid rock wool insulation polyiso: {}, # e.g. polyisocyanurate panel (or similar) - cellulose: {}, # e.g. blown, dry/stabilized fiber - mineral: {}, # e.g. semi-rigid rock wool insulation - drywall: {}, + cellulose: {}, # e.g. blown, dry/stabilized fibre door: {} # single composite material (45mm insulated steel door) }.freeze - # default inside+outside air film resistances (m2.K/W) + # default inside + outside air film resistances (m2.K/W) @@film = { shading: 0.000, # NA - partition: 0.000, - wall: 0.150, - roof: 0.140, - floor: 0.190, - basement: 0.120, - slab: 0.160, - door: 0.150, - window: 0.150, # ignored if SimpleGlazingMaterial - skylight: 0.140 # ignored if SimpleGlazingMaterial + partition: 0.150, # uninsulated wood- or steel-framed wall + wall: 0.150, # un/insulated wall + roof: 0.140, # un/insulated roof + floor: 0.190, # un/insulated (exposed) floor + basement: 0.120, # un/insulated basement wall + slab: 0.160, # un/insulated basement slab or slab-on-grade + door: 0.150, # standard, 45mm insulated steel (opaque) door + window: 0.150, # vertical fenestration, e.g. glazed doors, windows + skylight: 0.140 # e.g. domed 4' x 4' skylight }.freeze # default (~1980s) envelope Uo (W/m2•K), based on surface type @@uo = { - shading: 0.000, # N/A - partition: 0.000, # N/A - wall: 0.384, # rated Ro ~14.8 hr•ft2F/Btu - roof: 0.327, # rated Ro ~17.6 hr•ft2F/Btu - floor: 0.317, # rated Ro ~17.9 hr•ft2F/Btu (exposed floor) - basement: 0.000, # uninsulated - slab: 0.000, # uninsulated + shading: nil, # N/A + partition: nil, # N/A + wall: 0.384, # rated R14.8 hr•ft2F/Btu + roof: 0.327, # rated R17.6 hr•ft2F/Btu + floor: 0.317, # rated R17.9 hr•ft2F/Btu (exposed floor) + basement: nil, + slab: nil, door: 1.800, # insulated, unglazed steel door (single layer) window: 2.800, # e.g. patio doors (simple glazing) skylight: 3.500 # all skylight technologies @@ -128,9 +127,9 @@ module OSut # Standard opaque materials, taken from a variety of sources (e.g. energy # codes, NREL's BCL). Material identifiers are symbols, e.g.: - # - :brick # - :sand # - :concrete + # - :brick # # Material properties remain largely constant between projects. What does # tend to vary (between projects) are thicknesses. Actual OpenStudio opaque @@ -146,7 +145,12 @@ module OSut # - solar (sol) : 70% # - visible (vis) : 70% # - # These can also be explicitly set, here (e.g. a redundant 'sand' example): + # These can also be explicitly set (see :sand). + @@mats[:material ][:rgh] = "MediumSmooth" + @@mats[:material ][:k ] = 0.115 + @@mats[:material ][:rho] = 540.000 + @@mats[:material ][:cp ] = 1200.000 + @@mats[:sand ][:rgh] = "Rough" @@mats[:sand ][:k ] = 1.290 @@mats[:sand ][:rho] = 2240.000 @@ -165,14 +169,13 @@ module OSut @@mats[:brick ][:rho] = 1600.000 @@mats[:brick ][:cp ] = 790.000 - @@mats[:cladding ][:rgh] = "MediumSmooth" - @@mats[:cladding ][:k ] = 0.115 - @@mats[:cladding ][:rho] = 540.000 - @@mats[:cladding ][:cp ] = 1200.000 + @@mats[:drywall ][:k ] = 0.160 + @@mats[:drywall ][:rho] = 785.000 + @@mats[:drywall ][:cp ] = 1090.000 - @@mats[:sheathing][:k ] = 0.160 - @@mats[:sheathing][:rho] = 545.000 - @@mats[:sheathing][:cp ] = 1210.000 + @@mats[:mineral ][:k ] = 0.050 + @@mats[:mineral ][:rho] = 19.000 + @@mats[:mineral ][:cp ] = 960.000 @@mats[:polyiso ][:k ] = 0.025 @@mats[:polyiso ][:rho] = 25.000 @@ -183,27 +186,19 @@ module OSut @@mats[:cellulose][:rho] = 80.000 @@mats[:cellulose][:cp ] = 835.000 - @@mats[:mineral ][:k ] = 0.050 - @@mats[:mineral ][:rho] = 19.000 - @@mats[:mineral ][:cp ] = 960.000 - - @@mats[:drywall ][:k ] = 0.160 - @@mats[:drywall ][:rho] = 785.000 - @@mats[:drywall ][:cp ] = 1090.000 - @@mats[:door ][:rgh] = "MediumSmooth" @@mats[:door ][:k ] = 0.080 @@mats[:door ][:rho] = 600.000 @@mats[:door ][:cp ] = 1000.000 ## - # Generates an OpenStudio multilayered construction; materials if needed. + # Generates an OpenStudio multilayered construction, + materials if needed. # # @param model [OpenStudio::Model::Model] a model # @param [Hash] specs OpenStudio construction specifications # @option specs [#to_s] :id ("") construction identifier # @option specs [Symbol] :type (:wall), see @@uo - # @option specs [Numeric] :uo clear-field Uo, in W/m2.K, see @@uo + # @option specs [Numeric] :uo assembly clear-field Uo, in W/m2•K, see @@uo # @option specs [Symbol] :clad (:light) exterior cladding, see @@mass # @option specs [Symbol] :frame (:light) assembly framing, see @@mass # @option specs [Symbol] :finish (:light) interior finishing, see @@mass @@ -217,18 +212,22 @@ def genConstruction(model = nil, specs = {}) return mismatch("model", model, cl1, mth) unless model.is_a?(cl1) return mismatch("specs", specs, cl2, mth) unless specs.is_a?(cl2) - specs[:id ] = "" unless specs.key?(:id ) + specs[:id] = "" unless specs.key?(:id) + id = trim(specs[:id]) + id = "OSut|CON|#{specs[:type]}" if id.empty? + specs[:type] = :wall unless specs.key?(:type) chk = @@uo.keys.include?(specs[:type]) return invalid("surface type", mth, 2, ERR) unless chk - id = trim(specs[:id]) - id = "OSut|CON|#{specs[:type]}" if id.empty? specs[:uo] = @@uo[ specs[:type] ] unless specs.key?(:uo) u = specs[:uo] - 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 + + 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 + end # Optional specs. Log/reset if invalid. specs[:clad ] = :light unless specs.key?(:clad ) # exterior @@ -252,31 +251,41 @@ def genConstruction(model = nil, specs = {}) case specs[:type] when :shading - mt = :sheathing + mt = :material d = 0.015 a[:compo][:mat] = @@mats[mt] a[:compo][:d ] = d a[:compo][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" when :partition - d = 0.015 - mt = :drywall - a[:clad][:mat] = @@mats[mt] - a[:clad][:d ] = d - a[:clad][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" + unless specs[:clad] == :none + d = 0.015 + mt = :drywall + a[:clad][:mat] = @@mats[mt] + a[:clad][:d ] = d + a[:clad][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" + end - # TO DO: replace sheathing by mineral below a certain Uo factor. - mt = :sheathing + d = 0.015 + d = 0.100 if specs[:frame] == :medium + d = 0.200 if specs[:frame] == :heavy + d = 0.100 if u + mt = :concrete + mt = :material if specs[:frame] == :light + mt = :mineral if u a[:compo][:mat] = @@mats[mt] a[:compo][:d ] = d a[:compo][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" - mt = :drywall - a[:finish][:mat] = @@mats[mt] - a[:finish][:d ] = d - a[:finish][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" + unless specs[:finish] == :none + d = 0.015 + mt = :drywall + a[:finish][:mat] = @@mats[mt] + a[:finish][:d ] = d + a[:finish][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" + end when :wall unless specs[:clad] == :none - mt = :cladding + mt = :material mt = :brick if specs[:clad] == :medium mt = :concrete if specs[:clad] == :heavy d = 0.100 @@ -287,18 +296,22 @@ def genConstruction(model = nil, specs = {}) end mt = :drywall - mt = :polyiso if specs[:frame] == :medium - mt = :mineral if specs[:frame] == :heavy + mt = :mineral if specs[:frame] == :medium + mt = :polyiso if specs[:frame] == :heavy d = 0.100 - d = 0.015 if specs[:frame] == :light + d = 0.015 if specs[:frame] == :light a[:sheath][:mat] = @@mats[mt] a[:sheath][:d ] = d a[:sheath][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" - mt = :concrete - mt = :mineral if specs[:frame] == :light + mt = :mineral + mt = :cellulose if specs[:frame] == :medium + mt = :concrete if specs[:frame] == :heavy + mt = :material unless u d = 0.100 d = 0.200 if specs[:frame] == :heavy + d = 0.015 unless u + a[:compo][:mat] = @@mats[mt] a[:compo][:d ] = d a[:compo][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" @@ -316,25 +329,21 @@ def genConstruction(model = nil, specs = {}) when :roof unless specs[:clad] == :none mt = :concrete - mt = :cladding if specs[:clad] == :light + mt = :material if specs[:clad] == :light d = 0.015 d = 0.100 if specs[:clad] == :medium # e.g. terrace d = 0.200 if specs[:clad] == :heavy # e.g. parking garage a[:clad][:mat] = @@mats[mt] a[:clad][:d ] = d a[:clad][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" - - mt = :sheathing - d = 0.015 - a[:sheath][:mat] = @@mats[mt] - a[:sheath][:d ] = d - a[:sheath][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" end - mt = :cellulose + mt = :mineral mt = :polyiso if specs[:frame] == :medium - mt = :mineral if specs[:frame] == :heavy + mt = :cellulose if specs[:frame] == :heavy + mt = :material unless u d = 0.100 + d = 0.015 unless u a[:compo][:mat] = @@mats[mt] a[:compo][:d ] = d a[:compo][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" @@ -349,32 +358,28 @@ def genConstruction(model = nil, specs = {}) a[:finish][:d ] = d a[:finish][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" end - when :floor # exposed + when :floor unless specs[:clad] == :none - mt = :cladding + mt = :material d = 0.015 a[:clad][:mat] = @@mats[mt] a[:clad][:d ] = d a[:clad][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" - - mt = :sheathing - d = 0.015 - a[:sheath][:mat] = @@mats[mt] - a[:sheath][:d ] = d - a[:sheath][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" end - mt = :cellulose + mt = :mineral mt = :polyiso if specs[:frame] == :medium - mt = :mineral if specs[:frame] == :heavy - d = 0.100 # possibly an insulating layer to reset + mt = :cellulose if specs[:frame] == :heavy + mt = :material unless u + d = 0.100 + d = 0.015 unless u a[:compo][:mat] = @@mats[mt] a[:compo][:d ] = d a[:compo][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" unless specs[:finish] == :none mt = :concrete - mt = :sheathing if specs[:finish] == :light + mt = :material if specs[:finish] == :light d = 0.015 d = 0.100 if specs[:finish] == :medium d = 0.200 if specs[:finish] == :heavy @@ -382,7 +387,7 @@ def genConstruction(model = nil, specs = {}) a[:finish][:d ] = d a[:finish][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" end - when :slab # basement slab or slab-on-grade + when :slab mt = :sand d = 0.100 a[:clad][:mat] = @@mats[mt] @@ -405,18 +410,18 @@ def genConstruction(model = nil, specs = {}) a[:compo][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" unless specs[:finish] == :none - mt = :sheathing + mt = :material d = 0.015 a[:finish][:mat] = @@mats[mt] a[:finish][:d ] = d a[:finish][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" end - when :basement # wall + when :basement unless specs[:clad] == :none mt = :concrete - mt = :sheathing if specs[:clad] == :light + mt = :material if specs[:clad] == :light d = 0.100 - d = 0.015 if specs[:clad] == :light + d = 0.015 if specs[:clad] == :light a[:clad][:mat] = @@mats[mt] a[:clad][:d ] = d a[:clad][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" @@ -453,16 +458,14 @@ def genConstruction(model = nil, specs = {}) a[:finish][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" end end - when :door # opaque - # 45mm insulated (composite) steel door. + when :door mt = :door d = 0.045 a[:compo ][:mat ] = @@mats[mt] a[:compo ][:d ] = d a[:compo ][:id ] = "OSut|#{mt}|#{format('%03d', d*1000)[-3..-1]}" - when :window # e.g. patio doors (simple glazing) - # SimpleGlazingMaterial. + when :window a[:glazing][:u ] = specs[:uo ] a[:glazing][:shgc] = 0.450 a[:glazing][:shgc] = specs[:shgc] if specs.key?(:shgc) @@ -470,7 +473,6 @@ def genConstruction(model = nil, specs = {}) a[:glazing][:id ] += "|U#{format('%.1f', a[:glazing][:u])}" a[:glazing][:id ] += "|SHGC#{format('%d', a[:glazing][:shgc]*100)}" when :skylight - # SimpleGlazingMaterial. a[:glazing][:u ] = specs[:uo ] a[:glazing][:shgc] = 0.450 a[:glazing][:shgc] = specs[:shgc] if specs.key?(:shgc) @@ -530,7 +532,7 @@ def genConstruction(model = nil, specs = {}) # Adjust insulating layer thickness or conductivity to match requested Uo. unless glazed ro = 0 - ro = 1 / specs[:uo] - @@film[ specs[:type] ] if specs[:uo] > 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 @@ -5097,7 +5099,7 @@ def daylit?(space = nil, sidelit = true, toplit = true, baselit = true) (walls + roofs + floors).each do |surface| surface.subSurfaces.each do |sub| # All fenestrated subsurface types are considered, as user can set these - # explicitely (e.g. skylight in a wall) in OpenStudio. + # explicitly (e.g. skylight in a wall) in OpenStudio. return true if fenestration?(sub) end end @@ -5954,7 +5956,7 @@ def addSkyLights(spaces = [], opts = {}) # # For the simple case below (steep 4-sided hip roof, UNENCLOSED ventilated # attic), 90.1 users typically choose between either: - # 1. modelling the ventilated attic explicitely, or + # 1. modelling the ventilated attic explicitly, or # 2. ignoring the ventilated attic altogether. # # If skylights were added to the model, option (1) would require one or more diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index accd67d..a1495d3 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -20,167 +20,277 @@ expect(cls1.reset(DBG)).to eq(DBG) expect(cls1.level).to eq(DBG) expect(cls1.clean!).to eq(DBG) + mass = cls1.class_variable_get(:@@mass) + mats = cls1.class_variable_get(:@@mats) + film = cls1.class_variable_get(:@@film) + uo = cls1.class_variable_get(:@@uo) + model = OpenStudio::Model::Model.new + uo1 = 2.140 + uo2 = 0.214 + uo3 = 3.566 + uo4 = 4.812 + uo5 = 3.765 + uo6 = 3.698 + uo7 = 4.244 + uo8 = uo[:door] + uo9 = 0.900 + + # Typical uninsulated, framed cavity wall, suitable for light interzone + # assemblies (i.e. symmetrical, 3-layer construction). + specs = {type: :partition} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(3) + u = 1 / cls1.rsi(surface, film[:partition]) + expect(u).to be_within(TOL).of(uo1) + expect(surface.layers.first).to eq(surface.layers.last) - model = OpenStudio::Model::Model.new - specs = {} - specs[:type ] = :wall - specs[:uo ] = 0.210 # NECB2017 - surface = cls1.genConstruction(model, specs) + # An alternative to (uninsulated) :partition (+inputs, same outcome). + specs = {type: :wall, clad: :none, uo: nil} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) - expect(surface.layers.size).to eq(4) - u = 1 / cls1.rsi(surface, 0.140) - expect(u).to be_within(TOL).of(specs[:uo]) + expect(surface.layers.size).to eq(3) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo1) + expect(surface.layers.first).to eq(surface.layers.last) - specs[:type ] = :roof - specs[:uo ] = 1 / 5.46 # CCQ I1 - surface = cls1::genConstruction(model, specs) + # Insulated :partition variant. + specs = {type: :partition, uo: uo2} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) - expect(surface.layers.size).to eq(4) - u = 1 / cls1::rsi(surface, 0.140) - expect(u).to be_within(TOL).of(specs[:uo]) - - specs = {} - specs[:type ] = :roof - specs[:frame ] = :medium - specs[:finish] = :heavy - specs[:uo ] = 1 / 5.46 # CCQ I1 - surface = cls1::genConstruction(model, specs) + expect(surface.layers.size).to eq(3) + u = 1 / cls1.rsi(surface, film[:partition]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers.first).to eq(surface.layers.last) + + # An alternative to (insulated) :partition (+inputs, same outcome). + specs = {type: :wall, uo: uo2, clad: :none} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) - expect(surface.layers.size).to eq(4) - u = 1 / cls1::rsi(surface, 0.140) - expect(u).to be_within(TOL).of(specs[:uo]) - - specs = {} - specs[:type ] = :floor - specs[:frame ] = :medium - specs[:uo ] = 1 / 5.46 # CCQ I1 - surface = cls1::genConstruction(model, specs) + expect(surface.layers.size).to eq(3) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers.first).to eq(surface.layers.last) + + # A wall inherits a 4th (cladding) layer, by default. + specs = {type: :wall, uo: uo2} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(4) - u = 1 / cls1::rsi(surface, 0.190) - expect(u).to be_within(TOL).of(specs[:uo]) - - specs = {} - specs[:type ] = :slab - specs[:frame ] = :none - specs[:finish] = :none - surface = cls1::genConstruction(model, specs) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers.first).to_not eq(surface.layers.last) + + # Otherwise, a wall has a minimum of 2 layers. + specs = {type: :wall, uo: uo2, clad: :none, finish: :none} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(2) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers.first).to_not eq(surface.layers.last) - specs = {} - specs[:type ] = :slab - specs[:finish] = :none - specs[:uo ] = 0.379 # NECB2020, ZC8 - surface = cls1::genConstruction(model, specs) + # Default shading material. + specs = {type: :shading} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(1) + expect(surface.layers.first.nameString).to eq("OSut|material|015") + + # A single-layered, uninsulated e.g. 5/8" :partition (alternative :shading). + specs = {type: :partition, clad: :none, finish: :none} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(1) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo3) + expect(surface.layers.first.nameString).to eq("OSut|material|015") + + # A single-layered, uninsulated e.g. 4" concrete :partition. + specs = {type: :partition, clad: :none, finish: :none, frame: :medium} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(1) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo4) + expect(surface.layers.first.nameString).to eq("OSut|concrete|100") + + # A single-layered, uninsulated e.g. 8" concrete :partition. + specs = {type: :partition, clad: :none, finish: :none, frame: :heavy} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(1) + u = 1 / cls1.rsi(surface, film[:wall]) + expect(u).to be_within(TOL).of(uo5) + expect(surface.layers.first.nameString).to eq("OSut|concrete|200") + + # A light (minimal, 1x layer), uninsulated attic roof (alternative: shading). + specs = {type: :roof, uo: nil, clad: :none, finish: :none} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(1) + u = 1 / cls1.rsi(surface, film[:roof]) + expect(u).to be_within(TOL).of(uo6) + + # Insulated, cathredral ceiling construction (alternative :shading). + specs = {type: :roof, uo: uo2} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(3) - u = 1 / cls1::rsi(surface, 0.160) - expect(u).to be_within(TOL).of(specs[:uo]) + u = 1 / cls1.rsi(surface, film[:roof]) + expect(u).to be_within(TOL).of(uo2) - specs = {} - specs[:type ] = :slab - specs[:uo ] = 0.379 # NECB2020, ZC8 - surface = cls1::genConstruction(model, specs) + # Insulated, unfinished outdoor-facing plenum roof (polyiso above 4" slab). + specs = {type: :roof, uo: uo2, frame: :medium, finish: :medium} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) - expect(surface.layers.size).to eq(4) - u = 1 / cls1::rsi(surface, 0.160) - expect(u).to be_within(TOL).of(specs[:uo]) - - specs = {} - specs[:type ] = :basement - specs[:clad ] = :heavy - specs[:uo ] = 1 / 2.64 # CCQ I1 - surface = cls1::genConstruction(model, specs) + expect(surface.layers.size).to eq(3) + u = 1 / cls1.rsi(surface, film[:roof]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers[1].nameString).to eq("OSut|polyiso|108") + expect(surface.layers[2].nameString).to eq("OSut|concrete|100") + + # Insulated, parking garage roof (polyiso above 8" slab). + specs = {type: :roof, uo: uo2, clad: :heavy, frame: :medium, finish: :none} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(2) + u = 1 / cls1.rsi(surface, film[:roof]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers[0].nameString).to eq("OSut|concrete|200") + expect(surface.layers[1].nameString).to eq("OSut|polyiso|110") + + # Uninsulated plenum ceiling tiles (alternative :shading). + specs = {type: :roof, uo: nil, clad: :none, finish: :none} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(1) + u = 1 / cls1.rsi(surface, film[:roof]) + expect(u).to be_within(TOL).of(uo6) + + # Unfinished, insulated, framed attic floor (blown cellulose). + specs = {type: :floor, uo: uo2, frame: :heavy, finish: :none} + surface = cls1.genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(2) + u = 1 / cls1.rsi(surface, film[:floor]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers[1].nameString).to eq("OSut|cellulose|217") + + # Finished, insulated exposed floor (e.g. wood-framed, residential). + specs = {type: :floor, uo: uo2} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(3) - u = 1 / cls1::rsi(surface, 0.120) - expect(u).to be_within(TOL).of(specs[:uo]) - - specs = {} - specs[:type ] = :basement - specs[:clad ] = :none - specs[:finish] = :light - specs[:uo ] = 1 / 2.64 # CCQ I1 - surface = cls1::genConstruction(model, specs) + u = 1 / cls1.rsi(surface, film[:floor]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers[1].nameString).to eq("OSut|mineral|211") + + # Finished, insulated exposed floor (e.g. 4" slab, steel web joists). + specs = {type: :floor, uo: uo2, finish: :medium} + surface = cls1.genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(3) - u = 1 / cls1::rsi(surface, 0.120) - expect(u).to be_within(TOL).of(specs[:uo]) - - specs = {} - specs[:type ] = :door - specs[:frame ] = :medium # ... should be ignored - specs[:uo ] = 1.8 - surface = cls1::genConstruction(model, specs) + u = 1 / cls1.rsi(surface, film[:floor]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers[1].nameString).to eq("OSut|mineral|214") + expect(surface.layers[2].nameString).to eq("OSut|concrete|100") + + # Uninsulated slab-on-grade. + specs = {type: :slab, frame: :none, finish: :none} + surface = cls1::genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(2) + expect(surface.layers[0].nameString).to eq("OSut|sand|100") + expect(surface.layers[1].nameString).to eq("OSut|concrete|100") + + # Insulated slab-on-grade. + specs = {type: :slab, uo: uo2, finish: :none} + surface = cls1::genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(3) + u = 1 / cls1::rsi(surface, film[:slab]) + expect(u).to be_within(TOL).of(uo2) + expect(surface.layers[0].nameString).to eq("OSut|sand|100") + expect(surface.layers[1].nameString).to eq("OSut|polyiso|109") + expect(surface.layers[2].nameString).to eq("OSut|concrete|100") + + # 8" uninsulated basement wall. + specs = {type: :basement, clad: :none, finish: :none} + surface = cls1::genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(1) - u = 1 / cls1::rsi(surface, 0.150) - expect(u).to be_within(TOL).of(specs[:uo]) + u = 1 / cls1::rsi(surface, film[:basement]) + expect(surface.layers[0].nameString).to eq("OSut|concrete|200") + expect(u).to be_within(TOL).of(uo7) - specs = {} - specs[:type ] = :door - specs[:uo ] = 0.9 # CCQ I1 - surface = cls1::genConstruction(model, specs) + # 8" interior-insulated, finished basement wall. + specs = {type: :basement, uo: 2 * uo2, clad: :none} + surface = cls1::genConstruction(model, specs) + expect(surface).to_not be_nil + expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) + expect(surface.layers.size).to eq(3) + u = 1 / cls1::rsi(surface, film[:basement]) + expect(u).to be_within(TOL).of(2 * uo2) + expect(surface.layers[0].nameString).to eq("OSut|concrete|200") + expect(surface.layers[1].nameString).to eq("OSut|mineral|100") + expect(surface.layers[2].nameString).to eq("OSut|drywall|015") + + # Standard, insulated steel door (default Uo = 1.8 W/K•m). + specs = {type: :door} + surface = cls1::genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(1) - u = 1 / cls1::rsi(surface, 0.150) - expect(u).to be_within(TOL).of(specs[:uo]) + u = 1 / cls1::rsi(surface, film[:door]) + expect(u).to be_within(TOL).of(uo8) - specs = {} - specs[:type ] = :window - specs[:uo ] = 2.0 - surface = cls1::genConstruction(model, specs) + specs = {type: :door, uo: uo9} + surface = cls1::genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(1) - u = 1 / cls1::rsi(surface) # not necessary to specify film - expect(u).to be_within(TOL).of(specs[:uo]) + u = 1 / cls1::rsi(surface, film[:door]) + expect(u).to be_within(TOL).of(uo9) - specs = {} - specs[:type ] = :window - specs[:uo ] = 0.9 # CCQ I1 - surface = cls1::genConstruction(model, specs) + specs = {type: :window, uo: uo9} + surface = cls1::genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(1) - u = 1 / cls1::rsi(surface) - expect(u).to be_within(TOL).of(specs[:uo]) + u = 1 / cls1::rsi(surface) # not necessary to specify film + expect(u).to be_within(TOL).of(uo9) - specs = {} - specs[:type ] = :skylight - specs[:uo ] = 2.8 # CCQ I1 - surface = cls1::genConstruction(model, specs) + specs = {type: :skylight, uo: uo9} + surface = cls1::genConstruction(model, specs) expect(surface).to_not be_nil - expect(cls1.status).to be_zero expect(surface).to be_a(OpenStudio::Model::LayeredConstruction) expect(surface.layers.size).to eq(1) u = 1 / cls1::rsi(surface) - expect(u).to be_within(TOL).of(specs[:uo]) + expect(u).to be_within(TOL).of(uo9) + + expect(cls1.status.zero?).to be true end it "checks internal mass" do From 0aa21a63b57caa2ba02af2669beb37c45c44c142 Mon Sep 17 00:00:00 2001 From: rd2 Date: Tue, 16 Apr 2024 10:42:55 -0400 Subject: [PATCH 12/13] (Better) manages attic interzone constructions --- lib/osut/utils.rb | 1 - spec/osut_tests_spec.rb | 113 ++++++++++++++++------------------------ 2 files changed, 45 insertions(+), 69 deletions(-) diff --git a/lib/osut/utils.rb b/lib/osut/utils.rb index 96ae4e4..2b38034 100644 --- a/lib/osut/utils.rb +++ b/lib/osut/utils.rb @@ -7267,7 +7267,6 @@ def addSkyLights(spaces = [], opts = {}) end end - # New direct roof loop. No overlaps, so no need for relative space # coordinate adjustments. rooms.each do |space, room| diff --git a/spec/osut_tests_spec.rb b/spec/osut_tests_spec.rb index a1495d3..ae61294 100644 --- a/spec/osut_tests_spec.rb +++ b/spec/osut_tests_spec.rb @@ -425,7 +425,7 @@ expect(cls1.status).to be_zero expect(cls1.logs).to be_empty end - + it "checks if a set holds a construction" do translator = OpenStudio::OSVersion::VersionTranslator.new expect(mod1.clean!).to eq(DBG) @@ -4450,6 +4450,30 @@ module M core = [] attic = [] + # Fetch default construction sets. + oID = "90.1-2010 - SmOffice - ASHRAE 169-2013-3B" # building + aID = "90.1-2010 - - Attic - ASHRAE 169-2013-3B" # attic spacetype level + o_set = model.getDefaultConstructionSetByName(oID) + a_set = model.getDefaultConstructionSetByName(oID) + expect(o_set).to_not be_empty + expect(a_set).to_not be_empty + o_set = o_set.get + a_set = a_set.get + expect(o_set.defaultInteriorSurfaceConstructions).to_not be_empty + expect(a_set.defaultInteriorSurfaceConstructions).to_not be_empty + io_set = o_set.defaultInteriorSurfaceConstructions.get + ia_set = a_set.defaultInteriorSurfaceConstructions.get + expect(io_set.wallConstruction).to_not be_empty + expect(ia_set.wallConstruction).to_not be_empty + io_wall = io_set.wallConstruction.get.to_LayeredConstruction + ia_wall = ia_set.wallConstruction.get.to_LayeredConstruction + expect(io_wall).to_not be_empty + expect(ia_wall).to_not be_empty + io_wall = io_wall.get + ia_wall = ia_wall.get + expect(io_wall).to eq(ia_wall) # 2x drywall layers + expect(mod1.rsi(io_wall, 0.150)).to be_within(TOL).of(0.31) + model.getSpaces.each do |space| id = space.nameString @@ -4480,7 +4504,6 @@ module M # "GROSS ROOF AREA" (GRA), as per 90.1/NECB - excludes roof overhangs (60m2) gra1 = mod1.grossRoofArea(model.getSpaces) - puts mod1.logs unless mod1.status.zero? expect(mod1.status).to be_zero expect(gra1.round(2)).to eq(538.86) @@ -4494,8 +4517,8 @@ module M # 2. INDIRECTLY-CONDITIONED (e.g. plenum) # # For testing purposes, only the core zone is targeted for skylight wells. - # Context: NECBs and 90.1 require separate SRR% calculations for spaces - # conditioned differently (SEMI-CONDITIONED vs CONDITIONED). + # Context: NECBs and 90.1 require separate SRR% calculations for + # differently conditioned spaces (SEMI-CONDITIONED vs CONDITIONED). # See 'addSkyLights' doc. # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # @@ -4505,13 +4528,10 @@ module M # GRA is substantially lower (than previously-calculated gra1). For now, # calculated GRA is only valid BEFORE adding skylight wells. gra_attic = mod1.grossRoofArea(core) - expect(mod1.status).to be_zero expect(gra_attic.round(2)).to eq(157.77) # The method returns the GRA, calculated BEFORE adding skylights/wells. rm2 = mod1.addSkyLights(core, {srr: srr}) - puts mod1.logs unless mod1.status.zero? - expect(mod1.status).to be_zero expect(rm2.round(2)).to eq(gra_attic.round(2)) # New core skylight areas. Successfully achieved SRR%. @@ -4521,44 +4541,11 @@ module M ratio = sky_area1 / rm2 expect(ratio.round(2)).to eq(srr) - # Assign insulated constructions to new skylight well walls. - drywall = OpenStudio::Model::StandardOpaqueMaterial.new(model) - drywall.setName("drywall") - expect(drywall.setThickness(0.015)) - expect(drywall.setRoughness("MediumSmooth")) - expect(drywall.setConductivity(0.160)) - expect(drywall.setDensity(785)) - expect(drywall.setSpecificHeat(1090)) - expect(drywall.setThermalAbsorptance(0.9)) - expect(drywall.setSolarAbsorptance(0.7)) - expect(drywall.setVisibleAbsorptance(0.7)) - - composite = OpenStudio::Model::StandardOpaqueMaterial.new(model) - composite.setName("composite") - expect(composite.setThickness(0.100)) - expect(composite.setRoughness("MediumSmooth")) - expect(composite.setConductivity(0.030)) - expect(composite.setDensity(40)) - expect(composite.setSpecificHeat(960)) - expect(composite.setThermalAbsorptance(0.9)) - expect(composite.setSolarAbsorptance(0.7)) - expect(composite.setVisibleAbsorptance(0.7)) - - layers = OpenStudio::Model::OpaqueMaterialVector.new - layers << drywall - layers << composite - layers << drywall - construction = OpenStudio::Model::Construction.new(layers) - expect(mod1.rsi(construction, 0.240)).to be_within(TOL).of(3.76) - - mod1.facets(attic, "Surface", "Wall").each do |wall| - expect(wall.setConstruction(construction)).to be true - adj = wall.adjacentSurface - expect(adj).to_not be_empty - adj = adj.get - expect(wall.setConstruction(construction)).to be true - expect(adj.setConstruction(construction)).to be true - end + # Reset attic default construction set for insulated interzone walls. + construction = mod1.genConstruction(model, {type: :partition, uo: 0.3}) + expect(mod1.rsi(construction, 0.150)).to be_within(TOL).of(1/0.3) + expect(ia_set.setWallConstruction(construction)).to be true + expect(mod1.status).to be_zero file = File.join(__dir__, "files/osms/out/office_attic.osm") model.save(file, true) @@ -4588,21 +4575,18 @@ module M expect(mod1.setpoints(attic)[:cooling]).to be_within(TOL).of(23.89) # Here, GRA includes ALL plenum roof surfaces (not just vertically-cast - # areas onto core ceiling). This will make meeting the SRR% of 5% much - # harder. + # areas onto core ceiling). This makes meeting the SRR% of 5% much harder. gra_plenum = mod1.grossRoofArea(core) - expect(mod1.status).to be_zero expect(gra_plenum.round(2)).to eq(total.round(2)) rm2 = mod1.addSkyLights(core, {srr: srr}) - puts mod1.logs unless mod1.status.zero? - expect(mod1.status).to be_zero expect(rm2.round(2)).to eq(total.round(2)) - # New core skylight areas. Although the total skylight area is greater than - # in CASE 1, the method is unable to meet the requested SRR 5%. This is + # New core skylight areas. The total skylight area is greater than in CASE 1, + # and the method is unable to meet the requested SRR 5%. This is # understandable given the constrained roof/core overlap vs the ~4x greater - # roof area. A plenum vastly larger than the room(s) it serves is rare. + # roof area. A plenum vastly larger than the room(s) it serves is rare, but + # certainly problematic for the application of the NECBs. core_skies = mod1.facets(core, "Outdoors", "Skylight") sky_area2 = core_skies.sum(&:grossArea) expect(sky_area2.round(2)).to eq(8.93) @@ -4628,7 +4612,7 @@ module M core = core.get attic = attic.get - # Again, tag attic as an INDIRECTLY-CONDITIONED space. + # Again, tagging attic as an INDIRECTLY-CONDITIONED space. key = "indirectlyconditioned" val = core.nameString expect(attic.additionalProperties.setFeature(key, val)).to be true @@ -4636,24 +4620,21 @@ module M expect(mod1.unconditioned?(attic)).to be false expect(mod1.setpoints(attic)[:heating]).to be_within(TOL).of(21.11) expect(mod1.setpoints(attic)[:cooling]).to be_within(TOL).of(23.89) - expect(mod1.status).to be_zero gra_plenum = mod1.grossRoofArea(core) - expect(mod1.status).to be_zero expect(gra_plenum.round(2)).to eq(total.round(2)) - # Conflicting argument case: Here, the method can only add skylight wells - # through model "plenums" (in this context, :plenum is an all encompassing - # keyword for any INDIRECTLY-CONDITIONED, unoccupied space). Yet by passing - # option "plenum: false", the method is instructed to skip "plenum" - # skylight wells altogether. + # Conflicting argument case: Here, skylight wells must traverse plenums (in + # this context, :plenum is an all encompassing keyword for any INDIRECTLY- + # CONDITIONED, unoccupied space). Yet by passing option "plenum: false", + # the method is instructed to skip "plenum" skylight wells altogether. rm2 = mod1.addSkyLights(core, {srr: srr, plenum: false}) - expect(mod1.status).to be_zero expect(rm2.round(2)).to eq(total.round(2)) core_skies = mod1.facets(core, "Outdoors", "Skylight") sky_area2 = core_skies.sum(&:grossArea) expect(sky_area2.round(2)).to eq(0.00) + expect(mod1.status).to be_zero # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # # SEB case (flat ceiling plenum). @@ -4687,15 +4668,12 @@ module M expect(total.round(2)).to eq(82.21) gra_seb = mod1.grossRoofArea(model.getSpaces) - expect(mod1.status).to be_zero expect(gra_seb.round(2)).to eq(total.round(2)) srr = 0.04 # The method returns the GRA, calculated BEFORE adding skylights/wells. rm2 = mod1.addSkyLights(model.getSpaces, {srr: srr}) - puts mod1.logs unless mod1.status.zero? - expect(mod1.status).to be_zero expect(rm2.round(2)).to eq(gra_seb.round(2)) entry_skies = mod1.facets(entry, "Outdoors", "Skylight") @@ -4717,6 +4695,7 @@ module M construction = mod1.genConstruction(model, {type: :skylight, uo: 2.8}) expect(utility_sky.setConstruction(construction)).to be true expect(open_sky.setConstruction(construction)).to be true + expect(mod1.status).to be_zero file = File.join(__dir__, "files/osms/out/seb_sky.osm") model.save(file, true) @@ -4753,7 +4732,6 @@ module M bulk_roof_m2 = mod1.getRoofs(bulk).sum(&:grossArea) fine_roof_m2 = mod1.getRoofs(fine).sum(&:grossArea) - expect(mod1.status).to be_zero expect(gra_bulk.round(2)).to eq(bulk_roof_m2.round(2)) expect(gra_fine.round(2)).to eq(fine_roof_m2.round(2)) @@ -4770,14 +4748,13 @@ module M opts[:size ] = 2.4 opts[:clear] = true rm2 = mod1.addSkyLights(bulk, opts) - puts mod1.logs unless mod1.status.zero? - expect(mod1.status).to be_zero bulk_skies = mod1.facets(bulk, "Outdoors", "Skylight") sky_area2 = bulk_skies.sum(&:grossArea) expect(sky_area2.round(2)).to eq(128.19) ratio2 = sky_area2 / rm2 expect(ratio2.round(2)).to eq(srr) + expect(mod1.status).to be_zero file = File.join(__dir__, "files/osms/out/warehouse_sky.osm") model.save(file, true) From ac976e63d4d01711de65f73e0e1c400cd0ede59c Mon Sep 17 00:00:00 2001 From: rd2 Date: Wed, 17 Apr 2024 10:17:32 -0400 Subject: [PATCH 13/13] Extends to Ruby 3x --- osut.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osut.gemspec b/osut.gemspec index 4c5bf1e..d4799aa 100644 --- a/osut.gemspec +++ b/osut.gemspec @@ -23,10 +23,10 @@ Gem::Specification.new do |s| s.bindir = "exe" s.require_paths = ["lib"] s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } - s.required_ruby_version = [">= 2.5.0", "< 3"] + s.required_ruby_version = [">= 2.5.0", "< 4"] s.metadata = {} - s.add_dependency "oslg", ">= 0.2.10" + s.add_dependency "oslg", ">= 0.3.0" s.add_development_dependency "bundler", "~> 2.1" s.add_development_dependency "rake", "~> 13.0" s.add_development_dependency "rspec", "~> 3.11"