Layers panel
Layer Mapper
Applies a layer standard from a CSV with the columns old, new and optionally color and linetype: matching layers are renamed, or merged when the new name already exists, and you tick what to apply.
PYLAYERMAPPERLMAPWhat it does#
Reads a CSV that maps old layer names to new ones and brings the drawing into line. Each
layer that matches a row is renamed to the new name, or merged into it when a layer of that
name already exists. New names that no layer was renamed to can be created, with the colour
and linetype the CSV gives. Everything planned is shown with a tick box before anything
changes. The command is PYLAYERMAPPER.
The CSV#
One mapping per row: old,new[,color][,linetype].
old,new,color,linetype
A-WALL*,A-WALL,1,
E-LITE,E-LIGHTING,yellow,
OLD-HIDDEN,M-HIDDEN,4,HIDDEN- old may use
*(any run of characters) and?(one character), matched against the whole name, ignoring case. Each layer takes the first row that fits it, so put specific rows above general ones. - new is the standard name.
- color, optional, is a colour number from 1 to 255 or one of red, yellow, green, cyan, blue, magenta, white (black means 7 too).
- linetype, optional, must already be loaded in the drawing.
- Any columns after the fourth, such as a notes column, are ignored. Blank lines are
skipped, and so is a first row that reads like a header (
old,new,from,to,existing,standardand the like). - Cells may be separated by commas, semicolons or tabs, whichever the first row uses, and may be quoted. Excel's CSV, CSV UTF-8 and Unicode text files all read.
How to use it#
- Click Layer Mapper (or type
PYLAYERMAPPER) and pick the CSV. - If the standard names layers the drawing does not have, it lists them (with the colour and linetype each would get) and asks whether to create them too.
- A checklist shows every step, all ticked: Rename A to B, Merge A into B, Create B (color 3, DASHED). Layers that will be left alone are named above the list. Untick anything that should not change, then confirm.
- The steps run in plan order. A toast sums up what was renamed, merged (and how many objects moved) and created. Steps that failed are counted in the toast and listed in the pyACAD log.
How the plan is made#
- A layer is renamed when no layer has the new name yet. When several layers map to the
same new name (
A-WALL*above), the first is renamed and the rest are merged into it. - A layer is merged when a layer with the new name already exists: everything on it moves across, including objects inside blocks, and the old layer is deleted.
- A layer already called the new name is left as it is. A name that differs only in case is renamed, so the case comes into line.
- Layer 0, Defpoints, xref layers and new names AutoCAD would refuse (with characters such
as
< > / \ : ; ? * | , =) are skipped, with the reason.
Good to know / limits#
- The colour and linetype in the CSV are used only for layers it creates. A layer that is renamed or merged keeps its own colour and linetype.
- A created layer with no colour in the CSV gets colour 7, and one with no linetype gets Continuous. A linetype that is not loaded makes that create step fail; load it first.
- Unticking a rename that a later merge depends on makes the merge fail (its target does not exist); it is reported, the rest still run.
- The whole run is one AutoCAD command, so
Utakes it back.
Other uses#
- Bring a consultant's drawing onto your office standard before you xref it.
- Collapse a drawing's dozens of near-duplicate layers (
WALL,Walls,wall-new) into one with a few wildcard rows. - Keep one CSV per client standard and run the matching one on every drawing that comes in.
Source#
The tool's own files, word for word as shipped in Layers.panel/Layer_Mapper.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 -*-
"""Applies a layer standard from a CSV: renames, merges and creates layers.
The CSV has the columns old,new[,color][,linetype], one row per mapping; old may
use * and ? wildcards and the first row that fits a layer wins. A layer is
renamed to new, or merged into it when a layer of that name already exists.
New names that no layer was renamed to can be created too, with the row's color
and linetype. Everything planned is listed with a tick box before anything
changes, so a row can be left out. The pure work is layers.parse_mapping and
layers.plan_mapping.
"""
__title__ = 'Layer Mapper'
import codecs
from pyacad import doc, forms, layers, script, toast
CSV_FILTER = 'CSV files (*.csv)|*.csv|Text files (*.txt)|*.txt|All files (*.*)|*.*'
SHOWN = 8 # names listed in a prompt before "and N more"
def has_drawing():
try:
doc.active()
return True
except RuntimeError:
return False
def read_text(path):
"""The file's text: UTF-16 when it starts with that byte order mark (Excel's
"Unicode text"), UTF-8 when it decodes, else the Windows code page Excel
saves plain CSV in."""
with open(path, 'rb') as f:
data = f.read()
if data.startswith(codecs.BOM_UTF16_LE) or data.startswith(codecs.BOM_UTF16_BE):
return data.decode('utf-16')
for encoding in ('utf-8', 'cp1252'):
try:
return data.decode(encoding)
except (UnicodeDecodeError, LookupError):
continue
return data.decode('latin-1')
def listed(items, more='and %d more'):
"""Up to SHOWN items, one per line, then a count of the rest."""
lines = [' ' + str(item) for item in items[:SHOWN]]
if len(items) > SHOWN:
lines.append(' ' + more % (len(items) - SHOWN))
return '\n'.join(lines)
def words(step):
"""A plan step in words without its verb: 'MECH (color 4)', '0: reason'."""
return layers.describe_action(step).split(' ', 1)[1]
def apply(actions):
"""Runs the steps in plan order; returns (counts, objects moved, failures)."""
counts = {'rename': 0, 'merge': 0, 'create': 0}
moved = 0
failures = []
for step in actions:
kind = step[0]
try:
if kind == 'rename':
layers.rename(step[1], step[2])
elif kind == 'merge':
moved += layers.merge(step[1], step[2])
elif kind == 'create':
layers.create(step[1], step[2] or 7, step[3] or 'Continuous')
else:
continue
counts[kind] += 1
except Exception as error:
failures.append('%s: %s' % (layers.describe_action(step), error))
return counts, moved, failures
def summary(counts, moved):
parts = []
if counts['rename']:
parts.append('%d renamed' % counts['rename'])
if counts['merge']:
parts.append('%d merged (%d object%s moved)' % (counts['merge'], moved,
'' if moved == 1 else 's'))
if counts['create']:
parts.append('%d created' % counts['create'])
return ', '.join(parts) or 'nothing changed'
def run():
if not has_drawing():
toast.warning('Open a drawing first', 'Layer Mapper changes the active drawing')
return
path = forms.open_file(CSV_FILTER, 'Pick a layer standard (old,new,color,linetype)')
if not path:
return
try:
mapping = layers.parse_mapping(read_text(path))
except (IOError, OSError) as error:
toast.error('Could not read the CSV', str(error))
return
except ValueError as error:
toast.error('The CSV has a problem', str(error))
return
if not mapping:
toast.warning('The CSV has no mapping rows', 'Expected columns old,new[,color][,linetype]')
return
plan = layers.plan_mapping(layers.names(), mapping)
changes = [step for step in plan if step[0] in ('rename', 'merge')]
creates = [step for step in plan if step[0] == 'create']
skips = [step for step in plan if step[0] == 'skip']
if creates and not forms.confirm(
'%d layer%s in the standard %s not in this drawing:\n\n%s\n\nCreate them too?'
% (len(creates), '' if len(creates) == 1 else 's',
'is' if len(creates) == 1 else 'are',
listed([words(s) for s in creates])),
'Layer Mapper'):
creates = []
steps = changes + creates
if not steps:
if skips:
forms.alert('Nothing to change. These layers were left alone:\n\n%s'
% listed([words(s) for s in skips]),
'Layer Mapper')
else:
toast.info('Nothing to change', 'The layers already follow the standard')
return
prompt = 'Untick anything that should not change.'
if skips:
prompt += ' %d layer%s left alone: %s.' % (
len(skips), '' if len(skips) == 1 else 's',
'; '.join(words(s) for s in skips[:3])
+ ('; ...' if len(skips) > 3 else ''))
picked = forms.select_from_list([(layers.describe_action(s), s) for s in steps],
title='Layer Mapper: %d change%s'
% (len(steps), '' if len(steps) == 1 else 's'),
multiselect=True, prompt=prompt, checked=steps)
if not picked:
return
# The picker hands back what was ticked; run it in plan order, since a merge
# may target a name an earlier rename made.
picked = [step for step in steps if step in picked]
counts, moved, failures = apply(picked)
for failure in failures:
script.get_logger().warning(failure)
if failures:
toast.warning('Layer standard applied with %d problem%s'
% (len(failures), '' if len(failures) == 1 else 's'),
'%s. %s' % (summary(counts, moved), failures[0]))
else:
toast.success('Layer standard applied', summary(counts, moved))
if '__commandpath__' in globals():
run()bundle.yaml The bundle contract: the keys pyACAD reads for this tool. View on GitHubtitle: Layer Mapper
tooltip: Applies a layer standard from a CSV with the columns old,new and optionally color and linetype. Matching layers are renamed, or merged when the new name already exists, and you tick what to apply before anything changes.
context: doc
keytip: LP
alias: LMAP