From 15520ee4d4b0cf2ced00fbfd1dd5a37fdf9419c1 Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Fri, 1 May 2026 09:59:27 -0700 Subject: [PATCH 1/2] fix(translate): Use better practices when exporting --- dragonfly_trace/cli/translate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dragonfly_trace/cli/translate.py b/dragonfly_trace/cli/translate.py index 449dbd1..d3cff20 100644 --- a/dragonfly_trace/cli/translate.py +++ b/dragonfly_trace/cli/translate.py @@ -288,9 +288,9 @@ def model_to_trace700_xlsx( # save workbook to a byte stream out = io.BytesIO() workbook.save(out) - # retrieve bytes and write them directly to file objects with wb encoding + workbook.close() out.seek(0) # reset stream position to the beginning - excel_bytes = out.read() + excel_bytes = out.getvalue() if output_file is not None and output_file.name != '' and \ output_file.mode == 'wb': output_file.write(excel_bytes) From bc274bd3e5d8f2d159e86c8f8de15d5e46ea86f4 Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Mon, 4 May 2026 11:48:59 -0700 Subject: [PATCH 2/2] feat(xlsx): Add formatting to make tables look like TRACE 700 --- dragonfly_trace/airflows.py | 49 +++++++++++++++++++++++++ dragonfly_trace/loads.py | 43 ++++++++++++++++++++-- dragonfly_trace/writer.py | 71 ++++++++++++++++++++++++++++++++++--- 3 files changed, 156 insertions(+), 7 deletions(-) diff --git a/dragonfly_trace/airflows.py b/dragonfly_trace/airflows.py index dec3359..4345dc4 100644 --- a/dragonfly_trace/airflows.py +++ b/dragonfly_trace/airflows.py @@ -5,6 +5,55 @@ from ladybug.datatype.volumeflowrate import VolumeFlowRate from ladybug.datatype.volumeflowrateintensity import VolumeFlowRateIntensity +# formatting for each attribute in the airflows table +AIRFLOW_TABLE_FORMAT = ( + 'user', + 'default', + 'default', + 'default', + 'default', + 'user', + 'user', + 'user', + 'user', + 'locked', + 'locked', + 'locked', + 'locked', + 'default', + 'locked', + 'locked', + 'locked', + 'locked', + 'locked', + 'locked', + 'locked', + 'locked', + 'default', + 'user', + 'user', + 'user', + 'user', + 'default', + 'locked', + 'default', + 'locked', + 'default', + 'locked', + 'default', + 'locked', + 'default', + 'default', + 'default', + 'default', + 'default', + 'default', + 'default', + 'default', + 'default', + 'default' +) + def airflows_trace700_matrix(rooms, si_units=False): """Get a matrix for the "Airflows" table of the TRACE 700 Component Tree. diff --git a/dragonfly_trace/loads.py b/dragonfly_trace/loads.py index 38f0aff..6d3dc2f 100644 --- a/dragonfly_trace/loads.py +++ b/dragonfly_trace/loads.py @@ -5,6 +5,35 @@ from ladybug.datatype.power import Power from ladybug.datatype.energyflux import EnergyFlux +PEOPLE_AND_LIGHTS_TABLE_FORMAT = ( + 'user', + 'default', + 'default', + 'default', + 'user', + 'user', + 'varies', + 'varies', + 'default', + 'default', + 'user', + 'locked', + 'user', + 'user', + 'default' +) + +MISCELLANEOUS_LOADS_TABLE_FORMAT = ( + 'user', + 'locked', + 'default', + 'default', + 'user', + 'user', + 'default', + 'default' +) + def people_and_lights_trace700_matrix(rooms, si_units=False): """Get a matrix for the "People & Lighting" table of the TRACE 700 Component Tree. @@ -44,7 +73,7 @@ def people_and_lights_trace700_matrix(rooms, si_units=False): ] # loop through the rooms and add each of the attributes - load_mtx = [] + load_mtx, ppl_default = [], [] for room in rooms: # calculate the total number of people ppl_obj = room.properties.energy.people @@ -52,10 +81,12 @@ def people_and_lights_trace700_matrix(rooms, si_units=False): ppl_count = room.floor_area * ppl_obj.people_per_area sensible_ppl = ppl_obj.activity_max_sensible latent_ppl = ppl_obj.activity_max_latent + ppl_default.append(False) else: ppl_count = 0 sensible_ppl = 73.26775 latent_ppl = 73.26775 + ppl_default.append(True) # get the lighting power density light_obj = room.properties.energy.lighting if light_obj is not None: @@ -75,7 +106,7 @@ def people_and_lights_trace700_matrix(rooms, si_units=False): 1, 'workstation/person', light_type, - '', + '!UnInitialized!', lpd, flux_unit, 'Cooling Only (Design)' @@ -94,7 +125,13 @@ def people_and_lights_trace700_matrix(rooms, si_units=False): # round the numbers so that they display nicely for row_i in (6, 7): - load_matrix[row_i] = [round(val) for val in load_matrix[row_i]] + clean_vals = [] + for val, is_def in zip(load_matrix[row_i], ppl_default): + val = round(val) + if is_def: + val = str(val) + clean_vals.append(val) + load_matrix[row_i] = clean_vals for row_i in (4, 12): load_matrix[row_i] = [round(val, 3) for val in load_matrix[row_i]] diff --git a/dragonfly_trace/writer.py b/dragonfly_trace/writer.py index a6af3b6..289cf98 100644 --- a/dragonfly_trace/writer.py +++ b/dragonfly_trace/writer.py @@ -15,9 +15,43 @@ from honeybee.typing import clean_and_number_string from dragonfly.room2d import Room2D -from .airflows import airflows_trace700_matrix +from .airflows import airflows_trace700_matrix, AIRFLOW_TABLE_FORMAT from .loads import people_and_lights_trace700_matrix, \ - miscellaneous_loads_trace700_matrix + miscellaneous_loads_trace700_matrix, PEOPLE_AND_LIGHTS_TABLE_FORMAT, \ + MISCELLANEOUS_LOADS_TABLE_FORMAT + +# formatting for each attribute in the rooms table +ROOM_TABLE_FORMAT = ( + 'user', + 'locked', + 'locked', + 'default', + 'default', + 'default', + 'user', + 'user', + 'user', + 'user', + 'user', + 'default', + 'user', + 'user', + 'varies', + 'user', + 'user', + 'default', + 'default', + 'default', + 'default', + 'default', + 'default', + 'user', + 'default', + 'default', + 'default', + 'user', + 'default' +) def rooms_to_trace700_matrix(rooms, si_units=False): @@ -91,14 +125,14 @@ def rooms_to_trace700_matrix(rooms, si_units=False): cool_set_back = set_pt.cooling_setback heat_set_back = set_pt.heating_setback humid_pt = set_pt.dehumidifying_setpoint \ - if set_pt.dehumidifying_setpoint is not None else 50 + if set_pt.dehumidifying_setpoint is not None else '50' else: room_type = 'Unconditioned' cool_set_pt = 50 heat_set_pt = 0 cool_set_back = 50 heat_set_back = 0 - humid_pt = 50 + humid_pt = '50' # put all attributes into a list room_attr = [ @@ -481,15 +515,19 @@ def model_to_trace700_workbook( # add the Room table ws = workbook.active _add_workbook_table(ws, 'Rooms', room_matrix) + _apply_table_format(ws, ROOM_TABLE_FORMAT) # add the Airflows table ws = workbook.create_sheet('Airflows') _add_workbook_table(ws, 'Airflows', airflows_matrix) + _apply_table_format(ws, AIRFLOW_TABLE_FORMAT) # add the People & Lighting table ws = workbook.create_sheet('People & Lighting') _add_workbook_table(ws, 'People & Lighting', people_and_lights_matrix) + _apply_table_format(ws, PEOPLE_AND_LIGHTS_TABLE_FORMAT) # add the Miscellaneous Loads table ws = workbook.create_sheet('Miscellaneous Loads') _add_workbook_table(ws, 'Miscellaneous Loads', misc_loads_matrix) + _apply_table_format(ws, MISCELLANEOUS_LOADS_TABLE_FORMAT) return workbook @@ -548,3 +586,28 @@ def _add_workbook_table(ws, title, matrix): cell.font = bold_font cell.fill = grey_fill title_cell.font = title_font + + +def _apply_table_format(ws, formatting): + """Apply formatting to a worksheet to make it look like a TRACE 700 table.""" + # define formatting styles to be used to make the table like TRACE + default_font = openpyxl.styles.Font(color='FF0000') + locked_fill = openpyxl.styles.PatternFill( + start_color='A9A9A9', end_color='A9A9A9', fill_type='solid' + ) + locked_font = openpyxl.styles.Font(color='808080') + + # loop through the rows and apply the formatting + for row, tr_format in zip(ws.iter_rows(min_row=2), formatting): + if tr_format == 'locked': + for cell in row[1:]: + cell.fill = locked_fill + cell.font = locked_font + elif tr_format == 'default': + for cell in row[1:]: + cell.font = default_font + elif tr_format == 'varies': + for cell in row[1:]: + if isinstance(cell.value, str): + cell.font = default_font + cell.value = float(cell.value)