Convert architectural PDF drawings into editable CAD outputs with a FastAPI/Celery conversion pipeline and a Next.js upload, preview, history, and download UI.
DrawLift is a proof-of-concept full-stack application for PDF-to-CAD conversion. Users upload architectural drawings, choose 2D or 3D options, watch progress over Server-Sent Events, preview extracted pages and generated GLB models, then download DXF, optional DWG, or GLB artifacts.
The project is intentionally scoped as a realistic local/dev stack: Next.js frontend, FastAPI API, Celery worker, Redis, PostgreSQL, local file storage, and an operator-supplied DWG converter command when true DWG output is required.
Do not expose this proof-of-concept directly to untrusted public traffic without adding authentication, quota controls, malware scanning, and production storage hardening.
Current protections include PDF extension/MIME validation, Pydantic config validation, safe page-image path resolution, generated-file download checks, CORS configuration, environment-based secrets, and cleanup of expired job files. See docs/security.md for the scoped security model, risks, and recommended production controls.
Architectural PDF drawings often arrive as rasterized plans or mixed vector/raster documents. Turning those drawings into usable CAD is a multi-step process:
- Render PDF pages into images.
- Clean and normalize image data.
- Segment walls, doors, windows, rooms, and text.
- Vectorize detected features into CAD primitives.
- Export layered DXF, optionally generate 3D geometry and GLB, and optionally convert DXF to DWG.
DrawLift separates the interactive web workflow from CPU-heavy conversion work. FastAPI handles validation, job metadata, file serving, and SSE. Celery runs the conversion pipeline out of band so long-running conversions do not block HTTP requests.
- Docker and Docker Compose
- Python 3.11 for host backend development
- Node.js and npm for host frontend development
- Optional:
libredwg/dwgwriteor ODA FileConverter for true DWG output
cp .env.example .envThe default Docker Compose stack uses PostgreSQL, Redis, local storage volumes, and NEXT_PUBLIC_API_URL=http://localhost:8000.
The default ML runtime now targets the Yytsi/floorplan-to-3d-walls Torch
bundle (best.safetensors + config.yaml). On a fresh Docker models volume,
the backend/worker auto-download the bundle and preload the configured ML
backend at worker startup.
make docker-up
make docker-migrate
open http://localhost:3000Useful endpoints:
- Frontend: http://localhost:3000\
- Backend health: http://localhost:8000/api/v1/health\
- OpenAPI docs: http://localhost:8000/docs\
Run PostgreSQL, Redis, and the worker in Docker while running FastAPI and Next.js on the host:
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cd ../frontend
npm install
cd ..
make local-upStop services with:
make local-down- Open http://localhost:3000\.
- Drop or select a PDF file.
- Choose conversion options:
mode:2dor3ddpi: render resolution, constrained by backend validationfloor_height_m,slab_thickness_m, andinclude_ceilingfor 3D jobsoutput_format:dxf,dwg, orbothsegmenter:classicorml
- Submit the job and watch progress on
/jobs/{id}. - Preview extracted page images and, for 3D jobs, the GLB model.
- Download available outputs after completion.
- Use
/historyto filter, reopen, retry failed jobs, or delete jobs.
DWG is proprietary, so DrawLift keeps conversion in an optional sidecar. The
dwg-converter Compose profile now builds GNU LibreDWG from source and exports
its CLIs to the runtime services through a shared /opt/libredwg volume. By
default, Compose auto-configures:
DWG_CONVERTER_COMMAND='dwgwrite {input} {output}'Supported placeholders are {input}, {output}, {input_dir}, {output_dir}, and {stem}. Start the sidecar profile with:
make docker-up-dwgDwgConverterStep prefers GNU LibreDWG's dxf2dwg, then dwgwrite, and falls
back to ODA FileConverter when operators override DWG_CONVERTER_COMMAND.
Base path: /api/v1.
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Liveness/readiness check. |
POST |
/jobs |
Upload a PDF and JSON config as multipart form data. |
GET |
/jobs |
List jobs, newest first, with pagination and optional status filtering. |
GET |
/jobs/{job_id} |
Fetch status, progress, config, timestamps, errors, and page count. |
GET |
/jobs/{job_id}/stream |
Server-Sent Events progress stream backed by Redis Pub/Sub. |
GET |
/jobs/{job_id}/pages/{page_number} |
Serve an extracted page PNG. |
GET |
/jobs/{job_id}/download?format=dxf|dwg|glb |
Download completed outputs. |
POST |
/jobs/{job_id}/retry |
Re-enqueue a failed job with its stored input/config. |
DELETE |
/jobs/{job_id} |
Delete job metadata and local job files. |
Example upload:
curl -X POST http://localhost:8000/api/v1/jobs \
-F 'file=@floorplan.pdf;type=application/pdf' \
-F 'config={"mode":"3d","dpi":300,"floor_height_m":3.0,"output_format":"both","segmenter":"classic"}'See docs/api.md for payloads, status semantics, and edge cases.
Important content from the previous README, TODO, and implementation plan has been split into focused documents:
- Architecture — system boundaries, diagrams, data flow, job lifecycle.
- API — REST/SSE contract and examples.
- Pipeline — conversion stages, config, outputs, and edge cases.
- Design system — frontend conventions, tokens, shared components, accessibility.
- Security — scoped controls, concerns, and production hardening.
- Trade-offs and alternatives — decision log and alternatives considered.
- Risks and edge cases — realistic operational and product risks.
- Operations — setup, service management, tests, migrations, cleanup.
- Roadmap — production-SaaS roadmap: findings from all docs, staged delivery through Stage 7 (US-033 → US-044, Production Core) and Stage 8 (US-045 → US-056, SaaS Product & Billing).
TODO.md remains the detailed issue-tracking source of truth for user stories and historical progress.
flowchart LR
UI[Next.js frontend] -->|REST upload/status| API[FastAPI API]
UI -->|SSE progress| API
API -->|metadata| DB[(PostgreSQL)]
API -->|enqueue| Redis[(Redis broker/pubsub)]
Worker[Celery worker] -->|consume jobs| Redis
Worker -->|read/write status| DB
Worker -->|PDF/pages/DXF/DWG/GLB| Storage[(Local storage volume)]
API -->|serve pages/downloads| Storage
The worker composes pluggable pipeline steps: PDF parsing, preprocessing, segmentation, vectorization, optional extrusion, DXF writing, optional GLB writing, and optional DWG conversion.
Common quality checks:
cd backend
ruff check .
ruff format --check .
mypy app/
pytest
cd ../frontend
npm run lint
npm run format:check
npm run build
# root hooks (frontend deps auto-bootstrap if missing)
cd ..
pre-commit run --all-filesRoot service commands:
make help
make docker-up
make docker-down
make local-up
make local-status
make logs-worker
make download-model
make validate-modelRoot quality commands (per-project variants: make install-backend, make lint-frontend, ...):
make install # backend venv + deps, frontend npm dependencies
make lint # backend ruff (check + format check), frontend eslint + prettier
make typecheck # backend mypy, frontend tsc --noEmit
make test # backend pytest, frontend vitest (when configured)
make build # backend byte-compile, frontend next build
make check # lint + typecheck + test
make ci # install + check + buildWithin a single phase the backend and frontend targets are independent, so they
can run in parallel with -j2:
make -j2 lint
make -j2 typecheck
make -j2 test
make -j2 build
make -j2 checkKeep the phases themselves sequential: install rewrites backend/.venv and
frontend/node_modules, so never run it concurrently with lint, typecheck,
test, or build. Run make ci without -j, or spell the phases out:
make install && make -j2 check && make -j2 buildIssues, branches, and PRs are managed in GitHub. Use the user-story and issue conventions documented in TODO.md and the delivery summary in docs/roadmap.md.
Before opening a PR:
- Keep changes scoped to one issue or concern.
- Run relevant backend/frontend checks.
- Update docs when behavior, commands, API payloads, or trade-offs change.
- Reference the GitHub issue in the branch name or PR body where applicable.