Files
Wiring-Designer/backend/app/main.py
T
Kyle e4a80dc0e6 Add views, import/duplicate, DRC, jumps toggle, multi-wire select, auto-route, delete view tabs
- Roadmap #22: File→Import JSON to restore a diagram (drag-drop or file picker)
- Roadmap #23: Duplicate diagram from diagram list
- Roadmap #24: DRC panel with collapsible categories (unconnected pins, dup refs, no length, no P/N)
- Multi-view tabs: named views per diagram with independent device/wire layouts; snapshot-on-entry prevents bleed
- Keyboard arrow nudge for selected device(s)
- Undo for move, duplicate, label/property changes
- Wire jump arcs toggle (⌒ Jumps button in toolbar)
- Shift+click multi-wire selection; right-click bulk clear waypoints or auto-route
- Auto-route: obstacle-avoiding ortho routing for single or bulk selected wires
- Delete view tab with × button; right-click context menu for rename/delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 19:18:39 -04:00

95 lines
3.9 KiB
Python

import os
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from sqlalchemy import text, inspect as sa_inspect
from .database import Base, engine, SessionLocal
from .routers import bundles, connectors, diagrams, devices, export, octopart, views, wires
Base.metadata.create_all(bind=engine)
# Lightweight column migrations (SQLite ALTER TABLE IF NOT EXISTS unsupported)
def _migrate():
import sys
# ── Column additions ────────────────────────────────────────────────────────
try:
with engine.connect() as conn:
def _cols(table):
return {c["name"] for c in sa_inspect(engine).get_columns(table)}
if "waypoints" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN waypoints TEXT DEFAULT '[]'"))
conn.commit()
if "sheet_id" not in _cols("devices"):
conn.execute(text("ALTER TABLE devices ADD COLUMN sheet_id INTEGER REFERENCES sheets(id)"))
conn.commit()
if "sheet_id" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN sheet_id INTEGER REFERENCES sheets(id)"))
conn.commit()
if "twisted_pair" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN twisted_pair INTEGER DEFAULT 0"))
conn.commit()
if "twist_pitch" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN twist_pitch REAL DEFAULT 16.0"))
conn.commit()
if "shielded" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN shielded INTEGER DEFAULT 0"))
conn.commit()
if "bundle_id" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN bundle_id INTEGER REFERENCES wire_bundles(id)"))
conn.commit()
if "show_size_label" not in _cols("wires"):
conn.execute(text("ALTER TABLE wires ADD COLUMN show_size_label INTEGER DEFAULT 0"))
conn.commit()
# diagram_views / view_device_positions / view_wire_waypoints are created
# via Base.metadata.create_all — no ALTER TABLE needed for new tables
except Exception as e:
print(f"[migrate] column error: {e}", file=sys.stderr)
_migrate()
app = FastAPI(title="Wiring Diagram Maker", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(bundles.router, prefix="/api/bundles")
app.include_router(diagrams.router, prefix="/api/diagrams")
app.include_router(devices.router, prefix="/api/devices")
app.include_router(wires.router, prefix="/api/wires")
app.include_router(views.router, prefix="/api/views")
app.include_router(octopart.router, prefix="/api/octopart")
app.include_router(export.router, prefix="/api/export")
app.include_router(connectors.router, prefix="/api/connectors")
FRONTEND_DIR = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..", "frontend")
)
if os.path.isdir(FRONTEND_DIR):
for _sub in ("css", "js"):
_d = os.path.join(FRONTEND_DIR, _sub)
if os.path.isdir(_d):
app.mount(f"/{_sub}", StaticFiles(directory=_d), name=_sub)
_index = os.path.join(FRONTEND_DIR, "index.html")
@app.get("/")
async def _root():
return FileResponse(_index)
@app.exception_handler(404)
async def _spa_fallback(request: Request, exc):
if request.url.path.startswith("/api"):
return JSONResponse(status_code=404, content={"detail": "Not Found"})
return FileResponse(_index)