From 4963f7a46a1b442671f579d9430dd04d4d9be847 Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Sat, 28 Oct 2023 10:38:08 -0700 Subject: [PATCH] fix(simulate): Add a component to create an OSM from a Measure --- .../icon/HB Create OSM Measure.png | Bin 0 -> 1498 bytes .../json/HB_Create_OSM_Measure.json | 64 ++++++++ .../src/HB Create OSM Measure.py | 139 ++++++++++++++++++ .../user_objects/HB Create OSM Measure.ghuser | Bin 0 -> 7122 bytes 4 files changed, 203 insertions(+) create mode 100644 honeybee_grasshopper_energy/icon/HB Create OSM Measure.png create mode 100644 honeybee_grasshopper_energy/json/HB_Create_OSM_Measure.json create mode 100644 honeybee_grasshopper_energy/src/HB Create OSM Measure.py create mode 100644 honeybee_grasshopper_energy/user_objects/HB Create OSM Measure.ghuser diff --git a/honeybee_grasshopper_energy/icon/HB Create OSM Measure.png b/honeybee_grasshopper_energy/icon/HB Create OSM Measure.png new file mode 100644 index 0000000000000000000000000000000000000000..5ab023ff8d36f8830dfe211bfa63ad1cbf8145e8 GIT binary patch literal 1498 zcmV<01tt24P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1#d}2K~zXfy_O41 zlUEeSKTx!ah&ZAk6k23}n<7jQhMR#Ib&aq2V(L}|oeU>YoC6^nK7x-S2AO~`bs(S& z6+ue`l*edg%BWJFqPzqP2<4%IIC;o_r(f&Ll9J6W**`hS{Z7B{ckVs+e{ahCFETeb z=kU$Ume6qdzYHG5$f5$1EP4Fr-oCrw7q}PO{lEKX5wM5e4~Feb+YZ9msAwa|aN@)> z>EaT!18C?68ixQmjUNoCp8pJbR_2>ts(x#uaa`IpOqo3Ssv4zPqZuYBEG7Z<1iAkV zC~fbD_}(LST-p5+vbKJdcMRfwFW7wX!yZ&U9!5piOOz=F;Oy+uNf=!fO@pXFU}$ta zJEPPBDeUbXz%%E=UAT#g^`WYJh+zHj*%=5cODnuR-4fgUzhx&RmG{75j$<=n#;Rx< zG)_zFKy*?XYI+ATYo;xH1A>uPR!N2Cz}G(*tyDNoQVN3!6L2<5g20e4tX?OCd>FVa zk%2pPYPK4sS^6AKyHMokQp%Xhi%4%yn`>O&=)t0-t$#V}{MUSSVPE z_$xW=iVsJg26bS!8l_o=`ua(!`BD@$c7ew=LrTsqbkfJdFTTQ(6>A_L1kxoX*b^3s z>m_CQeDhY=&9+Cw3t%g?jSvDAX#|-X8Hq06kfE8D9I!tWW+tX^`QSsg*P>hHXr=tI zxD-&6v25jPI5;?B)w+$S>;XKsY*!X=Q;|jx&(u6Gv$&ddNewwC{z@)_k3=G`@*!%e zJ!Rye!n!WRi^K>z6peim3COPOhNNj2TfYf`Hp6(TQJQ6G&bx7~qz=_j2}ZW7eXjC3 zG4PxGISLz|K~mR;!lofe8i$cvJBUr>+tJQdhF)=huj~Cs9xM2_(rCmt7-GTE( z?KoTb2yq21IGHO)Ol~V;3mzjpvjx7PX>eV(33Hw2!^3MclCuiP#?)HcFXeU8LJmyI z$X9N@N|VOwgzb$ui>uWGI7iSk;uf5u7M#d#LS(iaQMq00Y&`>_Fwbon7A_JX?B7daSy)UPWcsFdopEQ95xAopd#x7024bN`5#+#J){mnx$F#471=pr;p$jP4~_5Qn(*2#+rxDIW>}BI$s4cDc)|_? zcccooA8&y**BD$QqndFHbHG4HXQuj-Hy77{6J_VR7EXRCu-lf1cNPd?G0nJuTB!VU zFh=#(e-w9;&PM*UX<2-esp&jDZJ|2-24a{$0Be`xRe$d)0000007*qoM6N<$f?4;n AO#lD@ literal 0 HcmV?d00001 diff --git a/honeybee_grasshopper_energy/json/HB_Create_OSM_Measure.json b/honeybee_grasshopper_energy/json/HB_Create_OSM_Measure.json new file mode 100644 index 00000000..fd0acb49 --- /dev/null +++ b/honeybee_grasshopper_energy/json/HB_Create_OSM_Measure.json @@ -0,0 +1,64 @@ +{ + "version": "1.7.0", + "nickname": "OSMMeasure", + "outputs": [ + [ + { + "access": "None", + "name": "osw", + "description": "File path to the OpenStudio Workflow JSON on this machine. This workflow\nis executed using the OpenStudio command line interface (CLI) and\nit includes measures to create the OSM from the measure", + "type": null, + "default": null + }, + { + "access": "None", + "name": "osm", + "description": "The file path to the OpenStudio Model (OSM) that has been generated\non this computer.", + "type": null, + "default": null + }, + { + "access": "None", + "name": "idf", + "description": "The file path of the EnergyPlus Input Data File (IDF) that has been\ngenerated on this computer.", + "type": null, + "default": null + } + ] + ], + "inputs": [ + { + "access": "item", + "name": "_measure", + "description": "A Measure from the \"HB Load Measure\" component that is intended to\ngenerate an OSM from input arguments. Measures can be downloaded\nfrom the NREL Building Components Library (BCL) at (https://bcl.nrel.gov/).", + "type": "System.Object", + "default": null + }, + { + "access": "list", + "name": "add_str_", + "description": "Optional additional text strings here to be written into the IDF.\nThe input here should be complete EnergyPlus objects as a single\nstring following the IDF format. This can be used to add addition\nEnergyPlus outputs in the resulting IDF among other features.", + "type": "System.Object", + "default": null + }, + { + "access": "item", + "name": "_folder_", + "description": "An optional folder on this computer, into which the IDF and OSM\nfiles will be written. If none, a sub-folder within the default\nsimulation folder will be used.", + "type": "string", + "default": null + }, + { + "access": "item", + "name": "_run", + "description": "Script variable Python", + "type": "bool", + "default": null + } + ], + "subcategory": "5 :: Simulate", + "code": "\nimport sys\nimport re\nimport os\nimport json\n\ntry:\n from ladybug.futil import preparedir, nukedir\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from honeybee.config import folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_energy.measure import Measure\n from honeybee_energy.run import run_osw\n from honeybee_energy.config import folders as energy_folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_energy:\\n\\t{}'.format(e))\n\ntry:\n from lbt_recipes.version import check_openstudio_version\nexcept ImportError as e:\n raise ImportError('\\nFailed to import lbt_recipes:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component) and _run:\n # check the presence of openstudio and check that the version is compatible\n check_openstudio_version()\n\n # process the simulation folder name and the directory\n _folder_ = folders.default_simulation_folder if _folder_ is None else _folder_\n clean_name = re.sub(r'[^.A-Za-z0-9_-]', '_', _measure.display_name)\n directory = os.path.join(_folder_, clean_name, 'openstudio')\n\n # delete any existing files in the directory and prepare it for simulation\n nukedir(directory, True)\n preparedir(directory)\n\n # create a dictionary representation of the .osw with steps to run\n # the model measure and the simulation parameter measure\n osw_dict = {'steps': []}\n # assign the measure_paths to the osw_dict\n if 'measure_paths' not in osw_dict:\n osw_dict['measure_paths'] = []\n if energy_folders.honeybee_openstudio_gem_path: # include honeybee-openstudio measure path\n measure_dir = os.path.join(energy_folders.honeybee_openstudio_gem_path, 'measures')\n osw_dict['measure_paths'].append(measure_dir)\n\n # add the measure to the OSW\n measure_paths = set() # set of all unique measure paths\n _measure.validate() # ensure that all required arguments have values\n measure_paths.add(os.path.dirname(_measure.folder))\n osw_dict['steps'].append(_measure.to_osw_dict()) # add measure to workflow\n\n # load the inject IDF measure if strings_to_inject have bee specified\n str_inject = None if add_str_ == [] or add_str_[0] is None \\\n else '\\n'.join(add_str_)\n if str_inject is not None and str_inject != '':\n assert energy_folders.inject_idf_measure_path is not None, \\\n 'Additional IDF strings input but the inject_idf measure is not installed.'\n idf_measure = Measure(energy_folders.inject_idf_measure_path)\n inject_idf = os.path.join(directory, 'inject.idf')\n with open(inject_idf, \"w\") as idf_file:\n idf_file.write(str_inject)\n units_arg = idf_measure.arguments[0]\n units_arg.value = inject_idf\n measure_paths.add(os.path.dirname(idf_measure.folder))\n osw_dict['steps'].append(idf_measure.to_osw_dict()) # add measure to workflow\n\n # write the dictionary to a workflow.osw\n for m_path in measure_paths:\n osw_dict['measure_paths'].append(m_path)\n osw_json = os.path.join(directory, 'workflow.osw')\n if (sys.version_info < (3, 0)): # we need to manually encode it as UTF-8\n with open(osw_json, 'w') as fp:\n workflow_str = json.dumps(osw_dict, indent=4, ensure_ascii=False)\n fp.write(workflow_str.encode('utf-8'))\n else:\n with open(osw_json, 'wb', encoding='utf-8') as fp:\n workflow_str = json.dump(osw_dict, fp, indent=4, ensure_ascii=False)\n osw = os.path.abspath(osw_json)\n osm, idf = run_osw(osw, silent=False)\n", + "category": "HB-Energy", + "name": "HB Create OSM Measure", + "description": "Run an OpenStudio Meausre that is intended to create an entire OSM file\n(OpenStudio Model). Examples of such measures include the \"Create DOE\nPrototype Building\" measure such as that wich can be downloaded here:\n_\nhttps://github.com/NREL/openstudio-model-articulation-gem/tree/develop/lib/\nmeasures/create_DOE_prototype_building\n-" +} \ No newline at end of file diff --git a/honeybee_grasshopper_energy/src/HB Create OSM Measure.py b/honeybee_grasshopper_energy/src/HB Create OSM Measure.py new file mode 100644 index 00000000..91c0a585 --- /dev/null +++ b/honeybee_grasshopper_energy/src/HB Create OSM Measure.py @@ -0,0 +1,139 @@ +# Honeybee: A Plugin for Environmental Analysis (GPL) +# This file is part of Honeybee. +# +# Copyright (c) 2023, Ladybug Tools. +# You should have received a copy of the GNU Affero General Public License +# along with Honeybee; If not, see . +# +# @license AGPL-3.0-or-later + +""" +Run an OpenStudio Meausre that is intended to create an entire OSM file +(OpenStudio Model). Examples of such measures include the "Create DOE +Prototype Building" measure such as that wich can be downloaded here: +_ +https://github.com/NREL/openstudio-model-articulation-gem/tree/develop/lib/ +measures/create_DOE_prototype_building + +- + Args: + _measure: A Measure from the "HB Load Measure" component that is intended to + generate an OSM from input arguments. Measures can be downloaded + from the NREL Building Components Library (BCL) at (https://bcl.nrel.gov/). + add_str_: Optional additional text strings here to be written into the IDF. + The input here should be complete EnergyPlus objects as a single + string following the IDF format. This can be used to add addition + EnergyPlus outputs in the resulting IDF among other features. + _folder_: An optional folder on this computer, into which the IDF and OSM + files will be written. If none, a sub-folder within the default + simulation folder will be used. + run_: Set to "True" to run the measure and generate the OSM. + + Returns: + report: Reports, errors, warnings. + osw: File path to the OpenStudio Workflow JSON on this machine. This workflow + is executed using the OpenStudio command line interface (CLI) and + it includes measures to create the OSM from the measure + osm: The file path to the OpenStudio Model (OSM) that has been generated + on this computer. + idf: The file path of the EnergyPlus Input Data File (IDF) that has been + generated on this computer. +""" + +ghenv.Component.Name = 'HB Create OSM Measure' +ghenv.Component.NickName = 'OSMMeasure' +ghenv.Component.Message = '1.7.0' +ghenv.Component.Category = 'HB-Energy' +ghenv.Component.SubCategory = '5 :: Simulate' +ghenv.Component.AdditionalHelpFromDocStrings = '0' + +import sys +import re +import os +import json + +try: + from ladybug.futil import preparedir, nukedir +except ImportError as e: + raise ImportError('\nFailed to import ladybug:\n\t{}'.format(e)) + +try: + from honeybee.config import folders +except ImportError as e: + raise ImportError('\nFailed to import honeybee:\n\t{}'.format(e)) + +try: + from honeybee_energy.measure import Measure + from honeybee_energy.run import run_osw + from honeybee_energy.config import folders as energy_folders +except ImportError as e: + raise ImportError('\nFailed to import honeybee_energy:\n\t{}'.format(e)) + +try: + from lbt_recipes.version import check_openstudio_version +except ImportError as e: + raise ImportError('\nFailed to import lbt_recipes:\n\t{}'.format(e)) + +try: + from ladybug_rhino.grasshopper import all_required_inputs +except ImportError as e: + raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) + + +if all_required_inputs(ghenv.Component) and _run: + # check the presence of openstudio and check that the version is compatible + check_openstudio_version() + + # process the simulation folder name and the directory + _folder_ = folders.default_simulation_folder if _folder_ is None else _folder_ + clean_name = re.sub(r'[^.A-Za-z0-9_-]', '_', _measure.display_name) + directory = os.path.join(_folder_, clean_name, 'openstudio') + + # delete any existing files in the directory and prepare it for simulation + nukedir(directory, True) + preparedir(directory) + + # create a dictionary representation of the .osw with steps to run + # the model measure and the simulation parameter measure + osw_dict = {'steps': []} + # assign the measure_paths to the osw_dict + if 'measure_paths' not in osw_dict: + osw_dict['measure_paths'] = [] + if energy_folders.honeybee_openstudio_gem_path: # include honeybee-openstudio measure path + measure_dir = os.path.join(energy_folders.honeybee_openstudio_gem_path, 'measures') + osw_dict['measure_paths'].append(measure_dir) + + # add the measure to the OSW + measure_paths = set() # set of all unique measure paths + _measure.validate() # ensure that all required arguments have values + measure_paths.add(os.path.dirname(_measure.folder)) + osw_dict['steps'].append(_measure.to_osw_dict()) # add measure to workflow + + # load the inject IDF measure if strings_to_inject have bee specified + str_inject = None if add_str_ == [] or add_str_[0] is None \ + else '\n'.join(add_str_) + if str_inject is not None and str_inject != '': + assert energy_folders.inject_idf_measure_path is not None, \ + 'Additional IDF strings input but the inject_idf measure is not installed.' + idf_measure = Measure(energy_folders.inject_idf_measure_path) + inject_idf = os.path.join(directory, 'inject.idf') + with open(inject_idf, "w") as idf_file: + idf_file.write(str_inject) + units_arg = idf_measure.arguments[0] + units_arg.value = inject_idf + measure_paths.add(os.path.dirname(idf_measure.folder)) + osw_dict['steps'].append(idf_measure.to_osw_dict()) # add measure to workflow + + # write the dictionary to a workflow.osw + for m_path in measure_paths: + osw_dict['measure_paths'].append(m_path) + osw_json = os.path.join(directory, 'workflow.osw') + if (sys.version_info < (3, 0)): # we need to manually encode it as UTF-8 + with open(osw_json, 'w') as fp: + workflow_str = json.dumps(osw_dict, indent=4, ensure_ascii=False) + fp.write(workflow_str.encode('utf-8')) + else: + with open(osw_json, 'wb', encoding='utf-8') as fp: + workflow_str = json.dump(osw_dict, fp, indent=4, ensure_ascii=False) + osw = os.path.abspath(osw_json) + osm, idf = run_osw(osw, silent=False) diff --git a/honeybee_grasshopper_energy/user_objects/HB Create OSM Measure.ghuser b/honeybee_grasshopper_energy/user_objects/HB Create OSM Measure.ghuser new file mode 100644 index 0000000000000000000000000000000000000000..3763839b5a70a51ffacfbcbe919dc6614c170e8b GIT binary patch literal 7122 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

KVo1+BSBcRgQa83SuSn2TDe& z{8V8x&o8Oe9&&rPb7Z_g|GZ7j>;0lZrTj9FqEFuN+6Rxk`Ka!*=jrlyG2h-cCMvbd zRQf5xG1()Uk(Xc!oEaETE+A8z*y;EXnp9Bc)^0kKR9?q@Gi?m?E4v zmWeL}4AYve#^3#_2A@2EzsTDd<_N?lb_P9l0qWLWC!hXF{Es{vg<4Dp6*@miZlXbuT)7;bb zho9!AFYETUBeQbrJ8-!~d*Qp98e@nk5HuUqp_Y8>YEc<{6JI;J16j(_f1@W>Sxx z=IsvZ*vDGj>Iwps!^?iu?`CD90#Z)=ucGp$vxO2@VBaQWdb`hFNwie&7V4Lt)f`Dn zN{O}Hk9(66(T~1C9SbQ)mcnkhVMdYGo^#^O;xbliA;AQ`39=q#v)V0?W^AzwKUW(E zqXRKWW;2g*y&vIhZh!pkr?1CtNro3XQ?4s}Gv&hvUqneiMc?p=dSujCYj-P9+ls*c*3=HfB2|C7;}*_!0JqUC*aH3p79a@+0JKt-ZvD3yQe2S+u%VVYUc8NzW_eQ z*yvlFZ8_lUE`x|++t$&^sNjC5TOr z-JU~#K-5cI@axiVOD)Z(FcK5!WQcnLi<&ZEmYfDM4MOUlqmVNO?;aVo3^ZrdJmmGKHehkIqQDNB{EvF`jLy}{d~ z0=k#6QgrxkhVBrRC`>#F3s-Tpz1do6^_B5<51d=!xO&Z`y+b$i2bKD9IFTDcIDc~d zaah%M?dRFt5Y;)u#PJGTmVQ-y^~BF%>bp;|)Te777O}abcK4vG^*{0beKT4$yrogX z6h~SzF0B4rC5aKs?>z&&tY)fLZ>StoFS^ThjvMzgmUovB0OOTZdU2QQH{7VcU(3I} z;+c(5hv}p580aKCevM8pnk&Bym|Ae#y*Nq_n6%qkrSWq;i%LL|chQ+i6qvL?B+i4c zPk2nMyuZ1u5d7u|QhFnn{E_>c;LX~3XZC1HZToDRGVKeok52A(Q_mch+=tTIzt#8d zg=^jLO5Y!T*6dy(*Wyn94VrYB3UaA`HA}?s>caFW1gBZ{R5aDx(6hMG^D4`~O!%HQ ztHl9QbtJ1Z@4sK}Z|2)mw@fADWZmL@=6m`(rI(uQq+#p13F47ViG6_Z*6mu9!OGK_ zVc%1PXey}a>Ri0R;bIv^F9lPWpPb##GPyD~+s~DJfx_Q%Tf)^glla7Uhc8^_<+B`{ z>9}y$&>~I4@>KMe@ZRLd>&$nPZm;>;bqi$Sj8W}hDmqc){3mcqoyT8+Rria~ZykoqN>0C#*`I{G>5H9RUdK6+G zlJu#Kjy!^qdf-m(EN6^%@2d`_jZoeFM95-K=K`*_R9FXDD^9SCdmJ>2tWYuce(Vp_ z;Be8wpfo^Luhf8JW|S=kmRnRO|7i%`HC$|~_0jJWwzU3-@EHXRY^k{5;%ZKcM4`)B zO3T_OjCgPH&z=@%dD;8V9=QF`xu{ESO6dy3-@ym*W_q%KkgY`@NY|1GfEJe2q>6lVhPb08PTZFFn+DYwJjZRJm8 zbU|Gm83F?W%=@>u5<$cx?A{nj7`FjA3WYo48;amlaWXPQ`<7S2-#G9e;HA3Dcn*@K5y;J z4u7dyVXbm~On$A~J8CxjwauvZbFkub+|LvuW-mZ4YS(ckf!Sk&IsI4?Iez!$O_?>J-AOa^0)RZQlqytM;v#zp>^G10kk>!nP>7QWdNl(On z|E9U)EZv#9DayN3*TK6h&l(3W%No2DW`({}!bW z3gE0R!)X&Rr1YJUw)6wNRYzzj{jBH0Q_fP)RedT5@J3ViRj@yjuB7u4+vjhKrJS~T z#U1F5I6N9o^|)NHuBxhH(C@XW5R3Y1agr^N`rP0MCdA3D9zWv~^c81zVBdCT^$ZYg z89Hl65RF^FR@K}hv*7IH`BI;7rBeWTFATrm^