Dynamo Version 3 Excel Export

In Dynamo v3 the default Excel exporting node doesn’t seem to exist anymore, but is there an equivalent node in v3?

Using the XML export node it struggles to overwrite the data without having to close the excel file every time I run the script, which makes testing the output format much harder (sometimes even when I close the file I still get an error the file is in use).

It also seems to struggle with list levels in the export compared to v2, and sometimes corrupts the file when I export.

Noting I’m saving this excel file on Sharepoint.as an xlsx file, I’ve also tried the Crumple export which is slightly improved but has strange formatting.

1 Like

My old Excel node in Crumple as well as the old one in Dynamo both depended on Microsoft interop tools I believe, which historically got messed with by Windows updates and in NET Core (2025+) to my understanding aren’t supported - I expect this motivated the move away from these nodes.

Personally in C# I am using ClosedXML which is just a wrapper around OpenXML to my understanding, and it’s fit all my need so far. There might be quirks in how this node behaves behind the scenes in Dynamo’s code, so might be worth showing examples here for the development team if they seem unintentional.

These days Crumple uses pandas via Cpython3 if installed and should handle multi-single sheet scenarios pretty cleanly. I haven’t added any post formatting, it just expects everything to be strings once it goes into Excel.

I’ve got guides on my nodes here:

2 Likes

Unfortunately, only Excel Interop allows this. A workaround is to display the xlsx data in a third-party window (such as WPF, Winform, HTML).

try this version with python and pandas

import clr
import sys
import System
import os
import sysconfig
# standard library path
sys.path.append(sysconfig.get_path("platstdlib"))
# site-package library path
sys.path.append(sysconfig.get_path("platlib"))
import pandas as pd
from openpyxl.worksheet.table import Table, TableStyleInfo
from openpyxl.utils import get_column_letter
import traceback
import numpy as np


def get_shape(lst, shape=()):
    """
    returns the shape of nested lists 
    """
    if not hasattr(lst, "__iter__") or isinstance(lst, (str, System.String)):
        # base case
        return {"shape":shape, "ndim":len(shape)}
    # peek ahead and assure all lists in the next depth
    # have the same length
    if hasattr(lst[0], "__iter__") and not isinstance(lst[0], (str, System.String)):
        l = len(lst[0])
    shape += (len(lst), )
    # recurse
    shape = get_shape(lst[0], shape)

    return shape #{"shape":shape, "ndim":len(shape)} 

def autosize_columns(df, ws):
    """
    A function that sets the width of each column in a given worksheet (ws) based on the maximum length of data in each column in the Pandas DataFrame (df).

    Args:
    - df: Pandas DataFrame containing the data to be written to the worksheet.
    - ws: Worksheet object representing the worksheet where the data will be written.

    Returns: None
    """
    margin = 7
    lstvalues = df.values.tolist()
    lstColvalues = df.columns.values.tolist()
    column_widths = []
    for row in [lstColvalues] + lstvalues:
        for i, cell in enumerate(row):
            current_lenCell = len(str(cell)) if cell is not None else 1
            if len(column_widths) > i:
                if current_lenCell > column_widths[i]:
                    column_widths[i] = current_lenCell
            else:
                column_widths += [current_lenCell]
    for i, column_width in enumerate(column_widths, 1):  # ,1 to start at 1
        ws.column_dimensions[get_column_letter(i)].width = column_width + margin

# Define list/unwrap list functions
def tolist(input):
    result = input if isinstance(input, list) else [input]
    return result

# Collect inputs
file_path   = IN[0]
has_header = IN[1]
sheet_names = np.array(tolist(IN[2]))
data_lists = IN[3] 
open_xlsx = IN[4]
#
# check structure
data_shape = get_shape(data_lists)
# check and add a level to iterate for zip
if data_shape["ndim"] == 2:
    data_lists = [data_lists]
#
print(sheet_names.shape, sheet_names.ndim)
print(data_shape)

if sheet_names.ndim ==1 and len(sheet_names) == len(data_lists):
    input_structure_is_correct = True
else:
    input_structure_is_correct = False

# Output lists
wasWritten_list, error_list = [],[]
    
if not input_structure_is_correct:
    error_list.append("wrong list input structure")
else:

    # create a Pandas Excel writer using XlsxWriter as the engine.
    with pd.ExcelWriter(file_path, mode="w", engine="openpyxl") as xlsx:
        for sheet_name, datas in zip(sheet_names, data_lists):
            try: 
                if has_header:
                    lst_columnsName = datas.pop(0)
                    df = pd.DataFrame(datas, columns = lst_columnsName)
                    df.to_excel(xlsx, sheet_name=sheet_name, index=False)
                else:
                    df = pd.DataFrame(datas)
                    df.to_excel(xlsx, sheet_name=sheet_name, index=False, header = False)
                #
                # get worksheet
                ws = xlsx.sheets[sheet_name]
                if has_header:
                    table = Table(displayName="Table_" + sheet_name, ref="A1:" + get_column_letter(ws.max_column) + str(ws.max_row))
                    # Add a default style with striped rows and banded columns
                    style = TableStyleInfo(name="TableStyleMedium9", showFirstColumn=False, showLastColumn=False, showRowStripes=True, showColumnStripes=False)
                    table.tableStyleInfo = style
                    ws.add_table(table)
                #
                autosize_columns(df, ws)
                #
                wasWritten_list.append(True)
                error_list.append(f"SUCCESS: Data written '{sheet_name}'")
            except Exception as ex:
                print(traceback.format_exc())
                wasWritten_list.append(False)
                error_list.append(f"ERROR: Data not written : {ex}")
                
if wasWritten_list and all(wasWritten_list) and open_xlsx:
    os.startfile(file_path)
    
OUT = data_lists
4 Likes

Thanks, this worked.

Does it overwrite the existing excel, or should I export a bunch of blank cells to do a ‘fake’ overwrite then re-export the data?

No, that’s not necessary; all the data in the sheet is replaced.

1 Like