Skip to content

Commit a6bca1b

Browse files
committed
Completes 'SmallOffice' skylight unittests
1 parent 52dc079 commit a6bca1b

File tree

1 file changed

+175
-1
lines changed

1 file changed

+175
-1
lines changed

tests/test_osut.py

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test01_osm_instantiation(self):
5151
model = openstudio.model.Model()
5252
self.assertTrue(isinstance(model, openstudio.model.Model))
5353
del model
54-
54+
5555
def test02_tuples(self):
5656
self.assertEqual(len(osut.sidz()), 6)
5757
self.assertEqual(len(osut.mass()), 4)
@@ -5010,6 +5010,180 @@ def test34_generated_skylight_wells(self):
50105010

50115011
model.save("./tests/files/osms/out/office_attic.osm", True)
50125012

5013+
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5014+
# Side test/comment: Why is it necessary to have 'addSkylights' return
5015+
# gross roof area (see 'rm2' above)?
5016+
#
5017+
# First, retrieving (newly-added) core roofs (i.e. skylight base
5018+
# surfaces).
5019+
rfs1 = osut.facets(core, "Outdoors", "RoofCeiling")
5020+
tot1 = sum([sk.grossArea() for sk in rfs1])
5021+
net = sum([sk.netArea() for sk in rfs1])
5022+
self.assertEqual(len(rfs1), 4)
5023+
self.assertAlmostEqual(tot1, 9.06, places=2) # 4x 2.265 m2
5024+
self.assertAlmostEqual(tot1 - net, sky_area1, places=2)
5025+
5026+
# In absence of skylight wells (more importantly, in absence of leader
5027+
# lines anchoring skylight base surfaces), OSut's 'roofs' &
5028+
# 'grossRoofArea' report not only on newly-added base surfaces (or
5029+
# their areas), but also overalpping areas of attic roofs above.
5030+
# Unfortunately, these become unreliable with newly-added skylight wells.
5031+
rfs2 = osut.roofs(core)
5032+
tot2 = sum([sk.grossArea() for sk in rfs2])
5033+
self.assertAlmostEqual(tot2, tot1, places=2)
5034+
self.assertAlmostEqual(tot2, osut.grossRoofArea(core), places=2)
5035+
5036+
# Fortunately, the addition of leader lines does not affect how
5037+
# OpenStudio reports surface areas.
5038+
rfs3 = osut.facets(attic, "Outdoors", "RoofCeiling")
5039+
tot3 = sum([sk.grossArea() for sk in rfs3])
5040+
self.assertAlmostEqual(tot3 + tot2, total2, places=2) # 598.76
5041+
5042+
# However, as discussed elsewhere (see 'addSkylights' doctring and
5043+
# inline comments), these otherwise valid areas are often overestimated
5044+
# for SRR% calculations (e.g. when overhangs and soffits are explicitely
5045+
# modelled). It is for this reason 'addSkylights' reports gross roof
5046+
# area BEFORE adding skylight wells. For higher-level applications
5047+
# relying on 'addSkylights' (e.g. an OpenStudio measure), it is better
5048+
# to store returned gross roof areas for subsequent reporting purposes.
5049+
5050+
# Deeper dive: Why are OSut's 'roofs' and 'grossRoofArea' unreliable
5051+
# with leader lines? Both rely on OSut's 'overlapping', itself relying
5052+
# on OpenStudio's 'join' and 'intersect': if neither are successful in
5053+
# joining (or intersecting) 2x polygons (e.g. attic roof vs cast core
5054+
# ceiling), there can be no identifiable overlap. In such cases, both
5055+
# 'roofs' and 'grossRoofArea' ignore overlapping attic roofs. A demo:
5056+
roof_north = model.getSurfaceByName("Attic_roof_north")
5057+
core_ceiling = model.getSurfaceByName("Core_ZN_ceiling")
5058+
self.assertTrue(roof_north)
5059+
self.assertTrue(core_ceiling)
5060+
roof_north = roof_north.get()
5061+
core_ceiling = core_ceiling.get()
5062+
5063+
t = openstudio.Transformation.alignFace(roof_north.vertices())
5064+
up = openstudio.Point3d(0,0,1) - openstudio.Point3d(0,0,0)
5065+
5066+
a_roof_north = t.inverse() * roof_north.vertices()
5067+
a_core_ceiling = t.inverse() * core_ceiling.vertices()
5068+
c_core_ceiling = osut.cast(a_core_ceiling, a_roof_north, up)
5069+
5070+
north_m2 = openstudio.getArea(a_roof_north)
5071+
ceiling_m2 = openstudio.getArea(c_core_ceiling)
5072+
self.assertTrue(north_m2)
5073+
self.assertTrue(ceiling_m2)
5074+
self.assertAlmostEqual(north_m2.get(), 192.98, places=2)
5075+
self.assertAlmostEqual(ceiling_m2.get(), 133.81, places=2)
5076+
5077+
# So far so good. Ensure clockwise winding.
5078+
a_roof_north = list(a_roof_north)
5079+
c_core_ceiling = list(c_core_ceiling)
5080+
a_roof_north.reverse()
5081+
c_core_ceiling.reverse()
5082+
self.assertFalse(openstudio.join(a_roof_north, c_core_ceiling, TOL2))
5083+
self.assertFalse(openstudio.intersect(a_roof_north, c_core_ceiling, TOL))
5084+
5085+
# A future revision of OSut's 'roofs' and 'grossRoofArea' would require:
5086+
# - a new method identifying leader lines amongst surface vertices
5087+
# - a new method identifying surface cutouts amongst surface vertices
5088+
# - a method to prune both leader lines and cutouts from surface vertices
5089+
# - have 'roofs' & 'grossRoofArea' rely on the remaining outer vertices
5090+
# ... @todo?
5091+
self.assertEqual(o.status(), 0)
5092+
del model
5093+
5094+
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5095+
# CASE 2:
5096+
path = openstudio.path("./tests/files/osms/in/smalloffice.osm")
5097+
model = translator.loadModel(path)
5098+
self.assertTrue(model)
5099+
model = model.get()
5100+
5101+
core = model.getSpaceByName("Core_ZN")
5102+
attic = model.getSpaceByName("Attic")
5103+
self.assertTrue(core)
5104+
self.assertTrue(attic)
5105+
core = core.get()
5106+
attic = attic.get()
5107+
5108+
# Tag attic as an INDIRECTLY-CONDITIONED space.
5109+
key = "indirectlyconditioned"
5110+
val = core.nameString()
5111+
self.assertTrue(attic.additionalProperties().setFeature(key, val))
5112+
self.assertFalse(osut.arePlenums(attic))
5113+
self.assertFalse(osut.isUnconditioned(attic))
5114+
self.assertAlmostEqual(osut.setpoints(attic)["heating"], 21.11, places=2)
5115+
self.assertAlmostEqual(osut.setpoints(attic)["cooling"], 23.89, places=2)
5116+
5117+
# Here, GRA includes ALL plenum roof surfaces (not just vertically-cast
5118+
# roof areas onto the core ceiling). More roof surfaces == greater
5119+
# skylight areas to meet the SRR% of 5%.
5120+
gra_plenum = osut.grossRoofArea(core)
5121+
self.assertAlmostEqual(gra_plenum, total1, places=2)
5122+
5123+
rm2 = osut.addSkyLights(core, dict(srr=srr))
5124+
if o.logs(): print(o.logs())
5125+
self.assertAlmostEqual(rm2, total1, places=2)
5126+
5127+
# The total skylight area is greater than in CASE 1. Nonetheless, the
5128+
# method is able to meet the requested SRR 5%. This may not be
5129+
# achievable in other circumstances, given the constrained roof/core
5130+
# overlap. Although a plenum vastly larger than the room(s) it serves is
5131+
# rare, it remains certainly problematic for the application of the
5132+
# Canadian NECB reference building skylight requirements.
5133+
core_skies = osut.facets(core, "Outdoors", "Skylight")
5134+
sky_area2 = sum([sk.grossArea() for sk in core_skies])
5135+
self.assertAlmostEqual(sky_area2, 29.94, places=2)
5136+
ratio2 = sky_area2 / rm2
5137+
self.assertAlmostEqual(ratio2, srr, places=2)
5138+
5139+
model.save("./tests/files/osms/out/office_plenum.osm", True)
5140+
5141+
self.assertEqual(o.status(), 0)
5142+
del model
5143+
5144+
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5145+
# CASE 2b:
5146+
path = openstudio.path("./tests/files/osms/in/smalloffice.osm")
5147+
model = translator.loadModel(path)
5148+
self.assertTrue(model)
5149+
model = model.get()
5150+
5151+
core = model.getSpaceByName("Core_ZN")
5152+
attic = model.getSpaceByName("Attic")
5153+
self.assertTrue(core)
5154+
self.assertTrue(attic)
5155+
core = core.get()
5156+
attic = attic.get()
5157+
5158+
# Again, tagging attic as an INDIRECTLY-CONDITIONED space.
5159+
key = "indirectlyconditioned"
5160+
val = core.nameString()
5161+
self.assertTrue(attic.additionalProperties().setFeature(key, val))
5162+
self.assertFalse(osut.arePlenums(attic))
5163+
self.assertFalse(osut.isUnconditioned(attic))
5164+
self.assertAlmostEqual(osut.setpoints(attic)["heating"], 21.11, places=2)
5165+
self.assertAlmostEqual(osut.setpoints(attic)["cooling"], 23.89, places=2)
5166+
5167+
gra_plenum = osut.grossRoofArea(core)
5168+
self.assertAlmostEqual(gra_plenum, total1, places=2)
5169+
5170+
# Conflicting argument case: Here, skylight wells must traverse plenums
5171+
# (in this context, "plenum" is an all encompassing keyword for any
5172+
# INDIRECTLY-CONDITIONED, unoccupied space). Yet by passing option
5173+
# "plenum: False", the method is instructed to skip "plenum" skylight
5174+
# wells altogether.
5175+
rm2 = osut.addSkyLights(core, dict(srr=srr, plenum=False))
5176+
self.assertTrue(o.is_warn())
5177+
self.assertEqual(len(o.logs()), 1)
5178+
msg = o.logs()[0]["message"]
5179+
self.assertTrue("Empty 'subsets (3)' (osut.addSkyLights)" in msg)
5180+
self.assertAlmostEqual(rm2, total1, places=2)
5181+
5182+
core_skies = osut.facets(core, "Outdoors", "Skylight")
5183+
sky_area2 = sum([sk.grossArea() for sk in core_skies])
5184+
self.assertAlmostEqual(sky_area2, 0.00, places=2)
5185+
self.assertEqual(o.clean(), DBG)
5186+
50135187
self.assertEqual(o.status(), 0)
50145188
del model
50155189

0 commit comments

Comments
 (0)