lib/osut/utils.rb in osut-0.2.6 vs lib/osut/utils.rb in osut-0.2.7

- old
+ new

@@ -626,11 +626,11 @@ # @return [Bool] false if invalid input def coolingTemperatureSetpoints?(model = nil) mth = "OSut::#{__callee__}" cl = OpenStudio::Model::Model - return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl) + return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl) model.getThermalZones.each do |zone| return true if minCoolScheduledSetpoint(zone)[:spt] end @@ -646,16 +646,16 @@ # @return [Bool] false if invalid input def airLoopsHVAC?(model = nil) mth = "OSut::#{__callee__}" cl = OpenStudio::Model::Model - return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl) + return mismatch("model", model, cl, mth, DBG, false) unless model.is_a?(cl) model.getThermalZones.each do |zone| - next if zone.canBePlenum - return true unless zone.airLoopHVACs.empty? - return true if zone.isPlenum + next if zone.canBePlenum + return true unless zone.airLoopHVACs.empty? + return true if zone.isPlenum end false end @@ -676,11 +676,11 @@ # standards/Standards.Space.rb#L1384 # # A space may be tagged as a plenum if: # # CASE A: its zone's "isPlenum" == true (SDK method) for a fully-developed - # OpenStudio model (complete with HVAC air loops); + # OpenStudio model (complete with HVAC air loops); OR # # CASE B: (IN ABSENCE OF HVAC AIRLOOPS) if it's excluded from a building's # total floor area yet linked to a zone holding an 'inactive' # thermostat, i.e. can't extract valid setpoints; OR # @@ -735,12 +735,12 @@ def availabilitySchedule(model = nil, avl = "") mth = "OSut::#{__callee__}" cl = OpenStudio::Model::Model limits = nil - return mismatch("model", model, cl, mth) unless model.is_a?(cl) - return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_s) + return mismatch("model", model, cl, mth) unless model.is_a?(cl) + return invalid("availability", avl, 2, mth) unless avl.respond_to?(:to_s) # Either fetch availability ScheduleTypeLimits object, or create one. model.getScheduleTypeLimitss.each do |l| break if limits next if l.lowerLimitValue.empty? @@ -767,13 +767,13 @@ secs = time.totalSeconds on = OpenStudio::Model::ScheduleDay.new(model, 1) off = OpenStudio::Model::ScheduleDay.new(model, 0) # Seasonal availability start/end dates. - year = model.yearDescription - return empty("yearDescription", mth, ERR) if year.empty? - year = year.get + year = model.yearDescription + return empty("yearDescription", mth, ERR) if year.empty? + year = year.get may01 = year.makeDate(OpenStudio::MonthOfYear.new("May"), 1) oct31 = year.makeDate(OpenStudio::MonthOfYear.new("Oct"), 31) case avl.to_s.downcase when "winter" # available from November 1 to April 30 (6 months) @@ -955,21 +955,21 @@ def defaultConstructionSet(model = nil, s = nil) mth = "OSut::#{__callee__}" cl1 = OpenStudio::Model::Model cl2 = OpenStudio::Model::Surface - return mismatch("model", model, cl1, mth) unless model.is_a?(cl1) - return invalid("s", mth, 2) unless s.respond_to?(NS) - id = s.nameString - return mismatch(id, s, cl2, mth) unless s.is_a?(cl2) + return mismatch("model", model, cl1, mth) unless model.is_a?(cl1) + return invalid("s", mth, 2) unless s.respond_to?(NS) + id = s.nameString + return mismatch(id, s, cl2, mth) unless s.is_a?(cl2) ok = s.isConstructionDefaulted log(ERR, "'#{id}' construction not defaulted (#{mth})") unless ok return nil unless ok - return empty("'#{id}' construction", mth, ERR) if s.construction.empty? + return empty("'#{id}' construction", mth, ERR) if s.construction.empty? base = s.construction.get - return empty("'#{id}' space", mth, ERR) if s.space.empty? + return empty("'#{id}' space", mth, ERR) if s.space.empty? space = s.space.get type = s.surfaceType ground = false exterior = false @@ -979,36 +979,36 @@ exterior = true end unless space.defaultConstructionSet.empty? set = space.defaultConstructionSet.get - return set if holdsConstruction?(set, base, ground, exterior, type) + return set if holdsConstruction?(set, base, ground, exterior, type) end unless space.spaceType.empty? spacetype = space.spaceType.get unless spacetype.defaultConstructionSet.empty? set = spacetype.defaultConstructionSet.get - return set if holdsConstruction?(set, base, ground, exterior, type) + return set if holdsConstruction?(set, base, ground, exterior, type) end end unless space.buildingStory.empty? story = space.buildingStory.get unless story.defaultConstructionSet.empty? set = story.defaultConstructionSet.get - return set if holdsConstruction?(set, base, ground, exterior, type) + return set if holdsConstruction?(set, base, ground, exterior, type) end end building = model.getBuilding unless building.defaultConstructionSet.empty? set = building.defaultConstructionSet.get - return set if holdsConstruction?(set, base, ground, exterior, type) + return set if holdsConstruction?(set, base, ground, exterior, type) end nil end @@ -1034,19 +1034,19 @@ ## # Total (standard opaque) layered construction thickness (in m). # # @param lc [OpenStudio::LayeredConstruction] a layered construction # - # @return [Double] total layered construction thickness - # @return [Double] 0 if invalid input + # @return [Float] total layered construction thickness + # @return [Float] 0 if invalid input def thickness(lc = nil) mth = "OSut::#{__callee__}" cl = OpenStudio::Model::LayeredConstruction - return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS) + return invalid("lc", mth, 1, DBG, 0.0) unless lc.respond_to?(NS) id = lc.nameString - return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl) + return mismatch(id, lc, cl, mth, DBG, 0.0) unless lc.is_a?(cl) ok = standardOpaqueLayers?(lc) log(ERR, "'#{id}' holds non-StandardOpaqueMaterial(s) (#{mth})") unless ok return 0.0 unless ok thickness = 0.0 @@ -1164,25 +1164,25 @@ mth = "OSut::#{__callee__}" cl = OpenStudio::Model::LayeredConstruction res = { index: nil, type: nil, r: 0.0 } i = 0 # iterator - return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS) - id = lc.nameString - return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl) + return invalid("lc", mth, 1, DBG, res) unless lc.respond_to?(NS) + id = lc.nameString + return mismatch(id, lc, cl1, mth, DBG, res) unless lc.is_a?(cl) lc.layers.each do |m| unless m.to_MasslessOpaqueMaterial.empty? m = m.to_MasslessOpaqueMaterial.get if m.thermalResistance < 0.001 || m.thermalResistance < res[:r] i += 1 next else - res[:r] = m.thermalResistance + res[:r ] = m.thermalResistance res[:index] = i - res[:type] = :massless + res[:type ] = :massless end end unless m.to_StandardOpaqueMaterial.empty? m = m.to_StandardOpaqueMaterial.get @@ -1191,13 +1191,13 @@ if d < 0.003 || k > 3.0 || d / k < res[:r] i += 1 next else - res[:r] = d / k + res[:r ] = d / k res[:index] = i - res[:type] = :standard + res[:type ] = :standard end end i += 1 end @@ -1229,10 +1229,32 @@ res end ## + # Return a scalar product of an OpenStudio Vector3d. + # + # @param v [OpenStudio::Vector3d] a vector + # @param m [Float] a scalar + # + # @return [OpenStudio::Vector3d] modified vector + # @return [OpenStudio::Vector3d] provided (or empty) vector if invalid input + def scalar(v = OpenStudio::Vector3d.new(0,0,0), m = 0) + mth = "OSut::#{__callee__}" + cl1 = OpenStudio::Vector3d + cl2 = Numeric + + return mismatch("vector", v, cl1, mth, DBG, v) unless v.is_a?(cl1) + return mismatch("x", v.x, cl2, mth, DBG, v) unless v.x.respond_to?(:to_f) + return mismatch("y", v.y, cl2, mth, DBG, v) unless v.y.respond_to?(:to_f) + return mismatch("z", v.z, cl2, mth, DBG, v) unless v.z.respond_to?(:to_f) + return mismatch("m", m, cl2, mth, DBG, v) unless m.respond_to?(:to_f) + + OpenStudio::Vector3d.new(m * v.x, m * v.y, m * v.z) + end + + ## # Flatten OpenStudio 3D points vs Z-axis (Z=0). # # @param pts [Array] an OpenStudio Point3D array/vector # # @return [Array] flattened OpenStudio 3D points @@ -1241,12 +1263,12 @@ cl1 = OpenStudio::Point3dVector cl2 = OpenStudio::Point3d v = OpenStudio::Point3dVector.new valid = pts.is_a?(cl1) || pts.is_a?(Array) - return mismatch("points", pts, cl1, mth, DBG, v) unless valid - pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) } + return mismatch("points", pts, cl1, mth, DBG, v) unless valid + pts.each { |pt| mismatch("pt", pt, cl2, mth, ERR, v) unless pt.is_a?(cl2) } pts.each { |pt| v << OpenStudio::Point3d.new(pt.x, pt.y, 0) } v end @@ -1266,45 +1288,52 @@ cl2 = OpenStudio::Point3d a = false return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s) return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s) + i1 = id1.to_s i2 = id2.to_s i1 = "poly1" if i1.empty? i2 = "poly2" if i2.empty? + valid1 = p1.is_a?(cl1) || p1.is_a?(Array) valid2 = p2.is_a?(cl1) || p2.is_a?(Array) + return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1 return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2 - return empty(i1, mth, ERR, a) if p1.empty? - return empty(i2, mth, ERR, a) if p2.empty? + return empty(i1, mth, ERR, a) if p1.empty? + return empty(i2, mth, ERR, a) if p2.empty? + p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) } p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) } - ft = OpenStudio::Transformation::alignFace(p1).inverse - ft_p1 = flatZ( (ft * p1).reverse ) - return false if ft_p1.empty? - area1 = OpenStudio::getArea(ft_p1) - return empty("#{i1} area", mth, ERR, a) if area1.empty? + # XY-plane transformation matrix ... needs to be clockwise for boost. + ft = OpenStudio::Transformation.alignFace(p1) + ft_p1 = flatZ( (ft.inverse * p1) ) + return false if ft_p1.empty? + cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL) + ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw + ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw + ft_p2 = flatZ( (ft.inverse * p2) ) if cw + return false if ft_p2.empty? + area1 = OpenStudio.getArea(ft_p1) + area2 = OpenStudio.getArea(ft_p2) + return empty("#{i1} area", mth, ERR, a) if area1.empty? + return empty("#{i2} area", mth, ERR, a) if area2.empty? area1 = area1.get - ft_p2 = flatZ( (ft * p2).reverse ) - return false if ft_p2.empty? - area2 = OpenStudio::getArea(ft_p2) - return empty("#{i2} area", mth, ERR, a) if area2.empty? area2 = area2.get - union = OpenStudio::join(ft_p1, ft_p2, TOL2) - return false if union.empty? + union = OpenStudio.join(ft_p1, ft_p2, TOL2) + return false if union.empty? union = union.get - area = OpenStudio::getArea(union) - return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty? + area = OpenStudio.getArea(union) + return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty? area = area.get + return false if area < TOL + return true if (area - area2).abs < TOL + return false if (area - area2).abs > TOL - return false if area < TOL - return true if (area - area2).abs < TOL - return false if (area - area2).abs > TOL - true end ## # Validate whether an OpenStudio polygon overlaps another. @@ -1322,43 +1351,280 @@ cl2 = OpenStudio::Point3d a = false return invalid("id1", mth, 3, DBG, a) unless id1.respond_to?(:to_s) return invalid("id2", mth, 4, DBG, a) unless id2.respond_to?(:to_s) + i1 = id1.to_s i2 = id2.to_s i1 = "poly1" if i1.empty? i2 = "poly2" if i2.empty? + valid1 = p1.is_a?(cl1) || p1.is_a?(Array) valid2 = p2.is_a?(cl1) || p2.is_a?(Array) + return mismatch(i1, p1, cl1, mth, DBG, a) unless valid1 return mismatch(i2, p2, cl1, mth, DBG, a) unless valid2 - return empty(i1, mth, ERR, a) if p1.empty? - return empty(i2, mth, ERR, a) if p2.empty? + return empty(i1, mth, ERR, a) if p1.empty? + return empty(i2, mth, ERR, a) if p2.empty? + p1.each { |v| return mismatch(i1, v, cl2, mth, ERR, a) unless v.is_a?(cl2) } p2.each { |v| return mismatch(i2, v, cl2, mth, ERR, a) unless v.is_a?(cl2) } - ft = OpenStudio::Transformation::alignFace(p1).inverse - ft_p1 = flatZ( (ft * p1).reverse ) - return false if ft_p1.empty? - area1 = OpenStudio::getArea(ft_p1) - return empty("#{i1} area", mth, ERR, a) if area1.empty? + # XY-plane transformation matrix ... needs to be clockwise for boost. + ft = OpenStudio::Transformation.alignFace(p1) + ft_p1 = flatZ( (ft.inverse * p1) ) + ft_p2 = flatZ( (ft.inverse * p2) ) + return false if ft_p1.empty? + return false if ft_p2.empty? + cw = OpenStudio.pointInPolygon(ft_p1.first, ft_p1, TOL) + ft_p1 = flatZ( (ft.inverse * p1).reverse ) unless cw + ft_p2 = flatZ( (ft.inverse * p2).reverse ) unless cw + return false if ft_p1.empty? + return false if ft_p2.empty? + area1 = OpenStudio.getArea(ft_p1) + area2 = OpenStudio.getArea(ft_p2) + return empty("#{i1} area", mth, ERR, a) if area1.empty? + return empty("#{i2} area", mth, ERR, a) if area2.empty? area1 = area1.get - ft_p2 = flatZ( (ft * p2).reverse ) - return false if ft_p2.empty? - area2 = OpenStudio::getArea(ft_p2) - return empty("#{i2} area", mth, ERR, a) if area2.empty? area2 = area2.get - union = OpenStudio::join(ft_p1, ft_p2, TOL2) - return false if union.empty? + union = OpenStudio.join(ft_p1, ft_p2, TOL2) + return false if union.empty? union = union.get - area = OpenStudio::getArea(union) - return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty? + area = OpenStudio.getArea(union) + return empty("#{i1}:#{i2} union area", mth, ERR, a) if area.empty? area = area.get + return false if area < TOL - return false if area < TOL - true + end + + ## + # Generate offset vertices (by width) for a 3- or 4-sided, convex polygon. + # + # @param p1 [OpenStudio::Point3dVector] OpenStudio Point3D vector/array + # @param w [Float] offset width (min: 0.0254m) + # @param v [Integer] OpenStudio SDK version, eg '321' for 'v3.2.1' (optional) + # + # @return [OpenStudio::Point3dVector] offset points if successful + # @return [OpenStudio::Point3dVector] original points if invalid input + def offset(p1 = [], w = 0, v = 0) + mth = "TBD::#{__callee__}" + cl = OpenStudio::Point3d + vrsn = OpenStudio.openStudioVersion.split(".").map(&:to_i).join.to_i + + valid = p1.is_a?(OpenStudio::Point3dVector) || p1.is_a?(Array) + return mismatch("pts", p1, cl1, mth, DBG, p1) unless valid + return empty("pts", mth, ERR, p1) if p1.empty? + valid = p1.size == 3 || p1.size == 4 + iv = true if p1.size == 4 + return invalid("pts", mth, 1, DBG, p1) unless valid + return invalid("width", mth, 2, DBG, p1) unless w.respond_to?(:to_f) + w = w.to_f + return p1 if w < 0.0254 + v = v.to_i if v.respond_to?(:to_i) + v = 0 unless v.respond_to?(:to_i) + v = vrsn if v.zero? + + p1.each { |x| return mismatch("p", x, cl, mth, ERR, p1) unless x.is_a?(cl) } + + unless v < 340 + # XY-plane transformation matrix ... needs to be clockwise for boost. + ft = OpenStudio::Transformation::alignFace(p1) + ft_pts = flatZ( (ft.inverse * p1) ) + return p1 if ft_pts.empty? + cw = OpenStudio::pointInPolygon(ft_pts.first, ft_pts, TOL) + ft_pts = flatZ( (ft.inverse * p1).reverse ) unless cw + offset = OpenStudio.buffer(ft_pts, w, TOL) + return p1 if offset.empty? + offset = offset.get + offset = ft * offset if cw + offset = (ft * offset).reverse unless cw + + pz = OpenStudio::Point3dVector.new + offset.each { |o| pz << OpenStudio::Point3d.new(o.x, o.y, o.z ) } + return pz + else # brute force approach + pz = {} + pz[:A] = {} + pz[:B] = {} + pz[:C] = {} + pz[:D] = {} if iv + + pz[:A][:p] = OpenStudio::Point3d.new(p1[0].x, p1[0].y, p1[0].z) + pz[:B][:p] = OpenStudio::Point3d.new(p1[1].x, p1[1].y, p1[1].z) + pz[:C][:p] = OpenStudio::Point3d.new(p1[2].x, p1[2].y, p1[2].z) + pz[:D][:p] = OpenStudio::Point3d.new(p1[3].x, p1[3].y, p1[3].z) if iv + + pzAp = pz[:A][:p] + pzBp = pz[:B][:p] + pzCp = pz[:C][:p] + pzDp = pz[:D][:p] if iv + + # Generate vector pairs, from next point & from previous point. + # :f_n : "from next" + # :f_p : "from previous" + # + # + # + # + # + # + # A <---------- B + # ^ + # \ + # \ + # C (or D) + # + pz[:A][:f_n] = pzAp - pzBp + pz[:A][:f_p] = pzAp - pzCp unless iv + pz[:A][:f_p] = pzAp - pzDp if iv + + pz[:B][:f_n] = pzBp - pzCp + pz[:B][:f_p] = pzBp - pzAp + + pz[:C][:f_n] = pzCp - pzAp unless iv + pz[:C][:f_n] = pzCp - pzDp if iv + pz[:C][:f_p] = pzCp - pzBp + + pz[:D][:f_n] = pzDp - pzAp if iv + pz[:D][:f_p] = pzDp - pzCp if iv + + # Generate 3D plane from vectors. + # + # + # | <<< 3D plane ... from point A, with normal B>A + # | + # | + # | + # <---------- A <---------- B + # |\ + # | \ + # | \ + # | C (or D) + # + pz[:A][:pl_f_n] = OpenStudio::Plane.new(pzAp, pz[:A][:f_n]) + pz[:A][:pl_f_p] = OpenStudio::Plane.new(pzAp, pz[:A][:f_p]) + + pz[:B][:pl_f_n] = OpenStudio::Plane.new(pzBp, pz[:B][:f_n]) + pz[:B][:pl_f_p] = OpenStudio::Plane.new(pzBp, pz[:B][:f_p]) + + pz[:C][:pl_f_n] = OpenStudio::Plane.new(pzCp, pz[:C][:f_n]) + pz[:C][:pl_f_p] = OpenStudio::Plane.new(pzCp, pz[:C][:f_p]) + + pz[:D][:pl_f_n] = OpenStudio::Plane.new(pzDp, pz[:D][:f_n]) if iv + pz[:D][:pl_f_p] = OpenStudio::Plane.new(pzDp, pz[:D][:f_p]) if iv + + # Project an extended point (pC) unto 3D plane. + # + # pC <<< projected unto extended B>A 3D plane + # eC | + # \ | + # \ | + # \| + # <---------- A <---------- B + # |\ + # | \ + # | \ + # | C (or D) + # + pz[:A][:p_n_pl] = pz[:A][:pl_f_n].project(pz[:A][:p] + pz[:A][:f_p]) + pz[:A][:n_p_pl] = pz[:A][:pl_f_p].project(pz[:A][:p] + pz[:A][:f_n]) + + pz[:B][:p_n_pl] = pz[:B][:pl_f_n].project(pz[:B][:p] + pz[:B][:f_p]) + pz[:B][:n_p_pl] = pz[:B][:pl_f_p].project(pz[:B][:p] + pz[:B][:f_n]) + + pz[:C][:p_n_pl] = pz[:C][:pl_f_n].project(pz[:C][:p] + pz[:C][:f_p]) + pz[:C][:n_p_pl] = pz[:C][:pl_f_p].project(pz[:C][:p] + pz[:C][:f_n]) + + pz[:D][:p_n_pl] = pz[:D][:pl_f_n].project(pz[:D][:p] + pz[:D][:f_p]) if iv + pz[:D][:n_p_pl] = pz[:D][:pl_f_p].project(pz[:D][:p] + pz[:D][:f_n]) if iv + + # Generate vector from point (e.g. A) to projected extended point (pC). + # + # pC + # eC ^ + # \ | + # \ | + # \| + # <---------- A <---------- B + # |\ + # | \ + # | \ + # | C (or D) + # + pz[:A][:n_p_n_pl] = pz[:A][:p_n_pl] - pzAp + pz[:A][:n_n_p_pl] = pz[:A][:n_p_pl] - pzAp + + pz[:B][:n_p_n_pl] = pz[:B][:p_n_pl] - pzBp + pz[:B][:n_n_p_pl] = pz[:B][:n_p_pl] - pzBp + + pz[:C][:n_p_n_pl] = pz[:C][:p_n_pl] - pzCp + pz[:C][:n_n_p_pl] = pz[:C][:n_p_pl] - pzCp + + pz[:D][:n_p_n_pl] = pz[:D][:p_n_pl] - pzDp if iv + pz[:D][:n_n_p_pl] = pz[:D][:n_p_pl] - pzDp if iv + + # Fetch angle between both extended vectors (A>pC & A>pB), + # ... then normalize (Cn). + # + # pC + # eC ^ + # \ | + # \ Cn + # \| + # <---------- A <---------- B + # |\ + # | \ + # | \ + # | C (or D) + # + a1 = OpenStudio.getAngle(pz[:A][:n_p_n_pl], pz[:A][:n_n_p_pl]) + a2 = OpenStudio.getAngle(pz[:B][:n_p_n_pl], pz[:B][:n_n_p_pl]) + a3 = OpenStudio.getAngle(pz[:C][:n_p_n_pl], pz[:C][:n_n_p_pl]) + a4 = OpenStudio.getAngle(pz[:D][:n_p_n_pl], pz[:D][:n_n_p_pl]) if iv + + # Generate new 3D points A', B', C' (and D') ... zigzag. + # + # + # + # + # A' ---------------------- B' + # \ + # \ A <---------- B + # \ \ + # \ \ + # \ \ + # C' C + pz[:A][:f_n].normalize + pz[:A][:n_p_n_pl].normalize + pzAp = pzAp + scalar(pz[:A][:n_p_n_pl], w) + pzAp = pzAp + scalar(pz[:A][:f_n], w * Math.tan(a1/2)) + + pz[:B][:f_n].normalize + pz[:B][:n_p_n_pl].normalize + pzBp = pzBp + scalar(pz[:B][:n_p_n_pl], w) + pzBp = pzBp + scalar(pz[:B][:f_n], w * Math.tan(a2/2)) + + pz[:C][:f_n].normalize + pz[:C][:n_p_n_pl].normalize + pzCp = pzCp + scalar(pz[:C][:n_p_n_pl], w) + pzCp = pzCp + scalar(pz[:C][:f_n], w * Math.tan(a3/2)) + + pz[:D][:f_n].normalize if iv + pz[:D][:n_p_n_pl].normalize if iv + pzDp = pzDp + scalar(pz[:D][:n_p_n_pl], w) if iv + pzDp = pzDp + scalar(pz[:D][:f_n], w * Math.tan(a4/2)) if iv + + # Re-convert to OpenStudio 3D points. + vec = OpenStudio::Point3dVector.new + vec << OpenStudio::Point3d.new(pzAp.x, pzAp.y, pzAp.z) + vec << OpenStudio::Point3d.new(pzBp.x, pzBp.y, pzBp.z) + vec << OpenStudio::Point3d.new(pzCp.x, pzCp.y, pzCp.z) + vec << OpenStudio::Point3d.new(pzDp.x, pzDp.y, pzDp.z) if iv + + return vec + end end ## # Callback when other modules extend OSlg #