@@ -206,6 +206,68 @@ def uo() -> dict:
206206 return _uo
207207
208208
209+ def are_standardOpaqueLayers (lc = None ) -> bool :
210+ """
211+ Validates if every material in a layered construction is standard & opaque.
212+
213+ Args:
214+ lc:
215+ an OpenStudio layered construction
216+
217+ Returns:
218+ Whether all layers are valid. False if invalid inputs (see logs).
219+
220+ """
221+ mth = "osut.are_standardOpaqueLayers"
222+ cl = openstudio .model .LayeredConstruction
223+
224+ if not hasattr (lc , CN .NS ):
225+ return oslg .invalid ("layered construction" , mth , 1 , DBG , 0.0 )
226+
227+ id = lc .nameString ()
228+
229+ if not isinstance (lc , cl ):
230+ return oslg .mismatch (id , lc , cl , mth , CN .DBG , 0.0 )
231+
232+ for m in lc .layers ():
233+ if not m .to_StandardOpaqueMaterial (): return False
234+
235+ return True
236+
237+
238+ def thickness (lc = None ) -> float :
239+ """
240+ Returns total (standard opaque) layered construction thickness (m).
241+
242+ Args:
243+ lc:
244+ an OpenStudio layered construction
245+
246+ Returns:
247+ Construction thickness. 0.0 if invalid inputs (see logs).
248+
249+ """
250+ mth = "osut.thickness"
251+ cl = openStudio .model .LayeredConstruction
252+ d = 0.0
253+
254+ if not hasattr (lc , CN .NS ):
255+ return oslg .invalid ("layered construction" , mth , 1 , DBG , 0.0 )
256+
257+ id = lc .nameString ()
258+
259+ if not isinstance (lc , cl ):
260+ return oslg .mismatch (id , lc , cl , mth , CN .DBG , 0.0 )
261+
262+ if not osut .are_standardOpaqueLayers (lc ):
263+ log (CN .ERR , "%s holds non-StandardOpaqueMaterial(s) %s" % (id , mth ))
264+ return d
265+
266+ for m in lc .layers (): d += m .thickness ()
267+
268+ return d
269+
270+
209271def rsi (lc = None , film = 0.0 , t = 0.0 ) -> float :
210272 """
211273 Returns a construction's 'standard calc' thermal resistance (m2•K/W), which
@@ -307,7 +369,7 @@ def insulatingLayer(lc=None) -> dict:
307369 i = 0 # iterator
308370
309371 if not hasattr (lc , CN .NS ):
310- return oslg .invalid ("lc " , mth , 1 , CN .DBG , res )
372+ return oslg .invalid ("layered construction " , mth , 1 , CN .DBG , res )
311373
312374 id = lc .nameString ()
313375
@@ -725,3 +787,106 @@ def genConstruction(model=None, specs=dict()):
725787 layer .setThickness (d )
726788
727789 return c
790+
791+
792+ def genShade (subs = openstudio .model .SubSurfaceVector ()) -> bool :
793+ """
794+ Generates solar shade(s) (e.g. roller, textile) for glazed OpenStudio
795+ SubSurfaces (v321+), controlled to minimize overheating in cooling months
796+ (May to October in Northern Hemisphere), when outdoor dry bulb temperature
797+ is above 18°C and impinging solar radiation is above 100 W/m2.
798+
799+ Args:
800+ subs:
801+ A list of sub surfaces.
802+
803+ Returns:
804+ Whether successfully generated. False if invalid input (see logs).
805+
806+ """
807+ # Filter OpenStudio warnings for ShadingControl:
808+ # ref: https://github.com/NREL/OpenStudio/issues/4911
809+ # str = ".*(?<!ShadingControl)$"
810+ # openstudio.Logger().instance().standardOutLogger().setChannelRegex(str)
811+
812+ mth = "osut.genShade"
813+ v = int ("" .join (openstudio .openStudioVersion ().split ("." )))
814+ cl = openstudio .model .SubSurfaceVector
815+
816+ if v < 321 : return False
817+
818+ if not isinstance (subs , cl ):
819+ return oslg .mismatch ("subs" , subs , cl , mth , CN .DBG , False )
820+
821+ if not subs :
822+ return oslg .empty ("subs" , mth , CN .WRN , False )
823+
824+ # Shading availability period.
825+ model = subs [0 ].model ()
826+ id = "onoff"
827+ onoff = model .getScheduleTypeLimitsByName (id )
828+
829+ if onoff :
830+ onoff = onoff .get ()
831+ else :
832+ onoff = openstudio .model .ScheduleTypeLimits (model )
833+ onoff .setName (id )
834+ onoff .setLowerLimitValue (0 )
835+ onoff .setUpperLimitValue (1 )
836+ onoff .setNumericType ("Discrete" )
837+ onoff .setUnitType ("Availability" )
838+
839+ # Shading schedule.
840+ id = "OSut.SHADE.Ruleset"
841+ sch = model .getScheduleRulesetByName (id )
842+
843+ if sch :
844+ sch = sch .get ()
845+ else :
846+ sch = openstudio .model .ScheduleRuleset (model , 0 )
847+ sch .setName (id )
848+ sch .setScheduleTypeLimits (onoff )
849+ sch .defaultDaySchedule .setName ("OSut.SHADE.Ruleset.Default" )
850+
851+ # Summer cooling rule.
852+ id = "OSut.SHADE.ScheduleRule"
853+ rule = model .getScheduleRuleByName (id )
854+
855+ if rule :
856+ rule = rule .get ()
857+ else :
858+ may = openstudio .MonthOfYear ("May" )
859+ october = openstudio .MonthOfYear ("Oct" )
860+ start = openstudio .Date (may , 1 )
861+ finish = openstudio .Date (october , 31 )
862+
863+ rule = openstudio .model .ScheduleRule (sch )
864+ rule .setName (id )
865+ rule .setStartDate (start )
866+ rule .setEndDate (finish )
867+ rule .setApplyAllDays (true )
868+ rule .daySchedule .setName ("OSut.SHADE.Rule.Default" )
869+ rule .daySchedule .addValue (openstudio .Time (0 ,24 ,0 ,0 ), 1 )
870+
871+ # Shade object.
872+ id = "OSut.SHADE"
873+ shd = mdl .getShadeByName (id )
874+
875+ if shd :
876+ shd = shd .get ()
877+ else :
878+ shd = openstudio .model .Shade (mdl )
879+ shd .setName (id )
880+
881+ # Shading control (unique to each call).
882+ id = "OSut.ShadingControl"
883+ ctl = openstudio .model .ShadingControl (shd )
884+ ctl .setName (id )
885+ ctl .setSchedule (sch )
886+ ctl .setShadingControlType ("OnIfHighOutdoorAirTempAndHighSolarOnWindow" )
887+ ctl .setSetpoint (18 ) # °C
888+ ctl .setSetpoint2 (100 ) # W/m2
889+ ctl .setMultipleSurfaceControlType ("Group" )
890+ ctl .setSubSurfaces (subs )
891+
892+ return True
0 commit comments