"""dragonfly energy translation commands."""
import click
import sys
import os
import logging
import json
import tempfile
import glob
import subprocess
from concurrent.futures import ProcessPoolExecutor, as_completed
from ladybug.commandutil import process_content_to_output
from ladybug.epw import EPW
from ladybug.stat import STAT
from honeybee.config import folders as hb_folders
from honeybee_energy.simulation.parameter import SimulationParameter
from honeybee_energy.run import HB_OS_MSG
from honeybee_energy.writer import energyplus_idf_version
from honeybee_energy.config import folders
from dragonfly.model import Model
from dragonfly_energy.properties.model import ModelEnergyProperties
from dragonfly_energy.gbxml.parameters import GBXMLParameters
from dragonfly_energy.run import set_building_district_loads
_logger = logging.getLogger(__name__)
@click.group(help='Commands for translating Dragonfly JSON files to/from OSM/IDF.')
def translate():
pass
@translate.command('model-to-osm')
@click.argument('model-file', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option('--sim-par-json', '-sp', help='Full path to a honeybee energy '
'SimulationParameter JSON that describes all of the settings for '
'the simulation. If None default parameters will be generated.',
default=None, show_default=True,
type=click.Path(exists=True, file_okay=True, dir_okay=False,
resolve_path=True))
@click.option('--epw-file', '-epw', help='Full path to an EPW file to be associated '
'with the exported OSM. This is typically not necessary but may be '
'used when a sim-par-json is specified that requests a HVAC sizing '
'calculation to be run as part of the translation process but no design '
'days are inside this simulation parameter.',
default=None, show_default=True,
type=click.Path(exists=True, file_okay=True, dir_okay=False,
resolve_path=True))
@click.option('--multiplier/--full-geometry', ' /-fg', help='Flag to note if the '
'multipliers on each Building story will be passed along to the '
'generated Honeybee Room objects or if full geometry objects should be '
'written for each story in the building.', default=True, show_default=True)
@click.option('--plenum/--no-plenum', '-p/-np', help='Flag to indicate whether '
'ceiling/floor plenum depths assigned to Room2Ds should generate '
'distinct 3D Rooms in the translation.', default=True, show_default=True)
@click.option('--no-ceil-adjacency/--ceil-adjacency', ' /-a', help='Flag to indicate '
'whether adjacencies should be solved between interior stories when '
'Room2Ds perfectly match one another in their floor plate. This ensures '
'that Surface boundary conditions are used instead of Adiabatic ones. '
'Note that this input has no effect when the object-per-model is Story.',
default=True, show_default=True)
@click.option('--merge-method', '-m', help='Text to describe how the Room2Ds should '
'be merged into individual Rooms during the translation. Specifying a '
'value here can be an effective way to reduce the number of Room '
'volumes in the resulting Model and, ultimately, yield a faster simulation '
'time with less results to manage. Choose from: None, Zones, PlenumZones, '
'Stories, PlenumStories.', type=str, default='None', show_default=True)
@click.option('--folder', '-f', help='Deprecated input that is no longer used.',
default=None, show_default=True,
type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
@click.option('--osm-file', '-osm', help='Optional path where the OSM will be written.',
type=str, default=None, show_default=True)
@click.option('--idf-file', '-idf', help='Optional path where the IDF will be written.',
type=str, default=None, show_default=True)
@click.option('--epjson-file', '-epjson', help='Optional path where the epJSON will '
'be written.', type=str, default=None, show_default=True)
@click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag 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 original 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=True, show_default=True)
@click.option('--resource-ids/--resource-names', ' /-rn', help='Flag 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=True, show_default=True)
@click.option('--log-file', '-log', help='Optional log file to output the paths to the '
'generated OSM and IDF files if they were successfully created. '
'By default this will be printed out to stdout',
type=click.File('w'), default='-', show_default=True)
def model_to_osm_cli(
model_file, sim_par_json, epw_file,
multiplier, plenum, no_ceil_adjacency, merge_method,
folder, osm_file, idf_file, epjson_file, geometry_ids, resource_ids, log_file
):
"""Translate a Dragonfly Model to an OpenStudio Model.
\b
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
"""
try:
full_geometry = not multiplier
no_plenum = not plenum
ceil_adjacency = not no_ceil_adjacency
geo_names = not geometry_ids
res_names = not resource_ids
model_to_osm(
model_file, sim_par_json, epw_file,
full_geometry, no_plenum, ceil_adjacency, merge_method,
folder, osm_file, idf_file, epjson_file, geo_names, res_names, log_file
)
except Exception as e:
_logger.exception('Model translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def model_to_osm(
model_file, sim_par_json=None, epw_file=None,
full_geometry=False, no_plenum=False, ceil_adjacency=False, merge_method='None',
folder=None, osm_file=None, idf_file=None, epjson_file=None,
geometry_names=False, resource_names=False, log_file=None,
multiplier=True, plenum=True, no_ceil_adjacency=True,
geometry_ids=True, resource_ids=True
):
"""Translate a Dragonfly Model to an OpenStudio Model.
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
sim_par_json: Full path to a honeybee energy SimulationParameter JSON that
describes all of the settings for the simulation. If None, default
parameters will be generated.
epw_file: Full path to an EPW file to be associated with the exported OSM.
This is typically not necessary but may be used when a sim-par-json is
specified that requests a HVAC sizing calculation to be run as part
of the translation process but no design days are inside this
simulation parameter.
full_geometry: Boolean to note if the multipliers on each Building story
will be passed along to the generated Honeybee Room objects or if
full geometry objects should be written for each story in the
building. (Default: False).
no_plenum: Boolean to indicate whether ceiling/floor plenum depths
assigned to Room2Ds should generate distinct 3D Rooms in the
translation. (Default: False).
ceil_adjacency: Boolean to indicate whether adjacencies should be solved
between interior stories when Room2Ds perfectly match one another
in their floor plate. This ensures that Surface boundary conditions
are used instead of Adiabatic ones. Note that this input has no
effect when the object-per-model is Story. (Default: False).
merge_method: An optional text string to describe how the Room2Ds should
be merged into individual Rooms during the translation. Specifying a
value here can be an effective way to reduce the number of Room
volumes in the resulting Model and, ultimately, yield a faster simulation
time with less results to manage. Note that Room2Ds will only be merged if
they form a contiguous volume. Otherwise, there will be multiple Rooms per
zone or story, each with an integer added at the end of their
identifiers. Choose from the following options:
* None - No merging will occur
* Zones - Room2Ds in the same zone will be merged
* PlenumZones - Only plenums in the same zone will be merged
* Stories - Rooms in the same story will be merged
* PlenumStories - Only plenums in the same story will be merged
folder: Deprecated input that is no longer used.
osm_file: Optional path where the OSM will be output.
idf_file: Optional path where the IDF will be output.
epjson_file: Optional path where the epJSON will be output.
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 original 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).
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).
log_file: Optional log file to output the paths to the generated OSM and]
IDF files if they were successfully created. By default this string
will be returned from this method.
"""
# check that honeybee-openstudio is installed
try:
from honeybee_openstudio.openstudio import openstudio, OSModel, os_path
from honeybee_openstudio.simulation import simulation_parameter_to_openstudio, \
assign_epw_to_model
from honeybee_openstudio.writer import model_to_openstudio
except ImportError as e: # honeybee-openstudio is not installed
raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
if folder is not None:
print('--folder is deprecated and no longer used.')
# initialize the OpenStudio model that will hold everything
os_model = OSModel()
# generate default simulation parameters
if sim_par_json is None:
sim_par = SimulationParameter()
sim_par.output.add_zone_energy_use()
sim_par.output.add_hvac_energy_use()
sim_par.output.add_electricity_generation()
sim_par.output.reporting_frequency = 'Monthly'
else:
with open(sim_par_json) as json_file:
data = json.load(json_file)
sim_par = SimulationParameter.from_dict(data)
# perform a check to be sure the EPW file is specified for sizing runs
def ddy_from_epw(epw_file, sim_par):
"""Produce a DDY from an EPW file."""
epw_obj = EPW(epw_file)
des_days = [epw_obj.approximate_design_day('WinterDesignDay'),
epw_obj.approximate_design_day('SummerDesignDay')]
sim_par.sizing_parameter.design_days = des_days
if epw_file is not None:
epw_folder, epw_file_name = os.path.split(epw_file)
ddy_file = os.path.join(epw_folder, epw_file_name.replace('.epw', '.ddy'))
stat_file = os.path.join(epw_folder, epw_file_name.replace('.epw', '.stat'))
if len(sim_par.sizing_parameter.design_days) == 0 and \
os.path.isfile(ddy_file):
try:
sim_par.sizing_parameter.add_from_ddy_996_004(ddy_file)
except AssertionError: # no design days within the DDY file
ddy_from_epw(epw_file, sim_par)
elif len(sim_par.sizing_parameter.design_days) == 0:
ddy_from_epw(epw_file, sim_par)
if sim_par.sizing_parameter.climate_zone is None and os.path.isfile(stat_file):
stat_obj = STAT(stat_file)
sim_par.sizing_parameter.climate_zone = stat_obj.ashrae_climate_zone
set_cz = True if sim_par.sizing_parameter.climate_zone is None else False
assign_epw_to_model(epw_file, os_model, set_cz)
# translate the simulation parameter and model to an OpenStudio Model
simulation_parameter_to_openstudio(sim_par, os_model)
# re-serialize the Dragonfly Model
model = Model.from_file(model_file)
model.convert_to_units('Meters')
# convert Dragonfly Model to Honeybee
multiplier = not full_geometry
hb_models = model.to_honeybee(
object_per_model='District', use_multiplier=multiplier,
exclude_plenums=no_plenum, solve_ceiling_adjacencies=ceil_adjacency,
merge_method=merge_method, enforce_adj=False
)
hb_model = hb_models[0]
# create the HBJSON for input to OpenStudio CLI
model_to_openstudio(
hb_model, os_model, use_geometry_names=geometry_names,
use_resource_names=resource_names, print_progress=True)
gen_files = []
# write the OpenStudio Model if specified
if osm_file is not None:
osm = os.path.abspath(osm_file)
os_model.save(osm, overwrite=True)
gen_files.append(osm)
# write the IDF if specified
workspace = None
if idf_file is not None:
idf = os.path.abspath(idf_file)
idf_translator = openstudio.energyplus.ForwardTranslator()
workspace = idf_translator.translateModel(os_model)
workspace.save(idf, overwrite=True)
gen_files.append(idf)
# write the epJSON if specified
if epjson_file is not None:
# get the EnergyPlus schema file used in translation
ep_path = folders.energyplus_path
assert ep_path is not None, 'EnergyPlus must be installed to write epJSON.'
ep_schema = os.path.join(ep_path, 'Energy+.schema.epJSON')
assert os.path.isfile(ep_schema), \
'No epJSON schema file was found at: {}'.format(ep_schema)
# translate the model to an EnergyPlus workspace
epjson = os.path.abspath(epjson_file)
if workspace is None:
idf_translator = openstudio.energyplus.ForwardTranslator()
workspace = idf_translator.translateModel(os_model)
# use the epjson translator to convert the IDF to JSON
epjson_str = openstudio.epjson.toJSONString(workspace, os_path(ep_schema))
with open(epjson, 'w') as epj:
epj.write(epjson_str)
gen_files.append(epjson)
return process_content_to_output(json.dumps(gen_files, indent=4), log_file)
@translate.command('model-to-idf')
@click.argument('model-file', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option('--sim-par-json', '-sp', help='Full path to a honeybee energy '
'SimulationParameter JSON that describes all of the settings for '
'the simulation. If None default parameters will be generated.',
default=None, show_default=True,
type=click.Path(exists=True, file_okay=True, dir_okay=False,
resolve_path=True))
@click.option('--multiplier/--full-geometry', ' /-fg', help='Flag to note if the '
'multipliers on each Building story will be passed along to the '
'generated Honeybee Room objects or if full geometry objects should be '
'written for each story in the building.', default=True, show_default=True)
@click.option('--plenum/--no-plenum', '-p/-np', help='Flag to indicate whether '
'ceiling/floor plenum depths assigned to Room2Ds should generate '
'distinct 3D Rooms in the translation.', default=True, show_default=True)
@click.option('--no-ceil-adjacency/--ceil-adjacency', ' /-a', help='Flag to indicate '
'whether adjacencies should be solved between interior stories when '
'Room2Ds perfectly match one another in their floor plate. This ensures '
'that Surface boundary conditions are used instead of Adiabatic ones. '
'Note that this input has no effect when the object-per-model is Story.',
default=True, show_default=True)
@click.option('--merge-method', '-m', help='Text to describe how the Room2Ds should '
'be merged into individual Rooms during the translation. Specifying a '
'value here can be an effective way to reduce the number of Room '
'volumes in the resulting Model and, ultimately, yield a faster simulation '
'time with less results to manage. Choose from: None, Zones, PlenumZones, '
'Stories, PlenumStories.', type=str, default='None', show_default=True)
@click.option('--additional-str', '-a', help='Text string for additional lines that '
'should be added to the IDF.', type=str, default='', show_default=True)
@click.option('--compact-schedules/--csv-schedules', ' /-c', help='Flag to note '
'whether any ScheduleFixedIntervals in the model should be included '
'in the IDF string as a Schedule:Compact or they should be written as '
'CSV Schedule:File and placed in a directory next to the output-file.',
default=True, show_default=True)
@click.option('--hvac-to-ideal-air/--hvac-check', ' /-h', help='Flag to note '
'whether any detailed HVAC system templates should be converted to '
'an equivalent IdealAirSystem upon export. If hvac-check is used'
'and the Model contains detailed systems, a ValueError will '
'be raised.', default=True, show_default=True)
@click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag to note whether a '
'cleaned version of all geometry display names should be used instead '
'of identifiers when translating the Model to IDF. Using this flag will '
'affect all Rooms, Faces, Apertures, Doors, and Shades. It will '
'generally result in more read-able names in the IDF but this means that '
'it will not be easy to map the EnergyPlus results back to the original '
'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=True, show_default=True)
@click.option('--resource-ids/--resource-names', ' /-rn', help='Flag to note whether a '
'cleaned version of all resource display names should be used instead '
'of identifiers when translating the Model to 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 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=True, show_default=True)
@click.option('--output-file', '-f', help='Optional IDF file to output the IDF string '
'of the translation. By default this will be printed out to stdout',
type=click.File('w'), default='-', show_default=True)
def model_to_idf_cli(
model_file, sim_par_json, multiplier, plenum, no_ceil_adjacency, merge_method,
additional_str, compact_schedules, hvac_to_ideal_air,
geometry_ids, resource_ids, output_file
):
"""Translate a Dragonfly Model to an IDF using direct-to-idf translators.
The resulting IDF should be simulate-able but not all Model properties might
make it into the IDF given that the direct-to-idf translators are used.
\b
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
"""
try:
full_geometry = not multiplier
no_plenum = not plenum
ceil_adjacency = not no_ceil_adjacency
csv_schedules = not compact_schedules
hvac_check = not hvac_to_ideal_air
geo_names = not geometry_ids
res_names = not resource_ids
model_to_idf(
model_file, sim_par_json, full_geometry, no_plenum, ceil_adjacency,
merge_method, additional_str, csv_schedules,
hvac_check, geo_names, res_names, output_file)
except Exception as e:
_logger.exception('Model translation failed.\n{}\n'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def model_to_idf(
model_file, sim_par_json=None,
full_geometry=False, no_plenum=False, ceil_adjacency=False, merge_method='None',
additional_str='', csv_schedules=False, hvac_check=False,
geometry_names=False, resource_names=False, output_file=None,
multiplier=True, plenum=True, no_ceil_adjacency=True,
compact_schedules=True, hvac_to_ideal_air=True, geometry_ids=True, resource_ids=True
):
"""Translate a Dragonfly Model to an IDF using direct-to-idf translators.
The resulting IDF should be simulate-able but not all Model properties might
make it into the IDF given that the direct-to-idf translators are used.
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
sim_par_json: Full path to a honeybee energy SimulationParameter JSON that
describes all of the settings for the simulation. If None, default
parameters will be generated.
full_geometry: Boolean to note if the multipliers on each Building story
will be passed along to the generated Honeybee Room objects or if
full geometry objects should be written for each story in the
building. (Default: False).
no_plenum: Boolean to indicate whether ceiling/floor plenum depths
assigned to Room2Ds should generate distinct 3D Rooms in the
translation. (Default: False).
ceil_adjacency: Boolean to indicate whether adjacencies should be solved
between interior stories when Room2Ds perfectly match one another
in their floor plate. This ensures that Surface boundary conditions
are used instead of Adiabatic ones. Note that this input has no
effect when the object-per-model is Story. (Default: False).
merge_method: An optional text string to describe how the Room2Ds should
be merged into individual Rooms during the translation. Specifying a
value here can be an effective way to reduce the number of Room
volumes in the resulting Model and, ultimately, yield a faster simulation
time with less results to manage. Note that Room2Ds will only be merged if
they form a contiguous volume. Otherwise, there will be multiple Rooms per
zone or story, each with an integer added at the end of their
identifiers. Choose from the following options:
* None - No merging will occur
* Zones - Room2Ds in the same zone will be merged
* PlenumZones - Only plenums in the same zone will be merged
* Stories - Rooms in the same story will be merged
* PlenumStories - Only plenums in the same story will be merged
additional_str: Text string for additional lines that should be added
to the IDF.
csv_schedules: Boolean to note whether any ScheduleFixedIntervals in the
model should be included in the IDF string as a Schedule:Compact or
they should be written as CSV Schedule:File and placed in a directory
next to the output_file. (Default: False).
hvac_check: Boolean to note whether any detailed HVAC system templates
should be converted to an equivalent IdealAirSystem upon export.
If hvac-check is used and the Model contains detailed systems, a
ValueError will be raised. (Default: False).
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 original 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).
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).
output_file: Optional IDF file to output the IDF string of the translation.
By default this string will be returned from this method.
"""
# check that the simulation parameters are there and load them
if sim_par_json is not None:
with open(sim_par_json) as json_file:
data = json.load(json_file)
sim_par = SimulationParameter.from_dict(data)
else:
sim_par = SimulationParameter()
sim_par.output.add_zone_energy_use()
sim_par.output.add_hvac_energy_use()
sim_par.output.add_electricity_generation()
sim_par.output.reporting_frequency = 'Monthly'
# re-serialize the Dragonfly Model
model = Model.from_file(model_file)
model.convert_to_units('Meters')
# convert Dragonfly Model to Honeybee
multiplier = not full_geometry
hb_models = model.to_honeybee(
object_per_model='District', use_multiplier=multiplier,
exclude_plenums=no_plenum, solve_ceiling_adjacencies=ceil_adjacency,
merge_method=merge_method, enforce_adj=False
)
hb_model = hb_models[0]
# reset the IDs to be derived from the display_names if requested
if geometry_names:
model.reset_ids()
if resource_names:
model.properties.energy.reset_resource_ids()
# set the schedule directory in case it is needed
sch_directory = None
if csv_schedules:
sch_path = os.path.abspath(model_file) if 'stdout' in str(output_file) \
else os.path.abspath(str(output_file))
sch_directory = os.path.join(os.path.split(sch_path)[0], 'schedules')
# create the strings for simulation parameters and model
ver_str = energyplus_idf_version() if folders.energyplus_version \
is not None else ''
sim_par_str = sim_par.to_idf()
hvac_to_ideal_air = not hvac_check
model_str = hb_model.to.idf(
hb_model, schedule_directory=sch_directory,
use_ideal_air_equivalent=hvac_to_ideal_air)
idf_str = '\n\n'.join([ver_str, sim_par_str, model_str, additional_str])
# write out the result
return process_content_to_output(idf_str, output_file)
@translate.command('model-to-gbxml')
@click.argument('model-file', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option('--si-units/--ip-units', '-si/-ip', help='Flag to note whether '
'the geometry, space loads, and construction properties are reported '
'in IP or SI units.', default=True, show_default=True)
@click.option('--full-geometry/--multiplier', '-fg/-m', help='Flag to note if the '
'multipliers on each Building story will be passed along to the '
'generated Honeybee Room objects or if full geometry objects should be '
'written for each story in the building.', default=True, show_default=True)
@click.option('--plenum/--no-plenum', '-p/-np', help='Flag to indicate whether '
'ceiling/floor plenum depths assigned to Room2Ds should generate '
'distinct 3D Rooms in the translation.', default=True, show_default=True)
@click.option('--ceil-adjacency/--no-ceil-adjacency', '-a/-na', help='Flag to indicate '
'whether adjacencies should be solved between interior stories when '
'Room2Ds perfectly match one another in their floor plate. This ensures '
'that Surface boundary conditions are used instead of Adiabatic ones. '
'Note that this input has no effect when the object-per-model is Story.',
default=True, show_default=True)
@click.option('--merge-method', '-m', help='Text to describe how the Room2Ds should '
'be merged into individual Rooms during the translation. Specifying a '
'value here can be an effective way to reduce the number of Room '
'volumes in the resulting Model and, ultimately, yield a faster simulation '
'time with less results to manage. Choose from: None, Zones, PlenumZones, '
'Stories, PlenumStories.', type=str, default='None', show_default=True)
@click.option('--default-subfaces/--triangulate-subfaces', ' /-t',
help='Flag 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, show_default=True)
@click.option('--triangulate-non-planar/--permit-non-planar', ' /-np',
help='Flag 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 gbXML files that are '
'to be corrected in other software.', default=True, show_default=True)
@click.option('--minimal/--complete-geometry', ' /-cg', help='Flag to note whether space '
'boundaries and shell geometry should be included in the exported '
'gbXML vs. just the minimal required non-manifold geometry.',
default=True, show_default=True)
@click.option('--interior-face-type', '-ift', help='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: InteriorFloor, Ceiling.',
type=str, default='InteriorFloor', show_default=True)
@click.option('--ground-face-type', '-gft', help='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: AutoAssign, '
'UndergroundSlab, SlabOnGrade, RaisedFloor.',
type=str, default='AutoAssign', show_default=True)
@click.option('--keep-geometry-ids/--reset-geometry-ids', ' /-gid', help='Flag to note '
'whether a cleaned version of geometry display names should be used '
'for the IDs that appear within the gbXML file. Using this flag will '
'affect all Rooms, Faces, Apertures, Doors, and Shades. 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=True, show_default=True)
@click.option('--keep-resource-ids/--reset-resource-ids', ' /-rid', help='Flag to note '
'whether a cleaned version of all resource display names should be '
'for the IDs that appear within the gbXML file. Using this flag will '
'affect all Materials, Constructions, ConstructionSets, Schedules, '
'Loads, and ProgramTypes. 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=True, show_default=True)
@click.option('--rect-geo-format', '-rg',
help='Text string to note how the rectangular geometry for '
'all Surfaces is written into the gbXML. BoundingRectangle sets the '
'width and height of the rectangular geometry using the bounding '
'rectangle around the geometry, which results in an overestimated '
'area for non-rectangular geo. SimpleArea will set the rectangle width '
'always equal to geometry area and the height always equal to one, '
'ensuring accurate areas and making it easy to check the geometry '
'area in the gbXML. SimpleAreaForNonRectOnly will report the width and '
'height of rectangular Face3D correctly but use simpler areas '
'for non-rectangular geometry.',
default='BoundingRectangle', show_default=True)
@click.option('--collapsed-holes/--explicit-holes', ' /-eh',
help='Flag to note whether holes in Surfaces should be represented '
'explicitly with their own PolyLoop or the hole and boundary '
'should be collapsed into a single PolyLoop that winds inwards to '
'cut out the holes.', default=True, show_default=True)
@click.option('--program-name', '-p', help='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, "OpenStudio" will be used.',
type=str, default=None, show_default=True)
@click.option('--program-version', '-v', help='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.',
type=str, default=None, show_default=True)
@click.option('--gbxml-schema-version', '-gv', help='Optional text to set the '
'version of the gbXML schema that is specified in the XML header '
'(eg. "5.00"). If unspecified, this will default to the latest version.',
type=str, default=None, show_default=True)
@click.option('--output-file', '-f', help='Optional gbXML file to output the string '
'of the translation. By default it printed out to stdout', default='-',
type=click.Path(file_okay=True, dir_okay=False, resolve_path=True))
def model_to_gbxml_cli(
model_file, si_units, full_geometry, plenum, ceil_adjacency, merge_method,
default_subfaces, triangulate_non_planar, minimal,
interior_face_type, ground_face_type, keep_geometry_ids, keep_resource_ids,
rect_geo_format, collapsed_holes,
program_name, program_version, gbxml_schema_version, output_file
):
"""Translate a Dragonfly Model to a gbXML file.
\b
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
"""
try:
ip_units = not si_units
multiplier = not full_geometry
no_plenum = not plenum
no_ceil_adjacency = not ceil_adjacency
triangulate_subfaces = not default_subfaces
permit_non_planar = not triangulate_non_planar
complete_geometry = not minimal
reset_geometry_ids = not keep_geometry_ids
reset_resource_ids = not keep_resource_ids
explicit_holes = not collapsed_holes
model_to_gbxml(
model_file, ip_units, multiplier, no_plenum, no_ceil_adjacency, merge_method,
triangulate_subfaces, permit_non_planar, complete_geometry,
interior_face_type, ground_face_type, reset_geometry_ids, reset_resource_ids,
rect_geo_format, explicit_holes,
program_name, program_version, gbxml_schema_version, output_file)
except Exception as e:
_logger.exception('Model translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def model_to_gbxml(
model_file, ip_units=False, multiplier=False, no_plenum=False,
no_ceil_adjacency=False, merge_method='None',
triangulate_subfaces=False, permit_non_planar=False, complete_geometry=False,
interior_face_type='InteriorFloor', ground_face_type='AutoAssign',
reset_geometry_ids=False, reset_resource_ids=False,
rect_geo_format='BoundingRectangle', explicit_holes=False,
program_name=None, program_version=None, gbxml_schema_version=None, output_file=None,
si_units=True, full_geometry=True, plenum=True, ceil_adjacency=True,
default_subfaces=True, triangulate_non_planar=True, minimal=True,
keep_geometry_ids=True, keep_resource_ids=True, collapsed_holes=True
):
"""Translate a Dragonfly Model to a gbXML file.
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
ip_units: A boolean to note whether the geometry, space loads, and
construction properties are reported in IP units (True) or SI
units (False). (Default: False).
multiplier: Boolean to note whether the multipliers on each Building
story are respected as integers or if full geometry objects
for each repeated story should be written for each story in the
building. Given that gbXML has no support for assigning multipliers
and is a non-manifold geometry schema that relies on all geometry
being modeled explicitly,this should almost never be set to True. However, if
the destination software supports a means of assigning the multipliers
to spaces after importing the gbXML (eg. TRACE 700), it may be useful
to set this to True. (Default: False).
no_plenum: Boolean to indicate whether ceiling/floor plenum depths
assigned to Room2Ds should not generate distinct 3D Rooms in the
translation. (Default: False).
no_ceil_adjacency: Boolean to indicate whether adjacencies should not be
solved between stories. Given that gbXML is fundamentally a
non-manifold geometry schema, this parameter should almost never
be set to True. However, if the destination software supports simulating
each story as a distinct entity, this may be useful. (Default: False).
merge_method: An optional text string to describe how the Room2Ds should
be merged into individual Rooms during the translation. Specifying a
value here can be an effective way to reduce the number of Room
volumes in the resulting Model and, ultimately, yield a faster simulation
time with less results to manage. Note that Room2Ds will only be merged if
they form a contiguous volume. Otherwise, there will be multiple Rooms per
zone or story, each with an integer added at the end of their
identifiers. Choose from the following options:
* None - No merging will occur
* Zones - Room2Ds in the same zone will be merged
* PlenumZones - Only plenums in the same zone will be merged
* Stories - Rooms in the same story will be merged
* PlenumStories - Only plenums in the same story will be merged
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: False).
permit_non_planar: 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 gbXML files that are
to be corrected in other software. (Default: False).
interior_face_type: Text string for the type to be used for all interior
floor/ceiling faces. (Default: InteriorFloor). Choose from the following.
* InteriorFloor
* Ceiling
ground_face_type: Text string for the type to be used for all ground-contact
floor faces. If AutoAssign, the ground types will be SlabOnGrade for floors
belonging to rooms with any above-ground walls and UndergroundSlab
for floors in rooms with all underground walls. Choose from the following.
* AutoAssign
* UndergroundSlab
* SlabOnGrade
* RaisedFloor
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: UndergroundSlab, SlabOnGrade, RaisedFloor.
reset_geometry_ids: Boolean to note whether a cleaned version of geometry
display names should be used for the IDs that appear within
the gbXML file. Using this flag will affect all Rooms, Faces,
Apertures, Doors, and Shades. It will generally result in more
read-able IDs in the gbXML file but this means that it will not be
easy to map results back to the input 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).
reset_resource_ids: Boolean to note whether a cleaned version of all
resource display names should be used for the IDs that appear within
the gbXML file. 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 gbXML file. 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).
rect_geo_format: Text string to note how the rectangular geometry for
all Surfaces is written into the gbXML. BoundingRectangle sets the
width and height of the rectangular geometry using the bounding
rectangle around the geometry, which results in an overestimated
area for non-rectangular geo. SimpleArea will set the rectangle width
always equal to geometry area and the height always equal to one,
ensuring accurate areas and making it easy to check the geometry
area in the gbXML. SimpleAreaForNonRectOnly will report the width and
height of rectangular Face3D correctly but use simpler areas
for non-rectangular geometry. (Default: BoundingRectangle). Choose
from the following.
* BoundingRectangle
* SimpleArea
* SimpleAreaForNonRectOnly
explicit_holes: Boolean to note whether holes in Surfaces should be
represented explicitly with their own PolyLoop or the hole and boundary
should be collapsed into a single PolyLoop that winds inwards to
cut out the holes. (Default: False).
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).
gbxml_schema_version: Optional text to set the version of the gbXML schema
that is specified in the XML header (eg. "5.00"). If None, this
will default to the latest version.
output_file: Optional gbXML file to output the string of the translation.
By default it will be returned from this method.
"""
# set up gbXML translation parameters
gbxml_par = GBXMLParameters(ip_units=ip_units)
gbxml_par.geometry_format.triangulate_non_planar = True
if permit_non_planar:
gbxml_par.geometry_format.triangulate_non_planar = False
if complete_geometry:
gbxml_par.geometry_format.include_shell_geometry = True
gbxml_par.geometry_format.include_space_boundaries = True
if interior_face_type:
gbxml_par.name_format.interior_face_type = interior_face_type
if ground_face_type:
gbxml_par.name_format.ground_face_type = ground_face_type
gbxml_par.geometry_format.ignore_multipliers = multiplier
gbxml_par.geometry_format.exclude_plenums = no_plenum
gbxml_par.geometry_format.ignore_ceiling_adjacencies = no_ceil_adjacency
gbxml_par.geometry_format.merge_method = merge_method
gbxml_par.geometry_format.triangulate_openings = triangulate_subfaces
gbxml_par.name_format.reset_geometry_ids = reset_geometry_ids
gbxml_par.name_format.reset_resource_ids = reset_resource_ids
gbxml_par.geometry_format.rect_geo_format = rect_geo_format
gbxml_par.geometry_format.explicit_holes = explicit_holes
gbxml_par.version_format.program_name = program_name
gbxml_par.version_format.program_version = program_version
gbxml_par.version_format.gbxml_schema_version = gbxml_schema_version
# re-serialize the Dragonfly Model and translate it
model = Model.from_dfjson(model_file)
gbxml_str = model.to_gbxml(gbxml_par)
# write out the gbXML file
return process_content_to_output(gbxml_str, output_file)
@translate.command('model-to-trace-gbxml')
@click.argument('model-file', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option('--multiplier/--full-geometry', ' /-fg', help='Flag to note if the '
'multipliers on each Building story will be passed along to the '
'generated Honeybee Room objects or if full geometry objects should be '
'written for each story in the building.', default=True, show_default=True)
@click.option('--no-plenum/--plenum', '-np/-p', help='Flag to indicate whether '
'ceiling/floor plenum depths assigned to Room2Ds should generate '
'distinct 3D Rooms in the translation.', default=True, show_default=True)
@click.option('--ceil-adjacency/--no-ceil-adjacency', ' /-na', help='Flag to indicate '
'whether adjacencies should be solved between interior stories when '
'Room2Ds perfectly match one another in their floor plate. This ensures '
'that Surface boundary conditions are used instead of Adiabatic ones. '
'Note that this input has no effect when the object-per-model is Story.',
default=True, show_default=True)
@click.option('--merge-method', '-m', help='Text to describe how the Room2Ds should '
'be merged into individual Rooms during the translation. Specifying a '
'value here can be an effective way to reduce the number of Room '
'volumes in the resulting Model and, ultimately, yield a faster simulation '
'time with less results to manage. Choose from: None, Zones, PlenumZones, '
'Stories, PlenumStories.', type=str, default='None', show_default=True)
@click.option('--single-window/--detailed-windows', ' /-dw', help='Flag to note '
'whether all windows within walls should be converted to a single '
'window with an area that matches the original geometry.',
default=True, show_default=True)
@click.option('--rect-sub-distance', '-r', help='A number for the resolution at which '
'non-rectangular Apertures will be subdivided into smaller rectangular '
'units. This is required as TRACE 3D plus cannot model non-rectangular '
'geometries. This can include the units of the distance (eg. 0.5ft) or, '
'if no units are provided, the value will be interpreted in the '
'honeybee model units.',
type=str, default='0.15m', show_default=True)
@click.option('--frame-merge-distance', '-m', help='A number for the maximum distance '
'between non-rectangular Apertures at which point the Apertures will be '
'merged into a single rectangular geometry. This is often helpful when '
'there are several triangular Apertures that together make a rectangle '
'when they are merged across their frames. This can include the units '
'of the distance (eg. 0.5ft) or, if no units are provided, the value '
'will be interpreted in the honeybee model units',
type=str, default='0.2m', show_default=True)
@click.option('--program-name', '-p', help='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, "OpenStudio" will be used.',
type=str, default=None, show_default=True)
@click.option('--program-version', '-v', help='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.',
type=str, default=None, show_default=True)
@click.option('--output-file', '-f', help='Optional gbXML file to output the string '
'of the translation. By default it printed out to stdout.', default='-',
type=click.Path(file_okay=True, dir_okay=False, resolve_path=True))
def model_to_trace_gbxml_cli(
model_file, multiplier, no_plenum, ceil_adjacency, merge_method,
single_window, rect_sub_distance, frame_merge_distance,
program_name, program_version, output_file
):
"""Translate a Dragonfly Model to a TRACE-compatible gbXML file.
\b
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
"""
try:
full_geometry = not multiplier
plenum = not no_plenum
no_ceil_adjacency = not ceil_adjacency
detailed_windows = not single_window
model_to_trace_gbxml(
model_file, full_geometry, plenum, no_ceil_adjacency, merge_method,
detailed_windows, rect_sub_distance, frame_merge_distance,
program_name, program_version, output_file)
except Exception as e:
_logger.exception('Model translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def model_to_trace_gbxml(
model_file, full_geometry=False,
plenum=False, no_ceil_adjacency=False, merge_method='None',
detailed_windows=False, rect_sub_distance='0.15m', frame_merge_distance='0.2m',
program_name=None, program_version=None, output_file=None,
multiplier=True, no_plenum=True, ceil_adjacency=True, single_window=True
):
"""Translate a Dragonfly Model to a gbXML file that is compatible with TRACE.
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
full_geometry: Boolean to note if the multipliers on each Building story
will be passed along to the generated Honeybee Room objects or if
full geometry objects should be written for each story in the
building. (Default: False).
plenum: Boolean to indicate whether ceiling/floor plenum depths
assigned to Room2Ds should generate distinct 3D Rooms in the
translation. (Default: False).
no_ceil_adjacency: Boolean to indicate whether adjacencies should be solved
between interior stories when Room2Ds perfectly match one another
in their floor plate. This ensures that Surface boundary conditions
are used instead of Adiabatic ones. Note that this input has no
effect when the object-per-model is Story. (Default: False).
merge_method: An optional text string to describe how the Room2Ds should
be merged into individual Rooms during the translation. Specifying a
value here can be an effective way to reduce the number of Room
volumes in the resulting Model and, ultimately, yield a faster simulation
time with less results to manage. Note that Room2Ds will only be merged if
they form a contiguous volume. Otherwise, there will be multiple Rooms per
zone or story, each with an integer added at the end of their
identifiers. Choose from the following options:
* None - No merging will occur
* Zones - Room2Ds in the same zone will be merged
* PlenumZones - Only plenums in the same zone will be merged
* Stories - Rooms in the same story will be merged
* PlenumStories - Only plenums in the same story will be merged
detailed_windows: A boolean for whether all windows within walls should be
left as they are (True) or converted to a single window with an area
that matches the original geometry (False). (Default: False).
rect_sub_distance: A number for the resolution at which non-rectangular
Apertures will be subdivided into smaller rectangular units. This is
required as TRACE 3D plus cannot model non-rectangular geometries.
This can include the units of the distance (eg. 0.5ft) or, if no units
are provided, the value will be interpreted in the honeybee model
units. (Default: 0.15m).
frame_merge_distance: A number for the maximum distance between non-rectangular
Apertures at which point the Apertures will be merged into a single
rectangular geometry. This is often helpful when there are several
triangular Apertures that together make a rectangle when they are
merged across their frames. This can include the units of the
distance (eg. 0.5ft) or, if no units are provided, the value will
be interpreted in the honeybee model units. (Default: 0.2m).
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).
output_file: Optional gbXML file to output the string of the translation.
By default it will be returned from this method.
"""
# set up TRACE 3D Plus translation par
gbxml_par = GBXMLParameters.for_trace_3d_plus()
if not full_geometry:
gbxml_par.geometry_format.ignore_multipliers = True
if plenum:
gbxml_par.geometry_format.exclude_plenums = False
if no_ceil_adjacency:
gbxml_par.geometry_format.ignore_ceiling_adjacencies = True
gbxml_par.geometry_format.merge_method = merge_method
if detailed_windows:
gbxml_par.geometry_format.opening_simplification = 'Rectangularized'
gbxml_par.version_format.program_name = program_name
gbxml_par.version_format.program_version = program_version
# serialize the model and translate it
model = Model.from_dfjson(model_file)
gbxml_str = model.to_gbxml(gbxml_par)
# write out the gbXML file
return process_content_to_output(gbxml_str, output_file)
@translate.command('model-to-sdd')
@click.argument('model-file', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option('--multiplier/--full-geometry', ' /-fg', help='Flag to note if the '
'multipliers on each Building story will be passed along to the '
'generated Honeybee Room objects or if full geometry objects should be '
'written for each story in the building.', default=True, show_default=True)
@click.option('--plenum/--no-plenum', '-p/-np', help='Flag to indicate whether '
'ceiling/floor plenum depths assigned to Room2Ds should generate '
'distinct 3D Rooms in the translation.', default=True, show_default=True)
@click.option('--no-ceil-adjacency/--ceil-adjacency', ' /-a', help='Flag to indicate '
'whether adjacencies should be solved between interior stories when '
'Room2Ds perfectly match one another in their floor plate. This ensures '
'that Surface boundary conditions are used instead of Adiabatic ones. '
'Note that this input has no effect when the object-per-model is Story.',
default=True, show_default=True)
@click.option('--merge-method', '-m', help='Text to describe how the Room2Ds should '
'be merged into individual Rooms during the translation. Specifying a '
'value here can be an effective way to reduce the number of Room '
'volumes in the resulting Model and, ultimately, yield a faster simulation '
'time with less results to manage. Choose from: None, Zones, PlenumZones, '
'Stories, PlenumStories.', type=str, default='None', show_default=True)
@click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag to note whether a '
'cleaned version of all geometry display names should be used instead '
'of identifiers when translating the Model to SDD. Using this flag will '
'affect all Rooms, Faces, Apertures, Doors, and Shades. It will '
'generally result in more read-able names in the SDD but this means that '
'it will not be easy to map the EnergyPlus results back to the original '
'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=True, show_default=True)
@click.option('--resource-ids/--resource-names', ' /-rn', help='Flag to note whether a '
'cleaned version of all resource display names should be used instead '
'of identifiers when translating the Model to SDD. 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 SDD. 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=True, show_default=True)
@click.option('--output-file', '-f', help='Optional gbXML file to output the string '
'of the translation. By default it printed out to stdout', default='-',
type=click.Path(file_okay=True, dir_okay=False, resolve_path=True))
def model_to_sdd_cli(
model_file, multiplier, plenum, no_ceil_adjacency, merge_method,
geometry_ids, resource_ids, output_file
):
"""Translate a Dragonfly Model to a CBECC SDD file.
\b
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
"""
try:
full_geometry = not multiplier
no_plenum = not plenum
ceil_adjacency = not no_ceil_adjacency
geo_names = not geometry_ids
res_names = not resource_ids
model_to_sdd(
model_file, full_geometry, no_plenum, ceil_adjacency, merge_method,
geo_names, res_names, output_file)
except Exception as e:
_logger.exception('Model translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def model_to_sdd(
model_file, full_geometry=False,
no_plenum=False, ceil_adjacency=False, merge_method='None',
geometry_names=False, resource_names=False, output_file=None,
multiplier=True, plenum=True, no_ceil_adjacency=True,
geometry_ids=True, resource_ids=True
):
"""Translate a Dragonfly Model to a CBECC SDD file.
Args:
model_file: Path to either a DFJSON or DFpkl file. This can also be a
HBJSON or a HBpkl from which a Dragonfly model should be derived.
full_geometry: Boolean to note if the multipliers on each Building story
will be passed along to the generated Honeybee Room objects or if
full geometry objects should be written for each story in the
building. (Default: False).
no_plenum: Boolean to indicate whether ceiling/floor plenum depths
assigned to Room2Ds should generate distinct 3D Rooms in the
translation. (Default: False).
ceil_adjacency: Boolean to indicate whether adjacencies should be solved
between interior stories when Room2Ds perfectly match one another
in their floor plate. This ensures that Surface boundary conditions
are used instead of Adiabatic ones. Note that this input has no
effect when the object-per-model is Story. (Default: False).
merge_method: An optional text string to describe how the Room2Ds should
be merged into individual Rooms during the translation. Specifying a
value here can be an effective way to reduce the number of Room
volumes in the resulting Model and, ultimately, yield a faster simulation
time with less results to manage. Note that Room2Ds will only be merged if
they form a contiguous volume. Otherwise, there will be multiple Rooms per
zone or story, each with an integer added at the end of their
identifiers. Choose from the following options:
* None - No merging will occur
* Zones - Room2Ds in the same zone will be merged
* PlenumZones - Only plenums in the same zone will be merged
* Stories - Rooms in the same story will be merged
* PlenumStories - Only plenums in the same story will be merged
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 original 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).
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).
output_file: Optional SDD file to output the string of the translation.
By default it will be returned from this method.
"""
# check that honeybee-openstudio is installed
try:
from honeybee_openstudio.openstudio import openstudio
from honeybee_openstudio.writer import model_to_openstudio
except ImportError as e: # honeybee-openstudio is not installed
raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
# re-serialize the Dragonfly Model
model = Model.from_dfjson(model_file)
model.convert_to_units('Meters')
# convert Dragonfly Model to Honeybee
multiplier = not full_geometry
hb_models = model.to_honeybee(
object_per_model='District', use_multiplier=multiplier,
exclude_plenums=no_plenum, solve_ceiling_adjacencies=ceil_adjacency,
enforce_adj=False)
hb_model = hb_models[0]
if geometry_names: # rename all face geometry so that it is easy to identify
hb_model.reset_ids() # sets the identifiers based on the display_name
for room in hb_model.rooms:
room.display_name = None
for face in room.faces:
face.display_name = None
for ap in face.apertures:
ap.display_name = None
for dr in face.apertures:
dr.display_name = None
room.rename_faces_by_attribute()
room.rename_apertures_by_attribute()
room.rename_doors_by_attribute()
hb_model.reset_ids()
# convert the Honeybee model to an OpenStudio Model
os_model = model_to_openstudio(
hb_model,
use_simple_window_constructions=True,
use_resource_names=resource_names
)
# write the SDD
out_path = None
if output_file is None or output_file.endswith('-'):
out_directory = tempfile.gettempdir()
f_name = os.path.basename(model_file).lower()
f_name = f_name.replace('.hbjson', '.xml').replace('.json', '.xml')
out_path = os.path.join(out_directory, f_name)
sdd = os.path.abspath(output_file) if out_path is None else out_path
sdd_translator = openstudio.sdd.SddForwardTranslator()
sdd_translator.modelToSDD(os_model, sdd)
# return the file contents if requested
if out_path is not None:
with open(sdd, 'r') as sdf:
file_contents = sdf.read()
if output_file is None:
return file_contents
else:
print(file_contents)
@translate.command('hb-models-to-osm')
@click.argument('model-folder', type=click.Path(
exists=True, file_okay=False, dir_okay=True, resolve_path=True))
@click.option('--sim-par-json', '-sp', help='Full path to a honeybee energy '
'SimulationParameter JSON that describes all of the settings for '
'the simulation. If None default parameters will be generated.',
default=None, show_default=True,
type=click.Path(exists=True, file_okay=True, dir_okay=False,
resolve_path=True))
@click.option('--epw-file', '-epw', help='Full path to an EPW file to be associated '
'with the exported OSM. This is typically not necessary but may be '
'used when a sim-par-json is specified that requests a HVAC sizing '
'calculation to be run as part of the translation process but no design '
'days are inside this simulation parameter.',
default=None, show_default=True,
type=click.Path(exists=True, file_okay=True, dir_okay=False,
resolve_path=True))
@click.option('--cpu-count', '-c', help='Optional integer to specify the number '
'of processors to be used in converting each HBJSON to an OSM.',
type=int, default=1, show_default=True)
@click.option('--output-folder', '-f', help='Optional path to an output folder '
'where the OSM files will be written. If unspecified, this will be '
'the same as the folder containing HBJSONs.',
default=None, show_default=True,
type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
def hb_models_to_osm_cli(model_folder, sim_par_json, epw_file, cpu_count, output_folder):
"""Translate a folder of HBJSONs to OSMs in the same folder.
\b
Args:
model_folder: Path to a folder containing HBJSONs to be translated to OSM.
"""
try:
hb_models_to_osm(model_folder, sim_par_json, epw_file, cpu_count, output_folder)
except Exception as e:
_logger.exception('Model translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def hb_models_to_osm(
model_folder, sim_par_json=None, epw_file=None, cpu_count=1, output_folder=None
):
"""Translate a folder of HBJSONs to OSMs in the same folder.
Args:
model_folder: Path to a folder containing HBJSONs to be translated to OSM.
sim_par_json: Full path to a honeybee energy SimulationParameter JSON that
describes all of the settings for the simulation. If None, default
parameters will be generated.
epw_file: Full path to an EPW file to be associated with the exported OSM.
This is typically not necessary but may be used when a sim-par-json is
specified that requests a HVAC sizing calculation to be run as part
of the translation process but no design days are inside this
simulation parameter.
cpu_count: Optional integer to specify the number of processors to be
used in converting each HBJSON to an OSM.
output_folder: Optional path to an output folder where the OSM files will
be written. If unspecified, this will be the same as the folder
containing HBJSONs.
"""
# find all .hbjson files in the target directory
hbjson_files, out_f = [], output_folder
hbjson_files.extend(glob.glob(os.path.join(model_folder, '*.hbjson')))
hbjson_files.extend(glob.glob(os.path.join(model_folder, '*.json')))
if not hbjson_files:
print('No HBJSON files found in: {}'.format(model_folder))
return
# execute translations in parallel
print('Translating {} HBJSON files to OSM.'.format(len(hbjson_files)))
with ProcessPoolExecutor(max_workers=cpu_count) as executor:
# submit all tasks to the executor
futures = {
executor.submit(_hbjson_to_osm, path, sim_par_json, epw_file, out_f): path
for path in hbjson_files
}
# yield results as soon as each process completes
for future in as_completed(futures):
success, original_path, output_path, msg = future.result()
filename = os.path.basename(original_path)
if success:
suc_str = 'SUCCESS: Translated {} -> {}'
print(suc_str.format(filename, os.path.basename(output_path)))
else:
print('FAILED: Could not translate {}'.format(filename))
print(' Error details: {}'.format(msg.strip()))
def _hbjson_to_osm(hbjson_path, sim_par_json, epw_file, output_folder):
"""Translate an HBJSON file to OSM using the Honeybee Energy CLI."""
# Define the output OSM file path
osm_path = hbjson_path.replace('.hbjson', '.osm').replace('.json', '.osm')
if output_folder is not None:
osm_path = os.path.join(output_folder, os.path.basename(osm_path))
# honeybee-energy CLI command for translation
cmd = [
hb_folders.python_exe_path, '-m',
'honeybee_energy', 'translate', 'model-to-osm',
hbjson_path, '--osm-file', osm_path
]
if sim_par_json is not None:
cmd.append('--sim-par-json')
cmd.append(sim_par_json)
if epw_file is not None:
cmd.append('--epw-file')
cmd.append(epw_file)
try:
# execute the CLI command
process = subprocess.run(
cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
return True, hbjson_path, osm_path, process.stdout
except subprocess.CalledProcessError as e:
return False, hbjson_path, None, e.stderr
@translate.command('building-district-loads')
@click.argument(
'scenario-file',
type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True)
)
@click.option(
'--loads-to-folder/--loads-to-log', ' /-l', help='Flag to note whether the loads '
'should be updated within the URBANopt project folder or they should instead be '
'included within the log file output from this command. The latter is useful '
'when binding building loads to a dragonfly Model.',
default=True, show_default=True)
@click.option(
'--log-file', '-log', help='Optional log file to output the paths to the '
'generated simulation files if they were successfully created. '
'By default this will be printed out to stdout',
type=click.File('w'), default='-', show_default=True)
def building_district_loads_cli(scenario_file, loads_to_folder, log_file):
"""Set the building loads to be used for DES simulation to district chilled/hot water.
\b
Args:
scenario_csv: The full path to a .csv file for the URBANopt scenario.
"""
try:
loads_to_log = not loads_to_folder
building_district_loads(scenario_file, loads_to_log, log_file)
except Exception as e:
_logger.exception('Building district loads translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
[docs]
def building_district_loads(
scenario_file, loads_to_log=False, log_file=None, loads_to_folder=True
):
"""Set the building loads to be used for DES simulation to district chilled/hot water.
If no district chilled/hot water loads are found in the SQL result file for
a given building, the zone sensible cooling/heating load will be used instead
and a warning will be returned from this function.
Args:
scenario_csv: The full path to a .csv file for the URBANopt scenario.
loads_to_log: Boolean to note whether the loads should be updated within
the URBANopt project folder (FAlse) or they should instead be included
within the log file output from this command (True). The latter is
useful when binding building loads to a dragonfly Model.
log_file: Optional log file to output the paths to the generated
simulation files if they were successfully created. By default this
string will be returned from this method.
"""
if loads_to_log:
building_loads, warnings = ModelEnergyProperties.des_building_loads(scenario_file)
content = {'warnings': warnings, 'building_loads': {}}
for bld_id, loads in building_loads.items():
bldg_dict = {
'cooling': loads['cooling'].to_dict(),
'heating': loads['heating'].to_dict(),
'shw': loads['shw'].to_dict()
}
content['building_loads'][bld_id] = bldg_dict
else:
content = set_building_district_loads(scenario_file)
return process_content_to_output(json.dumps(content, indent=4), log_file)