Cleanup panel
Drawing Cleanup
Purge, audit, delete zero-length and duplicate geometry and zoom extents in one run, from a checklist that remembers your last choice.
PYDRAWINGCLEANUP, PYDRAWINGCLEANUPCFG for Shift+ClickDCUWhat it does#
Opens a checklist of six steps, runs the ticked ones in order, and reports what each one did. The checklist opens with whatever you ticked last time.
| Step | Ticked at first | What it does |
|---|---|---|
| Purge everything unused | Yes | Erases unreferenced blocks, layers, linetypes, text styles, dimension styles, registered applications, multiline styles, table styles and multileader styles. It repeats until a pass finds nothing, so a block that was only used inside another unused block goes too. |
| Audit the drawing and fix errors | Yes | Queues AUDIT with fix set to Yes. |
| Delete zero-length lines and polylines | Yes | Erases lines and lightweight polylines with no length, in model space and every layout. |
| Delete duplicate entities | Yes | Finds entities of the same type, on the same layer and in the same space, with the same geometry, and erases every copy but the first. |
| Set every entity colour to ByLayer | No | Sets the colour of every entity in model space and the layouts, and of the attributes on block references, to ByLayer. |
| Zoom extents | Yes | Queues ZOOM Extents. |
How to use it#
- Click Drawing Cleanup, or type
PYDRAWINGCLEANUP. - Tick the steps you want and click OK. The choice is remembered for next time (an empty checklist is not).
- If a ticked step erases something (purge, zero-length, duplicates), the tool says exactly what it will delete and asks you to continue.
- A progress window follows the steps. Cancel stops the step that is running and skips the rest; whatever already finished stays done.
- A report window lists one line per step, for example:
Copy puts the report on the clipboard. A toast sums it up, such as "14 purged, 3 zero-length deleted, 12 duplicates deleted".Purge: 14 unused items removed Audit: queued; it runs when this tool finishes and reports on the command line Zero-length: 3 entities deleted Duplicates: 12 extra copies deleted (9 groups), 2 skipped on locked layers Zoom extents: queued - When the tool closes, AUDIT runs and prints its findings on the command line, then the view zooms to extents.
Limitations#
- AUDIT and ZOOM do not run in checklist order: they are queued as commands of their own and run after every other step has finished. Their results are on the command line, not in the report.
- Duplicates are judged by type, layer and geometry only. Colour, linetype, lineweight, text style and text rotation are not compared, so two lines in the same place on the same layer are duplicates even if one is red and dashed. Polylines are compared by their vertices and whether they are closed, not by arc segments or widths. Only lines, circles, arcs, lightweight polylines, text, mtext and block references are checked; a block reference's attribute values are part of its match, so two tags on one point with different values are both kept.
- Zero-length covers lines and lightweight polylines only, not old-style 2D or 3D polylines.
- Entities on locked layers are never erased or recoloured: the report counts them as skipped. Purge is not affected by layer locks.
- Geometry inside block definitions is not checked for duplicates or zero length, and ByLayer does not change the colours inside block definitions. Viewports keep their colour.
- Purge does not cover materials, visual styles, plot styles, groups or empty text. Use AutoCAD's PURGE for those.
Good to know#
Every edit the tool makes itself happens inside its own command, so U takes
it back. AUDIT and ZOOM are separate commands that run after it, so they come off the undo
stack first: undo those two, then the cleanup itself.
The remembered checklist is stored in
%APPDATA%\pyACAD\settings\cfg_pyacad_tab_cleanup_panel_drawing_cleanup_pushbutton.json.
Shift+Click (or PYDRAWINGCLEANUPCFG) deletes it, so the next run
opens with the default ticks again: everything but ByLayer colour.
Other uses#
- Before you issue a drawing, or before it goes to someone as an xref background.
- Tick only Set every entity colour to ByLayer to make a consultant's background follow your layer colours.
- Tick only Delete duplicate entities after a careless copy and paste in place.
Source#
The tool's own files, word for word as shipped in Cleanup.panel/Drawing_Cleanup.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 -*-
"""Drawing hygiene in one run: purge, audit, zero-length and duplicate
geometry, ByLayer colour if asked, zoom extents.
A checklist picks the steps and remembers the last choice (Shift+Click
forgets it). Steps that erase something are confirmed first. Every edit this
tool makes itself happens inside its own command, so Undo takes it back.
AUDIT and ZOOM are queued as commands of their own and run once the tool
finishes, so AUDIT's findings are on the command line rather than in the
report. Entities on locked layers are never touched.
"""
from pyacad import audit, doc, forms, script, toast
__title__ = 'Drawing Cleanup'
TITLE = 'Drawing Cleanup'
DEFAULTS = {'steps': None} # None: nothing remembered yet, so tick the defaults
WHAT_IT_ERASES = {
'purge': 'purge unused blocks, layers, linetypes and styles',
'zero_length': 'delete zero-length lines and polylines',
'duplicates': 'delete duplicate entities, keeping one of each',
}
def has_drawing():
try:
doc.active()
except RuntimeError:
return False
return True
def choose_steps():
"""The ticked steps in run order, or None when the checklist was cancelled."""
saved = script.get_config(DEFAULTS)
picked = forms.select_from_list(
[(label, key) for key, label, _ in audit.STEPS],
title=TITLE, multiselect=True,
prompt='Tick the steps to run. They run top to bottom.',
checked=audit.cleanup_plan(saved['steps']))
if picked is None:
return None
plan = audit.cleanup_plan(picked)
if plan:
# An empty checklist is not worth remembering: the next run would
# open with nothing ticked.
script.save_config({'steps': plan})
return plan
def confirmed(plan):
"""Asks before anything is erased; True to go ahead."""
erasing = [WHAT_IT_ERASES[step] for step in audit.deleting_steps(plan)]
if not erasing:
return True
if len(erasing) > 1:
what = ', '.join(erasing[:-1]) + ' and ' + erasing[-1]
else:
what = erasing[0]
return forms.confirm(
'This will %s.\n\nEntities on locked layers are left alone, and Undo '
'takes the changes back.\n\nContinue?' % what, TITLE)
class Watch(object):
"""Progress and cancel callbacks for one step's share of the bar."""
def __init__(self, bar, start, share):
self.bar = bar
self.start = start
self.share = share
self.stopped = False
def progress(self, done, total):
if not self.bar.update(self.start + self.share * float(done) / max(total, 1)):
self.stopped = True
def cancelled(self):
return self.stopped
def run_step(step, watch):
"""Runs one step; returns its result for audit.format_report()."""
if step == 'purge':
return audit.purge(all=True)
if step == 'audit':
audit.audit(fix=True)
return None # queued
if step == 'zero_length':
handles = audit.zero_length(watch.progress, watch.cancelled)
if watch.stopped:
return {'cancelled': True}
erased, skipped = audit.erase(handles)
return {'count': erased, 'skipped': skipped}
if step == 'duplicates':
groups = audit.duplicates(progress=watch.progress, cancelled=watch.cancelled)
if watch.stopped:
return {'cancelled': True}
erased, skipped = audit.erase(audit.duplicate_extras(groups))
return {'count': erased, 'skipped': skipped, 'groups': len(groups)}
if step == 'bylayer':
changed, skipped = audit.colors_to_bylayer(watch.progress, watch.cancelled)
if watch.stopped:
return {'cancelled': True}
return {'count': changed, 'skipped': skipped}
if step == 'zoom':
doc.send_command('_.ZOOM _E')
return None # queued
raise ValueError('Unknown step %s' % step)
def run_steps(plan):
"""Runs the plan in order; returns [(step, result)]. A failed step is
reported and the rest still run; Cancel stops the rest."""
results = []
share = 1.0 / len(plan)
with forms.progress(TITLE, audit.step_label(plan[0])) as bar:
for index, step in enumerate(plan):
if not bar.update(index * share, audit.step_label(step)):
results.extend((rest, {'not_run': True}) for rest in plan[index:])
break
try:
result = run_step(step, Watch(bar, index * share, share))
except Exception as error:
script.get_logger().error('%s step failed: %s' % (step, error))
result = {'error': str(error)}
results.append((step, result))
if isinstance(result, dict) and result.get('cancelled'):
results.extend((rest, {'not_run': True}) for rest in plan[index + 1:])
break
return results
def run():
if not has_drawing():
toast.warning('No drawing is open', 'Open a drawing, then run Drawing Cleanup')
return
plan = choose_steps()
if plan is None:
return # cancelled: say nothing
if not plan:
toast.info('Nothing to do', 'No steps were ticked')
return
if not confirmed(plan):
return
results = run_steps(plan)
report = audit.format_report(results)
forms.alert(report, TITLE, copy=report)
line = audit.summary(results)
if any(isinstance(result, dict) and result.get('error') for _, result in results):
toast.warning('Drawing Cleanup hit a problem', line)
else:
toast.success('Drawing cleaned', line)
if '__commandpath__' in globals():
run()config.py Runs on Shift+Click. View on GitHub# -*- coding: utf-8 -*-
"""Shift+Click: forgets the steps Drawing Cleanup remembered, so its next
run opens with the default steps ticked again (everything but ByLayer
colour)."""
from pyacad import script, toast
def run():
try:
script.reset_config()
except (IOError, OSError) as error:
toast.error('Could not reset Drawing Cleanup', str(error))
return
toast.success('Drawing Cleanup reset', 'The next run opens with the default steps ticked')
if '__commandpath__' in globals():
run()bundle.yaml The bundle contract: the keys pyACAD reads for this tool. View on GitHubtitle: Drawing Cleanup
tooltip: Purges, audits, deletes zero-length and duplicate geometry and zooms to extents in one run, from a checklist that remembers your last choice. Shift+Click resets the checklist to its defaults.
context: doc
keytip: CC
alias: DCU