Xrefs panel
Xref Repath
Change every xref's saved path in one go: relative, absolute, find and replace, or point them all at one folder, with a preview of each old and new path first.
PYXREFREPATHXRPWhat it does#
Works out a new saved path for each top-level xref in one of four ways, shows you the whole plan, and applies it only when you say yes:
| Mode | What it does to each path | Left alone when |
|---|---|---|
| Make relative | Relative to the drawing's folder: .\xref.dwg beside the drawing,
..\Xref\site.dwg from a sibling folder. |
It is already relative, or it is on another drive or network share. |
| Make absolute | Resolved against the drawing's folder into a full path. | It is already absolute. |
| Find and replace in paths | Every occurrence of the text you type is replaced, ignoring case. | The text is not in its path. |
| Point all at a folder | The path becomes <folder>\<the xref's file name>. |
That file is not in the folder, or the xref already points there. |
An xref with no saved path is always left alone. After the change, each xref is reloaded from its new path. An xref that was unloaded keeps its new path and stays unloaded until you reload it.
How to use it#
- Click Xref Repath, or type
PYXREFREPATH. - Pick the mode.
- Answer what the mode needs. Make relative and Make absolute need nothing more, but the drawing must have been saved: a toast says "Save the drawing first" otherwise. Find and replace asks for the text to find, then the text to put in its place (leave it empty to delete the text). Point all at a folder opens a folder picker, starting in the drawing's folder.
- Read the preview. It lists each change as the xref's name with a
fromline and atoline, then every xref left alone with the reason. It shows 12 of each; Copy puts the full plan on the clipboard. Click OK. - If there is anything to change, confirm Repath ... as shown? If the plan changes nothing, the tool stops after the preview.
- A toast reports the result, for example "Repathed 4 xrefs" with "2 left as they were" under it. Failures open a window with the reasons, as in Xref Manager.
Limitations#
- Nested xrefs are never repathed: they follow their parent. Open the parent drawing and repath there.
- AutoCAD accepts a path it cannot resolve without an error. After the repath the tool reads the statuses again: an xref that was loaded and is not loaded now is reported as failed. In the find and replace and folder modes, which are meant to fix paths, an xref that was missing and is still missing is reported too. The new path stays in place either way; Undo puts the old one back.
- Make absolute resolves a bare file name such as
site.dwgagainst the drawing's folder, even if AutoCAD had found that file on a support or project path. Check the preview for those. - Make relative cannot make a path on another drive or share relative, since there is no relative path between them. Those are listed as left alone.
- Find and replace works on the whole path, folders and file name alike, so a short
search such as
Achanges more than you meant. Read the preview.
Good to know#
The whole repath is one AutoCAD command, so one U undoes every path it
changed. Images, PDF underlays and other attachments are not touched: only DWG xrefs.
Other uses#
- The project moved to a new server: find
\\oldserver\projects, replace with\\newserver\projects. - Make every path relative before you zip the project for a consultant, so the xrefs resolve wherever they unzip it.
- Swap a whole set of backgrounds for another issue: keep each issue in its own folder
with the same file names (
Issue B\site.dwg,Issue C\site.dwg) and point all at the new folder. Any file the new folder lacks is reported and left on the old path.
Source#
The tool's own files, word for word as shipped in Xrefs.panel/Xref_Repath.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 -*-
"""Change the saved paths of the drawing's xrefs in one go.
Four ways: relative to the drawing's folder, absolute, find and replace text in
every path, or point each xref at the file of the same name in one folder (only
where that file is really there; the rest are reported and left alone). Every
change is shown, old path and new, before anything is touched. Each repathed
xref reloads from its new path, except one that was unloaded, which keeps its
new path and stays unloaded. Nested xrefs follow their parent and are not
repathed here.
"""
import os
from pyacad import doc, forms, script, toast, xrefs
__title__ = 'Xref Repath'
TITLE = 'Xref Repath'
MODES = [
('Make relative', 'relative'),
('Make absolute', 'absolute'),
('Find and replace in paths', 'replace'),
('Point all at a folder', 'folder'),
]
LIMIT = 12 # changes shown in the preview; Copy carries them all
def run():
if script.get_document_path() is None:
toast.warning('Open a drawing first')
return
rows = sorted([x for x in xrefs.list() if not x['nested']],
key=lambda x: x['name'].lower())
if not rows:
toast.info('This drawing has no xrefs')
return
choice = forms.ask_options('How should the xref paths change?',
[label for label, mode in MODES], title=TITLE)
if choice is None:
return
mode = dict(MODES)[choice]
drawing = doc.path()
host_dir = None if drawing == 'untitled' else os.path.dirname(drawing)
if mode in ('relative', 'absolute') and host_dir is None:
toast.warning('Save the drawing first', 'Relative paths start from its folder')
return
options = _ask(mode, host_dir)
if options is None:
return
plan = xrefs.plan_repath(rows, mode, host_dir=host_dir, **options)
full = xrefs.format_plan(rows, plan)
forms.alert(xrefs.format_plan(rows, plan, limit=LIMIT), TITLE, copy=full)
changes = [(row['name'], new) for row, (old, new, note) in zip(rows, plan) if not note]
if not changes:
return # the preview said so and why
if not forms.confirm('Repath %s as shown?' % _what(changes), TITLE):
return
unloaded = set(row['name'] for row in rows if row['status'] == 'unloaded')
watched = _watched(rows, mode)
with doc.lock():
results = [_repath(name, new, name not in unloaded) for name, new in changes]
# AutoCAD takes a path it cannot resolve without complaint; the status says.
results = xrefs.check_loaded(
results, [row for row in xrefs.list() if row['name'] in watched])
_report(results, len(rows) - len(changes))
def _watched(rows, mode):
"""Names whose status after the repath says whether it worked: any that
were loaded (a repath must not break them) and, in the two modes meant to
fix paths, any that were missing. A missing xref made relative is still
missing, and that is not this tool's failure."""
fixing = mode in ('replace', 'folder')
return set(row['name'] for row in rows
if row['status'] == 'loaded' or (fixing and row['status'] != 'unloaded'))
def _ask(mode, host_dir):
"""plan_repath's mode options from the person; None when they cancel."""
if mode == 'replace':
find = forms.ask_string('Text to find in every xref path (case does not matter):',
title=TITLE)
if find is None:
return None
if not find:
toast.warning('Nothing to find', 'Type the text to look for')
return None
replace = forms.ask_string('Replace "%s" with:' % find, title=TITLE)
if replace is None:
return None
return {'find': find, 'replace': replace}
if mode == 'folder':
folder = forms.pick_folder('Folder holding the xref files', initial=host_dir)
if not folder:
return None
return {'folder': folder, 'exists': os.path.isfile}
return {}
def _repath(name, new_path, reload):
"""(name, error) for one xref; error is None when it worked."""
try:
xrefs.repath(name, new_path, reload=reload)
except Exception as error:
return name, str(error) or error.__class__.__name__
return name, None
def _report(results, kept):
head = xrefs.headline(results, 'Repathed')
detail = '%d left as they were' % kept if kept else None
failed = len([1 for name, error in results if error])
if not failed:
toast.success(head, detail)
return
if failed == len(results):
toast.error(head, detail)
else:
toast.warning(head, detail)
forms.alert(xrefs.summarize(results, 'Repathed', limit=LIMIT), TITLE,
copy=xrefs.summarize(results, 'Repathed'))
def _what(changes):
"""'Site' for one change, '3 xrefs' for more."""
return changes[0][0] if len(changes) == 1 else _xrefs(len(changes))
def _xrefs(count):
return '1 xref' if count == 1 else '%d xrefs' % count
if '__commandpath__' in globals():
run()bundle.yaml The bundle contract: the keys pyACAD reads for this tool. View on GitHubtitle: Xref Repath
tooltip: Change every xref's saved path in one go: make them relative or absolute, find and replace text in them, or point them all at one folder. Shows the old and new paths before anything changes.
context: doc
keytip: XR
alias: XRP