Tools: DXF analysis
The six tools on this page run on pure ezdxf/shapely — no CAD kernel, no display, host-side or in-container alike. All of them:
- accept DXF and DWG. DWG is converted on demand via libredwg, repaired, validated, and cached before the tool sees it; a file libredwg cannot convert is refused explicitly rather than analyzed empty (see Handle DWG)
- accept both ASCII and binary DXF encodings transparently — the shared loader sniffs the encoding and routes text files through ezdxf’s recover mode (real-world files carry thousands of fixable defects), while binary files take the strict reader, because recover mode cannot parse binary DXF at all
- take the standard operation envelope; parameters listed below go in
operation.parameters - enforce the file-size limit and path validation, returning
{"error": "..."}on violation - treat layer patterns as fnmatch, also matching the suffix after the last
$so bound-xref layer names likeXREF-PLAN$0$C-PROP-LINEmatchC-PROP*(see Xref shells)
converted_from_dwg
Section titled “converted_from_dwg”When the input was a DWG, every response carries an extra object recording how it got to DXF, so the analyzed source is never silently the cache artifact:
| Field | Type | Meaning |
|---|---|---|
source_dwg |
string | filename of the original DWG |
converter |
string | binary used, dwg2dxf unless DWG2DXF_PATH overrides it |
cached |
bool | true when a previous conversion of this exact file was reused |
newline_repairs |
int | how many converter-damaged text values the repair pass had to fold back |
The key is path + size + mtime, so editing the source DWG invalidates the cache; a DXF input has no converted_from_dwg field at all. An unconvertible DWG returns {"error": "libredwg could not produce a parseable DXF for ..."} instead of results.
survey_dxf_layers
Section titled “survey_dxf_layers”Inventory which DXF layers actually carry geometry, and how much — modelspace plus geometry inside block references (INSERT expansion).
Use this before extract_parcels to discover the right layer_pattern: real civil drawings scatter boundary linework across hundreds of layers, and guessing patterns blind wastes calls.
| Parameter | Type / default | Description |
|---|---|---|
contains |
string, "" |
case-insensitive substring filter on layer names (e.g. "PROP", "ROAD") |
include_blocks |
bool, true |
expand INSERT references |
top |
int, 60 |
max layers returned, busiest first |
Returns a busiest-first list of layers with entity counts broken down by entity type, plus totals. Errors: missing file, unsupported format (anything that is not DXF or DWG), failed DWG conversion, unsafe path.
This is also the cheapest way to sanity-check a DWG conversion: implausibly low entity counts for the file size mean content was lost upstream.
extract_parcels
Section titled “extract_parcels”Extract closed parcel polygons and acreage from open boundary linework in a DXF file (civil/survey plat drawings).
Real plat linework draws parcels as open courses, not closed polylines. This tool nodes all linework on layers matching the pattern (splitting at every intersection) and assembles the enclosed faces — the full mechanism is described in Why parcels need polygonization.
| Parameter | Type / default | Description |
|---|---|---|
layer_pattern |
string, "C-PROP*" |
fnmatch pattern for boundary layers |
min_area_sqft |
float, 100 |
drop sliver faces below this area |
max_parcels |
int, 500 |
cap on returned parcels, largest first |
Returns each parcel’s area (sq ft and acres), perimeter, and centroid, plus diagnostics (dangling_segments, invalid_rings) explaining any linework that failed to close. Areas assume feet ($INSUNITS = 2); a units note warns otherwise. Errors: missing file, oversize file, unsupported format, failed DWG conversion.
extract_annotations
Section titled “extract_annotations”Extract all text content from a DXF file: modelspace and paperspace TEXT/MTEXT, text inside block definitions, MTEXT embedded in MULTILEADER callouts, and ATTRIB values on block references — with MTEXT formatting codes stripped.
Naive text queries silently miss MULTILEADER-embedded content and block-internal text; this tool walks all five sources. On civil drawings, multileaders are exactly where lot labels and callouts hide, and “silently missing one annotation” is the failure mode a title company cares about.
| Parameter | Type / default | Description |
|---|---|---|
pattern |
string, none | case-insensitive regex; matched against the plain text and a single-line whitespace-normalized form, so multi-line labels match one-line patterns |
layer_pattern |
string, "*" |
fnmatch filter on layer names, bound-xref suffixes included |
include_blocks |
bool, true |
walk block definitions |
include_paperspace |
bool, true |
walk paperspace layouts |
max_results |
int, 2000 |
cap on returned annotations; annotations_found still reports the full match count |
Returns annotations, each with its text, layer, insertion point, and container — "modelspace", "layout:C4", "block:NAME", or "...:insert:BLOCKNAME" for attribute values.
A drawing with no text anywhere returns annotations_found: 0 with an empty list. Zero is a valid answer, not an error — verified against a dense 16,000-entity mechanical drawing that is pure linework.
list_sheets
Section titled “list_sheets”Enumerate the paperspace layouts (plan-set sheets) in a DXF file with per-sheet metadata: name, paper size (mm and inches), entity count, and harvested title-block text.
Use this to build a sheet index of a plan set (“which sheet is the grading plan?”) without rendering anything. The title_block_text list is deliberately raw candidate strings — direct TEXT/MTEXT in the layout plus text and attribute values inside INSERTed title-block blocks (nested sub-blocks followed a couple of levels). The calling LLM interprets which string is the sheet title.
| Parameter | Type / default | Description |
|---|---|---|
include_model |
bool, false |
include the Model layout as a pseudo-sheet (callers usually want deliverable sheets only) |
max_text_per_sheet |
int, 60 |
cap on harvested strings per sheet |
measure_by_layer
Section titled “measure_by_layer”Roll up linework quantities per DXF layer: total length (curves flattened) and closed-polygon area/acreage recovered by noding the linework and assembling enclosed faces — the same pipeline as extract_parcels, so the two reconcile.
Use this for quantity-takeoff questions (“how many feet of road edge?”, “how much area do the easement boundaries enclose?”). Zero closed_area with nonzero length is normal for open linework (centerlines, profiles); dangling_segments explains why faces did not close.
| Parameter | Type / default | Description |
|---|---|---|
layer_pattern |
string, "*" |
fnmatch pattern for layers to measure — prefer something narrower like "C-ROAD*" on large files |
group_by |
string, "layer" |
"layer" = one rollup row per matched layer; "pattern" = pool all matched linework into a single rollup, letting faces close across layers |
include_blocks |
bool, true |
expand INSERT references, transforms applied |
min_area_sqft |
float, 100 |
drop sliver faces below this area |
Returns per-layer (or pooled) rollups with total length, closed area in sq ft and acres, and dangling-segment counts. Lengths/areas assume feet; units_note warns otherwise. Annotation entity types (TEXT, MTEXT, HATCH, DIMENSION, MULTILEADER) are never counted as linework.
render_svg
Section titled “render_svg”Render a DXF layout to an SVG file, filtered to layers matching a pattern — a quick visual of exactly the linework you care about (just the parcel boundaries, or one full sheet).
| Parameter | Type / default | Description |
|---|---|---|
layer_pattern |
string, "*" |
fnmatch pattern for layers to render |
output_name |
string, "render" |
output filename stem, no path separators; file becomes <output_name>.svg |
layout |
string, "Model" |
"Model" or a paperspace sheet name from list_sheets |
return_content |
"auto" | true | false, "auto" |
inline the SVG markup in the response. "auto" inlines up to RENDER_INLINE_MAX_BYTES (256 KB) |
hatch_policy |
"outline" | "solid" | "ignore" | "full", "outline" |
how HATCH entities are drawn. The single biggest lever on output size |
extents |
[min_x, min_y, max_x, max_y] |
modelspace window; content outside is cropped away |
stroke_color |
hex string | force every stroke to one color, e.g. "#1f5e8c" |
background |
"transparent" | "white" | "black" | hex, "transparent" |
transparent emits no background rect at all |
stroke_width |
float mm | constant stroke width for every entity; default follows the DXF lineweights |
coordinate_space |
int, 1000000 |
SVG viewBox resolution. Minor size knob |
Returns output_file, output_size_bytes, entities_rendered, svg_element_count, plus three ways to get the image itself: svg (the markup inline, when small enough), url (https://…/renders/<name>.svg, when a public base is configured), and resource_uri (render://<name>.svg, readable through MCP). When svg is omitted, content_note says why.
Behavior worth knowing (details in Render layer-filtered SVGs):
- Hatches are drawn as outlines by default. Measured on a 24-entity civil drainage layer:
"full"produces ~8 MB,"outline"produces 9.9 KB of the same drawing. - Frozen/off layers that match the pattern are force-rendered — naming a pattern means you want to see it.
- A pattern matching nothing returns an error with similar layer names as a hint, never an empty file.
- Rendering a full sheet of a large civil file can take ~10 s — prefer narrow patterns.