Tutorial for External References

Introduction

This tutorial uses the ezdxf.xref module to work with external references (XREF).

Attached XREFs are links to the modelspace of a specified drawing file. Changes made to the referenced drawing are automatically reflected in the current drawing when it’s opened or if the XREF is reloaded.

Important

AutoCAD can only display DWG files as attached XREFs. Any DXF file attached as an XREF to a DXF document must be converted to DWG in order to be viewed in AutoCAD. Fortunately, other CAD applications are more cooperative, BricsCAD has no problem displaying DXF files as XREFs.

The drawing add-on included in ezdxf does not display external references at all!

There are some example files included in the examples/xref folder of the repository:

  • attach_dxf_dwg_xref.py

  • detach_block_as_xref.py

  • embed_dxf_dwg_xref.py

  • load_table_resources.py

Supported Entities

All operations which move entities between layouts and XREFs copy these entities, therefore only entities which are copyable can be transferred. The following entities are not copyable:

  • All entities which are not documented by the DXF reference.

  • ACAD_TABLE

  • ACAD_PROXY_ENTITY

  • OLE2FRAME

  • ACIS based entities: BODY, 3DSOLID, REGION, …

  • Custom entities from applications on top of AutoCAD like Map 3D, Civil 3D or Architecture. The vertical integration stack is not documented by the DXF reference.

Unsupported entities are ignored and do not raise exceptions.

Environment Setup

Required imports to follow this tutorial:

import ezdxf
from ezdxf.addons import odafc
from ezdxf.document import Drawing
from ezdxf import xref, units, colors
from ezdxf.render import forms

DXFVERSION = "R2013"

Function to create a simple DXF file as XREF, the insertion point of the XREF is set to (5, 5):

def make_dxf_xref_document(name: str) -> Drawing:
    ref_doc = ezdxf.new(DXFVERSION, units=units.M)
    ref_doc.layers.add("GEAR", color=colors.YELLOW)
    msp = ref_doc.modelspace()
    gear = forms.gear(
        16, top_width=0.25, bottom_width=0.75, height=0.5, outside_radius=2.5
    )
    msp.add_lwpolyline(
        forms.translate(gear, (5, 5)), close=True, dxfattribs={"layer": "GEAR"}
    )
    ref_doc.header["$INSBASE"] = (5, 5, 0)
    ref_doc.saveas(name)
    return ref_doc

Create the DXF file:

make_dxf_xref_document("xref.dxf")

The XREF looks like this:

../_images/xref_doc.png

Attach a DXF File

Create a host document to which the XREF will be attached:

host_doc = ezdxf.new(DXFVERSION, units=units.M)

Attach the XREF by the ezdxf.xref.attach() function and save the host DXF file:

xref.attach(host_doc, block_name="dxf_xref", insert=(0, 0), filename="attached_xref.dxf")
host_doc.set_modelspace_vport(height=10, center=(0, 0))
host_doc.saveas("attach_host_dxf.dxf")

The attach() function is meant to simply attach an XREF once without any overhead, therefore the attach() function creates the required block definition automatically and raises an XrefDefinitionError exception if the block definition already exist. To attach additional XREF references use the method add_blockref():

msp.add_blockref("dxf_xref", insert=another_location)

The attached DXF file in BricsCAD:

../_images/xref_attached_dxf.png

Important

AutoCAD can not display DXF files as attached XREFs.

Attach a DWG File

Export the DXF file as DWG by the odafc add-on:

# It's not required to save the DXF file!
doc = make_dxf_xref_document("attached_xref.dxf")
try:
    odafc.export_dwg(doc, "attached_xref.dwg", replace=True)
except odafc.ODAFCError as e:
    print(str(e))

Attach the DWG file by the ezdxf.xref.attach() function and save the host DXF file:

host_doc = ezdxf.new(DXFVERSION, units=units.M)
xref.attach(host_doc, block_name="dwg_xref", filename="attached_xref.dwg", insert=(0, 0))
host_doc.set_modelspace_vport(height=10, center=(0, 0))
host_doc.saveas("attached_dwg.dxf")

Attached DWG file in Autodesk DWG TrueView 2023:

../_images/xref_attached_dwg.png

Detach an XREF

The detach() function writes the content of a block definition into the modelspace of a new DXF document and convert the block to an external reference (XREF). The new DXF document has to be written/exported by the caller. The function does not create any block references. These references should already exist and do not need to be changed since references to blocks and XREFs are the same.

    host_doc = ezdxf.new()
    make_block(host_doc, "GEAR")
    block_layout = host_doc.blocks.get("GEAR")
    detached_block_doc = xref.detach(block_layout, xref_filename="detached_gear.dxf")
    detached_block_doc.saveas("detached_gear.dxf")
    host_doc.set_modelspace_vport(height=10, center=(0, 0))
    host_doc.saveas("detach_host_dxf_xref.dxf")

Important

Save the host document after detaching the block! Detaching a block definition modifies the host document.

The detach() function returns a Drawing instance, so it’s possible to convert the DXF document to DWG by the odafc add-on if necessary (e.g. for Autodesk products). It’s important that the argument xref_filename match the filename of the exported DWG file:

    host_doc = ezdxf.new()
    make_block(host_doc, "GEAR")
    block_layout = host_doc.blocks.get("GEAR")
    detached_block_doc = xref.detach(block_layout, xref_filename="detached_gear.dwg")
    try:
        odafc.export_dwg(detached_block_doc, "detached_gear.dwg", replace=True)
    except odafc.ODAFCError as e:
        print(str(e))
    host_doc.set_modelspace_vport(height=10, center=(0, 0))
    host_doc.saveas("detach_host_dwg_xref.dxf")

It’s recommended to clean up the entity database of the host document afterwards:

host_doc.entitydb.purge()

For understanding, this is the make_block() function:

def make_block(doc: Drawing, name: str) -> None:
    blk = doc.blocks.new(name, base_point=(5, 5, 0))
    doc.layers.add("GEAR", color=colors.YELLOW)
    gear = forms.gear(
        16, top_width=0.25, bottom_width=0.75, height=0.5, outside_radius=2.5
    )
    blk.add_lwpolyline(
        forms.translate(gear, (5, 5)), close=True, dxfattribs={"layer": "GEAR"}
    )
    doc.modelspace().add_blockref(name, (0, 0))

Embed an XREF

The embed() function loads the content of the XREF into the block definition, this is the reverse operation of detaching an XREF.

For loading the content of DWG files is a loading function required, which loads the DWG file as Drawing document. The odafc add-on module provides such a function: readfile().

This example embeds the XREF “attached_xref.dwg” of the first example as content of the block definition “GEAR”, the “attach_host_dwg.dxf” file is the host DXF document:

import ezdxf
from ezdxf.addons import odafc

doc = ezdxf.readfile("attach_host_dwg.dxf")
gear_xref = doc.blocks.get("GEAR")

try:
    xref.embed(gear_xref, load_fn=odafc.readfile)
except FileNotFoundError as e:
    print(str(e))

The default loading function for DXF files is the ezdxf.readfile() function and doesn’t have to be specified. For the loading function from the recover module use a lambda function:

import ezdxf
from ezdxf import recover

doc = ezdxf.readfile("attach_host_dxf.dxf")
gear_xref = doc.blocks.get("GEAR")

try:
    xref.embed(gear_xref, load_fn=lambda f: recover.readfile(f)[0])
except FileNotFoundError as e:
    print(str(e))

Load Modelspace

The ezdxf.xref.load_modelspace() function loads the content of the modelspace of the source document into a layout of the target document, the modelspace of the target document is the default target layout.

Hint

Use this function to combine multiple existing DXF files. If the goal is just to add new entities to an existing document, rather load the source document as a template by ezdxf.readfile(), add your content and save the document as a new DXF file with the saveas() method.

Merge multiple DXF files:

import ezdxf
from ezdxf import colors, transform, xref
from ezdxf.math import Matrix44
from ezdxf.render import forms


def make_gear(name: str) -> None:
    doc = ezdxf.new()
    doc.layers.add("GEAR", color=colors.YELLOW)
    msp = doc.modelspace()
    gear = forms.gear(
        16, top_width=0.25, bottom_width=0.75, height=0.5, outside_radius=2.5
    )
    msp.add_lwpolyline(gear, close=True, dxfattribs={"layer": "GEAR"})
    doc.saveas(name)


make_gear("gear.dxf")
merged_doc = ezdxf.new()
for index in range(3):
    sdoc = ezdxf.readfile("gear.dxf")  # this could be different DXF files
    transform.inplace(sdoc.modelspace(), Matrix44.translate(index * 10, 0, 0))
    xref.load_modelspace(sdoc, merged_doc)
merged_doc.saveas("merged.dxf")
../_images/xref_merged.png

Load Paperspace

The function ezdxf.xref.load_paperspace() loads a paperspace layout as a new paperspace layout into the target document. To be clear this function loads only the content of the paperspace layout, the content of the modelspace isn’t loaded, therefore the loaded VIEWPORT entities show the content of the target modelspace.

Write Block

The function ezdxf.xref.write_block() writes the given entities into the modelspace of a new DXF document, this document can be, but doesn’t have to be used as an external referenced block.

Conflict Policies

Resources are definitions of layers, linetypes, text-, dimension-, mline- and mleader styles, materials and blocks.

A resource conflict occurs when the source and target documents contain elements such as layers, linetypes, text styles and so on that share the same name.

Many of the functions shown above support an argument to define the ezdxf.xref.ConflictPolicy, that gives you the choice how to handle resource name conflicts.

ConflictPolicy.KEEP

Keeps the existing resource name of the target document and ignore the resource from the source document. The loaded entities from the source document use the resources defined in the target document and may alter their visual appearance, when the resources are different.

ConflictPolicy.XREF_PREFIX

This policy handles the resource import like CAD applications by always renaming the loaded resources to <xref>$0$<name>, where xref is the name of source document, the $0$ part is a number to create a unique resource name and <name> is the name of the resource itself.

Important

This policy ALWAYS renames the resource, even if the loaded resource doesn’t have a conflict in the target document.

ConflictPolicy.NUM_PREFIX

This policy renames the loaded resources to $0$<name> only if the resource <name> already exists. The $0$ prefix is a number to create a unique resource name and <name> is the name of the resource itself.

Important

This policy renames the resource ONLY when the loaded resource has a conflict in the target document.

Load Table Resources

Resources are definitions of layers, linetypes, text-, dimension-, mline- and mleader styles, materials and blocks.

The Loader class is the low level tool to build a loading operation from simple loading commands. Study the source code of the xref module, most of loading commands used above are build upon the Loader class. This example shows how to import layer, linetype, text- and dimension style definitions:

import ezdxf
from ezdxf import xref

sdoc = ezdxf.new(setup=True)
tdoc = ezdxf.new()

# The default conflict policy is ConflictPolicy.KEEP
loader = xref.Loader(sdoc, tdoc)

# Load all layers:
loader.load_layers([layer.dxf.name for layer in sdoc.layers])

# Load specific linetypes:
loader.load_linetypes(["CENTER", "DASHED", "DASHDOT"])

# Load specific text style:
loader.load_text_styles(["OpenSans", "LiberationMono"])

# Load all DIMENSION styles, this command loads also the dependent text styles:
loader.load_dim_styles([dimstyle.dxf.name for dimstyle in sdoc.dimstyles])

# execute all loading commands:
loader.execute()
tdoc.saveas("target.dxf")

Note

Loading a layer does not load the entities which do reference this layer, a layer is not an entity container, it’s just an DXF attribute, see also Basic Concepts: Layers.