Extracting DWG Block Attributes with Python
To extract block attributes from a DWG file with Python, first convert the DWG to DXF with the ODA File Converter, then open the DXF with ezdxf, query INSERT entities, and read each attached ATTRIB sub-entity through insert.attribs — pulling att.dxf.tag and att.dxf.text into a tag-to-value dictionary per block reference. Title blocks, equipment tags, and drawing-register fields all live as attributes on block references, which makes them one of the richest metadata sources in a CAD drawing. This page is part of the Metadata Extraction Strategies reference and assumes you can run a conversion step and open a DXF document.
How ezdxf Handles Block Attributes
A block reference in DXF is an INSERT entity: a placement (insertion point, scale, rotation) of a named block definition. When that block was authored with attribute definitions (ATTDEF), each placed reference carries matching attribute instances (ATTRIB) that hold the actual values a drafter typed. ezdxf exposes those instances through the iterable insert.attribs; each ATTRIB has a dxf.tag (the fixed field name, e.g. DWG_NUMBER) and a dxf.text (the typed value, e.g. A-101).
The critical prerequisite is format: DWG is Autodesk’s closed binary format with no public specification, and ezdxf cannot read it. You must convert DWG to DXF first — the ODA File Converter batch workflow is the standard route — and then parse the DXF. Once you have DXF, attribute traversal is straightforward, with two subtleties: the block definition holds ATTDEF templates (including constant attributes that never appear on the reference), and attribute visibility flags distinguish shown fields from hidden ones.
The output of this harvest — a DWG_NUMBER, REVISION, or EQUIP_TAG mapped to a value and an insertion point — is exactly the structured attribute data that feeds block-attribute extraction across CAD files, where the same tag-to-value model is applied format-agnostically.
Production-Ready Script
This script assumes a DXF already produced by the ODA File Converter (the conversion command is shown in the notes). It walks every INSERT, reads its visible ATTRIBs, merges constant attributes from the block definition, and emits one record per block reference keyed by block name, handle, and insertion point.
# ezdxf>=1.1.0 (+ ODA File Converter for the DWG->DXF step) | python>=3.9
import ezdxf
import json
import sys
from pathlib import Path
from typing import Any
def constant_attribs(doc, block_name: str) -> dict[str, str]:
"""
Collect constant attributes from a block definition's ATTDEFs.
Constant attributes are defined once on the ATTDEF (dxf.tag with the
'Constant' flag) and are NOT repeated as ATTRIB on each INSERT.
"""
result: dict[str, str] = {}
if block_name not in doc.blocks:
return result
for entity in doc.blocks[block_name]:
if entity.dxftype() != "ATTDEF":
continue
# is_const is True when the ATTDEF flags mark the attribute constant.
if getattr(entity, "is_const", False):
result[entity.dxf.tag] = entity.dxf.text
return result
def attrib_value(att) -> str:
"""Return clean text for an ATTRIB, stripping MTEXT formatting if present."""
# Multiline attributes embed MTEXT; plain_text() removes formatting codes.
if hasattr(att, "plain_text"):
try:
return att.plain_text()
except Exception:
pass
return att.dxf.get("text", "")
def harvest_block_attributes(dxf_path: str, output_json: str) -> None:
try:
doc = ezdxf.readfile(dxf_path)
except (IOError, ezdxf.DXFStructureError) as exc:
sys.exit(f"Failed to load DXF (did the ODA conversion succeed?): {exc}")
msp = doc.modelspace()
records: list[dict[str, Any]] = []
for insert in msp.query("INSERT"):
block_name = insert.dxf.name
# Start from constant attributes defined on the block's ATTDEFs...
tags: dict[str, str] = constant_attribs(doc, block_name)
# ...then overlay the variable ATTRIB values on this reference.
# insert.attribs is empty when the block carries no attributes.
for att in insert.attribs:
tag = att.dxf.tag
# Track visibility: flag bit 1 on ATTRIB means "invisible".
invisible = bool(att.dxf.get("flags", 0) & 1)
value = attrib_value(att)
if tag in tags:
# Duplicate tag on one reference: keep first, note the clash.
tags[f"{tag}#dup@{att.dxf.handle}"] = value
else:
tags[tag] = value
if invisible:
tags.setdefault("_invisible_tags", "")
tags["_invisible_tags"] += (tag + " ")
if not tags:
continue # plain block reference with no attributes
ip = insert.dxf.insert # insertion point (Vec3)
records.append({
"block": block_name,
"handle": insert.dxf.handle,
"layer": insert.dxf.get("layer", "0"),
"insert_point": [float(ip[0]), float(ip[1]), float(ip[2])],
"attributes": tags,
})
Path(output_json).write_text(json.dumps(records, indent=2), encoding="utf-8")
print(f"Harvested attributes from {len(records)} block references -> {output_json}")
if __name__ == "__main__":
# DWG -> DXF first, e.g. via the ODA File Converter CLI:
# ODAFileConverter <in_dir> <out_dir> ACAD2018 DXF 0 1 "*.DWG"
harvest_block_attributes("converted.dxf", "block_attributes.json")
Key implementation notes:
insert.attribsyields only the variableATTRIBinstances present on the reference. Constant attributes never appear here — read them from theATTDEFs indoc.blocks[block_name]and merge, asconstant_attribs()does.att.dxf.tagis the fixed field name;att.dxf.textis the value. Tags are not guaranteed unique on a single reference, so detect duplicates rather than letting one silently overwrite another.- Attribute visibility is a flag bit, not a separate attribute. Bit
1ofATTRIB.dxf.flagsmarks the attribute invisible; a title block often hides internal bookkeeping fields that you still want to harvest. - Keying each record by block name, handle, and insertion point lets you disambiguate many placements of the same title block or equipment symbol across a sheet set.
- Run the ODA File Converter before this script. It is a separate executable, not a Python library; drive it from
subprocessin a batch step, as covered in the DWG-to-DXF conversion workflow.
Compatibility Matrix
| Component | Supported range | Notes |
|---|---|---|
ezdxf |
>=1.1.0 |
insert.attribs, ATTDEF.is_const, and plain_text() are stable in 1.1.0+. |
| ODA File Converter | 2024+ | Required for the DWG→DXF step; DWG is closed and unreadable by ezdxf. |
| Python | 3.9+ |
Uses pathlib, typing, and f-strings. |
| DXF format | R2000 (AC1015) – R2018 (AC1032) |
Convert DWG to an ezdxf-supported DXF revision (ACAD2018 is safe). |
| Attribute types | ATTRIB, ATTDEF | Variable values on ATTRIB; constant/template values on ATTDEF. |
| Multiline attributes | MTEXT-backed | Use plain_text() to strip MTEXT formatting codes. |
Fallback Strategies
1. Constant attributes are missing from the reference
If an expected tag (a fixed sheet-size or discipline code) never shows up in insert.attribs, it is almost certainly a constant attribute defined only on the ATTDEF. Read doc.blocks[block_name], collect ATTDEF entities where is_const is true, and merge their tag/text into the record. Skipping this step drops fields that appear on every printed sheet.
2. Multiline (MTEXT) attribute values
Newer drawings store long attribute values as multiline attributes backed by MTEXT. Reading att.dxf.text returns raw text laced with MTEXT control codes (\P, font runs). Call att.plain_text() to get the clean human-readable string; falling back to raw .text leaves formatting garbage in your metadata.
3. Duplicate tags on one reference
Nothing in DXF forbids two ATTRIBs with the same tag on a single INSERT. A naive dict[tag] = value silently loses one. Detect the collision (as the script does with a #dup@handle suffix) and decide policy explicitly — keep first, keep last, or concatenate — rather than dropping data unknowingly.
4. Attributes lost after exploding a block
If an upstream step exploded block references, the tag-to-value association is gone: constant attributes become plain TEXT and variable attributes lose their tag linkage. Always harvest attributes from intact INSERT entities before any explode operation. If you only have an exploded drawing, the tags cannot be reliably reconstructed.
5. Conversion artifacts from the ODA step
A failed or partial DWG→DXF conversion can yield a DXF with no INSERT entities or with proxy stand-ins for custom blocks. Verify the converted DXF opens and contains the expected block names before trusting an empty attribute harvest; an empty result usually means a bad conversion, not an attribute-free drawing.
FAQ
Why can't ezdxf read DWG files directly?
DWG is Autodesk’s closed, version-dependent binary format with no public specification. ezdxf reads and writes only DXF. Convert the DWG to DXF first with the ODA File Converter or another licensed converter, then parse the resulting DXF with ezdxf. The batch DWG-to-DXF conversion workflow covers automating that step.
Where do constant block attributes live?
Constant attributes are defined once on the ATTDEF in the block definition and are not written as ATTRIB sub-entities on each INSERT. To recover them, read the ATTDEF entities in doc.blocks[block_name], filter for is_const, and merge their tag and text with the variable ATTRIBs from the reference.
How do I read multiline (MTEXT) block attributes?
Multiline attributes store their content as embedded MTEXT. Reading att.dxf.text returns raw text with MTEXT formatting codes. Call att.plain_text() to strip the formatting and get the clean string value before writing it to your attribute table.
Why did my ATTRIBs disappear after exploding a block?
Exploding an INSERT converts constant attributes to plain TEXT and drops the tag association of variable attributes. Extract attributes from intact INSERT entities before any explode step; once exploded, the tag-to-value mapping cannot be reliably reconstructed.
Related Pages
- Metadata Extraction Strategies — parent reference on turning embedded CAD, GIS, and BIM metadata into structured attributes
- Extracting Block Attributes from CAD Files — sibling workflow applying the same tag-to-value model across formats
- Batch Converting DWG to DXF with ODA File Converter — related prerequisite that produces the DXF this harvest reads
- pydwg Integration — related reference on handling proprietary
.dwgfiles in Python pipelines