Data panel

Excel to Blocks

Reads a workbook made by Blocks to Excel and writes the attribute values and layers you edited back to the blocks, matched by handle.

On the ribbonButton, Data panel
NeedsAn open drawing, and a workbook from Blocks to Excel
CommandPYEXCELTOBLOCKS
AliasEXB
ShortcutNone set (bind one in Shortcuts)

What it does#

The return trip. It reads the workbook, finds each row's block reference by its Handle, compares the row with the block as it is now, and writes back what you changed: attribute values, and the layer. Before anything changes it says how much will change and asks. The command is PYEXCELTOBLOCKS.

How to use it#

  1. Export with Blocks to Excel and edit the workbook: change attribute values, or type another layer name in the Layer column. Save it and close Excel.
  2. With the same drawing open, click Excel to Blocks (or type PYEXCELTOBLOCKS) and pick the workbook.
  3. Read the question, for example Update 12 block references: 30 attribute values, 2 layer changes?, and click Yes.
  4. A toast says how many were updated. If rows were skipped or blocks could not be changed, a dialog lists them, with a Copy button for the full list.
  5. Not what you wanted? U takes the update back.

What goes back, and what does not#

  • It reads the sheet called Blocks, or the first sheet when there is no such sheet.
  • Columns are found by their header text, so you may reorder them, hide them, or delete the ones you do not need. Only Handle is required.
  • An attribute column is written to every listed block that has that tag. A changed cell is a change, and an emptied cell clears the value. A tag column the block does not have is ignored: this tool cannot add attributes.
  • A filled-in Layer cell that differs moves the block to that layer. The layer must already exist. An empty Layer cell leaves the block where it is.
  • Block, Space, X, Y, Z, Rotation and Scale are read-only: edits there are ignored.
  • Rows whose handle is no longer a block reference in the drawing are skipped and listed. Rows with no handle are ignored.
  • A block on a locked layer, or moved to a layer that does not exist, is left as it was and listed with the reason; the rest still go in.

Good to know / limits#

  • Use a workbook only on the drawing it came from. Handles are numbered per drawing, so a handle from another drawing can belong to an unrelated block here, and that block would be updated if it has the same tags.
  • Excel converts what you type. Type 007 into a General cell and it stores the number 7, which is what comes back. Format a column as Text before typing values with leading zeros, long numbers or anything that looks like a date.
  • When every row already matches the drawing, it says Nothing to update and changes nothing.
  • A file that is not an .xlsx, is damaged, or is locked by Excel is reported, and nothing changes.

Other uses#

  • Renumber a set of tags in Excel with a fill-down series, then write them back in one go.
  • Fill in a title block field on every layout from one column.
  • Sort blocks onto the right layers from a spreadsheet: filter by block name, fill the Layer column, run.

Source#

The tool's own files, word for word as shipped in Data.panel/Excel_to_Blocks.pushbutton. Every pyACAD tool is an ordinary bundle: copy these into your own extension and change what you like.

script.py Runs on a click. View on GitHub
# -*- coding: utf-8 -*-
"""Writes a workbook from Blocks to Excel back into the drawing.

Rows are matched to block references by Handle. Only attribute values and
Layer go back: a changed or cleared attribute value is written, a filled-in
Layer that differs moves the block to that layer (it must exist). Position,
rotation and scale columns are read-only. Rows whose block no longer exists are
skipped and listed, and the whole update is one Undo.
"""

from pyacad import blocks, forms, script, toast, xl

__title__ = 'Excel to Blocks'

TITLE = 'Excel to Blocks'
XLSX = 'Excel workbook (*.xlsx)|*.xlsx'
SHOWN = 15                      # handles listed in a dialog before "and N more"


def plural(count, word):
    return '%d %s%s' % (count, word, '' if count == 1 else 's')


def read_sheet(path):
    """The rows of the workbook's Blocks sheet (else its first sheet), or None
    after saying why it could not be read."""
    try:
        names = xl.sheets(path)
        return xl.read(path, sheet='Blocks' if 'Blocks' in names else None)
    except Exception as error:      # not a workbook, locked, damaged: say so either way
        toast.error('Could not read the workbook', str(error))
        return None


def listing(handles):
    shown = ', '.join(handles[:SHOWN])
    if len(handles) > SHOWN:
        shown += ' and %d more' % (len(handles) - SHOWN)
    return shown


def describe(changes):
    """'12 block references: 30 attribute values, 2 layers'."""
    values = sum(len(change.get('attributes', {})) for change in changes.values())
    moves = sum(1 for change in changes.values() if 'layer' in change)
    parts = []
    if values:
        parts.append(plural(values, 'attribute value'))
    if moves:
        parts.append(plural(moves, 'layer change'))
    return '%s: %s' % (plural(len(changes), 'block reference'), ', '.join(parts))


def report(skipped, failed):
    """The dialog text for rows that did not go in; None when they all did."""
    lines = []
    if skipped:
        lines.append('Skipped %s whose block is no longer in this drawing:\n%s'
                     % (plural(len(skipped), 'row'), listing(skipped)))
    if failed:
        lines.append('%s could not be updated:' % plural(len(failed), 'block'))
        for handle in sorted(failed)[:SHOWN]:
            lines.append('  %s: %s' % (handle, failed[handle]))
        if len(failed) > SHOWN:
            lines.append('  and %d more' % (len(failed) - SHOWN))
    return '\n\n'.join(lines) if lines else None


def full_report(skipped, failed):
    lines = ['Skipped (not in the drawing): %s' % h for h in skipped]
    lines += ['Failed %s: %s' % (h, failed[h]) for h in sorted(failed)]
    return '\n'.join(lines)


def run():
    if script.get_document_path() is None:
        toast.warning('No drawing is open', 'Open a drawing, then run %s' % TITLE)
        return

    path = forms.open_file(XLSX, 'Pick a workbook made by Blocks to Excel')
    if not path:
        return                                          # cancelled: say nothing
    sheet = read_sheet(path)
    if sheet is None:
        return

    try:
        changes, skipped = blocks.diff_rows(blocks.snapshots(), sheet)
    except ValueError as error:
        forms.alert(str(error), TITLE)
        return

    if not changes:
        text = report(skipped, {})
        if text:
            forms.alert('Nothing to update: every other row matches the drawing.\n\n' + text,
                        TITLE, copy=full_report(skipped, {}))
        else:
            toast.info('Nothing to update', 'The workbook matches the drawing')
        return

    if not forms.confirm('Update %s?\n\nUndo takes it back.' % describe(changes), TITLE):
        return

    result = blocks.apply_changes(changes)
    detail = plural(result['values'], 'attribute value') + ' written'
    if skipped:
        detail += ', %s skipped' % plural(len(skipped), 'row')
    if result['failed']:
        toast.warning('Updated %s' % plural(result['updated'], 'block reference'), detail)
    else:
        toast.success('Updated %s' % plural(result['updated'], 'block reference'), detail)

    text = report(skipped, result['failed'])
    if text:
        forms.alert(text, TITLE, copy=full_report(skipped, result['failed']))


if '__commandpath__' in globals():
    run()
bundle.yaml The bundle contract: the keys pyACAD reads for this tool. View on GitHub
title: Excel to Blocks
tooltip: Reads a workbook made by Blocks to Excel and writes the attribute values and layers you edited back to the blocks, matched by handle. Rows whose block is gone are skipped and listed.
context: doc
keytip: DE
alias: EXB