@@ -3015,9 +3015,8 @@ def width(pts=None) -> float:
30153015 if len (pts ) < 2 : return 0
30163016
30173017 xs = [pt .x () for pt in pts ]
3018- dx = max (xs ) - min (xs )
30193018
3020- return dx
3019+ return max ( xs ) - min ( xs )
30213020
30223021
30233022def height (pts = None ) -> float :
@@ -5088,6 +5087,71 @@ def realignedFace(pts=None, force=False) -> dict:
50885087 return out
50895088
50905089
5090+ def alignedWidth (pts = None , force = False ) -> float :
5091+ """Returns 'width' of a set of OpenStudio 3D points, once re/aligned.
5092+
5093+ Args:
5094+ pts (openstudio.Point3dVector):
5095+ A set of OpenStudio 3D points.
5096+ force (bool):
5097+ Whether to force rotation for aligned (yet narrow) boxes.
5098+
5099+ Returns:
5100+ float: Width along X-axis, once re/aligned.
5101+ 0.0: If invalid inputs (see logs).
5102+
5103+ """
5104+ mth = "osut.alignedWidth"
5105+ pts = osut .poly (pts , False , True , True , True )
5106+ if len (pts ) < 2 : return 0
5107+
5108+ try :
5109+ force = bool (force )
5110+ except :
5111+ oslg .log (CN .DBG , "Ignoring force input (%s)" % mth )
5112+ force = False
5113+
5114+ pts = osut .realignedFace (pts , force )["set" ]
5115+ if len (pts ) < 2 : return 0
5116+
5117+ xs = [pt .x () for pt in pts ]
5118+
5119+ return max (xs ) - min (xs )
5120+
5121+
5122+ def alignedHeight (pts = None , force = False ) -> float :
5123+ """Returns 'height' of a set of OpenStudio 3D points, once re/aligned.
5124+
5125+ Args:
5126+ pts (openstudio.Point3dVector):
5127+ A set of OpenStudio 3D points.
5128+ force (bool):
5129+ Whether to force rotation for aligned (yet narrow) boxes.
5130+
5131+ Returns:
5132+ float: Height along Y-axis, once re/aligned.
5133+ 0.0: If invalid inputs (see logs).
5134+
5135+ """
5136+
5137+ mth = "osut.alignedHeight"
5138+ pts = osut .poly (pts , False , True , True , True )
5139+ if len (pts ) < 2 : return 0
5140+
5141+ try :
5142+ force = bool (force )
5143+ except :
5144+ oslg .log (CN .DBG , "Ignoring force input (%s)" % mth )
5145+ force = False
5146+
5147+ pts = osut .realignedFace (pts , force )["set" ]
5148+ if len (pts ) < 2 : return 0
5149+
5150+ ys = [pt .y () for pt in pts ]
5151+
5152+ return max (ys ) - min (ys )
5153+
5154+
50915155def facets (spaces = [], boundary = "all" , type = "all" , sides = []) -> list :
50925156 """Returns an array of OpenStudio space surfaces or subsurfaces that match
50935157 criteria, e.g. exterior, north-east facing walls in hotel "lobby". Note
@@ -5304,3 +5368,74 @@ def genSlab(pltz=[], z=0):
53045368 slb = vtx
53055369
53065370 return slb
5371+
5372+
5373+ def roofs (spaces = []) -> list :
5374+ """Returns outdoor-facing, space-related roof surfaces. These include
5375+ outdoor-facing roofs of each space per se, as well as any outdoor-facing
5376+ roof surface of unoccupied spaces immediately above (e.g. plenums, attics)
5377+ overlapping any of the ceiling surfaces of each space. It does not include
5378+ surfaces labelled as 'RoofCeiling', which do not comply with ASHRAE 90.1 or
5379+ NECB tilt criteria - see 'isRoof'.
5380+
5381+ Args:
5382+ spaces (list):
5383+ Set of openstudio.model.Space instances.
5384+
5385+ Returns:
5386+ list of openstudio.model.Surface instances: roofs (may be empty).
5387+
5388+ """
5389+ mth = "osut.getRoofs"
5390+ up = openstudio .Point3d (0 ,0 ,1 ) - openstudio .Point3d (0 ,0 ,0 )
5391+ roofs = []
5392+ if isinstance (spaces , openstudio .model .Space ): spaces = [spaces ]
5393+
5394+ try :
5395+ spaces = list (spaces )
5396+ except :
5397+ spaces = []
5398+
5399+ spaces = [s for s in spaces if isinstance (s , openstudio .model .Space )]
5400+
5401+ # Space-specific outdoor-facing roof surfaces.
5402+ roofs = facets (spaces , "Outdoors" , "RoofCeiling" )
5403+ roofs = [roof for roof in roofs if isRoof (roof )]
5404+
5405+ for space in spaces :
5406+ # When unoccupied spaces are involved (e.g. plenums, attics), the
5407+ # target space may not share the same local transformation as the
5408+ # space(s) above. Fetching site transformation.
5409+ t0 = transforms (space )
5410+ if not t0 ["t" ]: continue
5411+
5412+ t0 = t0 ["t" ]
5413+
5414+ for ceiling in facets (space , "Surface" , "RoofCeiling" ):
5415+ cv0 = t0 * ceiling .vertices ()
5416+
5417+ floor = ceiling .adjacentSurface ()
5418+ if not floor : continue
5419+
5420+ other = floor .get ().space ()
5421+ if not other : continue
5422+
5423+ other = other .get ()
5424+ if other .partofTotalFloorArea (): continue
5425+
5426+ ti = transforms (other )
5427+ if not ti ["t" ]: continue
5428+
5429+ ti = ti ["t" ]
5430+
5431+ # @todo: recursive call for stacked spaces as atria (AirBoundaries).
5432+ for ruf in facets (other , "Outdoors" , "RoofCeiling" ):
5433+ if not isRoof (ruf ): continue
5434+
5435+ rvi = ti * ruf .vertices ()
5436+ cst = cast (cv0 , rvi , up )
5437+ if not overlapping (cst , rvi , False ): continue
5438+
5439+ if ruf not in roofs : roofs .append (ruf )
5440+
5441+ return roofs
0 commit comments