Data panel

Find and Replace Attributes

Replaces text inside block attribute values across the drawing, the selection or blocks you pick, in every tag or just one, with or without matching case.

On the ribbonButton, Data panel
NeedsAn open drawing
CommandPYFINDANDREPLACEATTRIBUTES
AliasFRA
ShortcutNone set (bind one in Shortcuts)

What it does#

Replaces one piece of text with another inside attribute values, in the block references you choose, in every attribute or only in one tag. AutoCAD's FIND can search attribute text too, but it cannot keep to one tag. The text is plain text, never a pattern, so a * or a . means itself. The command is PYFINDANDREPLACEATTRIBUTES.

How to use it#

  1. Optionally select the block references to work on.
  2. Click Find and Replace Attributes.
  3. Type the text to find. Your last search is filled in.
  4. Type what to put instead. Leave it empty to remove the text.
  5. Choose where: Selection (offered when something is selected, with how many objects), Whole drawing, or Pick blocks to select them at the command line now.
  6. Choose which attribute: Every tag, or one tag from the list of tags those blocks carry. The list has a search box.
  7. Choose Ignore case or Match case. Your last choice is offered first.
  8. The changes are made straight away, and a toast says how many values changed in how many block references. U takes them back.

Good to know / limits#

  • There is no preview: the replacement happens as soon as you choose the case option. Run Blocks to Excel first if you want to see what is there, and rely on U if the result is wrong.
  • It replaces every occurrence inside a value, not just whole values. Replacing 1 with 2 turns 101 into 202.
  • Blocks on locked layers are left alone, and the toast says how many could not be changed.
  • The find and replace text and the case choice are remembered under %APPDATA%\pyACAD\settings, for every drawing.
  • Only references placed directly in model space or on a layout are searched (or those you select or pick). Xrefs and blocks nested inside other blocks are not.
  • Plain text in the drawing (TEXT, MTEXT) is not touched: only attribute values.

Other uses#

  • Change a project number or revision letter in every title block at once, in the one tag that holds it.
  • Rename a room or system prefix across all the tags that carry it.
  • Remove a stray suffix, such as -OLD, by replacing it with nothing.

Source#

The tool's own files, word for word as shipped in Data.panel/Find_Replace_Attributes.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 -*-
"""Find and replace text inside block attribute values.

Asks what to find and what to put instead (both remembered for next time),
then where: the whole drawing, the selected blocks, or blocks picked now. Then
which attribute (every tag, or one) and whether case must match. The text is
plain, never a pattern. Every change is one Undo; blocks on locked layers are
left alone and reported.
"""

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

__title__ = 'Find and Replace Attributes'

TITLE = 'Find and Replace Attributes'
DEFAULTS = {'find': '', 'replace': '', 'match_case': False}
WHOLE = 'Whole drawing'
PICK = 'Pick blocks'
ALL_TAGS = 'Every tag'
IGNORE_CASE = 'Ignore case'
MATCH_CASE = 'Match case'


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


def choose_refs():
    """The block reference snapshots to search, or None when cancelled."""
    picked = sel.current()
    options = [WHOLE]
    selected = None
    if picked:
        selected = 'Selection (%s)' % plural(len(picked), 'object')
        options.insert(0, selected)
    options.append(PICK)
    where = forms.ask_options('Replace in which block references?', options, TITLE)
    if where is None:
        return None
    if where == WHOLE:
        return blocks.snapshots()
    if where == PICK:
        picked = sel.pick_many('Select block references: ')
        if not picked:
            return None
    return blocks.snapshots(picked)


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

    saved = script.get_config(DEFAULTS)
    find = forms.ask_string('Find this text in attribute values:', saved['find'], TITLE)
    if find is None:
        return
    if not find:
        toast.info('Nothing to find', 'Type the text to look for')
        return
    replace = forms.ask_string('Replace "%s" with (leave empty to remove it):' % find,
                               saved['replace'], TITLE)
    if replace is None:
        return

    refs = choose_refs()
    if refs is None:
        return
    tags = blocks.attribute_tags(refs)
    if not tags:
        toast.info('No attributes to search', 'None of those block references has attributes')
        return

    tag = forms.select_from_list([(ALL_TAGS, '')] + tags, title=TITLE,
                                 prompt='Replace in which attribute?')
    if tag is None:
        return
    case = forms.ask_options('Match case?', [IGNORE_CASE, MATCH_CASE] if not saved['match_case']
                             else [MATCH_CASE, IGNORE_CASE], TITLE)
    if case is None:
        return
    match_case = case == MATCH_CASE
    script.save_config({'find': find, 'replace': replace, 'match_case': match_case})

    found = blocks.replace_in_attributes(refs, find, replace, case_sensitive=match_case,
                                         tag=tag or None)
    if not found:
        toast.info('No matches', 'No attribute value contains "%s"' % find)
        return

    result = blocks.apply_changes(dict((handle, {'attributes': values})
                                       for handle, values in found.items()))
    detail = 'in %s' % plural(result['updated'], 'block reference')
    if result['failed']:
        detail += '; %s could not be changed (locked layer?)' % plural(len(result['failed']), 'block')
        toast.warning('Replaced %s' % plural(result['values'], 'value'), detail)
    else:
        toast.success('Replaced %s' % plural(result['values'], 'value'), detail)


if '__commandpath__' in globals():
    run()
bundle.yaml The bundle contract: the keys pyACAD reads for this tool. View on GitHub
title: Find and Replace Attributes
tooltip: Replaces text in block attribute values across the drawing, the selection or blocks you pick, in every tag or just one, with or without matching case. One Undo takes it all back.
context: doc
keytip: DF
alias: FRA