Skip to content

Commit

Permalink
feat(measure): Add components to load measures and include in OS export
Browse files Browse the repository at this point in the history
This commit adds a component to load measures into Grasshopper and assign their arguments.  It also adds changes to the "Model To OSM" component to incorporate measures into the openstudio workflow that creates the model.
  • Loading branch information
chriswmackey authored and Chris Mackey committed May 21, 2020
1 parent 48e61d1 commit f781f26
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 27 deletions.
8 changes: 4 additions & 4 deletions honeybee_grasshopper_energy/src/HB Blind Material.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@
assigned to a Honeybee Window construction.
"""

ghenv.Component.Name = "HB Blind Material"
ghenv.Component.Name = 'HB Blind Material'
ghenv.Component.NickName = 'BlindMat'
ghenv.Component.Message = '0.1.2'
ghenv.Component.Message = '0.1.3'
ghenv.Component.Category = 'HB-Energy'
ghenv.Component.SubCategory = "1 :: Constructions"
ghenv.Component.AdditionalHelpFromDocStrings = "6"
ghenv.Component.SubCategory = '1 :: Constructions'
ghenv.Component.AdditionalHelpFromDocStrings = '0'

try: # import the core honeybee dependencies
from honeybee.typing import clean_and_id_ep_string
Expand Down
190 changes: 190 additions & 0 deletions honeybee_grasshopper_energy/src/HB Load Measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Honeybee: A Plugin for Environmental Analysis (GPL)
# This file is part of Honeybee.
#
# Copyright (c) 2019, Ladybug Tools.
# You should have received a copy of the GNU General Public License
# along with Honeybee; If not, see <http://www.gnu.org/licenses/>.
#
# @license GPL-3.0+ <http://spdx.org/licenses/GPL-3.0+>

"""
Load OpenStudio measures into Grasshopper and assign the measure's input arguments.
The resulting measure object can be plugged into the "measures_" input of the
"HB Model To OSM" component in order to be included in the export to OpenStudio.
-
Read more about OpenStudio measures and creating your own here:
http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
-
You can also download several measures created by others from here:
https://bcl.nrel.gov/nrel/types/measure
-
Args:
_measure_path: Path to the folder in which the measure exists. This folder
must contain a measure.rb and a measure.xml file. Note that connecting
an input here will transform the component, essentially removing this
input and changing all of the other component inputs to be input
arguments for the measure.
Returns:
measure: A measure measure object can be plugged into the "measures_" input
of the "HB Model To OSM" component in order to be included in the
export to OpenStudio.
"""

ghenv.Component.Name = 'HB Load Measure'
ghenv.Component.NickName = 'LoadMeasure'
ghenv.Component.Message = '0.1.0'
ghenv.Component.Category = 'HB-Energy'
ghenv.Component.SubCategory = '5 :: Simulate'
ghenv.Component.AdditionalHelpFromDocStrings = "3"

try:
from honeybee_energy.measure import Measure
except ImportError as e:
raise ImportError('\nFailed to import honeybee_energy:\n\t{}'.format(e))

try: # import the ladybug_rhino dependencies
from ladybug_rhino.grasshopper import all_required_inputs, give_warning
except ImportError as e:
raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e))

import Grasshopper.Kernel as gh


def add_component_input_from_arg(argument):
"""Add an input parameter to this component using a MeasureArgument object.
Args:
argument: A honeybee-energy MeasureArgument object that will be used
to create a new input parameter for the component.
"""
# create the input parameter object
param = gh.Parameters.Param_ScriptVariable()

# assign the required properties to the input
arg_id = '_{}'.format(argument.identifier) if argument.required else \
'{}_'.format(argument.identifier)
param.NickName = arg_id
param.Name = arg_id if argument.display_name is None else argument.display_name
param.AllowTreeAccess = False # Measures don't support lists
param.Access = gh.GH_ParamAccess.item # Measures don't support lists
param.Optional = True # always needed so that default can come from measure file

# assign the optional properties to the input if they exist
if argument.type_text == 'Choice':
descr = [argument.description] if argument.description else []
descr.append('Choose from the following options:')
descr = descr + list(argument.valid_choices)
param.Description = '\n '.join(descr)
elif argument.description:
param.Description = argument.description
if argument.default_value is not None:
param.AddVolatileData(gh.Data.GH_Path(0), 0, argument.default_value)

# add the parameter to the compoent
index = ghenv.Component.Params.Input.Count
ghenv.Component.Params.RegisterInputParam(param, index)
ghenv.Component.Params.OnParametersChanged()

def transform_name_and_description(measure):
"""Transform this component's name and description to match a measure.
Args:
measure: A honeybee-energy Measure object that will be used to assign
this component's name and description.
"""
# assign the measure metadata
ghenv.Component.NickName = measure.identifier
ghenv.Component.Name = measure.display_name if measure.display_name \
else measure.identifier
if measure.description:
ghenv.Component.Description = measure.description


def transform_component(measure):
"""Transform this component to have a name and arguments that match a measure.
Args:
measure: A honeybee-energy Measure object that will be used to assign
this component's name, description, and input arguments.
"""
# assign the measure metadata
transform_name_and_description(measure)
# assign the input arguments
for arg in measure.arguments:
add_component_input_from_arg(arg)


def check_arguments_and_set_defaults(measure):
"""Check to be sure the names of component inputs align with measure arguments.
This method will also assign any default values from the measure if there is
no value input to the component.
Args:
measure: A honeybee-energy Measure object that will be used check this
component's input arguments.
"""
for i in range(1, ghenv.Component.Params.Input.Count):
# get the param and the measure argument object
param = ghenv.Component.Params.Input[i]
arg = measure_init.arguments[i - 1]

# check that the param matches the measure argument
assert arg.identifier in param.NickName, \
"This component's inputs do not match that of the input measure.\n" \
"Grab a fresh 'HB Load Measure' component and reload the measure."

# add any default values
if not param.VolatileDataCount or param.VolatileData[0][0] is None:
if arg.default_value is not None:
param.AddVolatileData(gh.Data.GH_Path(0), 0, arg.default_value)


def update_measure_arguments(measure):
"""Update the arguments of a measure object based on this component's inputs.
Args:
measure: A honeybee-energy Measure object to have its arguments updated
with the inputs to this component.
"""
for i in range(1, ghenv.Component.Params.Input.Count):
try:
value = ghenv.Component.Params.Input[i].VolatileData[0][0]
if value is not None:
measure.arguments[i - 1].value = str(value)
except IndexError: # there is no input for this value; just ignore it
pass


def is_measure_input():
"""Check if a measure path is input to this component.
This is needed because we don't know if there are default values for all
required inputs until we load the measure.
"""
if _measure_path is None:
msg = 'Input parameter _measure_path failed to collect data!'
print(msg)
give_warning(ghenv.Component, msg)
return False
return True


if is_measure_input():
# load the measure
measure_init = Measure(_measure_path)

# transform the component or check the inputs and set defaults
if ghenv.Component.Params.Input.Count == 1: # first time loading the measure
transform_component(measure_init)
else: # the component has already been transformed
transform_name_and_description(measure_init)
check_arguments_and_set_defaults(measure_init)

# if the measure has all inputs that it needs, output the measure
if all_required_inputs(ghenv.Component):
update_measure_arguments(measure_init)
measure = measure_init
50 changes: 31 additions & 19 deletions honeybee_grasshopper_energy/src/HB Model to OSM.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
parameters will automatically be used.
measures_: An optional list of measures to apply to the OpenStudio model
upon export. Use the "HB Load Measure" component to load a measure
into Grasshopper. Measures can be downloaded from the NREL
Building Components Library (BCL) at (https://bcl.nrel.gov/).
into Grasshopper and assign input arguments. Measures can be
downloaded from the NREL Building Components Library (BCL) at
(https://bcl.nrel.gov/).
add_str_: THIS OPTION IS JUST FOR ADVANCED USERS OF ENERGYPLUS.
You can input additional text strings here that you would like
written into the IDF. The strings input here should be complete
Expand Down Expand Up @@ -68,15 +69,13 @@

ghenv.Component.Name = "HB Model to OSM"
ghenv.Component.NickName = 'ModelToOSM'
ghenv.Component.Message = '0.4.6'
ghenv.Component.Message = '0.5.0'
ghenv.Component.Category = 'HB-Energy'
ghenv.Component.SubCategory = '5 :: Simulate'
ghenv.Component.AdditionalHelpFromDocStrings = "1"

import os
import sys
import json
import shutil

try:
from ladybug.futil import preparedir, nukedir
Expand All @@ -90,7 +89,8 @@

try:
from honeybee_energy.simulation.parameter import SimulationParameter
from honeybee_energy.run import to_openstudio_osw, run_osw, run_idf
from honeybee_energy.run import to_openstudio_osw, run_osw, run_idf, \
output_energyplus_files
from honeybee_energy.result.err import Err
except ImportError as e:
raise ImportError('\nFailed to import honeybee_energy:\n\t{}'.format(e))
Expand Down Expand Up @@ -155,26 +155,38 @@ def orphaned_warning(object_type):
with open(sim_par_json, 'w') as fp:
json.dump(sim_par_dict, fp)

# process any measures input to the component
measures = None if len(measures_) == 0 or measures_[0] is None else measures_
no_report_meas = True if measures is None else \
all(meas.type != 'ReportingMeasure' for meas in measures)

# collect the two jsons for output and write out the osw file
jsons = [model_json, sim_par_json]
osw = to_openstudio_osw(directory, model_json, sim_par_json, epw_file=_epw_file)
osw = to_openstudio_osw(directory, model_json, sim_par_json,
additional_measures=measures, epw_file=_epw_file)

# run the measure to translate the model JSON to an openstudio measure
if run_ > 0:
if run_ > 0 and not no_report_meas: # everything must run with OS CLI
osm, idf = run_osw(osw, measures_only=False)
sql, zsz, rdd, html, err = output_energyplus_files(os.path.dirname(idf))
elif run_ > 0: # no reporting measure; simulate separately from measure application
osm, idf = run_osw(osw)
# process the additional strings
if add_str_ != [] and add_str_[0] is not None and idf is not None:
add_str = '/n'.join(add_str_)
with open(idf, "a") as idf_file:
idf_file.write(add_str)

# run the resulting idf throught EnergyPlus
if run_ == 1:
sql, zsz, rdd, html, err = run_idf(idf, _epw_file)
if err is not None:
err_obj = Err(err)
print(err_obj.file_contents)
for warn in err_obj.severe_errors:
give_warning(ghenv.Component, warn)
for error in err_obj.fatal_errors:
raise Exception(error)
if idf is None: # measures failed to run correctly
raise Exception('Applying measures failed. Check run.log in:'
'\n{}'.format(os.path.join(directory, 'run')))
if run_ == 1: # run the resulting idf throught EnergyPlus
sql, zsz, rdd, html, err = run_idf(idf, _epw_file)

# parse the error log and report any warnings
if run_ == 1 and err is not None:
err_obj = Err(err)
print(err_obj.file_contents)
for warn in err_obj.severe_errors:
give_warning(ghenv.Component, warn)
for error in err_obj.fatal_errors:
raise Exception(error)
8 changes: 4 additions & 4 deletions honeybee_grasshopper_energy/src/HB Shade Material.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
shade) that can be assigned to a Honeybee Window construction.
"""

ghenv.Component.Name = "HB Shade Material"
ghenv.Component.Name = 'HB Shade Material'
ghenv.Component.NickName = 'ShadeMat'
ghenv.Component.Message = '0.1.2'
ghenv.Component.Message = '0.1.3'
ghenv.Component.Category = 'HB-Energy'
ghenv.Component.SubCategory = "1 :: Constructions"
ghenv.Component.AdditionalHelpFromDocStrings = "6"
ghenv.Component.SubCategory = '1 :: Constructions'
ghenv.Component.AdditionalHelpFromDocStrings = '0'

try: # import the core honeybee dependencies
from honeybee.typing import clean_and_id_ep_string
Expand Down
Binary file modified honeybee_grasshopper_energy/user_objects/HB Blind Material.ghuser
Binary file not shown.
Binary file not shown.
Binary file modified honeybee_grasshopper_energy/user_objects/HB Model to OSM.ghuser
Binary file not shown.
Binary file not shown.
Binary file modified samples/creating_constructions.gh
Binary file not shown.
Binary file modified samples/full_building_energy_model.gh
Binary file not shown.
Binary file modified samples/shoe_box_energy_model.gh
Binary file not shown.
Binary file modified samples/single_family_energy_model.gh
Binary file not shown.

0 comments on commit f781f26

Please sign in to comment.