honeybee-energy 1.116.106__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. honeybee_energy/__init__.py +24 -0
  2. honeybee_energy/__main__.py +4 -0
  3. honeybee_energy/_extend_honeybee.py +145 -0
  4. honeybee_energy/altnumber.py +21 -0
  5. honeybee_energy/baseline/__init__.py +2 -0
  6. honeybee_energy/baseline/create.py +608 -0
  7. honeybee_energy/baseline/data/__init__.py +1 -0
  8. honeybee_energy/baseline/data/constructions.csv +64 -0
  9. honeybee_energy/baseline/data/fen_ratios.csv +15 -0
  10. honeybee_energy/baseline/data/lpd_building.csv +21 -0
  11. honeybee_energy/baseline/data/pci_2016.csv +22 -0
  12. honeybee_energy/baseline/data/pci_2019.csv +22 -0
  13. honeybee_energy/baseline/data/pci_2022.csv +22 -0
  14. honeybee_energy/baseline/data/shw.csv +21 -0
  15. honeybee_energy/baseline/pci.py +512 -0
  16. honeybee_energy/baseline/result.py +371 -0
  17. honeybee_energy/boundarycondition.py +128 -0
  18. honeybee_energy/cli/__init__.py +69 -0
  19. honeybee_energy/cli/baseline.py +475 -0
  20. honeybee_energy/cli/edit.py +327 -0
  21. honeybee_energy/cli/lib.py +1154 -0
  22. honeybee_energy/cli/result.py +810 -0
  23. honeybee_energy/cli/setconfig.py +124 -0
  24. honeybee_energy/cli/settings.py +569 -0
  25. honeybee_energy/cli/simulate.py +380 -0
  26. honeybee_energy/cli/translate.py +1714 -0
  27. honeybee_energy/cli/validate.py +224 -0
  28. honeybee_energy/config.json +11 -0
  29. honeybee_energy/config.py +842 -0
  30. honeybee_energy/construction/__init__.py +1 -0
  31. honeybee_energy/construction/_base.py +374 -0
  32. honeybee_energy/construction/air.py +325 -0
  33. honeybee_energy/construction/dictutil.py +89 -0
  34. honeybee_energy/construction/dynamic.py +607 -0
  35. honeybee_energy/construction/opaque.py +460 -0
  36. honeybee_energy/construction/shade.py +319 -0
  37. honeybee_energy/construction/window.py +1096 -0
  38. honeybee_energy/construction/windowshade.py +847 -0
  39. honeybee_energy/constructionset.py +1655 -0
  40. honeybee_energy/dictutil.py +56 -0
  41. honeybee_energy/generator/__init__.py +5 -0
  42. honeybee_energy/generator/loadcenter.py +204 -0
  43. honeybee_energy/generator/pv.py +535 -0
  44. honeybee_energy/hvac/__init__.py +21 -0
  45. honeybee_energy/hvac/_base.py +124 -0
  46. honeybee_energy/hvac/_template.py +270 -0
  47. honeybee_energy/hvac/allair/__init__.py +22 -0
  48. honeybee_energy/hvac/allair/_base.py +349 -0
  49. honeybee_energy/hvac/allair/furnace.py +168 -0
  50. honeybee_energy/hvac/allair/psz.py +131 -0
  51. honeybee_energy/hvac/allair/ptac.py +163 -0
  52. honeybee_energy/hvac/allair/pvav.py +109 -0
  53. honeybee_energy/hvac/allair/vav.py +128 -0
  54. honeybee_energy/hvac/detailed.py +337 -0
  55. honeybee_energy/hvac/doas/__init__.py +28 -0
  56. honeybee_energy/hvac/doas/_base.py +345 -0
  57. honeybee_energy/hvac/doas/fcu.py +127 -0
  58. honeybee_energy/hvac/doas/radiant.py +329 -0
  59. honeybee_energy/hvac/doas/vrf.py +81 -0
  60. honeybee_energy/hvac/doas/wshp.py +91 -0
  61. honeybee_energy/hvac/heatcool/__init__.py +23 -0
  62. honeybee_energy/hvac/heatcool/_base.py +177 -0
  63. honeybee_energy/hvac/heatcool/baseboard.py +61 -0
  64. honeybee_energy/hvac/heatcool/evapcool.py +72 -0
  65. honeybee_energy/hvac/heatcool/fcu.py +92 -0
  66. honeybee_energy/hvac/heatcool/gasunit.py +53 -0
  67. honeybee_energy/hvac/heatcool/radiant.py +269 -0
  68. honeybee_energy/hvac/heatcool/residential.py +77 -0
  69. honeybee_energy/hvac/heatcool/vrf.py +54 -0
  70. honeybee_energy/hvac/heatcool/windowac.py +70 -0
  71. honeybee_energy/hvac/heatcool/wshp.py +62 -0
  72. honeybee_energy/hvac/idealair.py +699 -0
  73. honeybee_energy/internalmass.py +310 -0
  74. honeybee_energy/lib/__init__.py +1 -0
  75. honeybee_energy/lib/_loadconstructions.py +194 -0
  76. honeybee_energy/lib/_loadconstructionsets.py +117 -0
  77. honeybee_energy/lib/_loadmaterials.py +83 -0
  78. honeybee_energy/lib/_loadprogramtypes.py +125 -0
  79. honeybee_energy/lib/_loadschedules.py +87 -0
  80. honeybee_energy/lib/_loadtypelimits.py +64 -0
  81. honeybee_energy/lib/constructions.py +207 -0
  82. honeybee_energy/lib/constructionsets.py +95 -0
  83. honeybee_energy/lib/materials.py +67 -0
  84. honeybee_energy/lib/programtypes.py +125 -0
  85. honeybee_energy/lib/schedules.py +61 -0
  86. honeybee_energy/lib/scheduletypelimits.py +31 -0
  87. honeybee_energy/load/__init__.py +1 -0
  88. honeybee_energy/load/_base.py +190 -0
  89. honeybee_energy/load/daylight.py +397 -0
  90. honeybee_energy/load/dictutil.py +47 -0
  91. honeybee_energy/load/equipment.py +771 -0
  92. honeybee_energy/load/hotwater.py +543 -0
  93. honeybee_energy/load/infiltration.py +460 -0
  94. honeybee_energy/load/lighting.py +480 -0
  95. honeybee_energy/load/people.py +497 -0
  96. honeybee_energy/load/process.py +472 -0
  97. honeybee_energy/load/setpoint.py +816 -0
  98. honeybee_energy/load/ventilation.py +550 -0
  99. honeybee_energy/material/__init__.py +1 -0
  100. honeybee_energy/material/_base.py +166 -0
  101. honeybee_energy/material/dictutil.py +59 -0
  102. honeybee_energy/material/frame.py +367 -0
  103. honeybee_energy/material/gas.py +1087 -0
  104. honeybee_energy/material/glazing.py +854 -0
  105. honeybee_energy/material/opaque.py +1351 -0
  106. honeybee_energy/material/shade.py +1360 -0
  107. honeybee_energy/measure.py +472 -0
  108. honeybee_energy/programtype.py +723 -0
  109. honeybee_energy/properties/__init__.py +1 -0
  110. honeybee_energy/properties/aperture.py +333 -0
  111. honeybee_energy/properties/door.py +342 -0
  112. honeybee_energy/properties/extension.py +244 -0
  113. honeybee_energy/properties/face.py +274 -0
  114. honeybee_energy/properties/model.py +2640 -0
  115. honeybee_energy/properties/room.py +1747 -0
  116. honeybee_energy/properties/shade.py +314 -0
  117. honeybee_energy/properties/shademesh.py +262 -0
  118. honeybee_energy/reader.py +48 -0
  119. honeybee_energy/result/__init__.py +1 -0
  120. honeybee_energy/result/colorobj.py +648 -0
  121. honeybee_energy/result/emissions.py +290 -0
  122. honeybee_energy/result/err.py +101 -0
  123. honeybee_energy/result/eui.py +100 -0
  124. honeybee_energy/result/generation.py +160 -0
  125. honeybee_energy/result/loadbalance.py +890 -0
  126. honeybee_energy/result/match.py +202 -0
  127. honeybee_energy/result/osw.py +90 -0
  128. honeybee_energy/result/rdd.py +59 -0
  129. honeybee_energy/result/zsz.py +190 -0
  130. honeybee_energy/run.py +1577 -0
  131. honeybee_energy/schedule/__init__.py +1 -0
  132. honeybee_energy/schedule/day.py +626 -0
  133. honeybee_energy/schedule/dictutil.py +59 -0
  134. honeybee_energy/schedule/fixedinterval.py +1012 -0
  135. honeybee_energy/schedule/rule.py +619 -0
  136. honeybee_energy/schedule/ruleset.py +1867 -0
  137. honeybee_energy/schedule/typelimit.py +310 -0
  138. honeybee_energy/shw.py +315 -0
  139. honeybee_energy/simulation/__init__.py +1 -0
  140. honeybee_energy/simulation/control.py +214 -0
  141. honeybee_energy/simulation/daylightsaving.py +185 -0
  142. honeybee_energy/simulation/dictutil.py +51 -0
  143. honeybee_energy/simulation/output.py +646 -0
  144. honeybee_energy/simulation/parameter.py +606 -0
  145. honeybee_energy/simulation/runperiod.py +443 -0
  146. honeybee_energy/simulation/shadowcalculation.py +295 -0
  147. honeybee_energy/simulation/sizing.py +546 -0
  148. honeybee_energy/ventcool/__init__.py +5 -0
  149. honeybee_energy/ventcool/_crack_data.py +91 -0
  150. honeybee_energy/ventcool/afn.py +289 -0
  151. honeybee_energy/ventcool/control.py +269 -0
  152. honeybee_energy/ventcool/crack.py +126 -0
  153. honeybee_energy/ventcool/fan.py +493 -0
  154. honeybee_energy/ventcool/opening.py +365 -0
  155. honeybee_energy/ventcool/simulation.py +314 -0
  156. honeybee_energy/writer.py +1078 -0
  157. honeybee_energy-1.116.106.dist-info/METADATA +113 -0
  158. honeybee_energy-1.116.106.dist-info/RECORD +162 -0
  159. honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
  160. honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
  161. honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
  162. honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1714 @@
1
+ """honeybee energy translation commands."""
2
+ import click
3
+ import sys
4
+ import os
5
+ import logging
6
+ import json
7
+ import tempfile
8
+
9
+ from ladybug.commandutil import process_content_to_output
10
+ from ladybug.analysisperiod import AnalysisPeriod
11
+ from ladybug.epw import EPW
12
+ from ladybug.stat import STAT
13
+ from ladybug.futil import preparedir
14
+ from honeybee.model import Model
15
+ from honeybee.typing import clean_rad_string
16
+ from honeybee.config import folders as hb_folders
17
+
18
+ from honeybee_energy.simulation.parameter import SimulationParameter
19
+ from honeybee_energy.construction.dictutil import dict_to_construction
20
+ from honeybee_energy.construction.opaque import OpaqueConstruction
21
+ from honeybee_energy.construction.window import WindowConstruction
22
+ from honeybee_energy.schedule.dictutil import dict_to_schedule
23
+ from honeybee_energy.schedule.ruleset import ScheduleRuleset
24
+ from honeybee_energy.run import to_openstudio_sim_folder, run_osw, from_osm_osw, \
25
+ _parse_os_cli_failure, HB_OS_MSG
26
+ from honeybee_energy.writer import energyplus_idf_version, _preprocess_model_for_trace
27
+ from honeybee_energy.config import folders
28
+
29
+ _logger = logging.getLogger(__name__)
30
+
31
+
32
+ @click.group(help='Commands for translating Honeybee Models files.')
33
+ def translate():
34
+ pass
35
+
36
+
37
+ @translate.command('model-to-sim-folder')
38
+ @click.argument('model-file', type=click.Path(
39
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
40
+ @click.argument('epw-file', type=click.Path(
41
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
42
+ @click.option('--sim-par-json', '-sp', help='Full path to a honeybee energy '
43
+ 'SimulationParameter JSON that describes all of the settings for '
44
+ 'the simulation. This will be ignored if the input model-file is '
45
+ 'an OSM or IDF.', default=None, show_default=True,
46
+ type=click.Path(exists=False, file_okay=True, dir_okay=False,
47
+ resolve_path=True))
48
+ @click.option('--measures', '-m', help='Full path to a folder containing an OSW JSON '
49
+ 'be used as the base for the execution of the OpenStudio CLI. While this '
50
+ 'OSW can contain paths to measures that exist anywhere on the machine, '
51
+ 'the best practice is to copy the measures into this measures '
52
+ 'folder and use relative paths within the OSW. '
53
+ 'This makes it easier to move the inputs for this command from one '
54
+ 'machine to another.', default=None, show_default=True,
55
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
56
+ @click.option('--additional-string', '-as', help='An additional IDF text string to get '
57
+ 'appended to the IDF before simulation. The input should include '
58
+ 'complete EnergyPlus objects as a single string following the IDF '
59
+ 'format. This input can be used to include small EnergyPlus objects that '
60
+ 'are not currently supported by honeybee.', default=None, type=str)
61
+ @click.option('--additional-idf', '-ai', help='An IDF file with text to be '
62
+ 'appended before simulation. This input can be used to include '
63
+ 'large EnergyPlus objects that are not currently supported by honeybee.',
64
+ default=None, show_default=True,
65
+ type=click.Path(exists=False, file_okay=True, dir_okay=False,
66
+ resolve_path=True))
67
+ @click.option('--folder', '-f', help='Folder on this computer, into which the IDF '
68
+ 'and result files will be written. If None, the files will be output '
69
+ 'to the honeybee default simulation folder and placed in a project '
70
+ 'folder with the same name as the model-file.',
71
+ default=None, show_default=True,
72
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
73
+ @click.option('--log-file', '-log', help='Optional log file to output the paths of the '
74
+ 'generated files (osw, osm, idf) if successfully'
75
+ ' created. By default the list will be printed out to stdout',
76
+ type=click.File('w'), default='-', show_default=True)
77
+ def model_to_sim_folder(
78
+ model_file, epw_file, sim_par_json, measures, additional_string, additional_idf,
79
+ folder, log_file
80
+ ):
81
+ """Simulate a Model in EnergyPlus.
82
+
83
+ \b
84
+ Args:
85
+ model_file: Full path to a Model file as a HBJSON or HBPkl.
86
+ epw_file: Full path to an .epw file.
87
+ """
88
+ try:
89
+ # get a ddy variable that might get used later
90
+ epw_folder, epw_file_name = os.path.split(epw_file)
91
+ ddy_file = os.path.join(epw_folder, epw_file_name.replace('.epw', '.ddy'))
92
+ stat_file = os.path.join(epw_folder, epw_file_name.replace('.epw', '.stat'))
93
+
94
+ # sense what type of file has been input
95
+ proj_name = os.path.basename(model_file).lower()
96
+
97
+ # set the default folder to the default if it's not specified
98
+ if folder is None:
99
+ for ext in ('.hbjson', '.json', '.hbpkl', '.pkl'):
100
+ proj_name = proj_name.replace(ext, '')
101
+ folder = os.path.join(folders.default_simulation_folder, proj_name)
102
+ folder = os.path.join(folder, 'openstudio')
103
+ preparedir(folder, remove_content=False)
104
+
105
+ # process the simulation parameters and write new ones if necessary
106
+ def ddy_from_epw(epw_file, sim_par):
107
+ """Produce a DDY from an EPW file."""
108
+ epw_obj = EPW(epw_file)
109
+ des_days = [epw_obj.approximate_design_day('WinterDesignDay'),
110
+ epw_obj.approximate_design_day('SummerDesignDay')]
111
+ sim_par.sizing_parameter.design_days = des_days
112
+
113
+ if sim_par_json is None or not os.path.isfile(sim_par_json):
114
+ sim_par = SimulationParameter()
115
+ sim_par.output.add_zone_energy_use()
116
+ sim_par.output.add_hvac_energy_use()
117
+ sim_par.output.add_electricity_generation()
118
+ sim_par.output.reporting_frequency = 'Monthly'
119
+ else:
120
+ with open(sim_par_json) as json_file:
121
+ data = json.load(json_file)
122
+ sim_par = SimulationParameter.from_dict(data)
123
+ if len(sim_par.sizing_parameter.design_days) == 0 and \
124
+ os.path.isfile(ddy_file):
125
+ try:
126
+ sim_par.sizing_parameter.add_from_ddy_996_004(ddy_file)
127
+ except AssertionError: # no design days within the DDY file
128
+ ddy_from_epw(epw_file, sim_par)
129
+ elif len(sim_par.sizing_parameter.design_days) == 0:
130
+ ddy_from_epw(epw_file, sim_par)
131
+ if sim_par.sizing_parameter.climate_zone is None and \
132
+ os.path.isfile(stat_file):
133
+ stat_obj = STAT(stat_file)
134
+ sim_par.sizing_parameter.climate_zone = stat_obj.ashrae_climate_zone
135
+
136
+ # process the measures input if it is specified
137
+ base_osw = None
138
+ if measures is not None and measures != '' and os.path.isdir(measures):
139
+ for f_name in os.listdir(measures):
140
+ if f_name.lower().endswith('.osw'):
141
+ base_osw = os.path.join(measures, f_name)
142
+ # write the path of the measures folder into the OSW
143
+ with open(base_osw) as json_file:
144
+ osw_dict = json.load(json_file)
145
+ osw_dict['measure_paths'] = [os.path.abspath(measures)]
146
+ with open(base_osw, 'w') as fp:
147
+ json.dump(osw_dict, fp)
148
+ break
149
+
150
+ # Write the osw file to translate the model to osm
151
+ strings_to_inject = additional_string if additional_string is not None else ''
152
+ if additional_idf is not None and os.path.isfile(additional_idf):
153
+ with open(additional_idf, "r") as add_idf_file:
154
+ strings_to_inject = strings_to_inject + '\n' + add_idf_file.read()
155
+
156
+ # run the Model re-serialization and convert to OSM, OSW, and IDF
157
+ osm, osw, idf = None, None, None
158
+ model = Model.from_file(model_file)
159
+ osm, osw, idf = to_openstudio_sim_folder(
160
+ model, folder, epw_file=epw_file, sim_par=sim_par, enforce_rooms=True,
161
+ base_osw=base_osw, strings_to_inject=strings_to_inject,
162
+ print_progress=True)
163
+ gen_files = [osm]
164
+ if osw is not None:
165
+ gen_files.append(osw)
166
+ if idf is not None:
167
+ gen_files.append(idf)
168
+
169
+ log_file.write(json.dumps(gen_files, indent=4))
170
+ except Exception as e:
171
+ _logger.exception('Model simulation failed.\n{}'.format(e))
172
+ sys.exit(1)
173
+ else:
174
+ sys.exit(0)
175
+
176
+
177
+ @translate.command('model-to-osm')
178
+ @click.argument('model-file', type=click.Path(
179
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
180
+ @click.option('--sim-par-json', '-sp', help='Full path to a honeybee energy '
181
+ 'SimulationParameter JSON that describes all of the settings for '
182
+ 'the simulation. If None default parameters will be generated.',
183
+ default=None, show_default=True,
184
+ type=click.Path(exists=True, file_okay=True, dir_okay=False,
185
+ resolve_path=True))
186
+ @click.option('--epw-file', '-epw', help='Full path to an EPW file to be associated '
187
+ 'with the exported OSM. This is typically not necessary but may be '
188
+ 'used when a sim-par-json is specified that requests a HVAC sizing '
189
+ 'calculation to be run as part of the translation process but no design '
190
+ 'days are inside this simulation parameter.',
191
+ default=None, show_default=True,
192
+ type=click.Path(exists=True, file_okay=True, dir_okay=False,
193
+ resolve_path=True))
194
+ @click.option('--folder', '-f', help='Deprecated input that is no longer used.',
195
+ default=None, show_default=True,
196
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
197
+ @click.option('--osm-file', '-osm', help='Optional path where the OSM will be copied '
198
+ 'after it is translated in the folder. If None, the file will not '
199
+ 'be copied.', type=str, default=None, show_default=True)
200
+ @click.option('--idf-file', '-idf', help='Optional path where the IDF will be copied '
201
+ 'after it is translated in the folder. If None, the file will not '
202
+ 'be copied.', type=str, default=None, show_default=True)
203
+ @click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag to note whether a '
204
+ 'cleaned version of all geometry display names should be used instead '
205
+ 'of identifiers when translating the Model to OSM and IDF. '
206
+ 'Using this flag will affect all Rooms, Faces, Apertures, '
207
+ 'Doors, and Shades. It will generally result in more read-able names '
208
+ 'in the OSM and IDF but this means that it will not be easy to map '
209
+ 'the EnergyPlus results back to the original Honeybee Model. Cases '
210
+ 'of duplicate IDs resulting from non-unique names will be resolved '
211
+ 'by adding integers to the ends of the new IDs that are derived from '
212
+ 'the name.', default=True, show_default=True)
213
+ @click.option('--resource-ids/--resource-names', ' /-rn', help='Flag to note whether a '
214
+ 'cleaned version of all resource display names should be used instead '
215
+ 'of identifiers when translating the Model to OSM and IDF. '
216
+ 'Using this flag will affect all Materials, Constructions, '
217
+ 'ConstructionSets, Schedules, Loads, and ProgramTypes. It will generally '
218
+ 'result in more read-able names for the resources in the OSM and IDF. '
219
+ 'Cases of duplicate IDs resulting from non-unique names will be resolved '
220
+ 'by adding integers to the ends of the new IDs that are derived from '
221
+ 'the name.', default=True, show_default=True)
222
+ @click.option('--log-file', '-log', help='Optional log file to output the paths to the '
223
+ 'generated OSM and IDF files if they were successfully created. '
224
+ 'By default this will be printed out to stdout.',
225
+ type=click.File('w'), default='-', show_default=True)
226
+ def model_to_osm_cli(
227
+ model_file, sim_par_json, epw_file, folder, osm_file, idf_file,
228
+ geometry_ids, resource_ids, log_file):
229
+ """Translate a Honeybee Model file into an OpenStudio Model and corresponding IDF.
230
+
231
+ \b
232
+ Args:
233
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
234
+ """
235
+ try:
236
+ geo_names = not geometry_ids
237
+ res_names = not resource_ids
238
+ model_to_osm(
239
+ model_file, sim_par_json, epw_file, folder, osm_file, idf_file,
240
+ geo_names, res_names, log_file)
241
+ except Exception as e:
242
+ _logger.exception('Model translation failed.\n{}'.format(e))
243
+ sys.exit(1)
244
+ else:
245
+ sys.exit(0)
246
+
247
+
248
+ def model_to_osm(
249
+ model_file, sim_par_json=None, epw_file=None, folder=None,
250
+ osm_file=None, idf_file=None, geometry_names=False, resource_names=False,
251
+ log_file=None, geometry_ids=True, resource_ids=True
252
+ ):
253
+ """Translate a Honeybee Model file into an OpenStudio Model and corresponding IDF.
254
+
255
+ Args:
256
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
257
+ sim_par_json: Full path to a honeybee energy SimulationParameter JSON that
258
+ describes all of the settings for the simulation. If None, default
259
+ parameters will be generated.
260
+ epw_file: Full path to an EPW file to be associated with the exported OSM.
261
+ This is typically not necessary but may be used when a sim-par-json is
262
+ specified that requests a HVAC sizing calculation to be run as part
263
+ of the translation process but no design days are inside this
264
+ simulation parameter.
265
+ folder: Deprecated input that is no longer used.
266
+ osm_file: Optional path where the OSM will be output.
267
+ idf_file: Optional path where the IDF will be output.
268
+ geometry_names: Boolean to note whether a cleaned version of all geometry
269
+ display names should be used instead of identifiers when translating
270
+ the Model to OSM and IDF. Using this flag will affect all Rooms, Faces,
271
+ Apertures, Doors, and Shades. It will generally result in more read-able
272
+ names in the OSM and IDF but this means that it will not be easy to map
273
+ the EnergyPlus results back to the original Honeybee Model. Cases
274
+ of duplicate IDs resulting from non-unique names will be resolved
275
+ by adding integers to the ends of the new IDs that are derived from
276
+ the name. (Default: False).
277
+ resource_names: Boolean to note whether a cleaned version of all resource
278
+ display names should be used instead of identifiers when translating
279
+ the Model to OSM and IDF. Using this flag will affect all Materials,
280
+ Constructions, ConstructionSets, Schedules, Loads, and ProgramTypes.
281
+ It will generally result in more read-able names for the resources
282
+ in the OSM and IDF. Cases of duplicate IDs resulting from non-unique
283
+ names will be resolved by adding integers to the ends of the new IDs
284
+ that are derived from the name. (Default: False).
285
+ log_file: Optional log file to output the paths to the generated OSM and]
286
+ IDF files if they were successfully created. By default this string
287
+ will be returned from this method.
288
+ """
289
+ # check that honeybee-openstudio is installed
290
+ try:
291
+ from honeybee_openstudio.openstudio import openstudio, OSModel
292
+ from honeybee_openstudio.simulation import simulation_parameter_to_openstudio, \
293
+ assign_epw_to_model
294
+ from honeybee_openstudio.writer import model_to_openstudio
295
+ except ImportError as e: # honeybee-openstudio is not installed
296
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
297
+ if folder is not None:
298
+ print('--folder is deprecated and no longer used.')
299
+
300
+ # initialize the OpenStudio model that will hold everything
301
+ os_model = OSModel()
302
+ # generate default simulation parameters
303
+ if sim_par_json is None:
304
+ sim_par = SimulationParameter()
305
+ sim_par.output.add_zone_energy_use()
306
+ sim_par.output.add_hvac_energy_use()
307
+ sim_par.output.add_electricity_generation()
308
+ sim_par.output.reporting_frequency = 'Monthly'
309
+ else:
310
+ with open(sim_par_json) as json_file:
311
+ data = json.load(json_file)
312
+ sim_par = SimulationParameter.from_dict(data)
313
+
314
+ # use any specified EPW files to assign design days and the climate zone
315
+ def ddy_from_epw(epw_file, sim_par):
316
+ """Produce a DDY from an EPW file."""
317
+ epw_obj = EPW(epw_file)
318
+ des_days = [epw_obj.approximate_design_day('WinterDesignDay'),
319
+ epw_obj.approximate_design_day('SummerDesignDay')]
320
+ sim_par.sizing_parameter.design_days = des_days
321
+
322
+ if epw_file is not None:
323
+ epw_folder, epw_file_name = os.path.split(epw_file)
324
+ ddy_file = os.path.join(epw_folder, epw_file_name.replace('.epw', '.ddy'))
325
+ stat_file = os.path.join(epw_folder, epw_file_name.replace('.epw', '.stat'))
326
+ if len(sim_par.sizing_parameter.design_days) == 0 and \
327
+ os.path.isfile(ddy_file):
328
+ try:
329
+ sim_par.sizing_parameter.add_from_ddy_996_004(ddy_file)
330
+ except AssertionError: # no design days within the DDY file
331
+ ddy_from_epw(epw_file, sim_par)
332
+ elif len(sim_par.sizing_parameter.design_days) == 0:
333
+ ddy_from_epw(epw_file, sim_par)
334
+ if sim_par.sizing_parameter.climate_zone is None and os.path.isfile(stat_file):
335
+ stat_obj = STAT(stat_file)
336
+ sim_par.sizing_parameter.climate_zone = stat_obj.ashrae_climate_zone
337
+ set_cz = True if sim_par.sizing_parameter.climate_zone is None else False
338
+ assign_epw_to_model(epw_file, os_model, set_cz)
339
+
340
+ # translate the simulation parameter and model to an OpenStudio Model
341
+ simulation_parameter_to_openstudio(sim_par, os_model)
342
+ model = Model.from_file(model_file)
343
+ os_model = model_to_openstudio(
344
+ model, os_model, use_geometry_names=geometry_names,
345
+ use_resource_names=resource_names, print_progress=True)
346
+ gen_files = []
347
+
348
+ # write the OpenStudio Model if specified
349
+ if osm_file is not None:
350
+ osm = os.path.abspath(osm_file)
351
+ os_model.save(osm, overwrite=True)
352
+ gen_files.append(osm)
353
+
354
+ # write the IDF if specified
355
+ if idf_file is not None:
356
+ idf = os.path.abspath(idf_file)
357
+ idf_translator = openstudio.energyplus.ForwardTranslator()
358
+ workspace = idf_translator.translateModel(os_model)
359
+ workspace.save(idf, overwrite=True)
360
+ gen_files.append(idf)
361
+
362
+ return process_content_to_output(json.dumps(gen_files, indent=4), log_file)
363
+
364
+
365
+ @translate.command('model-to-idf')
366
+ @click.argument('model-file', type=click.Path(
367
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
368
+ @click.option('--sim-par-json', '-sp', help='Full path to a honeybee energy '
369
+ 'SimulationParameter JSON that describes all of the settings for the '
370
+ 'simulation. If None default parameters will be generated.',
371
+ default=None, show_default=True,
372
+ type=click.Path(exists=True, file_okay=True, dir_okay=False,
373
+ resolve_path=True))
374
+ @click.option('--additional-str', '-a', help='Text string for additional lines that '
375
+ 'should be added to the IDF.', type=str, default='', show_default=True)
376
+ @click.option('--compact-schedules/--csv-schedules', ' /-c', help='Flag to note '
377
+ 'whether any ScheduleFixedIntervals in the model should be included '
378
+ 'in the IDF string as a Schedule:Compact or they should be written as '
379
+ 'CSV Schedule:File and placed in a directory next to the output-file.',
380
+ default=True, show_default=True)
381
+ @click.option('--hvac-to-ideal-air/--hvac-check', ' /-h', help='Flag to note '
382
+ 'whether any detailed HVAC system templates should be converted to '
383
+ 'an equivalent IdealAirSystem upon export. If hvac-check is used'
384
+ 'and the Model contains detailed systems, a ValueError will '
385
+ 'be raised.', default=True, show_default=True)
386
+ @click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag to note whether a '
387
+ 'cleaned version of all geometry display names should be used instead '
388
+ 'of identifiers when translating the Model to IDF. Using this flag will '
389
+ 'affect all Rooms, Faces, Apertures, Doors, and Shades. It will '
390
+ 'generally result in more read-able names in the IDF but this means that '
391
+ 'it will not be easy to map the EnergyPlus results back to the original '
392
+ 'Honeybee Model. Cases of duplicate IDs resulting from non-unique names '
393
+ 'will be resolved by adding integers to the ends of the new IDs that are '
394
+ 'derived from the name.', default=True, show_default=True)
395
+ @click.option('--resource-ids/--resource-names', ' /-rn', help='Flag to note whether a '
396
+ 'cleaned version of all resource display names should be used instead '
397
+ 'of identifiers when translating the Model to IDF. Using this flag will '
398
+ 'affect all Materials, Constructions, ConstructionSets, Schedules, '
399
+ 'Loads, and ProgramTypes. It will generally result in more read-able '
400
+ 'names for the resources in the IDF. Cases of duplicate IDs resulting '
401
+ 'from non-unique names will be resolved by adding integers to the ends '
402
+ 'of the new IDs that are derived from the name.',
403
+ default=True, show_default=True)
404
+ @click.option('--output-file', '-f', help='Optional IDF file to output the IDF string '
405
+ 'of the translation. By default this will be printed out to stdout',
406
+ type=click.File('w'), default='-', show_default=True)
407
+ def model_to_idf_cli(model_file, sim_par_json, additional_str, compact_schedules,
408
+ hvac_to_ideal_air, geometry_ids, resource_ids, output_file):
409
+ """Translate a Model (HBJSON) file to a simplified IDF using direct-to-idf methods.
410
+
411
+ The direct-to-idf methods are faster than those that translate the model
412
+ to OSM but certain features like detailed HVAC systems and the Airflow Network
413
+ are not supported.
414
+
415
+ Args:
416
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
417
+ """
418
+ try:
419
+ csv_schedules = not compact_schedules
420
+ hvac_check = not hvac_to_ideal_air
421
+ geo_names = not geometry_ids
422
+ res_names = not resource_ids
423
+ model_to_idf(
424
+ model_file, sim_par_json, additional_str, csv_schedules,
425
+ hvac_check, geo_names, res_names, output_file)
426
+ except Exception as e:
427
+ _logger.exception('Model translation failed.\n{}'.format(e))
428
+ sys.exit(1)
429
+ else:
430
+ sys.exit(0)
431
+
432
+
433
+ def model_to_idf(
434
+ model_file, sim_par_json=None, additional_str='', csv_schedules=False,
435
+ hvac_check=False, geometry_names=False, resource_names=False, output_file=None,
436
+ compact_schedules=True, hvac_to_ideal_air=True, geometry_ids=True, resource_ids=True
437
+ ):
438
+ """Translate a Honeybee Model file to a simplified IDF using direct-to-idf methods.
439
+
440
+ The direct-to-idf methods are faster than those that translate the model
441
+ to OSM but certain features like detailed HVAC systems and the Airflow Network
442
+ are not supported.
443
+
444
+ Args:
445
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
446
+ sim_par_json: Full path to a honeybee energy SimulationParameter JSON that
447
+ describes all of the settings for the simulation. If None, default
448
+ parameters will be generated.
449
+ additional_str: Text string for additional lines that should be added
450
+ to the IDF.
451
+ csv_schedules: Boolean to note whether any ScheduleFixedIntervals in the
452
+ model should be included in the IDF string as a Schedule:Compact or
453
+ they should be written as CSV Schedule:File and placed in a directory
454
+ next to the output_file. (Default: False).
455
+ hvac_check: Boolean to note whether any detailed HVAC system templates
456
+ should be converted to an equivalent IdealAirSystem upon export.
457
+ If hvac-check is used and the Model contains detailed systems, a
458
+ ValueError will be raised. (Default: False).
459
+ geometry_names: Boolean to note whether a cleaned version of all geometry
460
+ display names should be used instead of identifiers when translating
461
+ the Model to OSM and IDF. Using this flag will affect all Rooms, Faces,
462
+ Apertures, Doors, and Shades. It will generally result in more read-able
463
+ names in the OSM and IDF but this means that it will not be easy to map
464
+ the EnergyPlus results back to the original Honeybee Model. Cases
465
+ of duplicate IDs resulting from non-unique names will be resolved
466
+ by adding integers to the ends of the new IDs that are derived from
467
+ the name. (Default: False).
468
+ resource_names: Boolean to note whether a cleaned version of all resource
469
+ display names should be used instead of identifiers when translating
470
+ the Model to OSM and IDF. Using this flag will affect all Materials,
471
+ Constructions, ConstructionSets, Schedules, Loads, and ProgramTypes.
472
+ It will generally result in more read-able names for the resources
473
+ in the OSM and IDF. Cases of duplicate IDs resulting from non-unique
474
+ names will be resolved by adding integers to the ends of the new IDs
475
+ that are derived from the name. (Default: False).
476
+ output_file: Optional IDF file to output the IDF string of the translation.
477
+ By default this string will be returned from this method.
478
+ """
479
+ # load simulation parameters or generate default ones
480
+ if sim_par_json is not None:
481
+ with open(sim_par_json) as json_file:
482
+ data = json.load(json_file)
483
+ sim_par = SimulationParameter.from_dict(data)
484
+ else:
485
+ sim_par = SimulationParameter()
486
+ sim_par.output.add_zone_energy_use()
487
+ sim_par.output.add_hvac_energy_use()
488
+ sim_par.output.add_electricity_generation()
489
+ sim_par.output.reporting_frequency = 'Monthly'
490
+
491
+ # re-serialize the Model to Python
492
+ model = Model.from_file(model_file)
493
+
494
+ # reset the IDs to be derived from the display_names if requested
495
+ if geometry_names:
496
+ id_map = model.reset_ids()
497
+ model.properties.energy.sync_detailed_hvac_ids(id_map['rooms'])
498
+ if resource_names:
499
+ model.properties.energy.reset_resource_ids()
500
+
501
+ # set the schedule directory in case it is needed
502
+ sch_directory = None
503
+ if csv_schedules:
504
+ sch_path = os.path.abspath(model_file) \
505
+ if output_file is not None and 'stdout' in str(output_file) \
506
+ else os.path.abspath(str(output_file))
507
+ sch_directory = os.path.join(os.path.split(sch_path)[0], 'schedules')
508
+
509
+ # create the strings for simulation parameters and model
510
+ ver_str = energyplus_idf_version() if folders.energyplus_version \
511
+ is not None else ''
512
+ sim_par_str = sim_par.to_idf()
513
+ hvac_to_ideal = not hvac_check
514
+ model_str = model.to.idf(
515
+ model, schedule_directory=sch_directory,
516
+ use_ideal_air_equivalent=hvac_to_ideal)
517
+ idf_str = '\n\n'.join([ver_str, sim_par_str, model_str, additional_str])
518
+
519
+ # write out the IDF file
520
+ return process_content_to_output(idf_str, output_file)
521
+
522
+
523
+ @translate.command('model-to-gbxml')
524
+ @click.argument('model-file', type=click.Path(
525
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
526
+ @click.option('--osw-folder', '-osw',
527
+ help='Deprecated input that is no longer used.', default=None,
528
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
529
+ @click.option('--default-subfaces/--triangulate-subfaces', ' /-t',
530
+ help='Flag to note whether sub-faces (including Apertures and Doors) '
531
+ 'should be triangulated if they have more than 4 sides (True) or whether '
532
+ 'they should be left as they are (False). This triangulation is '
533
+ 'necessary when exporting directly to EnergyPlus since it cannot accept '
534
+ 'sub-faces with more than 4 vertices.', default=True, show_default=True)
535
+ @click.option('--triangulate-non-planar/--permit-non-planar', ' /-np',
536
+ help='Flag to note whether any non-planar orphaned geometry in the '
537
+ 'model should be triangulated upon export. This can be helpful because '
538
+ 'OpenStudio simply raises an error when it encounters non-planar '
539
+ 'geometry, which would hinder the ability to save gbXML files that are '
540
+ 'to be corrected in other software.', default=True, show_default=True)
541
+ @click.option('--minimal/--full-geometry', ' /-fg', help='Flag to note whether space '
542
+ 'boundaries and shell geometry should be included in the exported '
543
+ 'gbXML vs. just the minimal required non-manifold geometry.',
544
+ default=True, show_default=True)
545
+ @click.option('--interior-face-type', '-ift', help='Text string for the type to be '
546
+ 'used for all interior floor faces. If unspecified, the interior types '
547
+ 'will be left as they are. Choose from: InteriorFloor, Ceiling.',
548
+ type=str, default='', show_default=True)
549
+ @click.option('--ground-face-type', '-gft', help='Text string for the type to be '
550
+ 'used for all ground-contact floor faces. If unspecified, the ground '
551
+ 'types will be left as they are. Choose from: UndergroundSlab, '
552
+ 'SlabOnGrade, RaisedFloor.', type=str, default='', show_default=True)
553
+ @click.option('--program-name', '-p', help='Optional text to set the name of the '
554
+ 'software that will appear under the programId and ProductName tags '
555
+ 'of the DocumentHistory section. This can be set things like "Ladybug '
556
+ 'Tools" or "Pollination" or some other software in which this gbXML '
557
+ 'export capability is being run. If None, "OpenStudio" will be used.',
558
+ type=str, default=None, show_default=True)
559
+ @click.option('--program-version', '-v', help='Optional text to set the version of '
560
+ 'the software that will appear under the DocumentHistory section. '
561
+ 'If None, and the program_name is also unspecified, only the version '
562
+ 'of OpenStudio will appear. Otherwise, this will default to "0.0.0" '
563
+ 'given that the version field is required.',
564
+ type=str, default=None, show_default=True)
565
+ @click.option('--output-file', '-f', help='Optional gbXML file to output the string '
566
+ 'of the translation. By default it printed out to stdout',
567
+ type=click.File('w'), default='-', show_default=True)
568
+ def model_to_gbxml_cli(
569
+ model_file, osw_folder, default_subfaces, triangulate_non_planar, minimal,
570
+ interior_face_type, ground_face_type, program_name, program_version, output_file):
571
+ """Translate a Honeybee Model (HBJSON) to a gbXML file.
572
+
573
+ \b
574
+ Args:
575
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
576
+ """
577
+ try:
578
+ triangulate_subfaces = not default_subfaces
579
+ permit_non_planar = not triangulate_non_planar
580
+ full_geometry = not minimal
581
+ model_to_gbxml(
582
+ model_file, osw_folder, triangulate_subfaces, permit_non_planar,
583
+ full_geometry, interior_face_type, ground_face_type,
584
+ program_name, program_version, output_file)
585
+ except Exception as e:
586
+ _logger.exception('Model translation failed.\n{}'.format(e))
587
+ sys.exit(1)
588
+ else:
589
+ sys.exit(0)
590
+
591
+
592
+ def model_to_gbxml(
593
+ model_file, osw_folder=None, triangulate_subfaces=False,
594
+ permit_non_planar=False, full_geometry=False,
595
+ interior_face_type='', ground_face_type='',
596
+ program_name=None, program_version=None, output_file=None,
597
+ default_subfaces=True, triangulate_non_planar=True, minimal=True
598
+ ):
599
+ """Translate a Honeybee Model file to a gbXML file.
600
+
601
+ Args:
602
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
603
+ osw_folder: Deprecated input that is no longer used.
604
+ triangulate_subfaces: Boolean to note whether sub-faces (including
605
+ Apertures and Doors) should be triangulated if they have more
606
+ than 4 sides (True) or whether they should be left as they are (False).
607
+ This triangulation is necessary when exporting directly to EnergyPlus
608
+ since it cannot accept sub-faces with more than 4 vertices. (Default: False).
609
+ permit_non_planar: Boolean to note whether any non-planar orphaned geometry
610
+ in the model should be triangulated upon export. This can be helpful
611
+ because OpenStudio simply raises an error when it encounters non-planar
612
+ geometry, which would hinder the ability to save gbXML files that are
613
+ to be corrected in other software. (Default: False).
614
+ full_geometry: Boolean to note whether space boundaries and shell geometry
615
+ should be included in the exported gbXML vs. just the minimal required
616
+ non-manifold geometry. (Default: False).
617
+ interior_face_type: Text string for the type to be used for all interior
618
+ floor faces. If unspecified, the interior types will be left as they are.
619
+ Choose from: InteriorFloor, Ceiling.
620
+ ground_face_type: Text string for the type to be used for all ground-contact
621
+ floor faces. If unspecified, the ground types will be left as they are.
622
+ Choose from: UndergroundSlab, SlabOnGrade, RaisedFloor.
623
+ program_name: Optional text to set the name of the software that will
624
+ appear under the programId and ProductName tags of the DocumentHistory
625
+ section. This can be set things like "Ladybug Tools" or "Pollination"
626
+ or some other software in which this gbXML export capability is being
627
+ run. If None, the "OpenStudio" will be used. (Default: None).
628
+ program_version: Optional text to set the version of the software that
629
+ will appear under the DocumentHistory section. If None, and the
630
+ program_name is also unspecified, only the version of OpenStudio will
631
+ appear. Otherwise, this will default to "0.0.0" given that the version
632
+ field is required. (Default: None).
633
+ output_file: Optional gbXML file to output the string of the translation.
634
+ By default it will be returned from this method.
635
+ """
636
+ # check that honeybee-openstudio is installed
637
+ try:
638
+ from honeybee_openstudio.writer import model_to_gbxml
639
+ except ImportError as e: # honeybee-openstudio is not installed
640
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
641
+ if osw_folder is not None:
642
+ print('--osw-folder is deprecated and no longer used.')
643
+
644
+ # load the model and translate it to a gbXML string
645
+ triangulate_non_planar = not permit_non_planar
646
+ model = Model.from_file(model_file)
647
+ gbxml_str = model_to_gbxml(
648
+ model, triangulate_non_planar_orphaned=triangulate_non_planar,
649
+ triangulate_subfaces=triangulate_subfaces, full_geometry=full_geometry,
650
+ interior_face_type=interior_face_type, ground_face_type=ground_face_type,
651
+ program_name=program_name, program_version=program_version
652
+ )
653
+
654
+ # write out the gbXML file
655
+ return process_content_to_output(gbxml_str, output_file)
656
+
657
+
658
+ @translate.command('model-to-trace-gbxml')
659
+ @click.argument('model-file', type=click.Path(
660
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
661
+ @click.option('--single-window/--detailed-windows', ' /-dw', help='Flag to note '
662
+ 'whether all windows within walls should be converted to a single '
663
+ 'window with an area that matches the original geometry.',
664
+ default=True, show_default=True)
665
+ @click.option('--rect-sub-distance', '-r', help='A number for the resolution at which '
666
+ 'non-rectangular Apertures will be subdivided into smaller rectangular '
667
+ 'units. This is required as TRACE 3D plus cannot model non-rectangular '
668
+ 'geometries. This can include the units of the distance (eg. 0.5ft) or, '
669
+ 'if no units are provided, the value will be interpreted in the '
670
+ 'honeybee model units.',
671
+ type=str, default='0.15m', show_default=True)
672
+ @click.option('--frame-merge-distance', '-m', help='A number for the maximum distance '
673
+ 'between non-rectangular Apertures at which point the Apertures will be '
674
+ 'merged into a single rectangular geometry. This is often helpful when '
675
+ 'there are several triangular Apertures that together make a rectangle '
676
+ 'when they are merged across their frames. This can include the units '
677
+ 'of the distance (eg. 0.5ft) or, if no units are provided, the value '
678
+ 'will be interpreted in the honeybee model units',
679
+ type=str, default='0.2m', show_default=True)
680
+ @click.option('--program-name', '-p', help='Optional text to set the name of the '
681
+ 'software that will appear under the programId and ProductName tags '
682
+ 'of the DocumentHistory section. This can be set things like "Ladybug '
683
+ 'Tools" or "Pollination" or some other software in which this gbXML '
684
+ 'export capability is being run. If None, "OpenStudio" will be used.',
685
+ type=str, default=None, show_default=True)
686
+ @click.option('--program-version', '-v', help='Optional text to set the version of '
687
+ 'the software that will appear under the DocumentHistory section. '
688
+ 'If None, and the program_name is also unspecified, only the version '
689
+ 'of OpenStudio will appear. Otherwise, this will default to "0.0.0" '
690
+ 'given that the version field is required.',
691
+ type=str, default=None, show_default=True)
692
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
693
+ default=None,
694
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
695
+ @click.option('--output-file', '-f', help='Optional gbXML file to output the string '
696
+ 'of the translation. By default it printed out to stdout.',
697
+ type=click.File('w'), default='-', show_default=True)
698
+ def model_to_trace_gbxml_cli(
699
+ model_file, single_window, rect_sub_distance, frame_merge_distance,
700
+ program_name, program_version, osw_folder, output_file):
701
+ """Translate a Honeybee Model (HBJSON) to a gbXML file.
702
+
703
+ \b
704
+ Args:
705
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
706
+ """
707
+ try:
708
+ detailed_windows = not single_window
709
+ model_to_trace_gbxml(
710
+ model_file, detailed_windows, rect_sub_distance,
711
+ frame_merge_distance, osw_folder,
712
+ program_name, program_version, output_file)
713
+ except Exception as e:
714
+ _logger.exception('Model translation failed.\n{}'.format(e))
715
+ sys.exit(1)
716
+ else:
717
+ sys.exit(0)
718
+
719
+
720
+ def model_to_trace_gbxml(
721
+ model_file, detailed_windows=False, rect_sub_distance='0.15m',
722
+ frame_merge_distance='0.2m', program_name=None, program_version=None,
723
+ osw_folder=None, output_file=None, single_window=True
724
+ ):
725
+ """Translate a Honeybee Model to a gbXML file that is compatible with TRACE 3D Plus.
726
+
727
+ Args:
728
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
729
+ detailed_windows: A boolean for whether all windows within walls should be
730
+ left as they are (True) or converted to a single window with an area
731
+ that matches the original geometry (False). (Default: False).
732
+ rect_sub_distance: A number for the resolution at which non-rectangular
733
+ Apertures will be subdivided into smaller rectangular units. This is
734
+ required as TRACE 3D plus cannot model non-rectangular geometries.
735
+ This can include the units of the distance (eg. 0.5ft) or, if no units
736
+ are provided, the value will be interpreted in the honeybee model
737
+ units. (Default: 0.15m).
738
+ frame_merge_distance: A number for the maximum distance between non-rectangular
739
+ Apertures at which point the Apertures will be merged into a single
740
+ rectangular geometry. This is often helpful when there are several
741
+ triangular Apertures that together make a rectangle when they are
742
+ merged across their frames. This can include the units of the
743
+ distance (eg. 0.5ft) or, if no units are provided, the value will
744
+ be interpreted in the honeybee model units. (Default: 0.2m).
745
+ program_name: Optional text to set the name of the software that will
746
+ appear under the programId and ProductName tags of the DocumentHistory
747
+ section. This can be set things like "Ladybug Tools" or "Pollination"
748
+ or some other software in which this gbXML export capability is being
749
+ run. If None, the "OpenStudio" will be used. (Default: None).
750
+ program_version: Optional text to set the version of the software that
751
+ will appear under the DocumentHistory section. If None, and the
752
+ program_name is also unspecified, only the version of OpenStudio will
753
+ appear. Otherwise, this will default to "0.0.0" given that the version
754
+ field is required. (Default: None).
755
+ osw_folder: Deprecated input that is no longer used.
756
+ output_file: Optional gbXML file to output the string of the translation.
757
+ By default it will be returned from this method.
758
+ """
759
+ # check that honeybee-openstudio is installed
760
+ try:
761
+ from honeybee_openstudio.writer import model_to_gbxml
762
+ except ImportError as e: # honeybee-openstudio is not installed
763
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
764
+ if osw_folder is not None:
765
+ print('--osw-folder is deprecated and no longer used.')
766
+
767
+ # load the model and translate it to a gbXML string
768
+ single_window = not detailed_windows
769
+ model = Model.from_file(model_file)
770
+ model = _preprocess_model_for_trace(
771
+ model, single_window=single_window, rect_sub_distance=rect_sub_distance,
772
+ frame_merge_distance=frame_merge_distance
773
+ )
774
+ gbxml_str = model_to_gbxml(
775
+ model, program_name=program_name, program_version=program_version
776
+ )
777
+
778
+ # write out the gbXML file
779
+ return process_content_to_output(gbxml_str, output_file)
780
+
781
+
782
+ @translate.command('model-to-sdd')
783
+ @click.argument('model-file', type=click.Path(
784
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
785
+ @click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag to note whether a '
786
+ 'cleaned version of all geometry display names should be used instead '
787
+ 'of identifiers when translating the Model to IDF. Using this flag will '
788
+ 'affect all Rooms, Faces, Apertures, Doors, and Shades. It will '
789
+ 'generally result in more read-able names in the IDF but this means that '
790
+ 'it will not be easy to map the EnergyPlus results back to the original '
791
+ 'Honeybee Model. Cases of duplicate IDs resulting from non-unique names '
792
+ 'will be resolved by adding integers to the ends of the new IDs that are '
793
+ 'derived from the name.', default=True, show_default=True)
794
+ @click.option('--resource-ids/--resource-names', ' /-rn', help='Flag to note whether a '
795
+ 'cleaned version of all resource display names should be used instead '
796
+ 'of identifiers when translating the Model to IDF. Using this flag will '
797
+ 'affect all Materials, Constructions, ConstructionSets, Schedules, '
798
+ 'Loads, and ProgramTypes. It will generally result in more read-able '
799
+ 'names for the resources in the IDF. Cases of duplicate IDs resulting '
800
+ 'from non-unique names will be resolved by adding integers to the ends '
801
+ 'of the new IDs that are derived from the name.',
802
+ default=True, show_default=True)
803
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
804
+ default=None,
805
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
806
+ @click.option('--output-file', '-f', help='Optional SDD file to output the string '
807
+ 'of the translation. By default it printed out to stdout.', default='-',
808
+ type=click.Path(file_okay=True, dir_okay=False, resolve_path=True))
809
+ def model_to_sdd_cli(model_file, geometry_ids, resource_ids, osw_folder, output_file):
810
+ """Translate a Honeybee Model file to a SDD file.
811
+
812
+ \b
813
+ Args:
814
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
815
+ """
816
+ try:
817
+ geo_names = not geometry_ids
818
+ res_names = not resource_ids
819
+ model_to_sdd(model_file, geo_names, res_names, osw_folder, output_file)
820
+ except Exception as e:
821
+ _logger.exception('Model translation failed.\n{}'.format(e))
822
+ sys.exit(1)
823
+ else:
824
+ sys.exit(0)
825
+
826
+
827
+ def model_to_sdd(
828
+ model_file, geometry_names=False, resource_names=False,
829
+ osw_folder=None, output_file=None,
830
+ geometry_ids=True, resource_ids=True
831
+ ):
832
+ """Translate a Honeybee Model file to a SDD file.
833
+
834
+ Args:
835
+ model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
836
+ geometry_names: Boolean to note whether a cleaned version of all geometry
837
+ display names should be used instead of identifiers when translating
838
+ the Model to OSM and IDF. Using this flag will affect all Rooms, Faces,
839
+ Apertures, Doors, and Shades. It will generally result in more read-able
840
+ names in the OSM and IDF but this means that it will not be easy to map
841
+ the EnergyPlus results back to the original Honeybee Model. Cases
842
+ of duplicate IDs resulting from non-unique names will be resolved
843
+ by adding integers to the ends of the new IDs that are derived from
844
+ the name. (Default: False).
845
+ resource_names: Boolean to note whether a cleaned version of all resource
846
+ display names should be used instead of identifiers when translating
847
+ the Model to OSM and IDF. Using this flag will affect all Materials,
848
+ Constructions, ConstructionSets, Schedules, Loads, and ProgramTypes.
849
+ It will generally result in more read-able names for the resources
850
+ in the OSM and IDF. Cases of duplicate IDs resulting from non-unique
851
+ names will be resolved by adding integers to the ends of the new IDs
852
+ that are derived from the name. (Default: False).
853
+ osw_folder: Deprecated input that is no longer used.
854
+ output_file: Optional SDD file to output the string of the translation.
855
+ By default it will be returned from this method.
856
+ """
857
+ # check that honeybee-openstudio is installed
858
+ try:
859
+ from honeybee_openstudio.openstudio import openstudio
860
+ from honeybee_openstudio.writer import model_to_openstudio
861
+ except ImportError as e: # honeybee-openstudio is not installed
862
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
863
+ if osw_folder is not None:
864
+ print('--folder is deprecated and no longer used.')
865
+
866
+ # translate the model to an OpenStudio Model
867
+ model = Model.from_file(model_file)
868
+
869
+ if geometry_names: # rename all face geometry so that it is easy to identify
870
+ model.reset_ids() # sets the identifiers based on the display_name
871
+ for room in model.rooms:
872
+ room.display_name = None
873
+ for face in room.faces:
874
+ face.display_name = None
875
+ for ap in face.apertures:
876
+ ap.display_name = None
877
+ for dr in face.apertures:
878
+ dr.display_name = None
879
+ room.rename_faces_by_attribute()
880
+ room.rename_apertures_by_attribute()
881
+ room.rename_doors_by_attribute()
882
+ model.reset_ids()
883
+
884
+ os_model = model_to_openstudio(
885
+ model,
886
+ use_simple_window_constructions=True,
887
+ use_resource_names=resource_names
888
+ )
889
+
890
+ # write the SDD
891
+ out_path = None
892
+ if output_file is None or output_file.endswith('-'):
893
+ out_directory = tempfile.gettempdir()
894
+ f_name = os.path.basename(model_file).lower()
895
+ f_name = f_name.replace('.hbjson', '.xml').replace('.json', '.xml')
896
+ out_path = os.path.join(out_directory, f_name)
897
+ sdd = os.path.abspath(output_file) if out_path is None else out_path
898
+ sdd_translator = openstudio.sdd.SddForwardTranslator()
899
+ sdd_translator.modelToSDD(os_model, sdd)
900
+
901
+ # return the file contents if requested
902
+ if out_path is not None:
903
+ with open(sdd, 'r') as sdf:
904
+ file_contents = sdf.read()
905
+ if output_file is None:
906
+ return file_contents
907
+ else:
908
+ print(file_contents)
909
+
910
+
911
+ @translate.command('model-from-osm')
912
+ @click.argument('osm-file', type=click.Path(
913
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
914
+ @click.option('--keep-properties/--reset-properties', ' /-r', help='Flag to note '
915
+ 'whether all energy properties should be reset to defaults upon import, '
916
+ 'meaning that only the geometry and boundary conditions are imported '
917
+ 'from the file.', default=True, show_default=True)
918
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
919
+ default=None,
920
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
921
+ @click.option('--output-file', '-f', help='Optional HBJSON file to output the string '
922
+ 'of the translation. By default it printed out to stdout.',
923
+ type=click.File('w'), default='-', show_default=True)
924
+ def model_from_osm_cli(osm_file, keep_properties, osw_folder, output_file):
925
+ """Translate a OpenStudio Model (OSM) to a Honeybee Model (HBJSON).
926
+
927
+ \b
928
+ Args:
929
+ osm_file: Path to a OpenStudio Model (OSM) file.
930
+ """
931
+ try:
932
+ reset_properties = not keep_properties
933
+ model_from_osm(osm_file, reset_properties, osw_folder, output_file)
934
+ except Exception as e:
935
+ _logger.exception('Model translation failed.\n{}'.format(e))
936
+ sys.exit(1)
937
+ else:
938
+ sys.exit(0)
939
+
940
+
941
+ def model_from_osm(osm_file, reset_properties=False, osw_folder=None, output_file=None,
942
+ keep_properties=True):
943
+ """Translate a OpenStudio Model (OSM) to a Honeybee Model (HBJSON).
944
+
945
+ Args:
946
+ osm_file: Path to a OpenStudio Model (OSM) file.
947
+ reset_properties: Boolean to note whether all energy properties should be
948
+ reset to defaults upon import, meaning that only the geometry and boundary
949
+ conditions are imported from the Openstudio Model. (Default: False).
950
+ osw_folder: Deprecated input that is no longer used.
951
+ output_file: Optional HBJSON file to output the string of the translation.
952
+ If None, it will be returned from this method. (Default: None).
953
+ """
954
+ # check that honeybee-openstudio is installed
955
+ try:
956
+ from honeybee_openstudio.reader import model_from_osm_file
957
+ except ImportError as e: # honeybee-openstudio is not installed
958
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
959
+ if osw_folder is not None:
960
+ print('--folder is deprecated and no longer used.')
961
+ # translate everything to a honeybee Model
962
+ model = model_from_osm_file(osm_file, reset_properties)
963
+ # write out the file
964
+ return process_content_to_output(json.dumps(model.to_dict()), output_file)
965
+
966
+
967
+ @translate.command('model-from-idf')
968
+ @click.argument('idf-file', type=click.Path(
969
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
970
+ @click.option('--keep-properties/--reset-properties', ' /-r', help='Flag to note '
971
+ 'whether all energy properties should be reset to defaults upon import, '
972
+ 'meaning that only the geometry and boundary conditions are imported '
973
+ 'from the file.', default=True, show_default=True)
974
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
975
+ default=None,
976
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
977
+ @click.option('--output-file', '-f', help='Optional HBJSON file to output the string '
978
+ 'of the translation. By default it printed out to stdout',
979
+ type=click.File('w'), default='-', show_default=True)
980
+ def model_from_idf_cli(idf_file, keep_properties, osw_folder, output_file):
981
+ """Translate an EnergyPlus Model (IDF) to a Honeybee Model (HBJSON).
982
+
983
+ \b
984
+ Args:
985
+ idf_file: Path to an EnergyPlus Model (IDF) file.
986
+ """
987
+ try:
988
+ reset_properties = not keep_properties
989
+ model_from_idf(idf_file, reset_properties, osw_folder, output_file)
990
+ except Exception as e:
991
+ _logger.exception('Model translation failed.\n{}'.format(e))
992
+ sys.exit(1)
993
+ else:
994
+ sys.exit(0)
995
+
996
+
997
+ def model_from_idf(idf_file, reset_properties=False, osw_folder=None, output_file=None,
998
+ keep_properties=True):
999
+ """Translate an EnergyPlus Model (IDF) to a Honeybee Model (HBJSON).
1000
+
1001
+ Args:
1002
+ idf_file: Path to an EnergyPlus Model (IDF) file.
1003
+ reset_properties: Boolean to note whether all energy properties should be
1004
+ reset to defaults upon import, meaning that only the geometry and boundary
1005
+ conditions are imported from the EnergyPlus Model. (Default: False).
1006
+ osw_folder: Deprecated input that is no longer used.
1007
+ output_file: Optional HBJSON file to output the string of the translation.
1008
+ If None, it will be returned from this method. (Default: None).
1009
+ """
1010
+ # check that honeybee-openstudio is installed
1011
+ try:
1012
+ from honeybee_openstudio.reader import model_from_idf_file
1013
+ except ImportError as e: # honeybee-openstudio is not installed
1014
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1015
+ if osw_folder is not None:
1016
+ print('--folder is deprecated and no longer used.')
1017
+ # translate everything to a honeybee Model
1018
+ model = model_from_idf_file(idf_file, reset_properties)
1019
+ # write out the file
1020
+ return process_content_to_output(json.dumps(model.to_dict()), output_file)
1021
+
1022
+
1023
+ @translate.command('model-from-gbxml')
1024
+ @click.argument('gbxml-file', type=click.Path(
1025
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1026
+ @click.option('--keep-properties/--reset-properties', ' /-r', help='Flag to note '
1027
+ 'whether all energy properties should be reset to defaults upon import, '
1028
+ 'meaning that only the geometry and boundary conditions are imported '
1029
+ 'from the file.', default=True, show_default=True)
1030
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1031
+ default=None,
1032
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1033
+ @click.option('--output-file', '-f', help='Optional HBJSON file to output the string '
1034
+ 'of the translation. By default it printed out to stdout',
1035
+ type=click.File('w'), default='-', show_default=True)
1036
+ def model_from_gbxml_cli(gbxml_file, keep_properties, osw_folder, output_file):
1037
+ """Translate a gbXML to a Honeybee Model (HBJSON).
1038
+
1039
+ \b
1040
+ Args:
1041
+ gbxml_file: Path to a gbXML file.
1042
+ """
1043
+ try:
1044
+ reset_properties = not keep_properties
1045
+ model_from_gbxml(gbxml_file, reset_properties, osw_folder, output_file)
1046
+ except Exception as e:
1047
+ _logger.exception('Model translation failed.\n{}'.format(e))
1048
+ sys.exit(1)
1049
+ else:
1050
+ sys.exit(0)
1051
+
1052
+
1053
+ def model_from_gbxml(gbxml_file, reset_properties=False, osw_folder=None,
1054
+ output_file=None, keep_properties=True):
1055
+ """Translate a gbXML to a Honeybee Model (HBJSON).
1056
+
1057
+ Args:
1058
+ gbxml_file: Path to a gbXML file.
1059
+ reset_properties: Boolean to note whether all energy properties should be
1060
+ reset to defaults upon import, meaning that only the geometry and boundary
1061
+ conditions are imported from the gbXML Model. (Default: False).
1062
+ osw_folder: Deprecated input that is no longer used.
1063
+ output_file: Optional HBJSON file to output the string of the translation.
1064
+ If None, it will be returned from this method. (Default: None).
1065
+ """
1066
+ # check that honeybee-openstudio is installed
1067
+ try:
1068
+ from honeybee_openstudio.reader import model_from_gbxml_file
1069
+ except ImportError as e: # honeybee-openstudio is not installed
1070
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1071
+ if osw_folder is not None:
1072
+ print('--folder is deprecated and no longer used.')
1073
+ # translate everything to a honeybee Model
1074
+ model = model_from_gbxml_file(gbxml_file, reset_properties)
1075
+ # write out the file
1076
+ return process_content_to_output(json.dumps(model.to_dict()), output_file)
1077
+
1078
+
1079
+ @translate.command('constructions-to-idf')
1080
+ @click.argument('construction-json', type=click.Path(
1081
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1082
+ @click.option('--output-file', '-f', help='Optional IDF file to output the IDF string '
1083
+ 'of the translation. By default this will be printed out to stdout',
1084
+ type=click.File('w'), default='-', show_default=True)
1085
+ def construction_to_idf(construction_json, output_file):
1086
+ """Translate a Construction JSON file to an IDF using direct-to-idf translators.
1087
+
1088
+ \b
1089
+ Args:
1090
+ construction_json: Full path to a Construction JSON file. This file should
1091
+ either be an array of non-abridged Constructions or a dictionary where
1092
+ the values are non-abridged Constructions.
1093
+ """
1094
+ try:
1095
+ # re-serialize the Constructions to Python
1096
+ with open(construction_json) as json_file:
1097
+ data = json.load(json_file)
1098
+ constr_list = data.values() if isinstance(data, dict) else data
1099
+ constr_objs = [dict_to_construction(constr) for constr in constr_list]
1100
+ mat_objs = set()
1101
+ for constr in constr_objs:
1102
+ try:
1103
+ for mat in constr.materials:
1104
+ mat_objs.add(mat)
1105
+ if constr.has_frame:
1106
+ mat_objs.add(constr.frame)
1107
+ if constr.has_shade:
1108
+ if constr.is_switchable_glazing:
1109
+ mat_objs.add(constr.switched_glass_material)
1110
+ except AttributeError: # not a construction with materials
1111
+ pass
1112
+
1113
+ # create the IDF strings
1114
+ idf_str_list = []
1115
+ idf_str_list.append('!- ============== MATERIALS ==============\n')
1116
+ idf_str_list.extend([mat.to_idf() for mat in mat_objs])
1117
+ idf_str_list.append('!- ============ CONSTRUCTIONS ============\n')
1118
+ idf_str_list.extend([constr.to_idf() for constr in constr_objs])
1119
+ idf_str = '\n\n'.join(idf_str_list)
1120
+
1121
+ # write out the IDF file
1122
+ output_file.write(idf_str)
1123
+ except Exception as e:
1124
+ _logger.exception('Construction translation failed.\n{}'.format(e))
1125
+ sys.exit(1)
1126
+ else:
1127
+ sys.exit(0)
1128
+
1129
+
1130
+ @translate.command('constructions-from-idf')
1131
+ @click.argument('construction-idf', type=click.Path(
1132
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1133
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1134
+ 'the output JSON file. Specifying an value here can produce more read-able'
1135
+ ' JSONs.', type=int, default=None, show_default=True)
1136
+ @click.option('--output-file', '-f', help='Optional JSON file to output the JSON '
1137
+ 'string of the translation. By default this will be printed out to stdout',
1138
+ type=click.File('w'), default='-', show_default=True)
1139
+ def construction_from_idf(construction_idf, indent, output_file):
1140
+ """Translate a Construction IDF file to a honeybee JSON as an array of constructions.
1141
+
1142
+ \b
1143
+ Args:
1144
+ construction_idf: Full path to a Construction IDF file. Only the constructions
1145
+ and materials in this file will be extracted.
1146
+ """
1147
+ try:
1148
+ # re-serialize the Constructions to Python
1149
+ opaque_constrs = OpaqueConstruction.extract_all_from_idf_file(construction_idf)
1150
+ win_constrs = WindowConstruction.extract_all_from_idf_file(construction_idf)
1151
+
1152
+ # create the honeybee dictionaries
1153
+ hb_obj_list = []
1154
+ for constr in opaque_constrs[0]:
1155
+ hb_obj_list.append(constr.to_dict())
1156
+ for constr in win_constrs[0]:
1157
+ hb_obj_list.append(constr.to_dict())
1158
+
1159
+ # write out the JSON file
1160
+ output_file.write(json.dumps(hb_obj_list, indent=indent))
1161
+ except Exception as e:
1162
+ _logger.exception('Construction translation failed.\n{}'.format(e))
1163
+ sys.exit(1)
1164
+ else:
1165
+ sys.exit(0)
1166
+
1167
+
1168
+ @translate.command('materials-from-osm')
1169
+ @click.argument('osm-file', type=click.Path(
1170
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1171
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1172
+ 'the output JSON file. Specifying an value here can produce more read-able'
1173
+ ' JSONs.', type=int, default=None, show_default=True)
1174
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1175
+ default=None,
1176
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1177
+ @click.option('--output-file', '-f', help='Optional JSON file to output the string '
1178
+ 'of the translation. By default it printed out to stdout',
1179
+ type=click.File('w'), default='-', show_default=True)
1180
+ def materials_from_osm(osm_file, indent, osw_folder, output_file):
1181
+ """Translate all Materials in an OpenStudio Model (OSM) to a Honeybee JSON.
1182
+
1183
+ The resulting JSON can be written into a user standards folder to add the
1184
+ materials to a users standards library.
1185
+
1186
+ \b
1187
+ Args:
1188
+ osm_file: Path to a OpenStudio Model (OSM) file.
1189
+ """
1190
+ try:
1191
+ try:
1192
+ from honeybee_openstudio.openstudio import openstudio
1193
+ from honeybee_openstudio.material import extract_all_materials
1194
+ except ImportError as e: # honeybee-openstudio is not installed
1195
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1196
+ ver_translator = openstudio.osversion.VersionTranslator() # in case OSM is old
1197
+ os_model = ver_translator.loadModel(osm_file)
1198
+ if not os_model.is_initialized():
1199
+ errors = '\n'.join(str(err.logMessage()) for err in ver_translator.errors())
1200
+ raise ValueError('Failed to load model from OSM.\n{}'.format(errors))
1201
+ materials = extract_all_materials(os_model.get())
1202
+ out_dict = {mat.identifier: mat.to_dict() for mat in materials.values()}
1203
+ output_file.write(json.dumps(out_dict, indent=indent))
1204
+ except Exception as e:
1205
+ _logger.exception('Material translation failed.\n{}'.format(e))
1206
+ sys.exit(1)
1207
+ else:
1208
+ sys.exit(0)
1209
+
1210
+
1211
+ @translate.command('constructions-from-osm')
1212
+ @click.argument('osm-file', type=click.Path(
1213
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1214
+ @click.option('--full/--abridged', ' /-a', help='Flag to note whether the objects '
1215
+ 'should be translated as an abridged specification instead of a '
1216
+ 'specification that fully describes the object. This option should be '
1217
+ 'used when the materials-from-osm command will be used to separately '
1218
+ 'translate all of the materials from the OSM.',
1219
+ default=True, show_default=True)
1220
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1221
+ 'the output JSON file. Specifying an value here can produce more read-able'
1222
+ ' JSONs.', type=int, default=None, show_default=True)
1223
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1224
+ default=None,
1225
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1226
+ @click.option('--output-file', '-f', help='Optional JSON file to output the string '
1227
+ 'of the translation. By default it printed out to stdout',
1228
+ type=click.File('w'), default='-', show_default=True)
1229
+ def constructions_from_osm(osm_file, full, indent, osw_folder, output_file):
1230
+ """Translate all Constructions in an OpenStudio Model (OSM) to a Honeybee JSON.
1231
+
1232
+ The resulting JSON can be written into a user standards folder to add the
1233
+ constructions to a users standards library.
1234
+
1235
+ \b
1236
+ Args:
1237
+ osm_file: Path to a OpenStudio Model (OSM) file.
1238
+ """
1239
+ try:
1240
+ try:
1241
+ from honeybee_openstudio.openstudio import openstudio
1242
+ from honeybee_openstudio.construction import extract_all_constructions
1243
+ except ImportError as e: # honeybee-openstudio is not installed
1244
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1245
+ ver_translator = openstudio.osversion.VersionTranslator() # in case OSM is old
1246
+ os_model = ver_translator.loadModel(osm_file)
1247
+ if not os_model.is_initialized():
1248
+ errors = '\n'.join(str(err.logMessage()) for err in ver_translator.errors())
1249
+ raise ValueError('Failed to load model from OSM.\n{}'.format(errors))
1250
+ constructions = extract_all_constructions(os_model.get())
1251
+ abridged = not full
1252
+ out_dict = {}
1253
+ for con in constructions.values():
1254
+ try:
1255
+ out_dict[con.identifier] = con.to_dict(abridged=abridged)
1256
+ except TypeError: # no abridged option
1257
+ out_dict[con.identifier] = con.to_dict()
1258
+ output_file.write(json.dumps(out_dict, indent=indent))
1259
+ except Exception as e:
1260
+ _logger.exception('Construction translation failed.\n{}'.format(e))
1261
+ sys.exit(1)
1262
+ else:
1263
+ sys.exit(0)
1264
+
1265
+
1266
+ @translate.command('construction-sets-from-osm')
1267
+ @click.argument('osm-file', type=click.Path(
1268
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1269
+ @click.option('--full/--abridged', ' /-a', help='Flag to note whether the objects '
1270
+ 'should be translated as an abridged specification instead of a '
1271
+ 'specification that fully describes the object. This option should be '
1272
+ 'used when the constructions-from-osm command will be used to separately '
1273
+ 'translate all of the constructions from the OSM.',
1274
+ default=True, show_default=True)
1275
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1276
+ 'the output JSON file. Specifying an value here can produce more read-able'
1277
+ ' JSONs.', type=int, default=None, show_default=True)
1278
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1279
+ default=None,
1280
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1281
+ @click.option('--output-file', '-f', help='Optional JSON file to output the string '
1282
+ 'of the translation. By default it printed out to stdout',
1283
+ type=click.File('w'), default='-', show_default=True)
1284
+ def construction_sets_from_osm(osm_file, full, indent, osw_folder, output_file):
1285
+ """Translate all ConstructionSets in an OpenStudio Model (OSM) to a Honeybee JSON.
1286
+
1287
+ The resulting JSON can be written into a user standards folder to add the
1288
+ constructions to a users standards library.
1289
+
1290
+ \b
1291
+ Args:
1292
+ osm_file: Path to a OpenStudio Model (OSM) file.
1293
+ """
1294
+ try:
1295
+ try:
1296
+ from honeybee_openstudio.openstudio import openstudio
1297
+ from honeybee_openstudio.construction import extract_all_constructions
1298
+ from honeybee_openstudio.constructionset import construction_set_from_openstudio
1299
+ except ImportError as e: # honeybee-openstudio is not installed
1300
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1301
+ ver_translator = openstudio.osversion.VersionTranslator() # in case OSM is old
1302
+ os_model = ver_translator.loadModel(osm_file)
1303
+ if not os_model.is_initialized():
1304
+ errors = '\n'.join(str(err.logMessage()) for err in ver_translator.errors())
1305
+ raise ValueError('Failed to load model from OSM.\n{}'.format(errors))
1306
+ os_model = os_model.get()
1307
+ constructions = extract_all_constructions(os_model)
1308
+ abridged = not full
1309
+ out_dict = {}
1310
+ for os_cons_set in os_model.getDefaultConstructionSets():
1311
+ if os_cons_set.nameString() != 'Default Generic Construction Set':
1312
+ con_set = construction_set_from_openstudio(os_cons_set, constructions)
1313
+ out_dict[con_set.identifier] = con_set.to_dict(abridged=abridged)
1314
+ output_file.write(json.dumps(out_dict, indent=indent))
1315
+ except Exception as e:
1316
+ _logger.exception('ConstructionSet translation failed.\n{}'.format(e))
1317
+ sys.exit(1)
1318
+ else:
1319
+ sys.exit(0)
1320
+
1321
+
1322
+ @translate.command('schedules-to-idf')
1323
+ @click.argument('schedule-json', type=click.Path(
1324
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1325
+ @click.option('--output-file', '-f', help='Optional IDF file to output the IDF '
1326
+ 'string of the translation. By default this will be printed out to stdout',
1327
+ type=click.File('w'), default='-', show_default=True)
1328
+ def schedule_to_idf(schedule_json, output_file):
1329
+ """Translate a Schedule JSON file to an IDF using direct-to-idf translators.
1330
+
1331
+ \b
1332
+ Args:
1333
+ schedule_json: Full path to a Schedule JSON file. This file should
1334
+ either be an array of non-abridged Schedules or a dictionary where
1335
+ the values are non-abridged Schedules.
1336
+ """
1337
+ try:
1338
+ # re-serialize the Schedule to Python
1339
+ with open(schedule_json) as json_file:
1340
+ data = json.load(json_file)
1341
+ sch_list = data.values() if isinstance(data, dict) else data
1342
+ sch_objs = [dict_to_schedule(sch) for sch in sch_list]
1343
+ type_objs = set()
1344
+ for sch in sch_objs:
1345
+ type_objs.add(sch.schedule_type_limit)
1346
+
1347
+ # set the schedule directory in case it is needed
1348
+ sch_path = os.path.abspath(schedule_json) if 'stdout' in str(output_file) \
1349
+ else os.path.abspath(str(output_file))
1350
+ sch_directory = os.path.join(os.path.split(sch_path)[0], 'schedules')
1351
+
1352
+ # create the IDF strings
1353
+ sched_strs = []
1354
+ used_day_sched_ids = []
1355
+ for sched in sch_objs:
1356
+ try: # ScheduleRuleset
1357
+ year_schedule, week_schedules = sched.to_idf()
1358
+ if week_schedules is None: # ScheduleConstant
1359
+ sched_strs.append(year_schedule)
1360
+ else: # ScheduleYear
1361
+ # check that day schedules aren't referenced by other schedules
1362
+ day_scheds = []
1363
+ for day in sched.day_schedules:
1364
+ if day.identifier not in used_day_sched_ids:
1365
+ day_scheds.append(day.to_idf(sched.schedule_type_limit))
1366
+ used_day_sched_ids.append(day.identifier)
1367
+ sched_strs.extend([year_schedule] + week_schedules + day_scheds)
1368
+ except AttributeError: # ScheduleFixedInterval
1369
+ sched_strs.append(sched.to_idf(sch_directory))
1370
+ idf_str_list = []
1371
+ idf_str_list.append('!- ========= SCHEDULE TYPE LIMITS =========\n')
1372
+ idf_str_list.extend([type_limit.to_idf() for type_limit in type_objs])
1373
+ idf_str_list.append('!- ============== SCHEDULES ==============\n')
1374
+ idf_str_list.extend(sched_strs)
1375
+ idf_str = '\n\n'.join(idf_str_list)
1376
+
1377
+ # write out the IDF file
1378
+ output_file.write(idf_str)
1379
+ except Exception as e:
1380
+ _logger.exception('Schedule translation failed.\n{}'.format(e))
1381
+ sys.exit(1)
1382
+ else:
1383
+ sys.exit(0)
1384
+
1385
+
1386
+ @translate.command('schedules-from-idf')
1387
+ @click.argument('schedule-idf', type=click.Path(
1388
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1389
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1390
+ 'the output JSON file. Specifying an value here can produce more read-able'
1391
+ ' JSONs.', type=int, default=None, show_default=True)
1392
+ @click.option('--output-file', '-f', help='Optional JSON file to output the JSON '
1393
+ 'string of the translation. By default this will be printed out to stdout',
1394
+ type=click.File('w'), default='-', show_default=True)
1395
+ def schedule_from_idf(schedule_idf, indent, output_file):
1396
+ """Translate a schedule IDF file to a honeybee JSON as an array of schedules.
1397
+
1398
+ \b
1399
+ Args:
1400
+ schedule_idf: Full path to a Schedule IDF file. Only the schedules
1401
+ and schedule type limits in this file will be extracted.
1402
+ """
1403
+ try:
1404
+ # re-serialize the schedules to Python
1405
+ schedules = ScheduleRuleset.extract_all_from_idf_file(schedule_idf)
1406
+ # create the honeybee dictionaries
1407
+ hb_obj_list = [sch.to_dict() for sch in schedules]
1408
+ # write out the JSON file
1409
+ output_file.write(json.dumps(hb_obj_list, indent=indent))
1410
+ except Exception as e:
1411
+ _logger.exception('Schedule translation failed.\n{}'.format(e))
1412
+ sys.exit(1)
1413
+ else:
1414
+ sys.exit(0)
1415
+
1416
+
1417
+ @translate.command('schedule-type-limits-from-osm')
1418
+ @click.argument('osm-file', type=click.Path(
1419
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1420
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1421
+ 'the output JSON file. Specifying an value here can produce more read-able'
1422
+ ' JSONs.', type=int, default=None, show_default=True)
1423
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1424
+ default=None,
1425
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1426
+ @click.option('--output-file', '-f', help='Optional JSON file to output the string '
1427
+ 'of the translation. By default it printed out to stdout',
1428
+ type=click.File('w'), default='-', show_default=True)
1429
+ def schedule_type_limits_from_osm(osm_file, indent, osw_folder, output_file):
1430
+ """Translate all ScheduleTypeLimits in an OpenStudio Model (OSM) to a Honeybee JSON.
1431
+
1432
+ The resulting JSON can be written into a user standards folder to add the
1433
+ type limits to a users standards library.
1434
+
1435
+ \b
1436
+ Args:
1437
+ osm_file: Path to a OpenStudio Model (OSM) file.
1438
+ """
1439
+ try:
1440
+ try:
1441
+ from honeybee_openstudio.openstudio import openstudio
1442
+ from honeybee_openstudio.schedule import schedule_type_limits_from_openstudio
1443
+ except ImportError as e: # honeybee-openstudio is not installed
1444
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1445
+ ver_translator = openstudio.osversion.VersionTranslator() # in case OSM is old
1446
+ os_model = ver_translator.loadModel(osm_file)
1447
+ if not os_model.is_initialized():
1448
+ errors = '\n'.join(str(err.logMessage()) for err in ver_translator.errors())
1449
+ raise ValueError('Failed to load model from OSM.\n{}'.format(errors))
1450
+ out_dict = {}
1451
+ for os_type_lim in os_model.get().getScheduleTypeLimitss():
1452
+ type_lim = schedule_type_limits_from_openstudio(os_type_lim)
1453
+ out_dict[type_lim.identifier] = type_lim.to_dict()
1454
+ output_file.write(json.dumps(out_dict, indent=indent))
1455
+ except Exception as e:
1456
+ _logger.exception('ScheduleTypeLimit translation failed.\n{}'.format(e))
1457
+ sys.exit(1)
1458
+ else:
1459
+ sys.exit(0)
1460
+
1461
+
1462
+ @translate.command('schedules-from-osm')
1463
+ @click.argument('osm-file', type=click.Path(
1464
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1465
+ @click.option('--full/--abridged', ' /-a', help='Flag to note whether the objects '
1466
+ 'should be translated as an abridged specification instead of a '
1467
+ 'specification that fully describes the object. This option should be '
1468
+ 'used when the schedule-type-limits-from-osm command will be used to '
1469
+ 'separately translate all of the type limits from the OSM.',
1470
+ default=True, show_default=True)
1471
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1472
+ 'the output JSON file. Specifying an value here can produce more read-able'
1473
+ ' JSONs.', type=int, default=None, show_default=True)
1474
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1475
+ default=None,
1476
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1477
+ @click.option('--output-file', '-f', help='Optional JSON file to output the string '
1478
+ 'of the translation. By default it printed out to stdout',
1479
+ type=click.File('w'), default='-', show_default=True)
1480
+ def schedules_from_osm(osm_file, full, indent, osw_folder, output_file):
1481
+ """Translate all Schedules in an OpenStudio Model (OSM) to a Honeybee JSON.
1482
+
1483
+ The resulting JSON can be written into a user standards folder to add the
1484
+ schedules to a users standards library.
1485
+
1486
+ \b
1487
+ Args:
1488
+ osm_file: Path to a OpenStudio Model (OSM) file.
1489
+ """
1490
+ try:
1491
+ try:
1492
+ from honeybee_openstudio.openstudio import openstudio
1493
+ from honeybee_openstudio.schedule import extract_all_schedules
1494
+ except ImportError as e: # honeybee-openstudio is not installed
1495
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1496
+ ver_translator = openstudio.osversion.VersionTranslator() # in case OSM is old
1497
+ os_model = ver_translator.loadModel(osm_file)
1498
+ if not os_model.is_initialized():
1499
+ errors = '\n'.join(str(err.logMessage()) for err in ver_translator.errors())
1500
+ raise ValueError('Failed to load model from OSM.\n{}'.format(errors))
1501
+ schedules = extract_all_schedules(os_model.get())
1502
+ abridged = not full
1503
+ out_dict = {}
1504
+ for sch in schedules.values():
1505
+ out_dict[sch.identifier] = sch.to_dict(abridged=abridged)
1506
+ output_file.write(json.dumps(out_dict, indent=indent))
1507
+ except Exception as e:
1508
+ _logger.exception('Schedule translation failed.\n{}'.format(e))
1509
+ sys.exit(1)
1510
+ else:
1511
+ sys.exit(0)
1512
+
1513
+
1514
+ @translate.command('programs-from-osm')
1515
+ @click.argument('osm-file', type=click.Path(
1516
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1517
+ @click.option('--full/--abridged', ' /-a', help='Flag to note whether the objects '
1518
+ 'should be translated as an abridged specification instead of a '
1519
+ 'specification that fully describes the object. This option should be '
1520
+ 'used when the schedules-from-osm command will be used to separately '
1521
+ 'translate all of the schedules from the OSM.',
1522
+ default=True, show_default=True)
1523
+ @click.option('--indent', '-i', help='Optional integer to specify the indentation in '
1524
+ 'the output JSON file. Specifying an value here can produce more read-able'
1525
+ ' JSONs.', type=int, default=None, show_default=True)
1526
+ @click.option('--osw-folder', '-osw', help='Deprecated input that is no longer used.',
1527
+ default=None,
1528
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True))
1529
+ @click.option('--output-file', '-f', help='Optional JSON file to output the string '
1530
+ 'of the translation. By default it printed out to stdout',
1531
+ type=click.File('w'), default='-', show_default=True)
1532
+ def programs_from_osm(osm_file, full, indent, osw_folder, output_file):
1533
+ """Translate all ProgramTypes in an OpenStudio Model (OSM) to a Honeybee JSON.
1534
+
1535
+ The resulting JSON can be written into a user standards folder to add the
1536
+ programs to a users standards library.
1537
+
1538
+ \b
1539
+ Args:
1540
+ osm_file: Path to a OpenStudio Model (OSM) file.
1541
+ """
1542
+ try:
1543
+ try:
1544
+ from honeybee_openstudio.openstudio import openstudio
1545
+ from honeybee_openstudio.schedule import extract_all_schedules
1546
+ from honeybee_openstudio.programtype import program_type_from_openstudio
1547
+ except ImportError as e: # honeybee-openstudio is not installed
1548
+ raise ImportError('{}\n{}'.format(HB_OS_MSG, e))
1549
+ ver_translator = openstudio.osversion.VersionTranslator() # in case OSM is old
1550
+ os_model = ver_translator.loadModel(osm_file)
1551
+ if not os_model.is_initialized():
1552
+ errors = '\n'.join(str(err.logMessage()) for err in ver_translator.errors())
1553
+ raise ValueError('Failed to load model from OSM.\n{}'.format(errors))
1554
+ os_model = os_model.get()
1555
+ schedules = extract_all_schedules(os_model)
1556
+ abridged = not full
1557
+ out_dict = {}
1558
+ for os_space_type in os_model.getSpaceTypes():
1559
+ program = program_type_from_openstudio(os_space_type, schedules)
1560
+ out_dict[program.identifier] = program.to_dict(abridged=abridged)
1561
+ output_file.write(json.dumps(out_dict, indent=indent))
1562
+ except Exception as e:
1563
+ _logger.exception('Program translation failed.\n{}'.format(e))
1564
+ sys.exit(1)
1565
+ else:
1566
+ sys.exit(0)
1567
+
1568
+
1569
+ @translate.command('model-occ-schedules')
1570
+ @click.argument('model-file', type=click.Path(
1571
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1572
+ @click.option('--threshold', '-t', help='A number between 0 and 1 for the threshold '
1573
+ 'at and above which a schedule value is considered occupied.',
1574
+ type=float, default=0.1, show_default=True)
1575
+ @click.option('--period', '-p', help='An AnalysisPeriod string to dictate '
1576
+ 'the start and end of the exported occupancy values '
1577
+ '(eg. "6/21 to 9/21 between 0 and 23 @1"). Note that the timestep '
1578
+ 'of the period will determine the timestep of output values. If '
1579
+ 'unspecified, the values will be annual.', default=None, type=str)
1580
+ @click.option('--output-file', '-f', help='Optional file to output the JSON of '
1581
+ 'occupancy values. By default this will be printed out to stdout',
1582
+ type=click.File('w'), default='-', show_default=True)
1583
+ def model_occ_schedules(model_file, threshold, period, output_file):
1584
+ """Translate a Model's occupancy schedules into a JSON of 0/1 values.
1585
+
1586
+ \b
1587
+ Args:
1588
+ model_file: Full path to a Model JSON or Pkl file.
1589
+ """
1590
+ try:
1591
+ # re-serialize the Model
1592
+ model = Model.from_file(model_file)
1593
+
1594
+ # loop through the rooms and collect all unique occupancy schedules
1595
+ scheds, room_occupancy = [], {}
1596
+ for room in model.rooms:
1597
+ people = room.properties.energy.people
1598
+ if people is not None:
1599
+ model.properties.energy._check_and_add_schedule(
1600
+ people.occupancy_schedule, scheds)
1601
+ room_occupancy[room.identifier] = people.occupancy_schedule.identifier
1602
+ else:
1603
+ room_occupancy[room.identifier] = None
1604
+
1605
+ # process the run period if it is supplied
1606
+ if period is not None and period != '' and period != 'None':
1607
+ a_per = AnalysisPeriod.from_string(period)
1608
+ else:
1609
+ a_per = AnalysisPeriod()
1610
+
1611
+ # convert occupancy schedules to lists of 0/1 values
1612
+ schedules = {}
1613
+ for sch in scheds:
1614
+ sch_data = sch.data_collection() if isinstance(sch, ScheduleRuleset) \
1615
+ else sch.data_collection
1616
+ if not a_per.is_annual:
1617
+ sch_data = sch_data.filter_by_analysis_period(a_per)
1618
+ values = []
1619
+ for val in sch_data.values:
1620
+ is_occ = 0 if val <= threshold else 1
1621
+ values.append(is_occ)
1622
+ schedules[sch.identifier] = values
1623
+
1624
+ # write out the JSON file
1625
+ occ_dict = {'schedules': schedules, 'room_occupancy': room_occupancy}
1626
+ output_file.write(json.dumps(occ_dict))
1627
+ except Exception as e:
1628
+ _logger.exception('Model translation failed.\n{}'.format(e))
1629
+ sys.exit(1)
1630
+ else:
1631
+ sys.exit(0)
1632
+
1633
+
1634
+ @translate.command('model-transmittance-schedules')
1635
+ @click.argument('model-file', type=click.Path(
1636
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True))
1637
+ @click.option('--period', '-p', help='An AnalysisPeriod string to dictate '
1638
+ 'the start and end of the exported occupancy values '
1639
+ '(eg. "6/21 to 9/21 between 0 and 23 @1"). Note that the timestep '
1640
+ 'of the period will determine the timestep of output values. If '
1641
+ 'unspecified, the values will be annual.', default=None, type=str)
1642
+ @click.option('--output-file', '-f', help='Optional file to output the JSON of '
1643
+ 'occupancy values. By default this will be printed out to stdout',
1644
+ type=click.File('w'), default='-', show_default=True)
1645
+ def model_trans_schedules(model_file, period, output_file):
1646
+ """Translate a Model's shade transmittance schedules into a JSON of fractional vals.
1647
+
1648
+ \b
1649
+ Args:
1650
+ model_file: Full path to a Model JSON or Pkl file.
1651
+ """
1652
+ try:
1653
+ # re-serialize the Model
1654
+ model = Model.from_file(model_file)
1655
+
1656
+ # loop through the rooms and collect all unique occupancy schedules
1657
+ scheds = []
1658
+ for shade in model.shades:
1659
+ t_sch = shade.properties.energy.transmittance_schedule
1660
+ if t_sch is not None:
1661
+ model.properties.energy._check_and_add_schedule(t_sch, scheds)
1662
+
1663
+ # process the run period if it is supplied
1664
+ if period is not None and period != '' and period != 'None':
1665
+ a_per = AnalysisPeriod.from_string(period)
1666
+ else:
1667
+ a_per = AnalysisPeriod()
1668
+
1669
+ # convert occupancy schedules to lists of 0/1 values
1670
+ schedules = {}
1671
+ for sch in scheds:
1672
+ sch_data = sch.data_collection() if isinstance(sch, ScheduleRuleset) \
1673
+ else sch.data_collection
1674
+ if not a_per.is_annual:
1675
+ sch_data = sch_data.filter_by_analysis_period(a_per)
1676
+ schedules[clean_rad_string(sch.identifier)] = sch_data.values
1677
+
1678
+ # write out the JSON file
1679
+ output_file.write(json.dumps(schedules))
1680
+ except Exception as e:
1681
+ _logger.exception('Model translation failed.\n{}'.format(e))
1682
+ sys.exit(1)
1683
+ else:
1684
+ sys.exit(0)
1685
+
1686
+
1687
+ def _run_translation_osw(osw, out_path):
1688
+ """Generic function used by all import methods that run OpenStudio CLI."""
1689
+ # run the measure to translate the model JSON to an openstudio measure
1690
+ _, idf = run_osw(osw, silent=True)
1691
+ if idf is not None and os.path.isfile(idf):
1692
+ if out_path is not None: # load the JSON string to stdout
1693
+ with open(out_path) as json_file:
1694
+ return json_file.read()
1695
+ else:
1696
+ _parse_os_cli_failure(os.path.dirname(osw))
1697
+
1698
+
1699
+ def _translate_osm_to_hbjson(osm_file, osw_folder):
1700
+ """Translate an OSM to a HBJSON for use in resource extraction commands."""
1701
+ # come up with a temporary path to write the HBJSON
1702
+ out_directory = os.path.join(
1703
+ hb_folders.default_simulation_folder, 'temp_translate')
1704
+ f_name = os.path.basename(osm_file).lower().replace('.osm', '.hbjson')
1705
+ out_path = os.path.join(out_directory, f_name)
1706
+ # run the OSW to translate the OSM to HBJSON
1707
+ osw = from_osm_osw(osm_file, out_path, osw_folder)
1708
+ # load the resulting HBJSON to a dictionary and return it
1709
+ _, idf = run_osw(osw, silent=True)
1710
+ if idf is not None and os.path.isfile(idf):
1711
+ with open(out_path) as json_file:
1712
+ return json.load(json_file)
1713
+ else:
1714
+ _parse_os_cli_failure(os.path.dirname(osw))