# coding=utf-8
"""Methods to write Honeybee Models to OpenStudio."""
from __future__ import division
import sys
import os
import tempfile
import json
import subprocess
import platform
import xml.etree.ElementTree as ET
from ladybug_geometry.geometry3d import Face3D
from honeybee.typing import clean_ep_string, clean_string
from honeybee.altnumber import autocalculate
from honeybee.facetype import RoofCeiling, Floor, AirBoundary
from honeybee.boundarycondition import Outdoors, Surface
from honeybee.model import Model
from honeybee_energy.config import folders as hbe_folders
from honeybee_energy.boundarycondition import Adiabatic, OtherSideTemperature
from honeybee_energy.construction.window import WindowConstruction
from honeybee_energy.construction.windowshade import WindowConstructionShade
from honeybee_energy.construction.dynamic import WindowConstructionDynamic
from honeybee_energy.hvac.idealair import IdealAirSystem
from honeybee_energy.hvac._template import _TemplateSystem
from honeybee_energy.hvac.detailed import DetailedHVAC
from honeybee_energy.lib.constructionsets import generic_construction_set
from honeybee_openstudio.openstudio import OSModel, OSPoint3dVector, OSPoint3d, \
OSShadingSurfaceGroup, OSShadingSurface, OSSubSurface, OSSurface, OSSpace, \
OSThermalZone, OSBuildingStory, OSSurfacePropertyOtherSideCoefficients, \
OSEnergyManagementSystemProgramCallingManager, openstudio, os_path, os_vector_len
from honeybee_openstudio.schedule import schedule_type_limits_to_openstudio, \
schedule_to_openstudio
from honeybee_openstudio.material import material_to_openstudio
from honeybee_openstudio.construction import construction_to_openstudio, \
air_mixing_to_openstudio, window_shading_control_to_openstudio, \
window_dynamic_ems_program_to_openstudio
from honeybee_openstudio.constructionset import construction_set_to_openstudio
from honeybee_openstudio.internalmass import internal_mass_to_openstudio
from honeybee_openstudio.load import people_to_openstudio, lighting_to_openstudio, \
electric_equipment_to_openstudio, gas_equipment_to_openstudio, \
hot_water_to_openstudio, process_to_openstudio, \
infiltration_to_openstudio, ventilation_to_openstudio, \
setpoint_to_openstudio_thermostat, setpoint_to_openstudio_humidistat, \
daylight_to_openstudio
from honeybee_openstudio.programtype import program_type_to_openstudio
from honeybee_openstudio.ventcool import ventilation_opening_to_openstudio, \
ventilation_fan_to_openstudio, ventilation_sim_control_to_openstudio, \
afn_crack_to_openstudio, ventilation_opening_to_openstudio_afn, \
ventilation_control_to_openstudio_afn, zone_temperature_sensor, \
outdoor_temperature_sensor, ventilation_control_program_manager
from honeybee_openstudio.shw import shw_system_to_openstudio
from honeybee_openstudio.hvac.idealair import ideal_air_system_to_openstudio
from honeybee_openstudio.hvac.template import template_hvac_to_openstudio
from honeybee_openstudio.generator import pv_properties_to_openstudio, \
electric_load_center_to_openstudio
from honeybee_openstudio.hvac.standards.utilities import \
rename_air_loop_nodes, rename_plant_loop_nodes
[docs]
def face_3d_to_openstudio(face_3d):
"""Convert a Face3D into an OpenStudio Point3dVector.
Args:
face_3d: A ladybug-geometry Face3D object for which an OpenStudio Point3dVector
string will be generated.
Returns:
An OpenStudio Point3dVector to be used to construct geometry objects.
"""
os_vertices = OSPoint3dVector()
for pt in face_3d.upper_left_counter_clockwise_vertices:
try:
os_vertices.append(OSPoint3d(pt.x, pt.y, pt.z))
except AttributeError: # using OpenStudio .NET bindings
os_vertices.Add(OSPoint3d(pt.x, pt.y, pt.z))
return os_vertices
[docs]
def shade_mesh_to_openstudio(shade_mesh, os_model):
"""Create OpenStudio objects from a ShadeMesh.
Args:
shade_mesh: A honeybee ShadeMesh for which OpenStudio objects will be returned.
os_model: The OpenStudio Model object to which the ShadeMesh will be added.
Returns:
A list of OpenStudio ShadingSurface objects.
"""
# loop through the mesh faces and create individual shade objects
os_shades = []
os_shd_group = OSShadingSurfaceGroup(os_model)
os_shd_group.setName(shade_mesh.identifier)
for i, shade in enumerate(shade_mesh.geometry.face_vertices):
# create the shade object with the geometry
shade_face = Face3D(shade)
os_vertices = face_3d_to_openstudio(shade_face)
os_shade = OSShadingSurface(os_vertices, os_model)
os_shade.setName('{}_{}'.format(shade_mesh.identifier, i))
os_shade.setShadingSurfaceGroup(os_shd_group)
os_shades.append(os_shade)
if shade_mesh._display_name is not None:
for os_shade in os_shades:
os_shade.setDisplayName(shade_mesh.display_name)
# assign the construction and transmittance
construction = shade_mesh.properties.energy.construction
if shade_mesh.properties.energy.is_construction_set_on_object and \
not construction.is_default:
os_construction = os_model.getConstructionByName(construction.identifier)
if os_construction.is_initialized():
os_construction = os_construction.get()
for os_shade in os_shades:
os_shade.setConstruction(os_construction)
trans_sched = shade_mesh.properties.energy.transmittance_schedule
if trans_sched is not None:
os_schedule = os_model.getScheduleByName(trans_sched.identifier)
if os_schedule.is_initialized():
os_schedule = os_schedule.get()
for os_shade in os_shades:
os_shade.setTransmittanceSchedule(os_schedule)
return os_shades
[docs]
def shade_to_openstudio(shade, os_model):
"""Create an OpenStudio object from a Shade.
Args:
shade: A honeybee Shade for which an OpenStudio object will be returned.
os_model: The OpenStudio Model object to which the Shade will be added.
Returns:
An OpenStudio ShadingSurface object.
"""
# create the shade object with the geometry
os_vertices = face_3d_to_openstudio(shade.geometry)
os_shade = OSShadingSurface(os_vertices, os_model)
os_shade.setName(shade.identifier)
if shade._display_name is not None:
os_shade.setDisplayName(shade.display_name)
# assign the construction and transmittance
construction = shade.properties.energy.construction
if shade.properties.energy.is_construction_set_on_object and \
not construction.is_default:
os_construction = os_model.getConstructionByName(construction.identifier)
if os_construction.is_initialized():
os_construction = os_construction.get()
os_shade.setConstruction(os_construction)
trans_sched = shade.properties.energy.transmittance_schedule
if trans_sched is not None:
os_schedule = os_model.getScheduleByName(trans_sched.identifier)
if os_schedule.is_initialized():
os_schedule = os_schedule.get()
os_shade.setTransmittanceSchedule(os_schedule)
# add the PVProperties if they exist
pv_prop = shade.properties.energy.pv_properties
if pv_prop is not None:
pv_properties_to_openstudio(pv_prop, os_shade, os_model)
return os_shade
[docs]
def door_to_openstudio(door, os_model):
"""Create an OpenStudio object from a Door.
Args:
door: A honeybee Door for which an OpenStudio object will be returned.
os_model: The OpenStudio Model object to which the Door will be added.
Returns:
An OpenStudio SubSurface object if the Door has a parent. An OpenStudio
ShadingSurface object if the Door has no parent.
"""
# convert the base geometry to OpenStudio
os_vertices = face_3d_to_openstudio(door.geometry)
# translate the geometry to either a SubSurface or a ShadingSurface
if door.has_parent: # translate the geometry as SubSurface
os_door = OSSubSurface(os_vertices, os_model)
if door.is_glass:
dr_type = 'GlassDoor'
else:
par = door.parent
dr_type = 'OverheadDoor' if isinstance(par.boundary_condition, Outdoors) and \
isinstance(par.type, (RoofCeiling, Floor)) else 'Door'
os_door.setSubSurfaceType(dr_type)
# assign the construction if it's hard set
construction = door.properties.energy.construction
if door.properties.energy.is_construction_set_on_object:
if construction.has_shade:
constr_id = construction.window_construction.identifier
elif construction.is_dynamic:
constr_id = '{}State0'.format(construction.constructions[0].identifier)
else:
constr_id = construction.identifier
os_construction = os_model.getConstructionByName(constr_id)
if os_construction.is_initialized():
os_construction = os_construction.get()
os_door.setConstruction(os_construction)
# assign the frame property if the window construction has one
if construction.has_frame:
frame_id = construction.frame.identifier
frame = os_model.getWindowPropertyFrameAndDividerByName(frame_id)
if frame.is_initialized():
os_frame = frame.get()
os_door.setWindowPropertyFrameAndDivider(os_frame)
# create the WindowShadingControl object if it is needed
if construction.has_shade:
shd_prop_str = window_shading_control_to_openstudio(construction, os_model)
os_door.setShadingControl(shd_prop_str)
else: # translate the geometry as ShadingSurface
os_door = OSShadingSurface(os_vertices, os_model)
cns = door.properties.energy.construction
os_construction = os_model.getConstructionByName(cns.identifier)
if os_construction.is_initialized():
os_construction = os_construction.get()
os_door.setConstruction(os_construction)
if door.is_glass:
trans_sch = 'Constant %.3f Transmittance' % cns.solar_transmittance
os_schedule = os_model.getScheduleByName(trans_sch)
if os_schedule.is_initialized():
os_schedule = os_schedule.get()
os_door.setTransmittanceSchedule(os_schedule)
# translate any shades assigned to the Door
for shd in door._outdoor_shades:
shade_to_openstudio(shd, os_model)
# set the object name and return it
os_door.setName(door.identifier)
if door._display_name is not None:
os_door.setDisplayName(door.display_name)
return os_door
[docs]
def aperture_to_openstudio(aperture, os_model):
"""Create an OpenStudio object from an Aperture.
Args:
aperture: A honeybee Aperture for which an OpenStudio object will be returned.
os_model: The OpenStudio Model object to which the Aperture will be added.
Returns:
An OpenStudio SubSurface object if the Aperture has a parent. An OpenStudio
ShadingSurface object if the Aperture has no parent.
"""
# convert the base geometry to OpenStudio
os_vertices = face_3d_to_openstudio(aperture.geometry)
# translate the geometry to either a SubSurface or a ShadingSurface
if aperture.has_parent: # translate the geometry as SubSurface
os_aperture = OSSubSurface(os_vertices, os_model)
if aperture.is_operable:
ap_type = 'OperableWindow'
else:
par = aperture.parent
ap_type = 'Skylight' if isinstance(par.boundary_condition, Outdoors) and \
isinstance(par.type, (RoofCeiling, Floor)) else 'FixedWindow'
os_aperture.setSubSurfaceType(ap_type)
# assign the construction if it's hard set
construction = aperture.properties.energy.construction
if aperture.properties.energy.is_construction_set_on_object:
if construction.has_shade:
constr_id = construction.window_construction.identifier
elif construction.is_dynamic:
constr_id = '{}State0'.format(construction.constructions[0].identifier)
else:
constr_id = construction.identifier
os_construction = os_model.getConstructionByName(constr_id)
if os_construction.is_initialized():
os_construction = os_construction.get()
os_aperture.setConstruction(os_construction)
# assign the frame property if the window construction has one
if construction.has_frame:
frame_id = construction.frame.identifier
frame = os_model.getWindowPropertyFrameAndDividerByName(frame_id)
if frame.is_initialized():
os_frame = frame.get()
os_aperture.setWindowPropertyFrameAndDivider(os_frame)
# create the WindowShadingControl object if it is needed
if construction.has_shade:
shd_prop_str = window_shading_control_to_openstudio(construction, os_model)
os_aperture.setShadingControl(shd_prop_str)
else: # translate the geometry as ShadingSurface
os_aperture = OSShadingSurface(os_vertices, os_model)
cns = aperture.properties.energy.construction
os_construction = os_model.getConstructionByName(cns.identifier)
if os_construction.is_initialized():
os_construction = os_construction.get()
os_aperture.setConstruction(os_construction)
trans_sch = 'Constant %.3f Transmittance' % cns.solar_transmittance
os_schedule = os_model.getScheduleByName(trans_sch)
if os_schedule.is_initialized():
os_schedule = os_schedule.get()
os_aperture.setTransmittanceSchedule(os_schedule)
# translate any shades assigned to the Aperture
for shd in aperture._outdoor_shades:
shade_to_openstudio(shd, os_model)
# set the object name and return it
os_aperture.setName(aperture.identifier)
if aperture._display_name is not None:
os_aperture.setDisplayName(aperture.display_name)
return os_aperture
[docs]
def face_to_openstudio(face, os_model, adj_map=None, ignore_complex_sub_faces=True):
"""Create an OpenStudio object from a Face.
This method also adds all Apertures, Doors, and Shades assigned to the Face.
Args:
face: A honeybee Face for which an OpenStudio object will be returned.
os_model: The OpenStudio Model object to which the Face will be added.
adj_map: An optional dictionary with keys for 'faces' and 'sub_faces'
that will have the space Surfaces and SubSurfaces added to it
such that adjacencies can be assigned after running this method.
ignore_complex_sub_faces: Boolean for whether sub-faces (including Apertures
and Doors) should be ignored if they have more than 4 sides (True) or
whether they should be left as they are (False). (Default: True).
Returns:
An OpenStudio Surface object if the Face has a parent. An OpenStudio
ShadingSurface object if the Face has no parent.
"""
# translate the geometry to either a SubSurface or a ShadingSurface
if face.has_parent:
# create the Surface
os_vertices = face_3d_to_openstudio(face.geometry)
os_face = OSSurface(os_vertices, os_model)
# select the correct face type
if isinstance(face.type, AirBoundary):
os_f_type = 'Wall' # air boundaries are not a Surface type in EnergyPlus
elif isinstance(face.type, RoofCeiling):
if face.altitude < 0:
os_f_type = 'Wall' # ensure E+ does not try to flip the Face
else:
os_f_type = 'RoofCeiling'
elif isinstance(face.type, Floor) and face.altitude > 0:
os_f_type = 'Wall' # ensure E+ does not try to flip the Face
else:
os_f_type = face.type.name
os_face.setSurfaceType(os_f_type)
# assign the boundary condition
fbc = face.boundary_condition
if not isinstance(fbc, (Surface, OtherSideTemperature)):
os_face.setOutsideBoundaryCondition(fbc.name)
if isinstance(fbc, Outdoors):
if not fbc.sun_exposure:
os_face.setSunExposure('NoSun')
if not fbc.wind_exposure:
os_face.setWindExposure('NoWind')
if fbc.view_factor != autocalculate:
os_face.setViewFactortoGround(fbc.view_factor)
elif isinstance(fbc, OtherSideTemperature):
srf_prop = OSSurfacePropertyOtherSideCoefficients(os_model)
srf_prop.setName('{}_OtherTemp'.format(face.identifier))
htc = fbc.heat_transfer_coefficient
srf_prop.setCombinedConvectiveRadiativeFilmCoefficient(htc)
if fbc.temperature == autocalculate:
srf_prop.setConstantTemperatureCoefficient(0)
srf_prop.setExternalDryBulbTemperatureCoefficient(1)
else:
srf_prop.setConstantTemperature(fbc.temperature)
srf_prop.setConstantTemperatureCoefficient(1)
srf_prop.setExternalDryBulbTemperatureCoefficient(0)
os_face.setSurfacePropertyOtherSideCoefficients(srf_prop)
# assign the construction if it's hard set, an AirBoundary, or Adiabatic
if face.properties.energy.is_construction_set_on_object or \
isinstance(face.type, AirBoundary) or \
isinstance(face.boundary_condition, (Adiabatic, OtherSideTemperature)):
construction_id = face.properties.energy.construction.identifier
os_construction = os_model.getConstructionByName(construction_id)
if os_construction.is_initialized():
os_construction = os_construction.get()
os_face.setConstruction(os_construction)
# create the sub-faces
sub_faces = {}
for ap in face.apertures:
# ignore apertures to be triangulated
if len(ap.geometry) <= 4 or not ignore_complex_sub_faces:
os_ap = aperture_to_openstudio(ap, os_model)
os_ap.setSurface(os_face)
sub_faces[ap.identifier] = os_ap
for dr in face.doors:
# ignore doors to be triangulated
if len(dr.geometry) <= 4 or not ignore_complex_sub_faces:
os_dr = door_to_openstudio(dr, os_model)
os_dr.setSurface(os_face)
sub_faces[dr.identifier] = os_dr
# update the adjacency map if it exists
if adj_map is not None:
adj_map['faces'][face.identifier] = os_face
adj_map['sub_faces'].update(sub_faces)
else:
os_vertices = face_3d_to_openstudio(face.punched_geometry)
os_face = OSShadingSurface(os_vertices, os_model)
for ap in face.apertures:
aperture_to_openstudio(ap.duplicate(), os_model)
for dr in face.doors:
door_to_openstudio(dr.duplicate(), os_model)
for shd in face._outdoor_shades:
shade_to_openstudio(shd, os_model)
# set the object name and return it
os_face.setName(face.identifier)
if face._display_name is not None:
os_face.setDisplayName(face.display_name)
return os_face
[docs]
def room_to_openstudio(room, os_model, adj_map=None, include_infiltration=True,
ignore_complex_sub_faces=True):
"""Create OpenStudio objects from a Room.
Args:
room: A honeybee Room for which an OpenStudio object will be returned.
os_model: The OpenStudio Model object to which the Room will be added.
adj_map: An optional dictionary with keys for 'faces' and 'sub_faces'
that will have the space Surfaces and SubSurfaces added to it
such that adjacencies can be assigned after running this method.
include_infiltration: Boolean for whether or not infiltration will be included
in the translation of the Room. It may be desirable to set this
to False if the building airflow is being modeled with the EnergyPlus
AirFlowNetwork. (Default: True).
ignore_complex_sub_faces: Boolean for whether sub-faces (including Apertures
and Doors) should be ignored if they have more than 4 sides (True) or
whether they should be left as they are (False). (Default: True).
Returns:
An OpenStudio Space object for the Room.
"""
# create the space
os_space = OSSpace(os_model)
os_space.setName('{}_Space'.format(room.identifier))
if room._display_name is not None:
os_space.setDisplayName(room.display_name)
if room.exclude_floor_area:
if sys.version_info < (3, 0): # .NET bindings are missing the method
os_space.setString(11, 'No')
else:
os_space.setPartofTotalFloorArea(False)
try:
os_space.setVolume(room.volume)
except AttributeError: # older OpenStudio bindings where method was not implemented
pass
# assign the construction set if specified
if room.properties.energy._construction_set is not None:
con_set_id = room.properties.energy.construction_set.identifier
os_con_set = os_model.getDefaultConstructionSetByName(con_set_id)
if os_con_set.is_initialized():
os_con_set = os_con_set.get()
os_space.setDefaultConstructionSet(os_con_set)
# assign the program type if specified
overridden_loads = room.properties.energy.has_overridden_space_loads
if not overridden_loads and room.properties.energy._program_type is not None:
# assign loads using the OpenStudio SpaceType
space_type_id = room.properties.energy.program_type.identifier
os_space_type = os_model.getSpaceTypeByName(space_type_id)
if os_space_type.is_initialized():
space_type_object = os_space_type.get()
os_space.setSpaceType(space_type_object)
elif overridden_loads:
# assign loads directly to the space
people = room.properties.energy.people
if people is not None:
os_people = people_to_openstudio(people, os_model)
os_people.setName('{}..{}'.format(people.identifier, room.identifier))
os_people.setSpace(os_space)
lighting = room.properties.energy.lighting
if lighting is not None:
os_lights = lighting_to_openstudio(lighting, os_model)
os_lights.setName('{}..{}'.format(lighting.identifier, room.identifier))
os_lights.setSpace(os_space)
equipment = room.properties.energy.electric_equipment
if equipment is not None:
os_equip = electric_equipment_to_openstudio(equipment, os_model)
os_equip.setName('{}..{}'.format(equipment.identifier, room.identifier))
os_equip.setSpace(os_space)
equipment = room.properties.energy.gas_equipment
if equipment is not None:
os_equip = gas_equipment_to_openstudio(equipment, os_model)
os_equip.setName('{}..{}'.format(equipment.identifier, room.identifier))
os_equip.setSpace(os_space)
infilt = room.properties.energy.infiltration
if infilt is not None and include_infiltration:
os_inf = infiltration_to_openstudio(infilt, os_model)
os_inf.setName('{}..{}'.format(infilt.identifier, room.identifier))
os_inf.setSpace(os_space)
# assign the ventilation and catch the case that the SpaceType one is not correct
if overridden_loads or room.properties.energy._ventilation is not None:
vent = room.properties.energy.ventilation
if vent is not None:
os_vent = os_model.getDesignSpecificationOutdoorAirByName(vent.identifier)
if os_vent.is_initialized():
os_vent = os_vent.get()
else:
os_vent = ventilation_to_openstudio(vent, os_model)
os_space.setDesignSpecificationOutdoorAir(os_vent)
# assign all process loads
for process in room.properties.energy.process_loads:
os_process = process_to_openstudio(process, os_model)
os_process.setName('{}..{}'.format(process.identifier, room.identifier))
os_process.setSpace(os_space)
# assign the daylight control if it is specified
daylight = room.properties.energy.daylighting_control
if daylight is not None:
os_daylight = daylight_to_openstudio(daylight, os_model)
os_daylight.setName('{}_Daylighting'.format(room.identifier))
os_daylight.setSpace(os_space)
# assign any internal mass definitions if specified
for mass in room.properties.energy.internal_masses:
os_mass = internal_mass_to_openstudio(mass, os_model)
os_mass.setName('{}::{}'.format(mass.identifier, room.identifier))
os_mass.setSpace(os_space)
# assign all of the faces to the room
for face in room.faces:
os_face = face_to_openstudio(face, os_model, adj_map,
ignore_complex_sub_faces=ignore_complex_sub_faces)
os_face.setSpace(os_space)
# add any assigned shades to a group for the room
child_shades = []
child_shades.extend(room._outdoor_shades)
for face in room._faces:
child_shades.extend(face._outdoor_shades)
for ap in face.apertures:
child_shades.extend(ap._outdoor_shades)
for dr in face.doors:
child_shades.extend(dr._outdoor_shades)
if len(child_shades) != 0:
os_shd_group = OSShadingSurfaceGroup(os_model)
os_shd_group.setName('{} Shades'.format(room.identifier))
os_shd_group.setSpace(os_space)
os_shd_group.setShadingSurfaceType('Space')
for shd in child_shades:
os_shade = shade_to_openstudio(shd, os_model)
os_shade.setShadingSurfaceGroup(os_shd_group)
return os_space
[docs]
def model_to_openstudio(
model, seed_model=None, schedule_directory=None,
use_geometry_names=False, use_resource_names=False,
triangulate_non_planar_orphaned=False, triangulate_subfaces=True,
use_simple_window_constructions=False, enforce_rooms=False, print_progress=False
):
"""Create an OpenStudio Model from a Honeybee Model.
The resulting Model will include all geometry (Rooms, Faces, Apertures,
Doors, Shades), all fully-detailed constructions + materials, all fully-detailed
schedules, and the room properties.
Args:
model: The Honeybee Model to be converted into an OpenStudio Model.
seed_model: An optional OpenStudio Model object to which the Honeybee
Model will be added. If None, a new OpenStudio Model will be
initialized within this method. (Default: None).
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_geometry_names: Boolean to note whether a cleaned version of all
geometry display names should be used instead of identifiers when
translating the Model to OSM and IDF. Using this flag will affect
all Rooms, Faces, Apertures, Doors, and Shades. It will generally
result in more read-able names in the OSM and IDF but this means
that it will not be easy to map the EnergyPlus results back to the
input Honeybee Model. Cases of duplicate IDs resulting from
non-unique names will be resolved by adding integers to the ends
of the new IDs that are derived from the name. (Default: False).
use_resource_names: Boolean to note whether a cleaned version of all
resource display names should be used instead of identifiers when
translating the Model to OSM and IDF. Using this flag will affect
all Materials, Constructions, ConstructionSets, Schedules, Loads,
and ProgramTypes. It will generally result in more read-able names
for the resources in the OSM and IDF. Cases of duplicate IDs
resulting from non-unique names will be resolved by adding integers
to the ends of the new IDs that are derived from the name. (Default: False).
triangulate_non_planar_orphaned: Boolean to note whether any non-planar
orphaned geometry in the model should be triangulated upon export.
This can be helpful because OpenStudio simply raises an error when
it encounters non-planar geometry, which would hinder the ability
to save files that are to be corrected later. (Default: False).
triangulate_subfaces: Boolean to note whether sub-faces (including
Apertures and Doors) should be triangulated if they have more
than 4 sides (True) or whether they should be left as they are (False).
This triangulation is necessary when exporting directly to EnergyPlus
since it cannot accept sub-faces with more than 4 vertices. (Default: True).
use_simple_window_constructions: Boolean to note whether the Model should
be translated with simple window constructions, all of which will
be represented with a single-layer glazing system construction. This
is useful for translation to gbXML since the U-value will only show
up if the construction is simple. (Default: False).
enforce_rooms: Boolean to note whether this method should enforce the
presence of Rooms in the Model, which is as necessary prerequisite
for simulation in EnergyPlus. (Default: False).
print_progress: Set to True to have the progress of the translation
printed as it is completed. (Default: False).
Usage:
.. code-block:: python
import os
from honeybee.model import Model
from honeybee.room import Room
from honeybee.config import folders
from honeybee_energy.lib.programtypes import office_program
import openstudio
from honeybee_openstudio.writer import model_to_openstudio
# Crate an 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()
hb_model = Model('Tiny_House', [room])
# translate the honeybee model to an openstudio model
os_model = model_to_openstudio(hb_model)
# save the OpenStudio model to an OSM
osm = os.path.join(folders.default_simulation_folder, 'in.osm')
os_model.save(osm, overwrite=True)
# save the OpenStudio model to an IDF file
idf_translator = openstudio.energyplus.ForwardTranslator()
workspace = idf_translator.translateModel(os_model)
idf = os.path.join(folders.default_simulation_folder, 'in.idf')
workspace.save(idf, overwrite=True)
"""
# check the model and check for rooms if this is enforced
assert isinstance(model, Model), \
'Expected Honeybee Model for model_to_openstudio. Got {}.'.format(type(model))
if enforce_rooms:
assert len(model.rooms) != 0, \
'Model contains no Rooms and therefore cannot be simulated in EnergyPlus.'
# 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)
if triangulate_non_planar_orphaned:
model.triangulate_non_planar_quads(0.01)
# remove the HVAC from any Rooms lacking setpoints
rem_msgs = model.properties.energy.remove_hvac_from_no_setpoints()
if len(rem_msgs) != 0:
print('\n'.join(rem_msgs))
# auto-assign stories if there are none since most OpenStudio measures need these
if len(model.stories) == 0 and len(model.rooms) != 0:
model.assign_stories_by_floor_height()
# reset the IDs to be derived from the display_names if requested
if use_geometry_names:
id_map = model.reset_ids()
model.properties.energy.sync_detailed_hvac_ids(id_map['rooms'])
if use_resource_names:
model.properties.energy.reset_resource_ids()
# resolve the properties across zones
single_zones, zone_dict = model.properties.energy.resolve_zones()
# make note of how the airflow will be modeled across the building
vent_sim_control = model.properties.energy.ventilation_simulation_control
use_simple_vent = True if vent_sim_control.vent_control_type == 'SingleZone' \
or sys.version_info < (3, 0) else False # AFN not supported in .NET
# create the OpenStudio model object and set properties for speed
os_model = OSModel() if seed_model is None else seed_model
os_model.setStrictnessLevel(openstudio.StrictnessLevel('None'))
os_model.setFastNaming(True)
# setup the Building
os_building = os_model.getBuilding()
if model._display_name is not None:
os_building.setName(clean_ep_string(model.display_name))
else:
os_building.setName(model.identifier)
os_model.setDayofWeekforStartDay('Sunday') # this avoids lots of warnings
os_model.alwaysOnDiscreteSchedule()
os_model.alwaysOffDiscreteSchedule()
os_model.alwaysOnContinuousSchedule()
if print_progress:
print('Model prepared for translation')
# write all of the schedules and type limits
schedules, type_limits = [], []
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
schedules.append(sched)
t_lim = sched.schedule_type_limit
if t_lim is not None:
for val in type_limits:
if val is t_lim:
break
else:
type_limits.append(t_lim)
if not always_on_included:
always_schedule = model.properties.energy._always_on_schedule()
schedules.append(always_schedule)
for stl in type_limits:
schedule_type_limits_to_openstudio(stl, os_model)
for sch in schedules:
schedule_to_openstudio(sch, os_model, schedule_directory)
if print_progress:
print('Translated {} Schedules'.format(len(schedules)))
# write all of the materials, constructions, and construction sets
w_cons = (WindowConstruction, WindowConstructionShade, WindowConstructionDynamic)
materials, constructions, dynamic_cons = [], [], []
all_constrs = model.properties.energy.constructions + \
generic_construction_set.constructions_unique
for constr in set(all_constrs):
try:
if use_simple_window_constructions and isinstance(constr, w_cons):
if isinstance(constr, WindowConstruction):
new_con = constr.to_simple_construction()
elif isinstance(constr, WindowConstructionShade):
new_con = constr.window_construction.to_simple_construction()
elif isinstance(constr, WindowConstructionDynamic):
new_con = constr.constructions[0].to_simple_construction()
materials.extend(new_con.materials)
constructions.append(new_con)
else:
materials.extend(constr.materials)
constructions.append(constr)
if constr.has_frame:
materials.append(constr.frame)
if constr.has_shade:
if constr.window_construction in all_constrs:
constructions.pop(-1) # avoid duplicate specification
if constr.is_switchable_glazing:
materials.append(constr.switched_glass_material)
elif constr.is_dynamic:
dynamic_cons.append(constr)
except AttributeError:
try: # AirBoundaryConstruction or ShadeConstruction
constructions.append(constr) # AirBoundaryConstruction
except TypeError:
pass # ShadeConstruction; no need to write it
for mat in set(materials):
material_to_openstudio(mat, os_model)
if print_progress:
print('Translated {} Materials'.format(len(materials)))
for constr in constructions:
construction_to_openstudio(constr, os_model)
if print_progress:
print('Translated {} Constructions'.format(len(constructions)))
os_generic_c_set = construction_set_to_openstudio(generic_construction_set, os_model)
os_building.setDefaultConstructionSet(os_generic_c_set)
c_sets = model.properties.energy.construction_sets
for con_set in c_sets:
construction_set_to_openstudio(con_set, os_model)
if print_progress:
print('Translated {} Construction Sets'.format(len(c_sets)))
# translate all of the programs
p_types = model.properties.energy.program_types
for program in p_types:
program_type_to_openstudio(program, os_model, use_simple_vent)
if print_progress:
print('Translated {} Program Types'.format(len(p_types)))
# create all of the spaces with all of their geometry
if print_progress:
print('Translating Rooms')
space_map, story_map = {}, {}
adj_map = {'faces': {}, 'sub_faces': {}}
rooms = model.rooms
for i, room in enumerate(rooms):
os_space = room_to_openstudio(room, os_model, adj_map, use_simple_vent,
triangulate_subfaces)
space_map[room.identifier] = os_space
try:
story_map[room.story].append(os_space)
except KeyError: # first room found on the story
story_map[room.story] = [os_space]
if print_progress and (i + 1) % 100 == 0:
print(' Translated {} out of {} Rooms'.format(i + 1, len(rooms)))
if print_progress:
print('Translated all {} Rooms'.format(len(rooms)))
# create all of the zones
if print_progress:
print('Translating Zones')
zone_map, zone_count = {}, 0
for room in single_zones:
os_zone = OSThermalZone(os_model)
os_zone.setName(room.identifier)
if room._display_name is not None:
os_zone.setDisplayName(room.display_name)
os_space = space_map[room.identifier]
os_space.setThermalZone(os_zone)
zone_map[room.identifier] = os_zone
if room.multiplier != 1:
os_zone.setMultiplier(room.multiplier)
os_zone.setCeilingHeight(room.geometry.max.z - room.geometry.min.z)
os_zone.setVolume(room.volume)
if room.properties.energy.setpoint is not None:
set_pt = room.properties.energy.setpoint
therm = setpoint_to_openstudio_thermostat(set_pt, os_model, room.identifier)
os_zone.setThermostatSetpointDualSetpoint(therm)
humid = setpoint_to_openstudio_humidistat(set_pt, os_model, room.identifier)
if humid is not None:
os_zone.setZoneControlHumidistat(humid)
daylight = room.properties.energy.daylighting_control
if daylight is not None:
dl_name = '{}_Daylighting'.format(room.identifier)
os_daylight = os_model.getDaylightingControlByName(dl_name)
if os_daylight.is_initialized():
os_daylight = os_daylight.get()
os_zone.setPrimaryDaylightingControl(os_daylight)
os_zone.setFractionofZoneControlledbyPrimaryDaylightingControl(
daylight.control_fraction)
zone_count += 1
if print_progress and zone_count % 100 == 0:
print(' Translated {} Zones'.format(zone_count))
for zone_id, zone_data in zone_dict.items():
rooms, z_prop, set_pt, vent = zone_data
mult, ceil_hgt, vol, _, _ = z_prop
os_zone = OSThermalZone(os_model)
os_zone.setName(zone_id)
for room in rooms:
os_space = space_map[room.identifier]
os_space.setThermalZone(os_zone)
zone_map[room.identifier] = os_zone
if mult != 1:
os_zone.setMultiplier(mult)
os_zone.setCeilingHeight(ceil_hgt)
os_zone.setVolume(vol)
if set_pt is not None:
therm = setpoint_to_openstudio_thermostat(set_pt, os_model, zone_id)
os_zone.setThermostatSetpointDualSetpoint(therm)
humid = setpoint_to_openstudio_humidistat(set_pt, os_model, zone_id)
if humid is not None:
os_zone.setZoneControlHumidistat(humid)
zone_count += 1
if print_progress and zone_count % 100 == 0:
print(' Translated {} Zones'.format(zone_count))
if print_progress:
print('Translated all {} Zones'.format(zone_count))
# triangulate any apertures or doors with more than 4 vertices
tri_sub_faces = []
if triangulate_subfaces:
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
os_ap = aperture_to_openstudio(ap, os_model)
os_face = adj_map['faces'][ap.parent.identifier]
os_ap.setSurface(os_face)
adj_map['sub_faces'][ap.identifier] = os_ap
tri_sub_faces.append(ap)
tri_doors, _ = model.triangulated_doors()
for tri_drs in tri_doors:
for i, dr in enumerate(tri_drs):
if i != 0:
dr.properties.energy.vent_opening = None
os_dr = door_to_openstudio(dr, os_model)
os_face = adj_map['faces'][dr.parent.identifier]
os_dr.setSurface(os_face)
adj_map['sub_faces'][dr.identifier] = os_dr
tri_sub_faces.append(dr)
# assign stories to the rooms
for story_id, os_spaces in story_map.items():
story = OSBuildingStory(os_model)
if story_id is not None: # the users has specified the name of the story
story.setName(story_id)
else: # give the room a dummy story so that it works with David's measures
story.setName('UndefinedStory')
for os_space in os_spaces:
os_space.setBuildingStory(story)
# assign adjacencies to all of the rooms
already_adj = set()
for room in model.rooms:
for face in room.faces:
if isinstance(face.boundary_condition, Surface):
if face.identifier not in already_adj:
# add the adjacency to the set
adj_id = face.boundary_condition.boundary_condition_object
already_adj.add(adj_id)
# get the openstudio Surfaces and set the adjacency
try:
base_os_face = adj_map['faces'][face.identifier]
adj_os_face = adj_map['faces'][adj_id]
except KeyError:
msg = 'Missing adjacency exists between Face "{}" ' \
'and Face "{}."'.format(face.identifier, adj_id)
print(msg)
base_os_face.setAdjacentSurface(adj_os_face)
# set the adjacency of all sub-faces
for sub_face in face.sub_faces:
if len(sub_face.geometry) <= 4 or not triangulate_subfaces:
adj_id = sub_face.boundary_condition.boundary_condition_object
try:
os_sub_face = adj_map['sub_faces'][sub_face.identifier]
adj_os_sub_face = adj_map['sub_faces'][adj_id]
except KeyError:
msg = 'Missing adjacency exists between subface "{}" ' \
'and subface "{}."'.format(
sub_face.identifier, adj_id)
print(msg)
os_sub_face.setAdjacentSubSurface(adj_os_sub_face)
for sub_face in tri_sub_faces:
if isinstance(sub_face.boundary_condition, Surface):
adj_id = sub_face.boundary_condition.boundary_condition_object
try:
os_sub_face = adj_map['sub_faces'][sub_face.identifier]
adj_os_sub_face = adj_map['sub_faces'][adj_id]
except KeyError:
msg = 'Missing adjacency exists between subface "{}" ' \
'and subface "{}."'.format(sub_face.identifier, adj_id)
print(msg)
os_sub_face.setAdjacentSubSurface(adj_os_sub_face)
# if simple ventilation is being used, write the relevant objects
if use_simple_vent:
for room in model.rooms: # add simple add air mixing and window ventilation
for face in room.faces:
if isinstance(face.type, AirBoundary): # write the air mixing objects
try:
adj_room = face.boundary_condition.boundary_condition_objects[-1]
target_zone = zone_map[room.identifier]
source_zone = zone_map[adj_room]
air_mixing_to_openstudio(face, target_zone, source_zone, os_model)
except AttributeError as e:
raise ValueError(
'Face "{}" is an Air Boundary but lacks a Surface boundary '
'condition.\n{}'.format(face.full_id, e))
# add simple window ventilation objects where applicable
if isinstance(face.boundary_condition, Outdoors):
for sub_f in face.sub_faces:
vent_open = sub_f.properties.energy.vent_opening
if vent_open is not None:
os_vent = ventilation_opening_to_openstudio(vent_open, os_model)
os_vent.addToThermalZone(zone_map[room.identifier])
else: # we are using the AFN!
# create the AFN reference crack for the model and make an outdoor sensor
vent_sim_ctrl = model.properties.energy.ventilation_simulation_control
os_ref_crack = ventilation_sim_control_to_openstudio(vent_sim_ctrl, os_model)
prog_manager = ventilation_control_program_manager(os_model) # EMS manager
outdoor_temperature_sensor(os_model) # add an EMS sensor for outdoor temperature
# loop though the geometry and assign all AFN properties
zone_air_nodes = {} # track EMS zone air temperature sensors
set_by_adj = set() # track the objects with properties set by adjacency
for room in model.rooms: # write an AirflowNetworkZone object in for the Room
os_zone = zone_map[room.identifier]
os_afn_room_node = os_zone.getAirflowNetworkZone()
os_afn_room_node.setVentilationControlMode('NoVent')
operable_sub_fs = [] # collect the sub-face objects for the EMS
opening_factors = [] # collect the maximum opening factors for the EMS
for face in room.faces: # write AFN crack infiltration for the Face
if face.identifier not in set_by_adj:
vent_crack = face.properties.energy.vent_crack
if vent_crack is not None:
os_crack = afn_crack_to_openstudio(
vent_crack, os_model, os_ref_crack)
os_crack.setName('{}_Crack'.format(face.identifier))
os_face = adj_map['faces'][face.identifier]
os_face.getAirflowNetworkSurface(os_crack)
if isinstance(face.boundary_condition, Surface):
adj_id = face.boundary_condition.boundary_condition_object
set_by_adj.add(adj_id)
for sub_f in face.sub_faces: # write AFN openings for each sub-face
vent_open = sub_f.properties.energy.vent_opening
if vent_open is not None:
os_opening, op_fac = ventilation_opening_to_openstudio_afn(
vent_open, os_model, os_ref_crack)
os_sub_f = adj_map['sub_faces'][sub_f.identifier]
os_afn_sf = os_sub_f.getAirflowNetworkSurface(os_opening)
if op_fac is not None:
operable_sub_fs.append(os_sub_f)
opening_factors.append(op_fac)
op_fac = 1 if op_fac == 0 else op_fac
os_afn_sf.setWindowDoorOpeningFactorOrCrackFactor(op_fac)
# translate the Room's VentilationControl to an EMS program
vent_control = room.properties.energy.window_vent_control
if vent_control is not None:
try:
zone_node = zone_air_nodes[os_zone.nameString()]
except KeyError:
zone_node = zone_temperature_sensor(os_zone, os_model)
zone_air_nodes[os_zone.nameString()] = zone_node
os_ems_program = ventilation_control_to_openstudio_afn(
vent_control, opening_factors, operable_sub_fs,
zone_node, os_model, room.identifier)
prog_manager.addProgram(os_ems_program)
if print_progress:
print('Assigned adjacencies to all Rooms')
if print_progress:
print('Translating Systems')
# write any ventilation fan definitions
for room in model.rooms:
for fan in room.properties.energy.fans:
os_fan = ventilation_fan_to_openstudio(fan, os_model)
os_fan.setName('{}..{}'.format(fan.identifier, room.identifier))
os_fan.addToThermalZone(zone_map[room.identifier])
# assign HVAC systems to all of the rooms
zone_rooms = {room.zone: room for room in single_zones}
for zone_id, zone_data in zone_dict.items():
for room in zone_data[0]:
if room.properties.energy.hvac is not None:
zone_rooms[zone_id] = room
break
ideal_air_count = 0
template_zones, template_hvac_dict, detailed_hvac_dict = {}, {}, {}
for zone_id, room in zone_rooms.items():
hvac = room.properties.energy.hvac
os_zone = zone_map[room.identifier]
if isinstance(hvac, IdealAirSystem):
os_hvac = ideal_air_system_to_openstudio(hvac, os_model, room)
if room.identifier != zone_id:
os_hvac.setName('{} Ideal Loads Air System'.format(zone_id))
os_hvac.addToThermalZone(os_zone)
ideal_air_count += 1
elif isinstance(hvac, _TemplateSystem):
template_hvac_dict[hvac.identifier] = hvac
set_pt = room.properties.energy.setpoint
if set_pt is not None:
try:
zone_list = template_zones[hvac.identifier]
except KeyError: # first zone found in the HVAC
zone_list = {'heated_zones': [], 'cooled_zones': []}
template_zones[hvac.identifier] = zone_list
if set_pt.heating_setpoint > 5:
zone_list['heated_zones'].append(os_zone)
if set_pt.cooling_setpoint < 33:
zone_list['cooled_zones'].append(os_zone)
elif isinstance(hvac, DetailedHVAC):
detailed_hvac_dict[hvac.identifier] = hvac
if print_progress and ideal_air_count != 0:
print(' Assigned {} Ideal Air Systems'.format(ideal_air_count))
# translate template HVAC systems
os_model.setFastNaming(False)
if len(template_hvac_dict) != 0:
for hvac_id, os_zones in template_zones.items():
hvac = template_hvac_dict[hvac_id]
template_hvac_to_openstudio(hvac, os_zones, os_model)
if print_progress:
print(' Assigned template HVAC: {}'.format(hvac.display_name))
if len(template_zones) != 0: # rename air and plant loop nodes for readability
rename_air_loop_nodes(os_model)
rename_plant_loop_nodes(os_model)
# translate detailed HVAC systems
if len(detailed_hvac_dict) != 0:
assert hbe_folders.ironbug_exe is not None, 'Detailed Ironbug HVAC System was ' \
'assigned but no Ironbug installation was found.'
for hvac_id, hvac in detailed_hvac_dict.items():
hvac_trans_dir = tempfile.gettempdir()
spec_file = os.path.join(hvac_trans_dir, '{}.json'.format(hvac.identifier))
with open(spec_file, 'w') as sf:
json.dump(hvac.specification, sf)
osm_file = os.path.join(hvac_trans_dir, '{}.osm'.format(hvac.identifier))
os_model.save(os_path(osm_file), overwrite=True)
cmds = [hbe_folders.ironbug_exe, osm_file, spec_file]
process = subprocess.Popen(
cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
result = process.communicate() # pause script until command is done
exist_os_model = OSModel.load(os_path(osm_file))
if exist_os_model.is_initialized():
os_model = exist_os_model.get()
else:
msg = 'Failed to apply Detailed HVAC "{}"\n{}\n{}'.format(
hvac_id, result[0], result[1])
raise ValueError(msg)
if print_progress:
print(' Assigned detailed HVAC: {}'.format(hvac.display_name))
# write service hot water and any SHW systems
shw_sys_dict = {}
for room in model.rooms:
hot_water = room.properties.energy.service_hot_water
if hot_water is not None and hot_water.flow_per_area != 0:
os_shw_conn = hot_water_to_openstudio(hot_water, room, os_model)
total_flow = (hot_water.flow_per_area / 3600000.) * room.floor_area
water_temp = hot_water.target_temperature
shw_sys = room.properties.energy.shw
shw_sys_id = shw_sys.identifier \
if shw_sys is not None else 'Default_District_SHW'
try: # try to add the hot water to the existing system
shw_sys_props = shw_sys_dict[shw_sys_id]
shw_sys_props[1].append(os_shw_conn)
shw_sys_props[2] += total_flow
if water_temp > shw_sys_props[3]:
shw_sys_props[3] = water_temp
except KeyError: # first time that the SHW system is encountered
shw_sys_props = [shw_sys, [os_shw_conn], total_flow, water_temp]
shw_sys_dict[shw_sys_id] = shw_sys_props
if len(shw_sys_dict) != 0:
# add all of the SHW Systems to the model
for shw_sys_props in shw_sys_dict.values():
shw_sys, os_shw_conns, total_flow, w_temp = shw_sys_props
shw_system_to_openstudio(shw_sys, os_shw_conns, total_flow, w_temp, os_model)
if print_progress:
shw_sys_name = shw_sys.display_name \
if shw_sys is not None else 'Default_District_SHW'
print(' Assigned SHW System: {}'.format(shw_sys_name))
# write any EMS programs for dynamic constructions
if len(dynamic_cons) != 0:
# create the program calling manager
os_prog_manager = OSEnergyManagementSystemProgramCallingManager(os_model)
os_prog_manager.setName('Dynamic_Window_Constructions')
os_prog_manager.setCallingPoint('BeginTimestepBeforePredictor')
# get all of the sub-faces with the dynamic construction
dyn_dict = {}
for room in model.rooms:
for face in room.faces:
for sf in face.sub_faces:
con = sf.properties.energy.construction
if isinstance(con, WindowConstructionDynamic):
os_sf = adj_map['sub_faces'][sf.identifier]
try:
dyn_dict[con.identifier].append(os_sf)
except KeyError:
dyn_dict[con.identifier] = [os_sf]
for con in dynamic_cons:
ems_program = window_dynamic_ems_program_to_openstudio(
con, dyn_dict[con.identifier], os_model)
os_prog_manager.addProgram(ems_program)
# add the orphaned objects
shade_count, shades_to_group = 0, []
for face in model.orphaned_faces:
shades_to_group.append(face_to_openstudio(face, os_model))
shade_count += 1
for aperture in model.orphaned_apertures:
shades_to_group.append(aperture_to_openstudio(aperture, os_model))
shade_count += 1
for door in model.orphaned_doors:
shades_to_group.append(door_to_openstudio(door, os_model))
shade_count += 1
for shade in model.orphaned_shades:
shades_to_group.append(shade_to_openstudio(shade, os_model))
shade_count += 1
for shade_mesh in model.shade_meshes:
shade_mesh_to_openstudio(shade_mesh, os_model)
shade_count += 1
if len(shades_to_group) != 0:
shd_group = OSShadingSurfaceGroup(os_model)
shd_group.setName('Orphaned Shades')
shd_group.setShadingSurfaceType('Building')
for os_shade in shades_to_group:
os_shade.setShadingSurfaceGroup(shd_group)
if print_progress and shade_count != 0:
print('Translated {} Shades'.format(shade_count))
# write the electric load center is any generator objects are in the model
os_pv_gens = os_model.getGeneratorPVWattss()
if os_vector_len(os_pv_gens) != 0:
load_center = model.properties.energy.electric_load_center
electric_load_center_to_openstudio(load_center, os_pv_gens, os_model)
# return the Model object
return os_model
[docs]
def model_to_osm(
model, seed_model=None, schedule_directory=None,
use_geometry_names=False, use_resource_names=False, print_progress=False
):
"""Translate a Honeybee Model to an OSM string.
Args:
model: The Honeybee Model to be converted into an OpenStudio Model.
seed_model: An optional OpenStudio Model object to which the Honeybee
Model will be added. If None, a new OpenStudio Model will be
initialized within this method. (Default: None).
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_geometry_names: Boolean to note whether a cleaned version of all
geometry display names should be used instead of identifiers when
translating the Model to OSM and IDF. Using this flag will affect
all Rooms, Faces, Apertures, Doors, and Shades. It will generally
result in more read-able names in the OSM and IDF but this means
that it will not be easy to map the EnergyPlus results back to the
input Honeybee Model. Cases of duplicate IDs resulting from
non-unique names will be resolved by adding integers to the ends
of the new IDs that are derived from the name. (Default: False).
use_resource_names: Boolean to note whether a cleaned version of all
resource display names should be used instead of identifiers when
translating the Model to OSM and IDF. Using this flag will affect
all Materials, Constructions, ConstructionSets, Schedules, Loads,
and ProgramTypes. It will generally result in more read-able names
for the resources in the OSM and IDF. Cases of duplicate IDs
resulting from non-unique names will be resolved by adding integers
to the ends of the new IDs that are derived from the name. (Default: False).
print_progress: Set to True to have the progress of the translation
printed as it is completed. (Default: False).
"""
# check that the input is a model
assert isinstance(model, Model), \
'Expected Honeybee Model for model_to_osm. Got {}.'.format(type(model))
# translate the Honeybee Model to an OpenStudio Model
os_model = model_to_openstudio(
model, seed_model, schedule_directory, use_geometry_names, use_resource_names,
print_progress=print_progress
)
return str(os_model)
[docs]
def model_to_idf(
model, seed_model=None, schedule_directory=None,
use_geometry_names=False, use_resource_names=False, print_progress=False
):
"""Translate a Honeybee Model to an IDF string using OpenStudio SDK translators.
Args:
model: The Honeybee Model to be converted into an OpenStudio Model.
seed_model: An optional OpenStudio Model object to which the Honeybee
Model will be added. If None, a new OpenStudio Model will be
initialized within this method. (Default: None).
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_geometry_names: Boolean to note whether a cleaned version of all
geometry display names should be used instead of identifiers when
translating the Model to OSM and IDF. Using this flag will affect
all Rooms, Faces, Apertures, Doors, and Shades. It will generally
result in more read-able names in the OSM and IDF but this means
that it will not be easy to map the EnergyPlus results back to the
input Honeybee Model. Cases of duplicate IDs resulting from
non-unique names will be resolved by adding integers to the ends
of the new IDs that are derived from the name. (Default: False).
use_resource_names: Boolean to note whether a cleaned version of all
resource display names should be used instead of identifiers when
translating the Model to OSM and IDF. Using this flag will affect
all Materials, Constructions, ConstructionSets, Schedules, Loads,
and ProgramTypes. It will generally result in more read-able names
for the resources in the OSM and IDF. Cases of duplicate IDs
resulting from non-unique names will be resolved by adding integers
to the ends of the new IDs that are derived from the name. (Default: False).
print_progress: Set to True to have the progress of the translation
printed as it is completed. (Default: False).
"""
# check that the input is a model
assert isinstance(model, Model), \
'Expected Honeybee Model for model_to_idf. Got {}.'.format(type(model))
# translate the Honeybee Model to an OpenStudio Model
os_model = model_to_openstudio(
model, seed_model, schedule_directory, use_geometry_names, use_resource_names,
print_progress=print_progress
)
# translate the model to an IDF string
if (sys.version_info < (3, 0)):
idf_translator = openstudio.EnergyPlusForwardTranslator()
else:
idf_translator = openstudio.energyplus.ForwardTranslator()
workspace = idf_translator.translateModel(os_model)
return str(workspace)
[docs]
def model_to_gbxml(
model, triangulate_non_planar_orphaned=True, triangulate_subfaces=False,
full_geometry=False, interior_face_type=None, ground_face_type=None,
program_name=None, program_version=None, print_progress=False
):
"""Translate a Honeybee Model to gbXML string using OpenStudio SDK translators.
Args:
model: The Honeybee Model to be converted into an OpenStudio Model.
triangulate_non_planar_orphaned: Boolean to note whether any non-planar
orphaned geometry in the model should be triangulated.
This can be helpful because OpenStudio simply raises an error when
it encounters non-planar geometry, which would hinder the ability
to save files that are to be corrected later. (Default: False).
triangulate_subfaces: Boolean to note whether sub-faces (including
Apertures and Doors) should be triangulated if they have more
than 4 sides (True) or whether they should be left as they are (False).
This triangulation is necessary when exporting directly to EnergyPlus
since it cannot accept sub-faces with more than 4 vertices. (Default: True).
full_geometry: Boolean to note whether space boundaries and shell geometry
should be included in the exported gbXML vs. just the minimal required
non-manifold geometry. (Default: False).
interior_face_type: Text string for the type to be used for all interior
floor faces. If unspecified, the interior types will be left as they are.
Choose from the following. InteriorFloor, Ceiling.
ground_face_type: Text string for the type to be used for all ground-contact
floor faces. If unspecified, the ground types will be left as they are.
Choose from the following. UndergroundSlab, SlabOnGrade, RaisedFloor.
program_name: Optional text to set the name of the software that will
appear under the programId and ProductName tags of the DocumentHistory
section. This can be set things like "Ladybug Tools" or "Pollination"
or some other software in which this gbXML export capability is being
run. If None, the "OpenStudio" will be used. (Default: None).
program_version: Optional text to set the version of the software that
will appear under the DocumentHistory section. If None, and the
program_name is also unspecified, only the version of OpenStudio will
appear. Otherwise, this will default to "0.0.0" given that the version
field is required. (Default: None).
print_progress: Set to True to have the progress of the translation
printed as it is completed. (Default: False).
"""
# check that the input is a model
assert isinstance(model, Model), \
'Expected Honeybee Model for model_to_gbxml. Got {}.'.format(type(model))
# remove degenerate geometry within native DesignBuilder tolerance of 0.02 meters
original_model = model
model = model.duplicate() # duplicate to avoid mutating the input
if model.units != 'Meters':
model.convert_to_units('Meters')
try:
model.remove_degenerate_geometry(0.02)
except ValueError:
error = 'Failed to remove degenerate Rooms.\nYour Model units system is: {}. ' \
'Is this correct?'.format(original_model.units)
raise ValueError(error)
# remove any detailed HVAC or AFN as this will only slow the translation down
v_control = model.properties.energy.ventilation_simulation_control
det_hvac_count = 0
for hvac in model.properties.energy.hvacs:
if hvac is not None and not isinstance(hvac, IdealAirSystem):
det_hvac_count += 1
if v_control.vent_control_type != 'SingleZone' or det_hvac_count != 0:
for room in model.rooms:
room.properties.energy.assign_ideal_air_equivalent()
v_control.vent_control_type = 'SingleZone'
# translate the Honeybee Model to an OpenStudio Model
os_model = model_to_openstudio(
model, triangulate_non_planar_orphaned=triangulate_non_planar_orphaned,
triangulate_subfaces=triangulate_subfaces,
use_simple_window_constructions=True, print_progress=print_progress
)
# translate the model to a gbXML string
if (sys.version_info < (3, 0)):
gbxml_translator = openstudio.GbXMLForwardTranslator()
else:
gbxml_translator = openstudio.gbxml.GbXMLForwardTranslator()
gbxml_str = gbxml_translator.modelToGbXMLString(os_model)
# set the program_name in the DocumentHistory if specified
if program_name is not None:
split_lines = gbxml_str.split('\n')
hist_start_i, hist_end_i = None, None
for i, line in enumerate(split_lines):
if '<DocumentHistory>' in line:
hist_start_i = i
elif '</DocumentHistory>' in line:
hist_end_i = i
d_hst = split_lines[hist_start_i:hist_end_i + 1]
for j, line in enumerate(d_hst):
if '<CreatedBy programId="openstudio"' in line:
prog_id = clean_string(program_name).lower()
d_hst[j] = line.replace('openstudio', prog_id)
k = j + 1
d_hst.insert(k, ' </ProgramInfo>')
d_hst.insert(k, ' <Platform>{}</Platform>'.format(platform.system()))
version = '0.0.0' if program_version is None else program_version
d_hst.insert(k, ' <Version>{}</Version>'.format(version))
d_hst.insert(k, ' <ProductName>{}</ProductName>'.format(program_name))
d_hst.insert(k, ' <ProgramInfo id="{}">'.format(prog_id))
split_lines[hist_start_i:hist_end_i + 1] = d_hst
gbxml_str = '\n'.join(split_lines)
# replace all interior floors with the specified type
if interior_face_type == 'InteriorFloor':
gbxml_str = gbxml_str.replace('="Ceiling"', '="InteriorFloor"')
elif interior_face_type == 'Ceiling':
gbxml_str = gbxml_str.replace('="InteriorFloor"', '="Ceiling"')
# replace all ground floors with the specified type
if ground_face_type == 'UndergroundSlab':
gbxml_str = gbxml_str.replace('="SlabOnGrade"', '="UndergroundSlab"')
gbxml_str = gbxml_str.replace('="RaisedFloor"', '="UndergroundSlab"')
elif ground_face_type == 'SlabOnGrade':
gbxml_str = gbxml_str.replace('="UndergroundSlab"', '="SlabOnGrade"')
gbxml_str = gbxml_str.replace('="RaisedFloor"', '="SlabOnGrade"')
elif ground_face_type == 'RaisedFloor':
gbxml_str = gbxml_str.replace('="UndergroundSlab"', '="RaisedFloor"')
gbxml_str = gbxml_str.replace('="SlabOnGrade"', '="RaisedFloor"')
# write the SpaceBoundary and ShellGeometry into the XML if requested
if full_geometry:
# get a dictionary of rooms in the model
room_dict = {room.identifier: room for room in model.rooms}
# register all of the namespaces within the OpenStudio-exported XML
ET.register_namespace('', 'http://www.gbxml.org/schema')
ET.register_namespace('xhtml', 'http://www.w3.org/1999/xhtml')
ET.register_namespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
ET.register_namespace('xsd', 'http://www.w3.org/2001/XMLSchema')
# parse the XML and get the building definition
root = ET.fromstring(gbxml_str)
gbxml_header = r'{http://www.gbxml.org/schema}'
building = root[0][1]
# loop through surfaces in the gbXML so that we know the name of the interior ones
surface_set = set()
for room_element in root[0].findall(gbxml_header + 'Surface'):
surface_set.add(room_element.get('id'))
# loop through the rooms in the XML and add them as space boundaries to the room
for room_element in building.findall(gbxml_header + 'Space'):
room_id = room_element.get('zoneIdRef')
if room_id:
room_id = room_element.get('id')
shell_element = ET.Element('ShellGeometry')
shell_element.set('id', '{}Shell'.format(room_id))
shell_geo_element = ET.SubElement(shell_element, 'ClosedShell')
hb_room = room_dict[room_id[:-6]] # remove '_Space' from the end
for face in hb_room:
face_xml, face_geo_xml = _face_to_gbxml_geo(face, surface_set)
if face_xml is not None:
room_element.append(face_xml)
shell_geo_element.append(face_geo_xml)
room_element.append(shell_element)
# convert the element tree back into a string
if sys.version_info >= (3, 0):
gbxml_str = ET.tostring(root, encoding='unicode', xml_declaration=True)
else:
gbxml_str = ET.tostring(root)
return gbxml_str
def _face_to_gbxml_geo(face, face_set):
"""Get an Element Tree of a gbXML SpaceBoundary for a Face.
Note that the resulting string is only meant to go under the "Space" tag and
it is not a Surface tag with all of the construction and boundary condition
properties assigned to it.
Args:
face: A honeybee Face for which an gbXML representation will be returned.
face_set: A set of surface identifiers in the model, used to evaluate whether
the geometry must be associated with its boundary condition surface.
Returns:
A tuple with two elements.
- face_element: The element tree for the SpaceBoundary definition of the Face.
- loop_element: The element tree for the PolyLoop definition of the Face,
which is useful in defining the shell.
"""
# create the face element and associate it with a surface in the model
face_element = ET.Element('SpaceBoundary')
face_element.set('isSecondLevelBoundary', 'false')
obj_id = None
if face.identifier in face_set:
obj_id = face.identifier
elif isinstance(face.boundary_condition, Surface):
bc_obj = face.boundary_condition.boundary_condition_object
if bc_obj in face_set:
obj_id = bc_obj
if obj_id is None:
return None, None
face_element.set('surfaceIdRef', obj_id)
# write the geometry of the face
geo_element = ET.SubElement(face_element, 'PlanarGeometry')
loop_element = ET.SubElement(geo_element, 'PolyLoop')
for pt in face.vertices:
pt_element = ET.SubElement(loop_element, 'CartesianPoint')
for coord in pt:
coord_element = ET.SubElement(pt_element, 'Coordinate')
coord_element.text = str(coord)
return face_element, loop_element