Skip to content

About

Drawing intelligence for AEC: analyze and safely edit DXF or vector PDF; DWG requires the optional ODA File Converter. Model-agnostic, typed, deterministic.

Topics

Resources

Contributing

Stars

48 stars

Watchers

1 watching

Forks

Latest commit

 

History

345 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cad-dxf-agent v0.12.0

Drawing Intelligence Platform for AEC professionals.

Upload a DXF or vector PDF—or a DWG when the optional ODA File Converter is installed—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.

CI License: Apache 2.0

Links: Gist One-Pager · Docs

ko-fi

What Is This?

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.

Capabilities

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
Georeferencing Resolve caller, .prj, or supported DXF GEODATA CRS metadata and transform drawing points to/from WGS84
Agent Mode Iterative multi-turn tool-use loop for complex requests (max 10 turns)

Key Principles

  • 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.

Scope

Supported Not Yet
DXF files (2D) DWG native editing; DWG import/export requires the separately installed ODA File Converter
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
Optional entity elevations and projected/geographic CRS metadata Full 3D solids and surfaces

Geospatial coordinates

load_dxf() resolves coordinate references in fail-closed precedence order: an explicit caller override, a same-name .prj sidecar, then supported DXF GEODATA. A present but invalid CRS, or a known $INSUNITS value that contradicts a projected CRS, raises CRSResolutionError instead of silently producing incorrect coordinates.

from cad_dxf_agent.core.crs import drawing_to_wgs84
from cad_dxf_agent.core.dxf_reader import load_dxf
from cad_dxf_agent.models.cad_schema import Point2D

context = load_dxf("site.dxf", crs="EPSG:26916")
longitude_latitude = drawing_to_wgs84(Point2D(x=500000, y=3600000), context.crs)

| 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:

Quickstart

Prerequisites

  • Python 3.11 or 3.12
  • pip

Install

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

Optional DWG support

DWG is not decoded natively by this package. Import and export require the separately installed ODA File Converter, which ezdxf detects at runtime. DXF and vector-PDF workflows do not require ODA.

Run this preflight before accepting DWG files:

python -c "from ezdxf.addons import odafc; print('DWG ready' if odafc.is_installed() else 'DWG unavailable: install ODA File Converter or export the drawing as DXF')"

If the result is DWG unavailable, install ODA File Converter and make its executable discoverable to ezdxf, or set CAD_ODA_PATH. The planned cloud-conversion fallback is not implemented; the application will fail clearly instead of uploading the drawing elsewhere.

Run Tests (Mock Mode — No API Key Needed)

# 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 smoke

Run All Quality Checks

make check

This runs: lint → format check → type check → tests → smoke test.

Individual Quality Commands

make lint        # ruff check
make format      # ruff format
make typecheck   # mypy
make test        # pytest
make smoke       # smoke test script
make security    # bandit + pip-audit

Launch the Desktop App

make run
# or: python -m cad_dxf_agent.app

This opens the PySide6 desktop window. You can:

  1. Click Open DXF to load a file
  2. Type a prompt (e.g., "Move the column east by 2 feet")
  3. Click Plan & Preview to see proposed changes
  4. Click Apply & Save As to save the edited DXF

Web App (self-hosted)

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 8322

The 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.

Testing Without an API Key (Mock Mode)

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.py

The mock provider responds to keywords like "move", "delete", "text", "rename" in your prompt.

Bring Your Own LLM Provider

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.

The engine is model-agnostic. You bring the provider, model, endpoint, and credentials; the engine only accepts and validates the structured ChangeSet. For portability, providers may use these shared environment variables:

Variable Meaning
CAD_LLM_MODEL Model identifier (optional)
CAD_LLM_API_KEY Provider API key (optional; never logged)
CAD_LLM_BASE_URL OpenAI-compatible or provider API endpoint (optional)

Provider-specific variables are also supported. CAD_GEMINI_API_KEY takes precedence over the generic key when requested through get_api_key("gemini-key").

1. Implement the provider interface

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.

2. Point the app at it

CAD_LLM_PROVIDER accepts a dotted import path — package.module:ClassName:

export CAD_LLM_PROVIDER=my_providers:MyProvider
python -m cad_dxf_agent.app

The 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.

AI Revision Notes

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 shift
  • REV 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_NOTES

Protected layers (TITLE, TITLEBLOCK, SEAL, REVISION) are never modified. The real title block revision table is out of scope for V1.

Revision Workflow

Compare two DXF versions, review structural changes, and apply approved edits to produce a new drawing.

CLI

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-all

Global 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"

Web App

The web revision workflow is a 5-step wizard in the Compare tab:

  1. Upload — Upload a revision DXF to compare against the current master
  2. Align — Automatic alignment (or manual control points if confidence is low)
  3. Review — Approve or reject each detected change
  4. Apply — Apply approved changes to produce a new DXF
  5. Download — Download a bundle (.zip) containing the updated master, diff overlay, changelog, and metadata

Troubleshooting

  • 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-points in 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.

Protected Layers

By default, these layers are protected and cannot be edited:

  • TITLE
  • TITLEBLOCK
  • SEAL
  • REVISION

Customize via environment variable:

CAD_PROTECTED_LAYERS=TITLE,TITLEBLOCK,SEAL,REVISION,CUSTOM_LAYER

Any operation targeting an entity on a protected layer will be blocked by the validator.

Observability (OpenTelemetry)

Optional distributed tracing for pipeline performance insights. Off by default, CI-safe (no network calls when disabled).

Enable

# 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

Spans Emitted

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.

Environment Variables

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

Architecture

Request Flow

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.

Two-Axis Intent Classification

Every prompt is classified on two independent axes:

  1. RequestClass — what the user wants done: edit, analyze, compare, query, generate
  2. 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.

Agent Mode

For complex requests, the AgentProvider runs an iterative tool-use loop:

  1. Sends prompt + drawing context + tool definitions to the LLM provider
  2. The provider returns tool calls (query tools: list entities, find by layer; edit tools: move, delete, add)
  3. ToolExecutor dispatches each call, enforcing protected-layer rules
  4. Results feed back to the provider for the next iteration (max 10 turns)
  5. Final ChangeSet extracted from accumulated tool calls

20+ tools split into query (read-only) and edit (state-changing) categories.

Project Structure

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)

Documentation

All docs live in 000-docs/ using flat chronological filing.

Key references:

License

Apache-2.0 — see LICENSE.

About

Drawing intelligence for AEC: analyze and safely edit DXF or vector PDF; DWG requires the optional ODA File Converter. Model-agnostic, typed, deterministic.

Topics

Resources

Contributing

Stars

48 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages