e4a80dc0e6
- 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>
166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from sqlalchemy.orm import Session
|
|
from typing import List
|
|
from .. import models, schemas
|
|
from ..database import get_db
|
|
import datetime
|
|
|
|
router = APIRouter(tags=["diagrams"])
|
|
|
|
|
|
@router.get("/", response_model=List[schemas.DiagramSummary])
|
|
def list_diagrams(db: Session = Depends(get_db)):
|
|
return db.query(models.Diagram).order_by(models.Diagram.updated_at.desc()).all()
|
|
|
|
|
|
@router.post("/", response_model=schemas.DiagramSummary)
|
|
def create_diagram(diagram: schemas.DiagramCreate, db: Session = Depends(get_db)):
|
|
db_diagram = models.Diagram(**diagram.model_dump())
|
|
db.add(db_diagram)
|
|
db.commit()
|
|
db.refresh(db_diagram)
|
|
return db_diagram
|
|
|
|
|
|
@router.get("/{diagram_id}", response_model=schemas.DiagramFull)
|
|
def get_diagram(diagram_id: int, db: Session = Depends(get_db)):
|
|
diagram = db.query(models.Diagram).filter(models.Diagram.id == diagram_id).first()
|
|
if not diagram:
|
|
raise HTTPException(status_code=404, detail="Diagram not found")
|
|
return diagram
|
|
|
|
|
|
@router.patch("/{diagram_id}", response_model=schemas.DiagramSummary)
|
|
def update_diagram(diagram_id: int, update: schemas.DiagramUpdate, db: Session = Depends(get_db)):
|
|
diagram = db.query(models.Diagram).filter(models.Diagram.id == diagram_id).first()
|
|
if not diagram:
|
|
raise HTTPException(status_code=404, detail="Diagram not found")
|
|
for key, value in update.model_dump(exclude_none=True).items():
|
|
setattr(diagram, key, value)
|
|
diagram.updated_at = datetime.datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(diagram)
|
|
return diagram
|
|
|
|
|
|
@router.delete("/{diagram_id}")
|
|
def delete_diagram(diagram_id: int, db: Session = Depends(get_db)):
|
|
diagram = db.query(models.Diagram).filter(models.Diagram.id == diagram_id).first()
|
|
if not diagram:
|
|
raise HTTPException(status_code=404, detail="Diagram not found")
|
|
db.delete(diagram)
|
|
db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.post("/{diagram_id}/duplicate", response_model=schemas.DiagramSummary, status_code=201)
|
|
def duplicate_diagram(diagram_id: int, db: Session = Depends(get_db)):
|
|
src = db.query(models.Diagram).filter(models.Diagram.id == diagram_id).first()
|
|
if not src:
|
|
raise HTTPException(status_code=404, detail="Diagram not found")
|
|
|
|
new_diag = models.Diagram(name=f"{src.name} (copy)", description=src.description or "")
|
|
db.add(new_diag)
|
|
db.flush()
|
|
|
|
id_map = {}
|
|
for dev in src.devices:
|
|
new_dev = models.Device(
|
|
diagram_id=new_diag.id,
|
|
device_type=dev.device_type,
|
|
label=dev.label,
|
|
reference=dev.reference,
|
|
x=dev.x + 30,
|
|
y=dev.y + 30,
|
|
width=dev.width,
|
|
height=dev.height,
|
|
properties=dev.properties,
|
|
pins=dev.pins,
|
|
)
|
|
db.add(new_dev)
|
|
db.flush()
|
|
id_map[dev.id] = new_dev.id
|
|
|
|
for wire in src.wires:
|
|
new_wire = models.Wire(
|
|
diagram_id=new_diag.id,
|
|
label=wire.label,
|
|
from_device_id=id_map.get(wire.from_device_id),
|
|
from_pin=wire.from_pin,
|
|
to_device_id=id_map.get(wire.to_device_id),
|
|
to_pin=wire.to_pin,
|
|
color_primary=wire.color_primary,
|
|
color_stripe=wire.color_stripe,
|
|
gauge=wire.gauge,
|
|
length=wire.length,
|
|
length_unit=wire.length_unit,
|
|
notes=wire.notes or "",
|
|
waypoints=wire.waypoints or [],
|
|
twisted_pair=wire.twisted_pair,
|
|
twist_pitch=wire.twist_pitch,
|
|
shielded=wire.shielded,
|
|
show_size_label=wire.show_size_label,
|
|
)
|
|
db.add(new_wire)
|
|
|
|
db.commit()
|
|
db.refresh(new_diag)
|
|
return new_diag
|
|
|
|
|
|
@router.post("/import", response_model=schemas.DiagramSummary, status_code=201)
|
|
async def import_diagram(request: Request, db: Session = Depends(get_db)):
|
|
data = await request.json()
|
|
|
|
new_diag = models.Diagram(
|
|
name=data.get("name", "Imported Diagram"),
|
|
description=data.get("description", ""),
|
|
)
|
|
db.add(new_diag)
|
|
db.flush()
|
|
|
|
id_map = {}
|
|
for dev_data in data.get("devices", []):
|
|
new_dev = models.Device(
|
|
diagram_id=new_diag.id,
|
|
device_type=dev_data.get("device_type", "generic"),
|
|
label=dev_data.get("label", ""),
|
|
reference=dev_data.get("reference", ""),
|
|
x=dev_data.get("x", 100),
|
|
y=dev_data.get("y", 100),
|
|
width=dev_data.get("width", 120),
|
|
height=dev_data.get("height", 60),
|
|
properties=dev_data.get("properties", {}),
|
|
pins=dev_data.get("pins", []),
|
|
)
|
|
db.add(new_dev)
|
|
db.flush()
|
|
if "id" in dev_data:
|
|
id_map[dev_data["id"]] = new_dev.id
|
|
|
|
for wire_data in data.get("wires", []):
|
|
new_wire = models.Wire(
|
|
diagram_id=new_diag.id,
|
|
label=wire_data.get("label", ""),
|
|
from_device_id=id_map.get(wire_data.get("from_device_id")),
|
|
from_pin=wire_data.get("from_pin", ""),
|
|
to_device_id=id_map.get(wire_data.get("to_device_id")),
|
|
to_pin=wire_data.get("to_pin", ""),
|
|
color_primary=wire_data.get("color_primary", "#FF0000"),
|
|
color_stripe=wire_data.get("color_stripe"),
|
|
gauge=wire_data.get("gauge", "18 AWG"),
|
|
length=wire_data.get("length"),
|
|
length_unit=wire_data.get("length_unit", "in"),
|
|
notes=wire_data.get("notes", ""),
|
|
waypoints=wire_data.get("waypoints", []),
|
|
twisted_pair=wire_data.get("twisted_pair", False),
|
|
twist_pitch=wire_data.get("twist_pitch", 16.0),
|
|
shielded=wire_data.get("shielded", False),
|
|
show_size_label=wire_data.get("show_size_label", False),
|
|
)
|
|
db.add(new_wire)
|
|
|
|
db.commit()
|
|
db.refresh(new_diag)
|
|
return new_diag
|