Drawing Intelligence Platform for AEC professionals.
Upload a DXF, PDF, or DWG — describe what you need in plain English, and get structured edits, compliance reports, quantity takeoffs, health assessments, RFIs, and drawing summaries — all without the AI ever touching your original file.
Links: Gist One-Pager · Docs
cad-dxf-agent is a multi-capability platform that handles DXF drawings through natural language. Describe what you need — an edit, a compliance check, a quantity takeoff, a health report — and the platform classifies your intent, selects the right processing pipeline, and delivers structured results.
| Capability | Description |
|---|---|
| Edit | Move, rotate, copy, scale, mirror, delete entities; add lines, polylines, circles, arcs, text, blocks |
| Compliance | ADA/IBC/custom rule validation with findings and remediation guidance |
| Health Report | Drawing quality metrics — layer hygiene, entity stats, potential issues |
| Quantity Takeoff | Automated extraction of counts, lengths, areas from drawing entities |
| Summary | Plain-English structured narrative of drawing contents |
| RFI Generation | Automated Request For Information based on detected ambiguities |
| Zone Detection | Closed-loop room/area detection with area calculation |
| Revision Comparison | Diff two DXF versions, review changes, apply approved edits |
| Agent Mode | Iterative multi-turn tool-use loop for complex requests (max 10 turns) |
- Safe edits — The LLM planner returns structured operations only. It never touches raw DXF data.
- Two-axis intent classification — Every prompt is classified by what (edit, analyze, compare, query, generate) and why (compliance, coordination, documentation, estimation, quality, general).
- Protected layers — Configurable layers (TITLE, TITLEBLOCK, SEAL, REVISION) cannot be edited. Enforced at both validator and tool executor levels.
- Save-as workflow — Original files are always preserved.
- Mock mode — The full pipeline works without an API key for testing and development.
| Supported | Not Yet |
|---|---|
| DXF files (2D) | DWG native editing |
| Model space + paper space layouts | 3D entities |
| LINE, LWPOLYLINE, TEXT, MTEXT, INSERT, CIRCLE, ARC | Dimension regeneration |
| 13 edit operations (move, edit_text, delete, add_block, rotate, copy, scale, mirror, add_line, add_polyline, add_circle, add_arc, add_text) | Xrefs |
| Compliance validation (ADA/IBC/custom) | Title block revision table |
| Health reports + quality metrics | |
| Automated quantity takeoff | |
| Plain-English drawing summaries | |
| RFI generation | |
| Zone/room detection with area calc | |
| Agent-mode iterative tool-use | |
| Protected layers + AI revision notes | |
| Revision comparison (CLI + web) | |
| Web app (Firebase + Cloud Run, auto-deploy) | |
| Desktop app (Windows + Linux) | |
| Pluggable LLM provider (bring your own) | |
| OpenTelemetry tracing (console, OTLP, GCP Trace) |
For full details see:
- Full Application Audit (v0.9.0) — complete feature documentation, architecture, and DevOps playbook
- 000-docs/ index — complete documentation inventory (60+ docs)
- V1 Blueprint — engineering architecture and module map
- ADR: LLM Plans, Not DXF Edits — core architectural decision
- Python 3.11 or 3.12
- pip
git clone https://github.com/jeremylongshore/cad-ai-agent.git
cd cad-ai-agent
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pre-commit install# All tests (~4700 tests across 10 tiers)
make test
# With coverage (65% threshold)
make test-cov
# By tier
make test-unit # ~3600 unit tests
make test-integration # ~100 integration tests
make test-web # ~420 web API tests
make test-e2e # ~33 end-to-end tests
# Eval scorecard (intent classification accuracy)
make scorecard # mock mode
# Smoke test
make smokemake checkThis runs: lint → format check → type check → tests → smoke test.
make lint # ruff check
make format # ruff format
make typecheck # mypy
make test # pytest
make smoke # smoke test script
make security # bandit + pip-auditmake run
# or: python -m cad_dxf_agent.appThis opens the PySide6 desktop window. You can:
- Click Open DXF to load a file
- Type a prompt (e.g., "Move the column east by 2 feet")
- Click Plan & Preview to see proposed changes
- Click Apply & Save As to save the edited DXF
The repo ships a full web UI — React + Vite frontend, FastAPI backend — that you run locally or self-host. There is no public hosted instance.
# Frontend (React + Vite)
cd web/frontend && npm run dev # http://localhost:3000
# Backend (FastAPI) — CAD_WEB_DEV_MODE=1 skips Firebase auth for local testing
CAD_WEB_DEV_MODE=1 uvicorn web.backend.main:app --port 8322The backend reuses the same pipeline as the CLI and desktop app, so configure CAD_LLM_PROVIDER the same way (see Bring Your Own LLM Provider). A deploy-web.yml workflow exists for deploying to Cloud Run + Firebase Hosting, but it is wired to its operator's cloud project — repoint it at your own before using it.
The default LLM provider is mock, which uses simple keyword matching to generate operations. This lets you test the entire pipeline offline:
# Set in .env or environment (this is the default):
export CAD_LLM_PROVIDER=mock
# Run the smoke test:
python scripts/smoke_test.pyThe mock provider responds to keywords like "move", "delete", "text", "rename" in your prompt.
The default mock provider only keyword-matches — it's for exercising the pipeline offline, not for real results. The LLM is fully pluggable: the planner never sees raw DXF, it only returns a structured ChangeSet, so any model that can follow that contract works. Point CAD_LLM_PROVIDER at your own provider — no fork required.
Subclass PlannerProvider (src/cad_dxf_agent/llm/providers.py):
# my_providers.py
from cad_dxf_agent.llm.providers import PlannerProvider
from cad_dxf_agent.models.ops_schema import ChangeSet
class MyProvider(PlannerProvider):
@property
def name(self) -> str:
return "my-provider"
def plan(
self,
prompt: str,
drawing_context: dict,
conversation_history: list[dict] | None = None,
) -> ChangeSet:
# Call your model and return a validated ChangeSet of structured
# operations. The engine validates and applies them — the model never
# touches DXF. Read your own keys / endpoint / model from the
# environment in __init__.
...The contract is small (plan() + a name) and the provider must be constructible with no arguments.
CAD_LLM_PROVIDER accepts a dotted import path — package.module:ClassName:
export CAD_LLM_PROVIDER=my_providers:MyProvider
python -m cad_dxf_agent.appThe class is imported and validated as a PlannerProvider at startup. A bad path, or a class that isn't a PlannerProvider, fails loudly — it never silently degrades to mock and produces keyword-matched nonsense.
The
src/cad_dxf_agent/llm/package contains existing provider implementations you can copy as a starting point.
Both paths use tool-use with vision — the planner analyzes DXF renders and, for complex multi-step requests, runs an iterative agent loop (up to 10 turns) over 20+ query/edit tools. The mock provider remains available for offline pipeline testing.
When enabled (default), the tool inserts a revision note on the AI_REV_NOTES layer after applying edits. Notes are generated deterministically from the operations — never from freeform LLM output.
Examples:
REV 8 - Column shiftREV 8 - Moved entity east 2'-0"REV 5 - Updated text to 'New Label'; Deleted entity
Configure via environment variables:
CAD_REVISION_NOTES_ENABLED=true # true/false
CAD_REVISION_NOTES_LAYER=AI_REV_NOTESProtected layers (TITLE, TITLEBLOCK, SEAL, REVISION) are never modified. The real title block revision table is out of scope for V1.
Compare two DXF versions, review structural changes, and apply approved edits to produce a new drawing.
cad-revision <command> [options]| Command | What it does |
|---|---|
cad-revision diff |
Compare two DXFs and output a changelog |
cad-revision align |
Check/preview alignment transform only |
cad-revision dry-run |
Full pipeline without writing any files |
cad-revision apply |
Apply approved changes to a new DXF |
cad-revision bundle |
Produce a zip with DXF + overlay + changelog |
cad-revision explain |
Human-readable explanation of changes |
Quick example:
# Compare and generate changelog
cad-revision diff master.dxf revision.dxf --output-dir ./out
# Full bundle with all changes auto-approved
cad-revision bundle master.dxf revision.dxf --output-dir ./bundle --approve-allGlobal flags: --json, --verbose, --version
Exit codes: 0 = no changes, 1 = changes found, 2 = error
Manual control points (for drawings with large coordinate offsets):
cad-revision diff master.dxf revision.dxf \
--control-points "100,200:105,205" "300,400:305,405"The web revision workflow is a 5-step wizard in the Compare tab:
- Upload — Upload a revision DXF to compare against the current master
- Align — Automatic alignment (or manual control points if confidence is low)
- Review — Approve or reject each detected change
- Apply — Apply approved changes to produce a new DXF
- Download — Download a bundle (.zip) containing the updated master, diff overlay, changelog, and metadata
- Units mismatch — Master in inches, revision in mm → low alignment confidence. Convert both files to matching units first.
- Partial revisions — Revision contains only a subset of the drawing → alignment may fail. Use manual control points (
--control-pointsin CLI, or the manual alignment UI in the web app). - Xrefs and dynamic blocks — Not yet supported. These are detected and skipped with a warning message.
By default, these layers are protected and cannot be edited:
TITLETITLEBLOCKSEALREVISION
Customize via environment variable:
CAD_PROTECTED_LAYERS=TITLE,TITLEBLOCK,SEAL,REVISION,CUSTOM_LAYERAny operation targeting an entity on a protected layer will be blocked by the validator.
Optional distributed tracing for pipeline performance insights. Off by default, CI-safe (no network calls when disabled).
# Install OTel extras
pip install -e ".[otel]"
# Enable console exporter (prints spans to stdout)
OTEL_ENABLED=1 python scripts/smoke_test.py
# Send to an OTLP collector (Jaeger, Grafana Tempo, etc.)
OTEL_ENABLED=1 OTEL_EXPORTER=otlp OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 python scripts/smoke_test.py
# GCP Cloud Trace (production)
OTEL_ENABLED=1 OTEL_EXPORTER=gcp-trace python scripts/smoke_test.py| Span Name | Attributes |
|---|---|
cad.load_dxf |
cad.file.name, cad.entities.count, cad.layers.count |
cad.build_context |
cad.entities.count |
cad.run_planner |
cad.mode, cad.ops.count |
cad.validate |
cad.ops.count, cad.validation.valid, cad.validation.blockers |
cad.apply_changeset |
cad.ops.count, cad.ops.success_count |
cad.save |
cad.save.output_basename |
cad.revision_note |
cad.revision.layer |
Privacy: No full file paths, no drawing text content, no API keys are ever included in span attributes.
| Variable | Default | Purpose |
|---|---|---|
OTEL_ENABLED |
(unset) | Enable tracing (1, true, yes) |
OTEL_EXPORTER |
console |
Exporter type: console, otlp, or gcp-trace |
OTEL_EXPORTER_OTLP_ENDPOINT |
(unset) | OTLP collector URL |
User Prompt
→ ObjectiveClassifier (2-axis: RequestClass × ObjectiveTag)
→ StrategyRegistry (maps classification → StagePipelineDefinition)
→ StageExecutor (runs ordered stages: deterministic + LLM)
→ ResponseBuilder (PlatformResponse envelope)
For edit requests, the stage pipeline includes:
Planner → ChangeSet → Validator → Preview → EditEngine → Save-As DXF + RevisionNotes
For analysis requests (compliance, health, takeoff, summary, RFI, zones), the pipeline runs deterministic extractors without the edit flow.
Every prompt is classified on two independent axes:
- RequestClass — what the user wants done:
edit,analyze,compare,query,generate - ObjectiveTag — why they want it:
compliance,coordination,documentation,estimation,quality,general
The StrategyRegistry maps each (RequestClass, ObjectiveTag) pair to a StagePipelineDefinition — an ordered list of StageHandler implementations.
For complex requests, the AgentProvider runs an iterative tool-use loop:
- Sends prompt + drawing context + tool definitions to the LLM provider
- The provider returns tool calls (query tools: list entities, find by layer; edit tools: move, delete, add)
ToolExecutordispatches each call, enforcing protected-layer rules- Results feed back to the provider for the next iteration (max 10 turns)
- Final ChangeSet extracted from accumulated tool calls
20+ tools split into query (read-only) and edit (state-changing) categories.
cad-dxf-agent/
src/cad_dxf_agent/
app.py # Desktop entry point
settings.py # Env-based configuration
otel.py # OpenTelemetry bootstrap
core/ # 41 modules — DXF I/O, validation, editing, analysis
llm/ # 22 modules — intent classification, planning, agent loop
models/ # 30 Pydantic schemas
cli/ # Revision CLI (cad-revision)
ui/ # PySide6 desktop UI
web/
frontend/ # React + Vite SPA (Firebase Hosting)
backend/ # FastAPI on Cloud Run (20+ endpoints)
tests/
unit/ # ~3600 unit tests
integration/ # ~100 integration tests
web/ # ~420 web API tests
eval/ # ~240 eval scorecard tests
e2e/ # ~33 end-to-end tests
benchmark/ # ~19 performance benchmarks
gui/ # ~10 PySide6 UI tests
property/ # ~7 fuzz/property tests
smoke/ # ~7 smoke tests
scripts/
smoke_test.py # Standalone smoke test
000-docs/ # All project docs (60+ files)
All docs live in 000-docs/ using flat chronological filing.
Key references:
- V1 Blueprint — architecture, module map, scope
- ADR: Local-First Architecture
- ADR: LLM Plans, Not DXF Edits
- ADR: AI Revision Notes on Safe Layer
MIT — see LICENSE.