osut 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bec97227aeda04a933080c621650e72003d97816d01d2a4716f146e6501b7571
4
- data.tar.gz: 82c0d1c211238120894a1bfaffed802b09461f22713957ed5c4a6b9bcb0ef757
3
+ metadata.gz: 2491bf7ed2ed4277ccfe4087a661660e720bd7f417b1e51f3c1f81715e53193f
4
+ data.tar.gz: 7c07191bf8cb725fdb9a04e15269aa6431fd7aa66955b9b99b2c1d61929033c6
5
5
  SHA512:
6
- metadata.gz: e3897a390ef6f90524503ce63ff70a3e5275b1d3abf6148ac7752eb82ec64d5bfa2eedbf56898accdd87c7f394adf435e20b30dec457d3ebe4b3fc92e9130be5
7
- data.tar.gz: 51529559490ab5f48e16a696ce657b867fe538ade32d07237b9fffc28e48b6da98905998c7819e019cdf8232fc2e16d8bc9cdb673280c2cc64861289f24370a5
6
+ metadata.gz: ed0ad6091cf8bd858a1b3de7c17b4e13fd21816871c1dc8bf539d4b7b03f232dfa1621d3e6a839eebde9a779d7562e9c56e04bf10a52c417d884ebba7128e9c0
7
+ data.tar.gz: 5be1c0c83a39becabf70d6202916b4012ca1923b2102084515dfccae765e9fffc5e4db27787523a1a7694a852e15029176216d1fff255e0653baba97c399eacd
data/.gitignore CHANGED
@@ -8,4 +8,6 @@ _yardoc
8
8
  doc/
9
9
  rdoc
10
10
 
11
+ spec/files/osms/out/
12
+
11
13
  .DS_Store
data/lib/osut/utils.rb CHANGED
@@ -41,6 +41,8 @@ module OSut
41
41
  ERR = OSut::ERROR # flag invalid .osm inputs (then exit via 'return')
42
42
  FTL = OSut::FATAL # not currently used in OSut
43
43
  NS = "nameString" # OpenStudio IdfObject nameString method
44
+ HEAD = 2.032 # standard 80" door
45
+ SILL = 0.762 # standard 30" window sill
44
46
 
45
47
  # This first set of utilities (~750 lines) help distinguishing spaces that
46
48
  # are directly vs indirectly CONDITIONED, vs SEMI-HEATED. The solution here
@@ -1447,6 +1449,9 @@ module OSut
1447
1449
  area = area.get
1448
1450
  return false if area < TOL
1449
1451
 
1452
+ delta = (area - area1 - area2).abs
1453
+ return false if delta < TOL
1454
+
1450
1455
  true
1451
1456
  end
1452
1457
 
@@ -1460,7 +1465,7 @@ module OSut
1460
1465
  # @return [OpenStudio::Point3dVector] offset points if successful
1461
1466
  # @return [OpenStudio::Point3dVector] original points if invalid input
1462
1467
  def offset(p1 = [], w = 0, v = 0)
1463
- mth = "TBD::#{__callee__}"
1468
+ mth = "OSut::#{__callee__}"
1464
1469
  cl = OpenStudio::Point3d
1465
1470
  vrsn = OpenStudio.openStudioVersion.split(".").map(&:to_i).join.to_i
1466
1471
 
@@ -1499,7 +1504,7 @@ module OSut
1499
1504
 
1500
1505
  pz = OpenStudio::Point3dVector.new
1501
1506
  offset.each { |o| pz << OpenStudio::Point3d.new(o.x, o.y, o.z ) }
1502
-
1507
+
1503
1508
  return pz
1504
1509
  else # brute force approach
1505
1510
  pz = {}
@@ -1684,6 +1689,589 @@ module OSut
1684
1689
  end
1685
1690
  end
1686
1691
 
1692
+ ##
1693
+ # Validate whether an OpenStudio planar surface is safe to process.
1694
+ #
1695
+ # @param s [OpenStudio::Model::PlanarSurface] a surface
1696
+ #
1697
+ # @return [Bool] true if valid surface
1698
+ def surface_valid?(s = nil)
1699
+ mth = "OSut::#{__callee__}"
1700
+ cl = OpenStudio::Model::PlanarSurface
1701
+
1702
+ return mismatch("surface", s, cl, mth, DBG, false) unless s.is_a?(cl)
1703
+
1704
+ id = s.nameString
1705
+ size = s.vertices.size
1706
+ last = size - 1
1707
+
1708
+ log(ERR, "#{id} #{size} vertices? need +3 (#{mth})") unless size > 2
1709
+ return false unless size > 2
1710
+
1711
+ [0, last].each do |i|
1712
+ v1 = s.vertices[i]
1713
+ v2 = s.vertices[i + 1] unless i == last
1714
+ v2 = s.vertices.first if i == last
1715
+ vec = v2 - v1
1716
+ bad = vec.length < TOL
1717
+
1718
+ # As is, this comparison also catches collinear vertices (< 10mm apart)
1719
+ # along an edge. Should avoid red-flagging such cases. TO DO.
1720
+ log(ERR, "#{id}: < #{TOL}m (#{mth})") if bad
1721
+ return false if bad
1722
+ end
1723
+
1724
+ # Add as many extra tests as needed ...
1725
+ true
1726
+ end
1727
+
1728
+ ##
1729
+ # Add sub surfaces (e.g. windows, doors, skylights) to surface.
1730
+ #
1731
+ # @param model [OpenStudio::Model::Model] a model
1732
+ # @param s [OpenStudio::Model::Surface] a model surface
1733
+ # @param subs [Array] requested sub surface attributes
1734
+ # @param clear [Bool] remove current sub surfaces if true
1735
+ # @param bfr [Double] safety buffer (m), when ~aligned along other edges
1736
+ #
1737
+ # @return [Bool] true if successful (check for logged messages if failures)
1738
+ def addSubs(model = nil, s = nil, subs = [], clear = false, bfr = 0.005)
1739
+ mth = "OSut::#{__callee__}"
1740
+ v = OpenStudio.openStudioVersion.split(".").join.to_i
1741
+ cl1 = OpenStudio::Model::Model
1742
+ cl2 = OpenStudio::Model::Surface
1743
+ cl3 = Array
1744
+ cl4 = Hash
1745
+ cl5 = Numeric
1746
+ min = 0.050 # minimum ratio value ( 5%)
1747
+ max = 0.950 # maximum ratio value (95%)
1748
+ no = false
1749
+
1750
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
1751
+ # Exit if mismatched or invalid argument classes.
1752
+ return mismatch("model", model, cl1, mth, DBG, no) unless model.is_a?(cl1)
1753
+ return mismatch("surface", s, cl2, mth, DBG, no) unless s.is_a?(cl2)
1754
+ return mismatch("subs", subs, cl3, mth, DBG, no) unless subs.is_a?(cl3)
1755
+ return no unless surface_valid?(s)
1756
+
1757
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
1758
+ # Clear existing sub surfaces if requested.
1759
+ nom = s.nameString
1760
+
1761
+ unless clear == true || clear == false
1762
+ log(WRN, "#{nom}: Keeping existing sub surfaces (#{mth})")
1763
+ clear = false
1764
+ end
1765
+
1766
+ s.subSurfaces.map(&:remove) if clear
1767
+
1768
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
1769
+ # Allowable sub surface types ... & Frame&Divider enabled
1770
+ # - "FixedWindow" | true
1771
+ # - "OperableWindow" | true
1772
+ # - "Door" | false
1773
+ # - "GlassDoor" | true
1774
+ # - "OverheadDoor" | false
1775
+ # - "Skylight" | false if v < 321
1776
+ # - "TubularDaylightDome" | false
1777
+ # - "TubularDaylightDiffuser" | false
1778
+ type = "FixedWindow"
1779
+ types = OpenStudio::Model::SubSurface.validSubSurfaceTypeValues
1780
+ stype = s.surfaceType # Wall, RoofCeiling or Floor
1781
+
1782
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
1783
+ # Fetch transform, as if host surface vertices were to "align", i.e.:
1784
+ # - rotated/tilted ... then flattened along XY plane
1785
+ # - all Z-axis coordinates ~= 0
1786
+ # - vertices with the lowest X-axis values are "aligned" along X-axis (0)
1787
+ # - vertices with the lowest Z-axis values are "aligned" along Y-axis (0)
1788
+ # - Z-axis values are represented as Y-axis values
1789
+ tr = OpenStudio::Transformation.alignFace(s.vertices)
1790
+
1791
+ # Aligned vertices of host surface, and fetch attributes.
1792
+ aligned = tr.inverse * s.vertices
1793
+ max_x = aligned.max_by(&:x).x
1794
+ max_y = aligned.max_by(&:y).y
1795
+ mid_x = max_x / 2
1796
+ mid_y = max_y / 2
1797
+
1798
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
1799
+ # Assign default values to certain sub keys (if missing), +more validation.
1800
+ subs.each_with_index do |sub, index|
1801
+ return mismatch("sub", sub, cl4, mth, DBG, no) unless sub.is_a?(cl4)
1802
+
1803
+ # Required key:value pairs (either set by the user or defaulted).
1804
+ sub[:id ] = "" unless sub.key?(:id ) # "Window 007"
1805
+ sub[:type ] = type unless sub.key?(:type ) # "FixedWindow"
1806
+ sub[:count ] = 1 unless sub.key?(:count ) # for an array
1807
+ sub[:multiplier] = 1 unless sub.key?(:multiplier)
1808
+ sub[:frame ] = nil unless sub.key?(:frame ) # frame/divider
1809
+ sub[:assembly ] = nil unless sub.key?(:assembly ) # construction
1810
+
1811
+ # Optional key:value pairs.
1812
+ # sub[:ratio ] # e.g. %FWR
1813
+ # sub[:head ] # e.g. std 80" door + frame/buffers (+ m)
1814
+ # sub[:sill ] # e.g. std 30" sill + frame/buffers (+ m)
1815
+ # sub[:height ] # any sub surface height, below "head" (+ m)
1816
+ # sub[:width ] # e.g. 1.200 m
1817
+ # sub[:offset ] # if array (+ m)
1818
+ # sub[:centreline] # left or right of base surface centreline (+/- m)
1819
+ # sub[:r_buffer ] # buffer between sub/array and right-side corner (+ m)
1820
+ # sub[:l_buffer ] # buffer between sub/array and left-side corner (+ m)
1821
+
1822
+ sub[:id] = "#{nom}|#{index}" if sub[:id].empty?
1823
+ id = sub[:id]
1824
+
1825
+ # If sub surface type is invalid, log/reset. Additional corrections may
1826
+ # be enabled once a sub surface is actually instantiated.
1827
+ unless types.include?(sub[:type])
1828
+ log(WRN, "Reset invalid '#{id}' type to '#{type}' (#{mth})")
1829
+ sub[:type] = type
1830
+ end
1831
+
1832
+ # Log/ignore (optional) frame & divider object.
1833
+ unless sub[:frame].nil?
1834
+ if sub[:frame].respond_to?(:frameWidth)
1835
+ sub[:frame] = nil if sub[:type] == "Skylight" && v < 321
1836
+ sub[:frame] = nil if sub[:type] == "Door"
1837
+ sub[:frame] = nil if sub[:type] == "OverheadDoor"
1838
+ sub[:frame] = nil if sub[:type] == "TubularDaylightDome"
1839
+ sub[:frame] = nil if sub[:type] == "TubularDaylightDiffuser"
1840
+ log(WRN, "Skip '#{id}' FrameDivider (#{mth})") if sub[:frame].nil?
1841
+ else
1842
+ sub[:frame] = nil
1843
+ log(WRN, "Skip '#{id}' invalid FrameDivider object (#{mth})")
1844
+ end
1845
+ end
1846
+
1847
+ # The (optional) "assembly" must reference a valid OpenStudio
1848
+ # construction base, to explicitly assign to each instantiated sub
1849
+ # surface. If invalid, log/reset/ignore. Additional checks are later
1850
+ # activated once a sub surface is actually instantiated.
1851
+ unless sub[:assembly].nil?
1852
+ unless sub[:assembly].respond_to?(:isFenestration)
1853
+ log(WRN, "Skip invalid '#{id}' construction (#{mth})")
1854
+ sub[:assembly] = nil
1855
+ end
1856
+ end
1857
+
1858
+ # Log/reset negative numerical values. Set ~0 values to 0.
1859
+ sub.each do |key, value|
1860
+ next if key == :id
1861
+ next if key == :type
1862
+ next if key == :frame
1863
+ next if key == :assembly
1864
+
1865
+ return mismatch(key, value, cl5, mth, DBG, no) unless value.is_a?(cl5)
1866
+ next if key == :centreline
1867
+
1868
+ negative(key, mth, WRN) if value < 0
1869
+ value = 0.0 if value.abs < TOL
1870
+ end
1871
+ end
1872
+
1873
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
1874
+ # Log/reset (or abandon) conflicting user-set geometry key:value pairs:
1875
+ # :head e.g. std 80" door + frame/buffers (+ m)
1876
+ # :sill e.g. std 30" sill + frame/buffers (+ m)
1877
+ # :height any sub surface height, below "head" (+ m)
1878
+ # :width e.g. 1.200 m
1879
+ # :offset if array (+ m)
1880
+ # :centreline left or right of base surface centreline (+/- m)
1881
+ # :r_buffer buffer between sub/array and right-side corner (+ m)
1882
+ # :l_buffer buffer between sub/array and left-side corner (+ m)
1883
+ #
1884
+ # If successful, this will generate sub surfaces and add them to the model.
1885
+ subs.each do |sub|
1886
+ # Set-up unique sub parameters:
1887
+ # - Frame & Divider "width"
1888
+ # - minimum "clear glazing" limits
1889
+ # - buffers, etc.
1890
+ id = sub[:id]
1891
+ frame = 0
1892
+ frame = sub[:frame].frameWidth unless sub[:frame].nil?
1893
+ frames = 2 * frame
1894
+ buffer = frame + bfr
1895
+ buffers = 2 * buffer
1896
+ dim = 0.200 unless (3 * frame) > 0.200
1897
+ dim = 3 * frame if (3 * frame) > 0.200
1898
+ glass = dim - frames
1899
+ min_sill = buffer
1900
+ min_head = buffers + glass
1901
+ max_head = max_y - buffer
1902
+ max_sill = max_head - (buffers + glass)
1903
+ min_ljamb = buffer
1904
+ max_ljamb = max_x - (buffers + glass)
1905
+ min_rjamb = buffers + glass
1906
+ max_rjamb = max_x - buffer
1907
+ max_height = max_y - buffers
1908
+ max_width = max_x - buffers
1909
+
1910
+ # Default sub surface "head" & "sill" height (unless user-specified).
1911
+ typ_head = HEAD # standard 80" door
1912
+ typ_sill = SILL # standard 30" window sill
1913
+
1914
+ if sub.key?(:ratio)
1915
+ typ_head = mid_y * (1 + sub[:ratio]) if sub[:ratio] > 0.75
1916
+ typ_head = mid_y * (1 + sub[:ratio]) unless stype.downcase == "wall"
1917
+ typ_sill = mid_y * (1 - sub[:ratio]) if sub[:ratio] > 0.75
1918
+ typ_sill = mid_y * (1 - sub[:ratio]) unless stype.downcase == "wall"
1919
+ end
1920
+
1921
+ # Log/reset "height" if beyond min/max.
1922
+ if sub.key?(:height)
1923
+ unless sub[:height].between?(glass, max_height)
1924
+ sub[:height] = glass if sub[:height] < glass
1925
+ sub[:height] = max_height if sub[:height] > max_height
1926
+ log(WRN, "Reset '#{id}' height to #{sub[:height]} m (#{mth})")
1927
+ end
1928
+ end
1929
+
1930
+ # Log/reset "head" height if beyond min/max.
1931
+ if sub.key?(:head)
1932
+ unless sub[:head].between?(min_head, max_head)
1933
+ sub[:head] = max_head if sub[:head] > max_head
1934
+ sub[:head] = min_head if sub[:head] < min_head
1935
+ log(WRN, "Reset '#{id}' head height to #{sub[:head]} m (#{mth})")
1936
+ end
1937
+ end
1938
+
1939
+ # Log/reset "sill" height if beyond min/max.
1940
+ if sub.key?(:sill)
1941
+ unless sub[:sill].between?(min_sill, max_sill)
1942
+ sub[:sill] = max_sill if sub[:sill] > max_sill
1943
+ sub[:sill] = min_sill if sub[:sill] < min_sill
1944
+ log(WRN, "Reset '#{id}' sill height to #{sub[:sill]} m (#{mth})")
1945
+ end
1946
+ end
1947
+
1948
+ # At this point, "head", "sill" and/or "height" have been tentatively
1949
+ # validated (and/or have been corrected) independently from one another.
1950
+ # Log/reset "head" & "sill" heights if conflicting.
1951
+ if sub.key?(:head) && sub.key?(:sill) && sub[:head] < sub[:sill] + glass
1952
+ sill = sub[:head] - glass
1953
+
1954
+ if sill < min_sill
1955
+ sub[:ratio ] = 0 if sub.key?(:ratio)
1956
+ sub[:count ] = 0
1957
+ sub[:multiplier] = 0
1958
+ sub[:height ] = 0 if sub.key?(:height)
1959
+ sub[:width ] = 0 if sub.key?(:width)
1960
+ log(ERR, "Skip: invalid '#{id}' head/sill combo (#{mth})")
1961
+ next
1962
+ else
1963
+ sub[:sill] = sill
1964
+ log(WRN, "(Re)set '#{id}' sill height to #{sub[:sill]} m (#{mth})")
1965
+ end
1966
+ end
1967
+
1968
+ # Attempt to reconcile "head", "sill" and/or "height". If successful,
1969
+ # all 3x parameters are set (if missing), or reset if invalid.
1970
+ if sub.key?(:head) && sub.key?(:sill)
1971
+ height = sub[:head] - sub[:sill]
1972
+
1973
+ if sub.key?(:height) && (sub[:height] - height).abs > TOL
1974
+ log(WRN, "(Re)set '#{id}' height to #{height} m (#{mth})")
1975
+ end
1976
+
1977
+ sub[:height] = height
1978
+ elsif sub.key?(:head) # no "sill"
1979
+ if sub.key?(:height)
1980
+ sill = sub[:head] - sub[:height]
1981
+
1982
+ if sill < min_sill
1983
+ sill = min_sill
1984
+ height = sub[:head] - sill
1985
+
1986
+ if height < glass
1987
+ sub[:ratio ] = 0 if sub.key?(:ratio)
1988
+ sub[:count ] = 0
1989
+ sub[:multiplier] = 0
1990
+ sub[:height ] = 0 if sub.key?(:height)
1991
+ sub[:width ] = 0 if sub.key?(:width)
1992
+ log(ERR, "Skip: invalid '#{id}' head/height combo (#{mth})")
1993
+ next
1994
+ else
1995
+ sub[:sill ] = sill
1996
+ sub[:height] = height
1997
+ log(WRN, "(Re)set '#{id}' height to #{sub[:height]} m (#{mth})")
1998
+ end
1999
+ else
2000
+ sub[:sill] = sill
2001
+ end
2002
+ else
2003
+ sub[:sill ] = typ_sill
2004
+ sub[:height] = sub[:head] - sub[:sill]
2005
+ end
2006
+ elsif sub.key?(:sill) # no "head"
2007
+ if sub.key?(:height)
2008
+ head = sub[:sill] + sub[:height]
2009
+
2010
+ if head > max_head
2011
+ head = max_head
2012
+ height = head - sub[:sill]
2013
+
2014
+ if height < glass
2015
+ sub[:ratio ] = 0 if sub.key?(:ratio)
2016
+ sub[:count ] = 0
2017
+ sub[:multiplier] = 0
2018
+ sub[:height ] = 0 if sub.key?(:height)
2019
+ sub[:width ] = 0 if sub.key?(:width)
2020
+ log(ERR, "Skip: invalid '#{id}' sill/height combo (#{mth})")
2021
+ next
2022
+ else
2023
+ sub[:head ] = head
2024
+ sub[:height] = height
2025
+ log(WRN, "(Re)set '#{id}' height to #{sub[:height]} m (#{mth})")
2026
+ end
2027
+ else
2028
+ sub[:head] = head
2029
+ end
2030
+ else
2031
+ sub[:head ] = typ_head
2032
+ sub[:height] = sub[:head] - sub[:sill]
2033
+ end
2034
+ elsif sub.key?(:height) # neither "head" nor "sill"
2035
+ head = typ_head
2036
+ sill = head - sub[:height]
2037
+
2038
+ if sill < min_sill
2039
+ sill = min_sill
2040
+ head = sill + sub[:height]
2041
+ end
2042
+
2043
+ sub[:head] = head
2044
+ sub[:sill] = sill
2045
+ else
2046
+ sub[:head ] = typ_head
2047
+ sub[:sill ] = typ_sill
2048
+ sub[:height] = sub[:head] - sub[:sill]
2049
+ end
2050
+
2051
+ # Log/reset "width" if beyond min/max.
2052
+ if sub.key?(:width)
2053
+ unless sub[:width].between?(glass, max_width)
2054
+ sub[:width] = glass if sub[:width] < glass
2055
+ sub[:width] = max_width if sub[:width] > max_width
2056
+ log(WRN, "Reset '#{id}' width to #{sub[:width]} m (#{mth})")
2057
+ end
2058
+ end
2059
+
2060
+ # Log/reset "count" if < 1.
2061
+ if sub.key?(:count)
2062
+ if sub[:count] < 1
2063
+ sub[:count] = 1
2064
+ log(WRN, "Reset '#{id}' count to #{sub[:count]} (#{mth})")
2065
+ end
2066
+ end
2067
+
2068
+ sub[:count] = 1 unless sub.key?(:count)
2069
+
2070
+ # Log/reset if left-sided buffer under min jamb position.
2071
+ if sub.key?(:l_buffer)
2072
+ if sub[:l_buffer] < min_ljamb
2073
+ sub[:l_buffer] = min_ljamb
2074
+ log(WRN, "Reset '#{id}' left buffer to #{sub[:l_buffer]} m (#{mth})")
2075
+ end
2076
+ end
2077
+
2078
+ # Log/reset if right-sided buffer beyond max jamb position.
2079
+ if sub.key?(:r_buffer)
2080
+ if sub[:r_buffer] > max_rjamb
2081
+ sub[:r_buffer] = min_rjamb
2082
+ log(WRN, "Reset '#{id}' right buffer to #{sub[:r_buffer]} m (#{mth})")
2083
+ end
2084
+ end
2085
+
2086
+ centre = mid_x
2087
+ centre += sub[:centreline] if sub.key?(:centreline)
2088
+ n = sub[:count ]
2089
+ h = sub[:height ] + frames
2090
+ w = 0 # overall width of sub(s) bounding box (to calculate)
2091
+ x0 = 0 # left-side X-axis coordinate of sub(s) bounding box
2092
+ xf = 0 # right-side X-axis coordinate of sub(s) bounding box
2093
+
2094
+ # Log/reset "offset", if conflicting vs "width".
2095
+ if sub.key?(:ratio)
2096
+ if sub[:ratio] < TOL
2097
+ sub[:ratio ] = 0
2098
+ sub[:count ] = 0
2099
+ sub[:multiplier] = 0
2100
+ sub[:height ] = 0 if sub.key?(:height)
2101
+ sub[:width ] = 0 if sub.key?(:width)
2102
+ log(ERR, "Skip: '#{id}' ratio ~0 (#{mth})")
2103
+ next
2104
+ end
2105
+
2106
+ # Log/reset if "ratio" beyond min/max?
2107
+ unless sub[:ratio].between?(min, max)
2108
+ sub[:ratio] = min if sub[:ratio] < min
2109
+ sub[:ratio] = max if sub[:ratio] > max
2110
+ log(WRN, "Reset ratio (min/max) to #{sub[:ratio]} (#{mth})")
2111
+ end
2112
+
2113
+ # Log/reset "count" unless 1.
2114
+ unless sub[:count] == 1
2115
+ sub[:count] = 1
2116
+ log(WRN, "Reset count (ratio) to 1 (#{mth})")
2117
+ end
2118
+
2119
+ area = s.grossArea * sub[:ratio] # sub m2, including (optional) frames
2120
+ w = area / h
2121
+ width = w - frames
2122
+ x0 = centre - w/2
2123
+ xf = centre + w/2
2124
+
2125
+ if sub.key?(:l_buffer)
2126
+ if sub.key?(:centreline)
2127
+ log(WRN, "Skip #{id} left buffer (vs centreline) (#{mth})")
2128
+ else
2129
+ x0 = sub[:l_buffer] - frame
2130
+ xf = x0 + w
2131
+ centre = x0 + w/2
2132
+ end
2133
+ elsif sub.key?(:r_buffer)
2134
+ if sub.key?(:centreline)
2135
+ log(WRN, "Skip #{id} right buffer (vs centreline) (#{mth})")
2136
+ else
2137
+ xf = max_x - sub[:r_buffer] + frame
2138
+ x0 = xf - w
2139
+ centre = x0 + w/2
2140
+ end
2141
+ end
2142
+
2143
+ # Too wide?
2144
+ if x0 < min_ljamb || xf > max_rjamb
2145
+ sub[:ratio ] = 0 if sub.key?(:ratio)
2146
+ sub[:count ] = 0
2147
+ sub[:multiplier] = 0
2148
+ sub[:height ] = 0 if sub.key?(:height)
2149
+ sub[:width ] = 0 if sub.key?(:width)
2150
+ log(ERR, "Skip: invalid (ratio) width/centreline (#{mth})")
2151
+ next
2152
+ end
2153
+
2154
+ if sub.key?(:width) && (sub[:width] - width).abs > TOL
2155
+ sub[:width] = width
2156
+ log(WRN, "Reset width (ratio) to #{sub[:width]} (#{mth})")
2157
+ end
2158
+
2159
+ sub[:width] = width unless sub.key?(:width)
2160
+ else
2161
+ unless sub.key?(:width)
2162
+ sub[:ratio ] = 0 if sub.key?(:ratio)
2163
+ sub[:count ] = 0
2164
+ sub[:multiplier] = 0
2165
+ sub[:height ] = 0 if sub.key?(:height)
2166
+ sub[:width ] = 0 if sub.key?(:width)
2167
+ log(ERR, "Skip: missing '#{id}' width (#{mth})")
2168
+ next
2169
+ end
2170
+
2171
+ width = sub[:width] + frames
2172
+ gap = (max_x - n * width) / (n + 1)
2173
+ gap = sub[:offset] - width if sub.key?(:offset)
2174
+ gap = 0 if gap < bfr
2175
+ offset = gap + width
2176
+
2177
+ if sub.key?(:offset) && (offset - sub[:offset]).abs > TOL
2178
+ sub[:offset] = offset
2179
+ log(WRN, "Reset sub offset to #{sub[:offset]} m (#{mth})")
2180
+ end
2181
+
2182
+ sub[:offset] = offset unless sub.key?(:offset)
2183
+
2184
+ # Overall width (including frames) of bounding box around array.
2185
+ w = n * width + (n - 1) * gap
2186
+ x0 = centre - w/2
2187
+ xf = centre + w/2
2188
+
2189
+ if sub.key?(:l_buffer)
2190
+ if sub.key?(:centreline)
2191
+ log(WRN, "Skip #{id} left buffer (vs centreline) (#{mth})")
2192
+ else
2193
+ x0 = sub[:l_buffer] - frame
2194
+ xf = x0 + w
2195
+ centre = x0 + w/2
2196
+ end
2197
+ elsif sub.key?(:r_buffer)
2198
+ if sub.key?(:centreline)
2199
+ log(WRN, "Skip #{id} right buffer (vs centreline) (#{mth})")
2200
+ else
2201
+ xf = max_x - sub[:r_buffer] + frame
2202
+ x0 = xf - w
2203
+ centre = x0 + w/2
2204
+ end
2205
+ end
2206
+
2207
+ # Too wide?
2208
+ if x0 < bfr || xf > max_x - bfr
2209
+ sub[:ratio ] = 0 if sub.key?(:ratio)
2210
+ sub[:count ] = 0
2211
+ sub[:multiplier] = 0
2212
+ sub[:height ] = 0 if sub.key?(:height)
2213
+ sub[:width ] = 0 if sub.key?(:width)
2214
+ log(ERR, "Skip: invalid array width/centreline (#{mth})")
2215
+ next
2216
+ end
2217
+ end
2218
+
2219
+ # Initialize left-side X-axis coordinate of only/first sub.
2220
+ pos = x0 + frame
2221
+
2222
+ # Generate sub(s).
2223
+ sub[:count].times do |i|
2224
+ name = "#{id}:#{i}"
2225
+ fr = 0
2226
+ fr = sub[:frame].frameWidth if sub[:frame]
2227
+
2228
+ vec = OpenStudio::Point3dVector.new
2229
+ vec << OpenStudio::Point3d.new(pos, sub[:head], 0)
2230
+ vec << OpenStudio::Point3d.new(pos, sub[:sill], 0)
2231
+ vec << OpenStudio::Point3d.new(pos + sub[:width], sub[:sill], 0)
2232
+ vec << OpenStudio::Point3d.new(pos + sub[:width], sub[:head], 0)
2233
+ vec = tr * vec
2234
+
2235
+ # Log/skip if conflict between individual sub and base surface.
2236
+ vc = vec
2237
+ vc = offset(vc, fr, 300) if fr > 0
2238
+ ok = fits?(vc, s.vertices, name, nom)
2239
+ log(ERR, "Skip '#{name}': won't fit in '#{nom}' (#{mth})") unless ok
2240
+ break unless ok
2241
+
2242
+ # Log/skip if conflicts with existing subs (even if same array).
2243
+ s.subSurfaces.each do |sb|
2244
+ nome = sb.nameString
2245
+ fd = sb.windowPropertyFrameAndDivider
2246
+ fr = 0 if fd.empty?
2247
+ fr = fd.get.frameWidth unless fd.empty?
2248
+ vk = sb.vertices
2249
+ vk = offset(vk, fr, 300) if fr > 0
2250
+ oops = overlaps?(vc, vk, name, nome)
2251
+ log(ERR, "Skip '#{name}': overlaps '#{nome}' (#{mth})") if oops
2252
+ ok = false if oops
2253
+ break if oops
2254
+ end
2255
+
2256
+ break unless ok
2257
+
2258
+ sb = OpenStudio::Model::SubSurface.new(vec, model)
2259
+ sb.setName(name)
2260
+ sb.setSubSurfaceType(sub[:type])
2261
+ sb.setConstruction(sub[:assembly]) if sub[:assembly]
2262
+ ok = sb.allowWindowPropertyFrameAndDivider
2263
+ sb.setWindowPropertyFrameAndDivider(sub[:frame]) if sub[:frame] && ok
2264
+ sb.setMultiplier(sub[:multiplier]) if sub[:multiplier] > 1
2265
+ sb.setSurface(s)
2266
+
2267
+ # Reset "pos" if array.
2268
+ pos += sub[:offset] if sub.key?(:offset)
2269
+ end
2270
+ end
2271
+
2272
+ true
2273
+ end
2274
+
1687
2275
  ##
1688
2276
  # Callback when other modules extend OSlg
1689
2277
  #
data/lib/osut/version.rb CHANGED
@@ -29,5 +29,5 @@
29
29
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
30
 
31
31
  module OSut
32
- VERSION = "0.2.8".freeze
32
+ VERSION = "0.3.0".freeze
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Bourgeois
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-01 00:00:00.000000000 Z
11
+ date: 2023-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oslg
@@ -90,7 +90,7 @@ licenses:
90
90
  - BSD-3-Clause
91
91
  metadata:
92
92
  homepage_uri: https://github.com/rd2/osut
93
- source_code_uri: https://github.com/rd2/osut/tree/v0.2.8
93
+ source_code_uri: https://github.com/rd2/osut/tree/v0.3.0
94
94
  bug_tracker_uri: https://github.com/rd2/osut/issues
95
95
  post_install_message:
96
96
  rdoc_options: []