Skip to content

Commit 2f8ab94

Browse files
committed
Adds (getter) 'roofs' (tested)
1 parent c88bea5 commit 2f8ab94

File tree

2 files changed

+228
-4
lines changed

2 files changed

+228
-4
lines changed

src/osut/osut.py

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

30233022
def 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+
50915155
def 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

tests/test_osut.py

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3851,7 +3851,97 @@ def test27_polygon_attributes(self):
38513851

38523852
# def test31_convexity(self):
38533853

3854-
# def test32_outdoor_roofs(self):
3854+
def test32_outdoor_roofs(self):
3855+
o = osut.oslg
3856+
self.assertEqual(o.status(), 0)
3857+
self.assertEqual(o.reset(INF), INF)
3858+
self.assertEqual(o.level(), INF)
3859+
self.assertEqual(o.status(), 0)
3860+
3861+
translator = openstudio.osversion.VersionTranslator()
3862+
3863+
path = openstudio.path("./tests/files/osms/in/5ZoneNoHVAC.osm")
3864+
model = translator.loadModel(path)
3865+
self.assertTrue(model)
3866+
model = model.get()
3867+
3868+
spaces = {}
3869+
roofs = {}
3870+
3871+
for space in model.getSpaces():
3872+
for s in space.surfaces():
3873+
if s.surfaceType().lower() != "roofceiling": continue
3874+
if s.outsideBoundaryCondition().lower() != "outdoors": continue
3875+
3876+
self.assertFalse(space.nameString() in spaces)
3877+
spaces[space.nameString()] = s.nameString()
3878+
3879+
self.assertEqual(len(spaces), 5)
3880+
3881+
# for key, value in spaces.items(): print(key, value)
3882+
# "Story 1 East Perimeter Space" "Surface 18"
3883+
# "Story 1 North Perimeter Space" "Surface 12"
3884+
# "Story 1 Core Space" "Surface 30"
3885+
# "Story 1 South Perimeter Space" "Surface 24"
3886+
# "Story 1 West Perimeter Space" "Surface 6"
3887+
3888+
for space in model.getSpaces():
3889+
rufs = osut.roofs(space)
3890+
self.assertEqual(len(rufs), 1)
3891+
ruf = rufs[0]
3892+
self.assertTrue(isinstance(ruf, openstudio.model.Surface))
3893+
roofs[space.nameString()] = ruf.nameString()
3894+
3895+
self.assertEqual(len(roofs), len(spaces))
3896+
3897+
for id, surface in spaces.items():
3898+
self.assertTrue(id in roofs.keys())
3899+
self.assertEqual(roofs[id], surface)
3900+
3901+
del(model)
3902+
3903+
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
3904+
# CASE 2: None of the occupied spaces have outdoor-facing roofs, yet
3905+
# plenum above has 4 outdoor-facing roofs (each matches a space ceiling).
3906+
path = openstudio.path("./tests/files/osms/out/seb2.osm")
3907+
model = translator.loadModel(path)
3908+
self.assertTrue(model)
3909+
model = model.get()
3910+
3911+
occupied = []
3912+
spaces = {}
3913+
roofs = {}
3914+
3915+
for space in model.getSpaces():
3916+
if not space.partofTotalFloorArea(): continue
3917+
3918+
occupied.append(space.nameString())
3919+
3920+
for s in space.surfaces():
3921+
if s.surfaceType().lower() != "roofceiling": continue
3922+
if s.outsideBoundaryCondition().lower() != "outdoors": continue
3923+
3924+
self.assertFalse(space.nameString() in spaces)
3925+
spaces[space.nameString()] = s.nameString()
3926+
3927+
self.assertEqual(len(occupied), 4)
3928+
self.assertFalse(spaces)
3929+
3930+
for space in model.getSpaces():
3931+
if not space.partofTotalFloorArea(): continue
3932+
3933+
rufs = osut.roofs(space)
3934+
self.assertEqual(len(rufs), 1)
3935+
ruf = rufs[0]
3936+
self.assertTrue(isinstance(ruf, openstudio.model.Surface))
3937+
roofs[space.nameString()] = ruf.nameString()
3938+
3939+
self.assertEqual(len(roofs), 4)
3940+
self.assertEqual(o.status(), 0)
3941+
3942+
for occ in occupied:
3943+
self.assertTrue(occ in roofs.keys())
3944+
self.assertTrue("plenum" in roofs[occ].lower())
38553945

38563946
# def test33_leader_line_anchors_inserts(self):
38573947

@@ -3864,7 +3954,6 @@ def test35_facet_retrieval(self):
38643954
self.assertEqual(o.level(), DBG)
38653955
self.assertEqual(o.status(), 0)
38663956

3867-
version = int("".join(openstudio.openStudioVersion().split(".")))
38683957
translator = openstudio.osversion.VersionTranslator()
38693958

38703959
path = openstudio.path("./tests/files/osms/out/seb2.osm")

0 commit comments

Comments
 (0)