Skip to content

Commit 89e41ea

Browse files
committed
feat(xlsx): Add an exporter directly to Excel
1 parent a035fdc commit 89e41ea

4 files changed

Lines changed: 268 additions & 14 deletions

File tree

dragonfly_trace/cli/translate.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
import click
33
import sys
44
import logging
5+
import io
6+
import base64
57

68
from ladybug.commandutil import process_content_to_output
79
from dragonfly.model import Model
810
from dragonfly_trace.writer import model_to_trace700_csv as model_to_csv
11+
from dragonfly_trace.writer import model_to_trace700_workbook as model_to_workbook
912

1013

1114
_logger = logging.getLogger(__name__)
@@ -147,3 +150,157 @@ def model_to_trace700_csv(
147150
)
148151

149152
return process_content_to_output(csv_str, output_file)
153+
154+
155+
@translate.command('model-to-trace700-xlsx')
156+
@click.argument('model-file', type=click.Path(
157+
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
158+
@click.option('--multiplier/--full-geometry', ' /-fg', help='Flag to note if the '
159+
'multipliers on each Building story will be passed along to the '
160+
'generated Honeybee Room objects or if full geometry objects should be '
161+
'written for each story in the building.', default=True, show_default=True)
162+
@click.option('--plenum/--separate-plenum', '-p/-sp', help='Flag to indicate whether '
163+
'ceiling/floor plenum depths assigned to Room2Ds should simply be '
164+
'reported as plenum depths in the tables or they should be used to generate '
165+
'distinct separated plenum rooms in the translation.',
166+
default=True, show_default=True)
167+
@click.option('--merge-method', '-m', help='Text to describe how the Room2Ds should '
168+
'be merged into individual Rooms during the translation. Specifying a '
169+
'value here can be an effective way to reduce the number of Room '
170+
'volumes in the resulting Model and, ultimately, yield a faster simulation '
171+
'time with less results to manage. Choose from: None, Zones, PlenumZones, '
172+
'Stories, PlenumStories.', type=str, default='None', show_default=True)
173+
@click.option('--imperial/--metric', '-ip/-si', help='Flag to note whether imperial '
174+
'or metric units should be used for values in the output XLSX.',
175+
default=True, show_default=True)
176+
@click.option('--geometry-ids/--geometry-names', ' /-gn', help='Flag to note whether a '
177+
'cleaned version of all geometry display names should be used instead '
178+
'of identifiers when translating the Model.',
179+
default=True, show_default=True)
180+
@click.option('--resource-ids/--resource-names', ' /-rn', help='Flag to note whether a '
181+
'cleaned version of all resource display names should be used instead '
182+
'of identifiers when translating the Model.',
183+
default=True, show_default=True)
184+
@click.option('--output-file', '-f', help='Optional XLSX file to output the content '
185+
'of the translation. By default it printed out to stdout.',
186+
type=click.File('wb'), default='-', show_default=True)
187+
def model_to_trace700_xlsx_cli(
188+
model_file, multiplier, plenum, merge_method, imperial,
189+
geometry_ids, resource_ids, output_file
190+
):
191+
"""Translate a Dragonfly Model to an Excel file with tables for TRACE 700 attributes.
192+
193+
The resulting Excel tables can be copied into the tables that appear in the
194+
Component Tree view of TRACE 700. The order and organization of rooms in
195+
the resulting matrix should match that of the gbXML produced from the same model.
196+
197+
\b
198+
Args:
199+
model_file: Full path to a Dragonfly Model file (DFJSON or DFpkl).
200+
"""
201+
try:
202+
full_geometry = not multiplier
203+
separate_plenum = not plenum
204+
metric = not imperial
205+
geo_names = not geometry_ids
206+
res_names = not resource_ids
207+
model_to_trace700_xlsx(
208+
model_file, full_geometry, separate_plenum, merge_method,
209+
metric, geo_names, res_names, output_file
210+
)
211+
except Exception as e:
212+
_logger.exception('System translation failed.\n{}'.format(e))
213+
sys.exit(1)
214+
else:
215+
sys.exit(0)
216+
217+
218+
def model_to_trace700_xlsx(
219+
model_file, full_geometry=False, separate_plenum=False, merge_method='None',
220+
metric=False, geometry_names=False, resource_names=False, output_file=None,
221+
multiplier=True, plenum=True, imperial=True, geometry_ids=True, resource_ids=True
222+
):
223+
"""Translate a Dragonfly Model to an Excel file with tables for TRACE 700 attributes.
224+
225+
The resulting Excel tables can be copied into the tables that appear in the
226+
Component Tree view of TRACE 700. The order and organization of rooms in
227+
the resulting matrix should match that of the gbXML produced from the same model.
228+
229+
Args:
230+
model: A dragonfly Model for which a TRACE 700 XLSX file will be returned.
231+
multiplier: If True, the multipliers on this Model's Stories will be
232+
passed along to the XLSX. If False, full geometry objects will be written
233+
for each and every floor in the building that are represented through
234+
multipliers and all resulting multipliers will be 1. (Default: True).
235+
separate_plenum: Boolean to indicate whether ceiling/floor plenum depths
236+
assigned to Room2Ds should simply be reported as plenum depths in the
237+
tables or they should be used to generate distinct separated plenum
238+
rooms in the translation. (Default: False).
239+
merge_method: An optional text string to describe how the Room2Ds should
240+
be merged into individual Rooms during the translation. Specifying a
241+
value here can be an effective way to reduce the number of Room
242+
volumes in the resulting model and, ultimately, yield a faster
243+
simulation time in the destination engine with fewer results
244+
to manage. Note that Room2Ds will only be merged if they form a
245+
continuous volume. Otherwise, there will be multiple Rooms per
246+
zone or story, each with an integer added at the end of their
247+
identifiers. Choose from the following options:
248+
249+
* None - No merging of Room2Ds will occur
250+
* Zones - Room2Ds in the same zone will be merged
251+
* PlenumZones - Only plenums in the same zone will be merged
252+
* Stories - Rooms in the same story will be merged
253+
* PlenumStories - Only plenums in the same story will be merged
254+
255+
metric: Boolean to note whether the units of the values in the resulting
256+
matrix are in SI (True) instead of IP (False). (Default: False).
257+
geometry_names: Boolean to note whether a cleaned version of all geometry
258+
display names should be used instead of identifiers when translating
259+
the Model to OSM and IDF. Using this flag will affect all Rooms, Faces,
260+
Apertures, Doors, and Shades. It will generally result in more read-able
261+
names in the OSM and IDF but this means that it will not be easy to map
262+
the EnergyPlus results back to the original Honeybee Model. Cases
263+
of duplicate IDs resulting from non-unique names will be resolved
264+
by adding integers to the ends of the new IDs that are derived from
265+
the name. (Default: False).
266+
resource_names: Boolean to note whether a cleaned version of all resource
267+
display names should be used instead of identifiers when translating
268+
the Model to OSM and IDF. Using this flag will affect all Materials,
269+
Constructions, ConstructionSets, Schedules, Loads, and ProgramTypes.
270+
It will generally result in more read-able names for the resources
271+
in the OSM and IDF. Cases of duplicate IDs resulting from non-unique
272+
names will be resolved by adding integers to the ends of the new IDs
273+
that are derived from the name. (Default: False).
274+
output_file: Optional XLSX file to output the XLSX content of the translation.
275+
By default this content will be returned from this method as a
276+
base64 string.
277+
"""
278+
# load the model and translate it to a Workbook
279+
model = Model.from_file(model_file)
280+
exclude_plenums = not separate_plenum
281+
workbook = model_to_workbook(
282+
model, multiplier, exclude_plenums, merge_method,
283+
metric, geometry_names, resource_names
284+
)
285+
if isinstance(output_file, str):
286+
workbook.save(output_file)
287+
else:
288+
# save workbook to a byte stream
289+
out = io.BytesIO()
290+
workbook.save(out)
291+
# retrieve bytes and write them directly to file objects with wb encoding
292+
out.seek(0) # reset stream position to the beginning
293+
excel_bytes = out.read()
294+
if output_file is not None and output_file.name != '<stdout>' and \
295+
output_file.mode == 'wb':
296+
output_file.write(excel_bytes)
297+
else: # convert the bytes to a base64 string
298+
b = base64.b64encode(excel_bytes)
299+
if output_file is None:
300+
f_contents = b.decode('utf-8')
301+
return f_contents
302+
elif output_file.mode == 'w':
303+
f_contents = b.decode('utf-8')
304+
output_file.write(f_contents)
305+
else:
306+
output_file.write(b)

dragonfly_trace/loads.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from ladybug.datatype.power import Power
66
from ladybug.datatype.energyflux import EnergyFlux
7-
from honeybee.altnumber import autocalculate
87

98

109
def people_and_lights_trace700_matrix(rooms, si_units=False):
@@ -51,18 +50,8 @@ def people_and_lights_trace700_matrix(rooms, si_units=False):
5150
ppl_obj = room.properties.energy.people
5251
if ppl_obj is not None:
5352
ppl_count = room.floor_area * ppl_obj.people_per_area
54-
act_sch = ppl_obj.activity_schedule
55-
try: # ScheduleRuleset
56-
vals = []
57-
for sch in act_sch.typical_day_schedules:
58-
vals.extend(sch.values)
59-
act_level = max(vals)
60-
except AttributeError: # ScheduleFixedInterval
61-
act_level = max(act_sch.values)
62-
latent_fract = ppl_obj.latent_fraction \
63-
if ppl_obj.latent_fraction != autocalculate else 0.5
64-
sensible_ppl = act_level * (1 - latent_fract)
65-
latent_ppl = act_level * latent_fract
53+
sensible_ppl = ppl_obj.activity_max_sensible
54+
latent_ppl = ppl_obj.activity_max_latent
6655
else:
6756
ppl_count = 0
6857
sensible_ppl = 73.26775

dragonfly_trace/writer.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from __future__ import division
44

55
from collections import OrderedDict
6+
try:
7+
import openpyxl
8+
except Exception: # we are in Python 2 and Excel export is not possible
9+
openpyxl = None
610

711
from ladybug.datatype.area import Area
812
from ladybug.datatype.distance import Distance
@@ -396,3 +400,106 @@ def model_to_trace700_csv(
396400
csv_matrix.append(','.join([str(val) for val in row]))
397401

398402
return '\n'.join(csv_matrix)
403+
404+
405+
def model_to_trace700_workbook(
406+
model, use_multiplier=True, exclude_plenums=True, merge_method=None,
407+
si_units=False, geometry_names=False, resource_names=False
408+
):
409+
"""Generate an Excel Workbook (openpyxl) with TRACE 700 attributes of a Model.
410+
411+
The resulting openpyxl Workbook can be saved and opened in Excel. The data
412+
in the tables can then be copied into the tables that appear in the Component
413+
Tree view of TRACE 700. The order and organization of rooms in the resulting
414+
matrix should match that of the gbXML produced from the same model.
415+
416+
Args:
417+
model: A dragonfly Model for which a TRACE 700 Excel Workbook will be returned.
418+
use_multiplier: If True, the multipliers on this Model's Stories will be
419+
passed along to the Workbook. If False, full geometry objects will be written
420+
for each and every floor in the building that are represented through
421+
multipliers and all resulting multipliers will be 1. (Default: True).
422+
exclude_plenums: Boolean to indicate whether ceiling/floor plenum depths
423+
assigned to Room2Ds should be ignored during translation. This
424+
results in each Room2D translating to a single Honeybee Room at
425+
the full floor_to_ceiling_height instead of a base Room with (a)
426+
plenum Room(s). (Default: True).
427+
merge_method: An optional text string to describe how the Room2Ds should
428+
be merged into individual Rooms during the translation. Specifying a
429+
value here can be an effective way to reduce the number of Room
430+
volumes in the resulting model and, ultimately, yield a faster
431+
simulation time in the destination engine with fewer results
432+
to manage. Note that Room2Ds will only be merged if they form a
433+
continuous volume. Otherwise, there will be multiple Rooms per
434+
zone or story, each with an integer added at the end of their
435+
identifiers. Choose from the following options:
436+
437+
* None - No merging of Room2Ds will occur
438+
* Zones - Room2Ds in the same zone will be merged
439+
* PlenumZones - Only plenums in the same zone will be merged
440+
* Stories - Rooms in the same story will be merged
441+
* PlenumStories - Only plenums in the same story will be merged
442+
443+
si_units: Boolean to note whether the units of the values in the resulting
444+
matrix are in SI (True) instead of IP (False). (Default: False).
445+
geometry_names: Boolean to note whether a cleaned version of all geometry
446+
display names should be used instead of identifiers when translating
447+
the Model to OSM and IDF. Using this flag will affect all Rooms, Faces,
448+
Apertures, Doors, and Shades. It will generally result in more read-able
449+
names in the OSM and IDF but this means that it will not be easy to map
450+
the EnergyPlus results back to the original Honeybee Model. Cases
451+
of duplicate IDs resulting from non-unique names will be resolved
452+
by adding integers to the ends of the new IDs that are derived from
453+
the name. (Default: False).
454+
resource_names: Boolean to note whether a cleaned version of all resource
455+
display names should be used instead of identifiers when translating
456+
the Model to OSM and IDF. Using this flag will affect all Materials,
457+
Constructions, ConstructionSets, Schedules, Loads, and ProgramTypes.
458+
It will generally result in more read-able names for the resources
459+
in the OSM and IDF. Cases of duplicate IDs resulting from non-unique
460+
names will be resolved by adding integers to the ends of the new IDs
461+
that are derived from the name. (Default: False).
462+
463+
Returns:
464+
A base64 string of content to be written into an Excel file. The contents
465+
contain all tables
466+
needed to specify room loads in TRACE 700.
467+
"""
468+
# check that we could successfully import
469+
assert openpyxl is not None, 'Export to Excel is only available in Python 3. ' \
470+
'Either switch to using Python 3 or use the model_to_trace700_csv instead.'
471+
472+
# get the matrices to be written to CSV format
473+
room_matrix, airflows_matrix, people_and_lights_matrix, misc_loads_matrix = \
474+
model_to_trace700_matrix(
475+
model, use_multiplier, exclude_plenums, merge_method,
476+
si_units, geometry_names, resource_names
477+
)
478+
479+
# put all of the matrices into one master Excel workbook
480+
workbook = openpyxl.Workbook()
481+
# add the Room table
482+
ws = workbook.active
483+
ws.title = 'Rooms'
484+
title_cell = ws['A1']
485+
title_cell.value = 'Rooms'
486+
title_cell.font = openpyxl.styles.Font(size=16, bold=True)
487+
for row in room_matrix:
488+
ws.append(row)
489+
# add the Airflows table
490+
ws = workbook.create_sheet('Airflows')
491+
ws['A1'] = 'Airflows'
492+
for row in airflows_matrix:
493+
ws.append(row)
494+
# add the People & Lighting table
495+
ws = workbook.create_sheet('People & Lighting')
496+
ws['A1'] = 'People & Lighting'
497+
for row in people_and_lights_matrix:
498+
ws.append(row)
499+
# add the Miscellaneous Loads table
500+
ws = workbook.create_sheet('Miscellaneous Loads')
501+
ws['A1'] = 'Miscellaneous Loads'
502+
for row in misc_loads_matrix:
503+
ws.append(row)
504+
505+
return workbook

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
dragonfly-energy>=1.42.0
1+
dragonfly-energy>=1.42.10
2+
openpyxl==3.1.5

0 commit comments

Comments
 (0)