Architektur-Uebersicht
Benning E-CAD ist eine Flask-Webanwendung mit einer Canvas-basierten Zeichenflaeche im Browser. Der Stack:
Request-Flow
Browser └─ GET /ecad/editor/42 → Flask renders editor.html └─ POST /api/auth/login → Flask API Blueprint → SQLAlchemy → SQLite └─ PUT /api/pages/1 → Flask → JSON in canvas_data Column └─ WS /socket.io → SocketIO → In-Memory presence dict └─ POST /api/ki/generate → Flask → DeepSeek API → JSON response
Backend-Module
├── app/
│ ├── __init__.py # App-Factory, SocketIO-Events, Page-Routes
│ ├── config.py # Flask-Konfiguration (SECRET_KEY, DB-URI)
│ ├── models.py # SQLAlchemy ORM: User, Project, SchaltplanPage, Symbol, Template
│ ├── api/
│ │ ├── __init__.py # Blueprint-Registrierung: api_bp = Blueprint('api', ...)
│ │ ├── routes.py # ALLE Endpoints (4300 Zeilen). Einzel-Blueprints geplant.
│ │ ├── ai_api.py # KI-Hilfsfunktionen (ausgelagert)
│ │ ├── export_api.py # PDF/PNG Export-Hilfsfunktionen
│ │ └── helpers.py # Gemeinsame Hilfsfunktionen
│ ├── services/
│ │ └── vde_engine.py # VDE-Regelpruefer (deterministisch, kein KI)
│ ├── static/
│ │ ├── js/editor/ # Frontend-Module (siehe Abschnitt 3)
│ │ ├── symbols/ # SVG-Symboldateien + symbol_index.json
│ │ └── bmk_reference.json # BMK-Konventionen
│ └── templates/ # Jinja2 HTML-Templates
├── Dockerfile # Python 3.12-slim, EXPOSE 5000
├── requirements.txt # Flask, SQLAlchemy, ReportLab, eventlet, ...
├── entrypoint.sh # gunicorn -k eventlet -w 1
└── .env # DEEPSEEK_API_KEY, SECRET_KEY
Blueprint-Struktur (routes.py)
Alle API-Endpoints sind in einem einzigen Blueprint api_bp mit Prefix /api registriert. Logische Gruppierung durch Kommentar-Header:
# Sektionen in routes.py: # AUTH → /api/auth/* # PROJECTS → /api/projects/* # PAGES → /api/pages/* # SYMBOLS → /api/symbols/* # TEMPLATES → /api/templates/* # EXPORT → /api/projects/:id/export/* # BMK → /api/projects/:id/bmk/* # KI → /api/ki/* # BOM → /api/projects/:id/bom* # KLEMMPLAN → /api/projects/:id/klemmplan* # QUERVERWEISE → /api/projects/:id/querverweise # KABELPLAN → /api/projects/:id/kabelplan # KONTAKTSPIEGEL → /api/projects/:id/kontaktspiegel* # MAKROS → /api/macros* # PROJECT-TPL → /api/project-templates, /api/projects/from-template # COMPLETE-EXPORT → /api/projects/:id/export/complete
Frontend-Dateien
Der gesamte Editor lebt in editor.html und laedt die JS-Module als <script type="module">. Kein Bundler, kein Build-Step.
| Datei | Aufgabe |
|---|---|
| core.js | Globaler State: state-Objekt (elements, wires, selectedIds, tool, zoom, pan). Einzige Source of Truth. |
| engine-render.js | Canvas-Rendering: zeichnet Elements, Wires, Grid, Selektionen, Annotations. requestAnimationFrame-Loop. |
| events-tools.js | Mouse/Touch/Keyboard-Handler. Tool-Logik: Select, Move, Wire, Place, Delete, Pan. |
| wire-system.js | Wire-Routing, Ortho-Modus, Snap-on-Pin, Wire-Verbindungslogik. |
| symbols.js | Symbol-Bibliothek, SVG-Laden, Cache-Verwaltung, Pin-Definitionen. |
| grid-snap.js | Grid-Darstellung, Snap-to-Grid Logik (20px Raster). |
| layers.js | Layer-System: Symbole, Leitungen, Annotations, Grid jeweils auf eigenem Layer. |
| netlist-engine.js | Netlist-Extraktion: verbundene Pins finden, Potential-Netz aufbauen. |
| annotations.js | Text-Annotationen, Dimensionspfeile, Freitext-Kommentare. |
| pages-save-ui.js | Seiten-Verwaltungs-UI: Tabs, Hinzufuegen, Umbenennen, Drag-Reorder. Auto-Save-Logik. |
| commands.js | Undo/Redo-Command-Stack. Alle aendernden Aktionen als Command-Objekte. |
| revision-system.js | Server-seitige Revisionen: speichert Snapshots, laedt Versionshistorie. |
| search-replace.js | Suchen & Ersetzen fuer BMK, Labels, Symboltypen ueber alle Seiten. |
| measure-tool.js | Messwerkzeug: Abstands-Messung auf dem Canvas in mm. |
| spatial-hash.js | Spatial Hashing fuer performante Hit-Tests bei vielen Elementen. |
| symbol-cache.js | LRU-Cache fuer geladene SVG-Symbole. Verhindert redundante Server-Requests. |
| pro-features.js / phase8-features.js | Erweiterte Pro-Funktionen aus Phase 7+8: Schaltschrank-Layout, Verteilerplan, etc. |
| api.js | Alle fetch()-Wrapper fuer die REST API. Zentraler HTTP-Client. |
Datenbank-Modelle
User
id, username (unique), password_hash, email (unique), company_name, company_logo, created_at → hat: projects (cascade delete), custom_symbols
Project
id, user_id (FK), name, description, created_at, updated_at, last_accessed, is_favorite → hat: pages (cascade delete), document_parts
SchaltplanPage
id, project_id (FK), page_number, title, page_format (A4/A3/A2), orientation (quer/hoch)
canvas_data (JSON): { elements: [...], wires: [...], _versions: [...] }
created_at, updated_at
canvas_data.elements[]: { id, symbolId, x, y, width, height, bmk, label, voltage, rotation, ... }
canvas_data.wires[]: { id, segments:[{x1,y1,x2,y2}], color, potential, crossSection, number, wireType }
canvas_data._versions[]: Auto-Snapshots (max 20). { timestamp, elements, wires }
Symbol
id, name, category, subcategory, svg_data (Text), description, iec_number tags (JSON Array), is_custom (bool), user_id (FK, null=global), created_at
Template
id, name, description, format (A4/A3), company_data (JSON), logo_path, created_at # Schriftfeld-Templates fuer PDF-Export
Neues Symbol erstellen
Option A: SVG-Datei + symbol_index.json (empfohlen fuer globale Symbole)
SVG erstellen
Erstelle die SVG-Datei im passenden Unterordner:
/opt/vbwork-ecad/app/static/symbols/
└── meine-kategorie/
└── mein-symbol.svg
SVG-Konventionen:
- ViewBox:
0 0 60 60(Standard), andere Verhaeltnisse moeglich - Stroke-Farbe:
#333333(wird im Renderer ueberschrieben) - Keine eingebetteten Scripts, keine foreignObject-Elemente
- Pin-Positionen als Kommentar dokumentieren (z.B.
<!-- pin1: 0,30 pin2: 60,30 -->)
symbol_index.json aktualisieren
ssh root@87.106.42.193 nano /opt/vbwork-ecad/app/static/symbols/symbol_index.json
Eintrag hinzufuegen:
{
"categories": [
{
"name": "Meine Kategorie",
"symbols": [
{
"name": "Mein Symbol",
"file": "meine-kategorie/mein-symbol.svg",
"description": "Beschreibung",
"iec_number": "IEC 60617-XX",
"tags": ["tag1", "tag2"]
}
]
}
]
}
Container neu starten
cd /opt/vbwork-ecad && docker compose restart
Option B: API (fuer benutzerspezifische Symbole)
POST /api/symbols
Content-Type: application/json
{
"name": "Mein Symbol",
"category": "Meine Kategorie",
"svg_data": "<svg viewBox='0 0 60 60'>...</svg>",
"description": "Beschreibung",
"iec_number": "IEC 60617-XX",
"tags": ["tag1"]
}
Das Symbol wird in der DB gespeichert und ist nur fuer den erstellenden Benutzer sichtbar.
Neuen API-Blueprint hinzufuegen
Aktuell sind alle Endpoints in routes.py. Fuer neue Funktionen kann ein separater Blueprint erstellt werden:
Blueprint-Datei erstellen
# /opt/vbwork-ecad/app/api/mein_api.py
from flask import Blueprint, request, jsonify
from flask_login import login_required, current_user
from .. import db
mein_bp = Blueprint('mein', __name__)
def ok(data=None, status=200):
return jsonify({'ok': True, 'data': data}), status
def err(msg, status=400):
return jsonify({'ok': False, 'error': msg}), status
@mein_bp.route('/mein-endpoint', methods=['GET'])
@login_required
def mein_endpoint():
return ok({'message': 'Hallo'})
Blueprint in __init__.py registrieren
# /opt/vbwork-ecad/app/api/__init__.py from .mein_api import mein_bp # Entweder eigener Prefix: app.register_blueprint(mein_bp, url_prefix='/api/mein') # Oder gleicher api_bp Prefix (dann in mein_api.py api_bp statt mein_bp nutzen): from .api import api_bp # importieren und Route dazufuegen
Container neu starten & testen
cd /opt/vbwork-ecad && docker compose restart curl https://vbwork.benning.one/api/mein/mein-endpoint
Tests schreiben und ausfuehren
Test-Setup
# /opt/vbwork-ecad/tests/conftest.py (muss erstellt werden)
import pytest
from app import create_app, db
from app.models import User
@pytest.fixture
def app():
app = create_app()
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['WTF_CSRF_ENABLED'] = False
with app.app_context():
db.create_all()
yield app
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def auth_client(client):
# Erstelle Testuser und logge ein
with client.application.app_context():
u = User(username='test', email='test@test.com')
u.set_password('test123')
db.session.add(u)
db.session.commit()
client.post('/api/auth/login', json={'username': 'test', 'password': 'test123'})
return client
Beispiel-Test
# /opt/vbwork-ecad/tests/test_projects.py
def test_create_project(auth_client):
r = auth_client.post('/api/projects', json={'name': 'Test Projekt'})
assert r.status_code == 201
data = r.get_json()
assert data['ok'] is True
assert data['data']['name'] == 'Test Projekt'
assert data['data']['page_count'] == 1 # Erste Seite wird auto-erstellt
def test_project_not_found(auth_client):
r = auth_client.get('/api/projects/99999')
assert r.status_code == 404
def test_health(client):
r = client.get('/api/health')
assert r.status_code == 200
assert r.get_json()['data']['status'] == 'ok'
Tests ausfuehren
ssh root@87.106.42.193 cd /opt/vbwork-ecad docker exec -it vbwork-ecad-app-1 python -m pytest tests/ -v
Docker-Setup
Produktiv (auf dem VPS)
cd /opt/vbwork-ecad # Container-Status docker compose ps # Logs verfolgen docker compose logs -f # Container neu starten (nach Code-Aenderungen) docker compose restart # Kompletter Rebuild (nach requirements.txt Aenderungen) docker compose down docker compose build --no-cache docker compose up -d
Lokal (Entwicklung)
# Voraussetzungen: Docker Desktop, git git clone git@github.com:... /local/vbwork-ecad cd /local/vbwork-ecad cp .env.example .env # DEEPSEEK_API_KEY setzen docker compose up -d # App laeuft auf http://localhost:5000
Dockerfile
FROM python:3.12-slim WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends gcc COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN chmod +x /app/entrypoint.sh RUN mkdir -p /app/data /app/app/static/symbols EXPOSE 5000 CMD ["/app/entrypoint.sh"] # entrypoint.sh: # gunicorn -k eventlet -w 1 --bind 0.0.0.0:5000 "app:create_app()"
Volumes
# docker-compose.yml (relevant): volumes: - /opt/vbwork-ecad/data:/app/data # SQLite-Datenbankdatei - /opt/vbwork-ecad/app:/app/app # Live-Reload fuer Entwicklung
Deployment-Prozess
Code-Aenderungen deployen
ssh root@87.106.42.193 cd /opt/vbwork-ecad git pull origin main docker compose restart
DB-Migration (neue Felder)
# Keine automatische Migration - manuell in SQLite
docker exec -it vbwork-ecad-app-1 python -c "
from app import create_app, db
app = create_app()
with app.app_context():
db.create_all() # Erstellt neue Tabellen, aendert keine bestehenden
"
Nginx-Reverse-Proxy
# /etc/nginx/sites-available/vbwork (vereinfacht)
location /ecad {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # fuer WebSocket/SocketIO
}
Backup
# SQLite-Datenbank sichern cp /opt/vbwork-ecad/data/ecad.db /opt/vbwork-ecad/data/ecad.db.backup.$(date +%Y%m%d)
Umgebungsvariablen
| Variable | Pflicht | Beschreibung |
|---|---|---|
| SECRET_KEY | Ja | Flask Session-Signing-Key. Mind. 32 Zeichen zufaellig. |
| DEEPSEEK_API_KEY | Nein | Fuer KI-Endpoints (/api/ki/*). Ohne Key: KI-Funktionen fehlschlagen. |
| DATABASE_URL | Nein | Default: sqlite:////app/data/ecad.db. Fuer PostgreSQL: postgresql://user:pw@host/db |
| FLASK_ENV | Nein | production (default) oder development (aktiviert Debugger, kein Produktiveinsatz!) |