# coding=utf-8
"""Methods to write to idf."""
from .config import folders
from ladybug_geometry.geometry3d import Face3D
from honeybee.room import Room
from honeybee.face import Face
from honeybee.boundarycondition import Outdoors, Surface, Ground
from honeybee.facetype import Floor, RoofCeiling, AirBoundary
try:
from itertools import izip as zip # python 2
except ImportError:
xrange = range # python 3
[docs]
def generate_idf_string(object_type, values, comments=None):
"""Get an IDF string representation of an EnergyPlus object.
Args:
object_type: Text representing the expected start of the IDF object.
(ie. WindowMaterial:Glazing).
values: A list of values associated with the EnergyPlus object in the
order that they are supposed to be written to IDF format.
comments: A list of text comments with the same length as the values.
If None, no comments will be written into the object.
Returns:
ep_str -- Am EnergyPlus IDF string representing a single object.
"""
if comments is not None:
space_count = tuple((25 - len(str(n))) for n in values)
spaces = tuple(s_c * ' ' if s_c > 0 else ' ' for s_c in space_count)
body_str = '\n '.join('{},{}!- {}'.format(val, spc, com) for val, spc, com in
zip(values[:-1], spaces[:-1], comments[:-1]))
ep_str = '{},\n {}'.format(object_type, body_str)
if len(values) == 1: # ensure we don't have an extra line break
ep_str = ''.join(
(ep_str, '{};{}!- {}'.format(values[-1], spaces[-1], comments[-1])))
else: # include an extra line break
end_str = '\n {};{}!- {}'.format(values[-1], spaces[-1], comments[-1]) \
if comments[-1] != '' else '\n {};'.format(values[-1])
ep_str = ''.join((ep_str, end_str))
else:
body_str = '\n '.join('{},'.format(val) for val in values[:-1])
ep_str = '{},\n {}'.format(object_type, body_str)
if len(values) == 1: # ensure we don't have an extra line break
ep_str = ''.join((ep_str, '{};'.format(values[-1])))
else: # include an extra line break
ep_str = ''.join((ep_str, '\n {};'.format(values[-1])))
return ep_str
[docs]
def shade_mesh_to_idf(shade_mesh):
"""Generate an IDF string representation of a ShadeMesh.
Note that the resulting string will possess both the Shading object
as well as a ShadingProperty:Reflectance if the Shade's construction
is not in line with the EnergyPlus default of 0.2 reflectance.
Args:
shade_mesh: A honeybee ShadeMesh for which an IDF representation
will be returned.
"""
trans_sched = shade_mesh.properties.energy.transmittance_schedule.identifier if \
shade_mesh.properties.energy.transmittance_schedule is not None else ''
all_shd_str = []
for i, shade in enumerate(shade_mesh.geometry.face_vertices):
# process the geometry to get upper-left vertices
shade_face = Face3D(shade)
ul_verts = shade_face.upper_left_counter_clockwise_vertices
# create the Shading:Detailed IDF string
values = (
'{}_{}'.format(shade_mesh.identifier, i),
trans_sched,
len(ul_verts),
',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
)
comments = (
'name',
'transmittance schedule',
'number of vertices',
''
)
shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
all_shd_str.append(shade_str)
# create the ShadingProperty:Reflectance if construction is not default
construction = shade_mesh.properties.energy.construction
if not construction.is_default:
values = (
shade_mesh.identifier,
construction.solar_reflectance,
construction.visible_reflectance
)
comments = (
'shading surface name',
'diffuse solar reflectance',
'diffuse visible reflectance'
)
if construction.is_specular:
values = values + (1, construction.identifier)
comments = comments + ('glazed fraction', 'glazing construction')
constr_str = generate_idf_string(
'ShadingProperty:Reflectance', values, comments)
all_shd_str.append(constr_str)
return '\n\n'.join(all_shd_str)
[docs]
def shade_to_idf(shade):
"""Generate an IDF string representation of a Shade.
Note that the resulting string will possess both the Shading object
as well as a ShadingProperty:Reflectance if the Shade's construction
is not in line with the EnergyPlus default of 0.2 reflectance.
Args:
shade: A honeybee Shade for which an IDF representation will be returned.
"""
# create the Shading:Detailed IDF string
trans_sched = shade.properties.energy.transmittance_schedule.identifier if \
shade.properties.energy.transmittance_schedule is not None else ''
ul_verts = shade.upper_left_vertices
if shade.has_parent and not isinstance(shade.parent, Room):
if isinstance(shade.parent, Face):
base_srf = shade.parent.identifier
else: # aperture or door for parent
try:
base_srf = shade.parent.parent.identifier
except AttributeError:
base_srf = 'unknown' # aperture without a parent (not simulate-able)
values = (
shade.identifier,
base_srf,
trans_sched,
len(shade.vertices),
',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
)
comments = (
'name',
'base surface',
'transmittance schedule',
'number of vertices',
''
)
shade_str = generate_idf_string('Shading:Zone:Detailed', values, comments)
else: # orphaned shade
values = (
shade.identifier,
trans_sched,
len(shade.vertices),
',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
)
comments = (
'name',
'transmittance schedule',
'number of vertices',
''
)
shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
# create the ShadingProperty:Reflectance IDF string if construction is not default
construction = shade.properties.energy.construction
if construction.is_default:
return shade_str
else:
values = (
shade.identifier,
construction.solar_reflectance,
construction.visible_reflectance
)
comments = (
'shading surface name',
'diffuse solar reflectance',
'diffuse visible reflectance'
)
if construction.is_specular:
values = values + (1, construction.identifier)
comments = comments + ('glazed fraction of surface', 'glazing construction')
constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
return '\n\n'.join((shade_str, constr_str))
[docs]
def door_to_idf(door):
"""Generate an IDF string representation of a Door.
Note that the resulting string does not include full construction definitions
but it will include a WindowShadingControl definition if a WindowConstructionShade
is assigned to the door. It will also include a ventilation object if the door
has a VentilationOpening object assigned to it.
Also note that shades assigned to the Door are not included in the resulting
string. To write these objects into a final string, you must loop through the
Door.shades, and call the to.idf method on each one.
Args:
door: A honeybee Door for which an IDF representation will be returned.
"""
# set defaults for missing fields
door_bc_obj = door.boundary_condition.boundary_condition_object if \
isinstance(door.boundary_condition, Surface) else ''
construction = door.properties.energy.construction
constr_name = construction.identifier if not construction.has_shade \
else construction.window_construction.identifier
frame_name = construction.frame.identifier if construction.has_frame else ''
if door.has_parent:
parent_face = door.parent.identifier
parent_room = door.parent.parent.identifier if door.parent.has_parent \
else 'unknown'
else:
parent_room = parent_face = 'unknown'
# create the fenestration surface string
ul_verts = door.upper_left_vertices
values = (
door.identifier,
'Door' if not door.is_glass else 'GlassDoor',
constr_name,
parent_face,
door_bc_obj,
door.boundary_condition.view_factor,
frame_name,
'1',
len(door.vertices),
',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
)
comments = (
'name',
'surface type',
'construction name',
'building surface name',
'boundary condition object',
'view factor to ground',
'frame and divider name',
'multiplier',
'number of vertices',
''
)
fen_str = generate_idf_string('FenestrationSurface:Detailed', values, comments)
# create the WindowShadingControl object if it is needed
if construction.has_shade:
shd_prop_str = construction.to_shading_control_idf(door.identifier, parent_room)
fen_str = '\n\n'.join((fen_str, shd_prop_str))
# create the VentilationOpening object if it is needed
if door.properties.energy.vent_opening is not None:
try:
vent_str = door.properties.energy.vent_opening.to_idf()
fen_str = '\n\n'.join((fen_str, vent_str))
except AssertionError: # door does not have a parent room
pass
return fen_str
[docs]
def orphaned_door_to_idf(door):
"""Generate an IDF string representation of an orphaned Door.
The resulting string will possess both the Shading object as well as
a ShadingProperty:Reflectance that aligns with the Door's exterior
construction properties. However, a transmittance schedule that matches
the transmittance of a window construction will only be referenced and
not included in the resulting string.
Args:
door: An orphaned Door for which an IDF shade representation will
be returned.
"""
# create the Shading:Detailed IDF string
cns = door.properties.energy.construction
trans_sch = 'Constant %.3f Transmittance' % cns.solar_transmittance \
if door.is_glass else ''
verts = door.upper_left_vertices
verts_str = ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in verts)
values = (door.identifier, trans_sch, len(verts), verts_str)
comments = ('name', 'transmittance schedule', 'number of vertices', '')
shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
# create the ShadingProperty:Reflectance
comments = (
'shade surface name', 'diffuse solar reflectance', 'diffuse visible reflectance')
if door.is_glass:
values = (door.identifier, 0.2, 0.2, 1, cns.identifier)
comments = comments + ('glazed fraction of surface', 'glazing construction')
else:
values = (door.identifier, cns.outside_solar_reflectance,
cns.outside_visible_reflectance)
constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
return '\n\n'.join((shade_str, constr_str))
[docs]
def aperture_to_idf(aperture):
"""Generate an IDF string representation of an Aperture.
Note that the resulting string does not include full construction definitions
but it will include a WindowShadingControl definition if a WindowConstructionShade
is assigned to the aperture. It will also include a ventilation object if the
aperture has a VentilationOpening object assigned to it.
Also note that shades assigned to the Aperture are not included in the resulting
string. To write these objects into a final string, you must loop through the
Aperture.shades, and call the to.idf method on each one.
Args:
aperture: A honeybee Aperture for which an IDF representation will be returned.
"""
# set defaults for missing fields
ap_bc_obj = aperture.boundary_condition.boundary_condition_object if \
isinstance(aperture.boundary_condition, Surface) else ''
construction = aperture.properties.energy.construction
frame_name = construction.frame.identifier if construction.has_frame else ''
if construction.has_shade:
constr_name = construction.window_construction.identifier
elif construction.is_dynamic:
constr_name = '{}State0'.format(construction.constructions[0].identifier)
else:
constr_name = construction.identifier
if aperture.has_parent:
parent_face = aperture.parent.identifier
parent_room = aperture.parent.parent.identifier if aperture.parent.has_parent \
else 'unknown'
else:
parent_room = parent_face = 'unknown'
# create the fenestration surface string
ul_verts = aperture.upper_left_vertices
values = (
aperture.identifier,
'Window',
constr_name,
parent_face,
ap_bc_obj,
aperture.boundary_condition.view_factor,
frame_name,
'1',
len(aperture.vertices),
',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
)
comments = (
'name',
'surface type',
'construction name',
'building surface name',
'boundary condition object',
'view factor to ground',
'frame and divider name',
'multiplier',
'number of vertices',
''
)
fen_str = generate_idf_string('FenestrationSurface:Detailed', values, comments)
# create the WindowShadingControl object if it is needed
if construction.has_shade:
shd_prop_str = construction.to_shading_control_idf(
aperture.identifier, parent_room)
fen_str = '\n\n'.join((fen_str, shd_prop_str))
# create the VentilationOpening object if it is needed
if aperture.properties.energy.vent_opening is not None:
try:
vent_str = aperture.properties.energy.vent_opening.to_idf()
fen_str = '\n\n'.join((fen_str, vent_str))
except AssertionError: # aperture does not have a parent room
pass
return fen_str
[docs]
def orphaned_aperture_to_idf(aperture):
"""Generate an IDF string representation of an orphaned Aperture.
The resulting string will possess both the Shading object as well as
a ShadingProperty:Reflectance that aligns with the Aperture's exterior
construction properties. However, a transmittance schedule that matches
the transmittance of the window construction will only be referenced and
not included in the resulting string. All transmittance schedules follow
the format of 'Constant %.3f Transmittance'.
Args:
aperture: An orphaned Aperture for which an IDF shade representation will
be returned.
"""
# create the Shading:Detailed IDF string
cns = aperture.properties.energy.construction
trans_sch = 'Constant %.3f Transmittance' % cns.solar_transmittance
verts = aperture.upper_left_vertices
verts_str = ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in verts)
values = (aperture.identifier, trans_sch, len(verts), verts_str)
comments = ('name', 'transmittance schedule', 'number of vertices', '')
shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
# create the ShadingProperty:Reflectance
values = (aperture.identifier, 0.2, 0.2, 1, cns.identifier)
comments = (
'shade surface name', 'diffuse solar reflectance', 'diffuse visible reflectance',
'glazed fraction of surface', 'glazing construction'
)
constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
return '\n\n'.join((shade_str, constr_str))
[docs]
def face_to_idf(face):
"""Generate an IDF string representation of a Face.
Note that the resulting string does not include full construction definitions.
Also note that this does not include any of the shades assigned to the Face
in the resulting string. Nor does it include the strings for the
apertures or doors. To write these objects into a final string, you must
loop through the Face.shades, Face.apertures, and Face.doors and call the
to.idf method on each one.
Args:
face: A honeybee Face for which an IDF representation will be returned.
"""
# select the correct face type
if isinstance(face.type, AirBoundary):
face_type = 'Wall' # air boundaries are not a Surface type in EnergyPlus
elif isinstance(face.type, RoofCeiling):
if face.altitude < 0:
face_type = 'Wall' # ensure E+ does not try to flip the Face
elif isinstance(face.boundary_condition, (Outdoors, Ground)):
face_type = 'Roof' # E+ distinguishes between Roof and Ceiling
else:
face_type = 'Ceiling'
elif isinstance(face.type, Floor) and face.altitude > 0:
face_type = 'Wall' # ensure E+ does not try to flip the Face
else:
face_type = face.type.name
# select the correct boundary condition
bc_name, append_txt = face.boundary_condition.name, None
if isinstance(face.boundary_condition, Surface):
face_bc_obj = face.boundary_condition.boundary_condition_object
elif face.boundary_condition.name == 'OtherSideTemperature':
face_bc_obj = '{}_OtherTemp'.format(face.identifier)
append_txt = face.boundary_condition.to_idf(face_bc_obj)
bc_name = 'OtherSideCoefficients'
else:
face_bc_obj = ''
# process the geometry correctly if it has holes
ul_verts = face.upper_left_vertices
if face.geometry.has_holes and isinstance(face.boundary_condition, Surface):
# check if the first vertex is the upper-left vertex
pt1, found_i = ul_verts[0], False
for pt in ul_verts[1:]:
if pt == pt1:
found_i = True
break
if found_i: # reorder the vertices to have boundary first
ul_verts = reversed(ul_verts)
# assemble the values and the comments
values = (
face.identifier,
face_type,
face.properties.energy.construction.identifier,
face.parent.identifier if face.has_parent else 'unknown',
'',
bc_name,
face_bc_obj,
face.boundary_condition.sun_exposure_idf,
face.boundary_condition.wind_exposure_idf,
face.boundary_condition.view_factor,
len(face.vertices),
',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in ul_verts)
)
comments = (
'name',
'surface type',
'construction name',
'zone name',
'space name',
'boundary condition',
'boundary condition object',
'sun exposure',
'wind exposure',
'view factor to ground',
'number of vertices',
''
)
face_idf = generate_idf_string('BuildingSurface:Detailed', values, comments)
return face_idf if not append_txt else face_idf + append_txt
[docs]
def orphaned_face_to_idf(face):
"""Generate an IDF string representation of an orphaned Face.
The resulting string will possess both the Shading object as well as
a ShadingProperty:Reflectance that aligns with the Face's exterior
construction properties.
Args:
face: An orphaned Face for which an IDF representation will be returned.
"""
# create the Shading:Detailed IDF string
verts = face.punched_geometry.upper_left_counter_clockwise_vertices
verts_str = ',\n '.join('%.3f, %.3f, %.3f' % (v.x, v.y, v.z) for v in verts)
values = (face.identifier, '', len(verts), verts_str)
comments = ('name', 'transmittance schedule', 'number of vertices', '')
shade_str = generate_idf_string('Shading:Building:Detailed', values, comments)
# create the ShadingProperty:Reflectance IDF string
cns = face.properties.energy.construction
values = (
face.identifier, cns.outside_solar_reflectance, cns.outside_visible_reflectance)
comments = (
'shade surface name', 'diffuse solar reflectance', 'diffuse visible reflectance')
constr_str = generate_idf_string('ShadingProperty:Reflectance', values, comments)
return '\n\n'.join((shade_str, constr_str))
[docs]
def room_to_idf(room):
"""Generate an IDF string representation of a Room.
The resulting string will include all internal gain definitions for the Room
(people, lights, equipment), infiltration definitions, ventilation requirements,
and thermostat objects. However, complete schedule definitions assigned to
these objects are excluded and the Room's hvac is also excluded.
Also note that this method does not write any of the geometry of the Room
into the resulting string. To represent the Room geometry, you must loop
through the Room.shades and Room.faces and call the to.idf method on
each one. Note that you will likely also need to call to.idf on the
apertures, doors and shades of each face as well as the shades on each
aperture.
Args:
room: A honeybee Room for which an IDF representation will be returned.
"""
# list of zone strings that will eventually be joined
clean_name = room.display_name.replace('\n', '')
zone_str = ['!- ________ZONE:{}________\n'.format(clean_name)]
# write the zone definition
ceil_height = room.geometry.max.z - room.geometry.min.z
zone_values = (room.identifier, '', '', '', '', '', room.multiplier,
ceil_height, room.volume, room.floor_area)
zone_comments = ('name', 'north', 'x', 'y', 'z', 'type', 'multiplier',
'ceiling height', 'volume', 'floor area')
zone_str.append(generate_idf_string('Zone', zone_values, zone_comments))
# write the load definitions
people = room.properties.energy.people
lighting = room.properties.energy.lighting
electric_equipment = room.properties.energy.electric_equipment
gas_equipment = room.properties.energy.gas_equipment
shw = room.properties.energy.service_hot_water
infiltration = room.properties.energy.infiltration
ventilation = room.properties.energy.ventilation
if people is not None:
zone_str.append(people.to_idf(room.identifier))
if lighting is not None:
zone_str.append(lighting.to_idf(room.identifier))
if electric_equipment is not None:
zone_str.append(electric_equipment.to_idf(room.identifier))
if gas_equipment is not None:
zone_str.append(gas_equipment.to_idf(room.identifier))
if shw is not None:
shw_str, shw_sch = shw.to_idf(room)
zone_str.append(shw_str)
zone_str.extend(shw_sch)
if infiltration is not None:
zone_str.append(infiltration.to_idf(room.identifier))
# write the ventilation and thermostat
if ventilation is not None:
zone_str.append(ventilation.to_idf(room.identifier))
if room.properties.energy.is_conditioned and \
room.properties.energy.setpoint is not None:
zone_str.append(room.properties.energy.setpoint.to_idf(room.identifier))
# write any ventilation fan definitions
for fan in room.properties.energy._fans:
zone_str.append(fan.to_idf(room.identifier))
# write the daylighting control
if room.properties.energy.daylighting_control is not None:
zone_str.extend(room.properties.energy.daylighting_control.to_idf())
# write any process load definitions
for p_load in room.properties.energy._process_loads:
zone_str.append(p_load.to_idf(room.identifier))
# write any internal mass definitions
for int_mass in room.properties.energy._internal_masses:
zone_str.append(int_mass.to_idf(room.identifier))
return '\n\n'.join(zone_str)
[docs]
def model_to_idf(
model, schedule_directory=None, use_ideal_air_equivalent=True,
patch_missing_adjacencies=False
):
r"""Generate an IDF string representation of a Model.
The resulting string will include all geometry (Rooms, Faces, Shades, Apertures,
Doors), all fully-detailed constructions + materials, all fully-detailed
schedules, and the room properties (loads, thermostats with setpoints, and HVAC).
Essentially, the string includes everything needed to simulate the model
except the simulation parameters. So joining this string with the output of
SimulationParameter.to_idf() should create a simulate-able IDF.
Args:
model: A honeybee Model for which an IDF representation will be returned.
schedule_directory: An optional file directory to which all file-based
schedules should be written to. If None, all ScheduleFixedIntervals
will be translated to Schedule:Compact and written fully into the
IDF string instead of to Schedule:File. (Default: None).
use_ideal_air_equivalent: Boolean to note whether any detailed HVAC system
templates should be converted to an equivalent IdealAirSystem upon export.
If False and the Model contains detailed systems, a ValueError will
be raised since this method does not support the translation of
detailed systems. (Default:True).
patch_missing_adjacencies: Boolean to note whether any missing adjacencies
in the model should be replaced with Adiabatic boundary conditions.
This is useful when the input model is only a portion of a much
larger model. (Default: False).
Usage:
.. code-block:: python
import os
from ladybug.futil import write_to_file
from honeybee.model import Model
from honeybee.room import Room
from honeybee.config import folders
from honeybee_energy.lib.programtypes import office_program
from honeybee_energy.hvac.idealair import IdealAirSystem
from honeybee_energy.simulation.parameter import SimulationParameter
# Get input Model
room = Room.from_box('Tiny House Zone', 5, 10, 3)
room.properties.energy.program_type = office_program
room.properties.energy.add_default_ideal_air()
model = Model('Tiny House', [room])
# Get the input SimulationParameter
sim_par = SimulationParameter()
sim_par.output.add_zone_energy_use()
ddy_file = 'C:/EnergyPlusV9-0-1/WeatherData/USA_CO_Golden-NREL.724666_TMY3.ddy'
sim_par.sizing_parameter.add_from_ddy_996_004(ddy_file)
# create the IDF string for simulation parameters and model
idf_str = '\n\n'.join((sim_par.to_idf(), model.to.idf(model)))
# write the final string into an IDF
idf = os.path.join(folders.default_simulation_folder, 'test_file', 'in.idf')
write_to_file(idf, idf_str, True)
"""
# duplicate model to avoid mutating it as we edit it for energy simulation
original_model = model
model = model.duplicate()
# scale the model if the units are not meters
if model.units != 'Meters':
model.convert_to_units('Meters')
# remove degenerate geometry within native E+ tolerance of 0.01 meters
try:
model.remove_degenerate_geometry(0.01)
except ValueError:
error = 'Failed to remove degenerate Rooms.\nYour Model units system is: {}. ' \
'Is this correct?'.format(original_model.units)
raise ValueError(error)
# convert model to simple ventilation and Ideal Air Systems
model.properties.energy.ventilation_simulation_control.vent_control_type = \
'SingleZone'
if use_ideal_air_equivalent:
for room in model.rooms:
room.properties.energy.assign_ideal_air_equivalent()
# patch missing adjacencies
if patch_missing_adjacencies:
model.properties.energy.missing_adjacencies_to_adiabatic()
# write the building object into the string
model_str = ['!- =======================================\n'
'!- ================ MODEL ================\n'
'!- =======================================\n']
# write all of the schedules and type limits
sched_strs = []
type_limits = []
used_day_sched_ids, used_day_count = {}, 1
always_on_included = False
all_scheds = model.properties.energy.schedules + \
model.properties.energy.orphaned_trans_schedules
for sched in all_scheds:
if sched.identifier == 'Always On':
always_on_included = True
try: # ScheduleRuleset
year_schedule, week_schedules = sched.to_idf()
if week_schedules is None: # ScheduleConstant
sched_strs.append(year_schedule)
else: # ScheduleYear
# check that day schedules aren't referenced by other model schedules
day_scheds = []
for day in sched.day_schedules:
if day.identifier not in used_day_sched_ids:
day_scheds.append(day.to_idf(sched.schedule_type_limit))
used_day_sched_ids[day.identifier] = day
elif day != used_day_sched_ids[day.identifier]:
new_day = day.duplicate()
new_day.identifier = 'Schedule Day {}'.format(used_day_count)
day_scheds.append(new_day.to_idf(sched.schedule_type_limit))
for i, week_sch in enumerate(week_schedules):
week_schedules[i] = \
week_sch.replace(day.identifier, new_day.identifier)
used_day_count += 1
sched_strs.extend([year_schedule] + week_schedules + day_scheds)
except TypeError: # ScheduleFixedInterval
if schedule_directory is None:
sched_strs.append(sched.to_idf_compact())
else:
sched_strs.append(sched.to_idf(schedule_directory))
t_lim = sched.schedule_type_limit
if t_lim is not None and not _instance_in_array(t_lim, type_limits):
type_limits.append(t_lim)
if not always_on_included:
always_schedule, _ = model.properties.energy._always_on_schedule().to_idf()
sched_strs.append(always_schedule)
model_str.append('!- ========= SCHEDULE TYPE LIMITS =========\n')
model_str.extend([type_limit.to_idf() for type_limit in set(type_limits)])
model_str.append('!- ============== SCHEDULES ==============\n')
model_str.extend(sched_strs)
# get the default generic construction set
# must be imported here to avoid circular imports
from .lib.constructionsets import generic_construction_set
# write all of the materials and constructions
materials = []
construction_strs = []
dynamic_cons = []
all_constrs = model.properties.energy.constructions + \
generic_construction_set.constructions_unique
for constr in set(all_constrs):
try:
materials.extend(constr.materials)
construction_strs.append(constr.to_idf())
if constr.has_frame:
materials.append(constr.frame)
if constr.has_shade:
if constr.window_construction in all_constrs:
construction_strs.pop(-1) # avoid duplicate specification
if constr.is_switchable_glazing:
materials.append(constr.switched_glass_material)
construction_strs.append(constr.to_shaded_idf())
elif constr.is_dynamic:
dynamic_cons.append(constr)
except AttributeError:
try: # AirBoundaryConstruction or ShadeConstruction
construction_strs.append(constr.to_idf()) # AirBoundaryConstruction
except TypeError:
pass # ShadeConstruction; no need to write it
model_str.append('!- ============== MATERIALS ==============\n')
model_str.extend([mat.to_idf() for mat in set(materials)])
model_str.append('!- ============ CONSTRUCTIONS ============\n')
model_str.extend(construction_strs)
# write all of the HVAC systems
model_str.append('!- ============ HVAC SYSTEMS ============\n')
for room in model.rooms:
if room.properties.energy.hvac is not None \
and room.properties.energy.setpoint is not None:
try:
model_str.append(room.properties.energy.hvac.to_idf(room))
except AttributeError:
raise TypeError(
'HVAC system type "{}" does not support direct translation to IDF.\n'
'Use the export to OpenStudio workflow instead.'.format(
room.properties.energy.hvac.__class__.__name__))
# get the default air boundary construction
# must be imported here to avoid circular imports
from .lib.constructions import air_boundary
# write all of the zone geometry
model_str.append('!- ============ ZONE GEOMETRY ============\n')
ap_objs = []
found_ab = []
for room in model.rooms:
model_str.append(room.to.idf(room))
for face in room.faces:
model_str.append(face.to.idf(face))
if isinstance(face.type, AirBoundary): # write the air mixing objects
air_constr = face.properties.energy.construction
try:
if face.identifier not in found_ab:
adj_face = face.boundary_condition.boundary_condition_object
adj_room = face.boundary_condition.boundary_condition_objects[-1]
try:
model_str.append(
air_constr.to_cross_mixing_idf(face, adj_room))
except AttributeError: # opaque construction for air boundary
model_str.append(
air_boundary.to_cross_mixing_idf(face, adj_room))
found_ab.append(adj_face)
except AttributeError as e:
raise ValueError(
'Face "{}" is an Air Boundary but lacks a Surface boundary '
'condition.\n{}'.format(face.full_id, e))
for ap in face.apertures:
if len(ap.geometry) <= 4: # ignore apertures to be triangulated
model_str.append(ap.to.idf(ap))
ap_objs.append(ap)
for shade in ap.outdoor_shades:
model_str.append(shade.to.idf(shade))
for dr in face.doors:
if len(dr.geometry) <= 4: # ignore doors to be triangulated
model_str.append(dr.to.idf(dr))
for shade in dr.outdoor_shades:
model_str.append(shade.to.idf(shade))
for shade in face.outdoor_shades:
model_str.append(shade.to.idf(shade))
for shade in room.outdoor_shades:
model_str.append(shade.to.idf(shade))
# triangulate any apertures or doors with more than 4 vertices
tri_apertures, _ = model.triangulated_apertures()
for tri_aps in tri_apertures:
for i, ap in enumerate(tri_aps):
if i != 0:
ap.properties.energy.vent_opening = None
model_str.append(ap.to.idf(ap))
ap_objs.append(ap)
tri_doors, _ = model.triangulated_doors()
for tri_drs in tri_doors:
for i, dr in enumerate(tri_drs):
if i != 0:
ap.properties.energy.vent_opening = None
model_str.append(dr.to.idf(dr))
# write all context shade geometry
model_str.append('!- ========== CONTEXT GEOMETRY ==========\n')
pv_objects = []
for shade in model.orphaned_shades:
model_str.append(shade.to.idf(shade))
if shade.properties.energy.pv_properties is not None:
pv_objects.append(shade)
for face in model.orphaned_faces:
model_str.append(face.to.idf_shade(face))
for ap in face.apertures:
model_str.append(ap.to.idf_shade(ap))
for dr in face.doors:
model_str.append(dr.to.idf_shade(dr))
for ap in model.orphaned_apertures:
model_str.append(ap.to.idf_shade(ap))
for dr in model.orphaned_doors:
model_str.append(dr.to.idf_shade(dr))
for shade_mesh in model.shade_meshes:
model_str.append(shade_mesh.to.idf(shade_mesh))
# write any EMS programs for dynamic constructions
if len(dynamic_cons) != 0:
model_str.append('!- ========== EMS PROGRAMS ==========\n')
dyn_dict = {}
for ap in ap_objs:
con = ap.properties.energy.construction
try:
dyn_dict[con.identifier].append(ap.identifier)
except KeyError:
dyn_dict[con.identifier] = [ap.identifier]
for con in dynamic_cons:
model_str.append(con.to_program_idf(dyn_dict[con.identifier]))
model_str.append(dynamic_cons[0].idf_program_manager(dynamic_cons))
# write any generator objects that were discovered in the model
if len(pv_objects) != 0:
model_str.append('!- ========== PHOTOVOLTAIC GENERATORS ==========\n')
for shade in pv_objects:
model_str.append(shade.properties.energy.pv_properties.to_idf(shade))
model_str.extend(model.properties.energy.electric_load_center.to_idf(pv_objects))
return '\n\n'.join(model_str)
[docs]
def energyplus_idf_version(version_array=None):
"""Get IDF text for the version of EnergyPlus.
This will match the version of EnergyPlus found in the config if it it exists.
It will be None otherwise.
Args:
version_array: An array of up to 3 integers for the version of EnergyPlus
for which an IDF string should be generated. If None, the energyplus_version
from the config will be used if it exists.
"""
if version_array:
ver_str = '.'.join((str(d) for d in version_array))
return generate_idf_string('Version', [ver_str], ['version identifier'])
elif folders.energyplus_version:
ver_str = '.'.join((str(d) for d in folders.energyplus_version))
return generate_idf_string('Version', [ver_str], ['version identifier'])
return None
def _instance_in_array(object_instance, object_array):
"""Check if a specific object instance is already in an array.
This can be much faster than `if object_instance in object_array`
when you expect to be testing a lot of the same instance of an object for
inclusion in an array since the builtin method uses an == operator to
test inclusion.
"""
for val in object_array:
if val is object_instance:
return True
return False