1

Architektur-Uebersicht

Benning E-CAD ist eine Flask-Webanwendung mit einer Canvas-basierten Zeichenflaeche im Browser. Der Stack:

Flask 3.x Backend
REST API, Session-Auth, PDF-Export via ReportLab, SocketIO fuer Echtzeit-Collaboration
SQLite / SQLAlchemy DB
Persistenz. Datei: /app/data/ecad.db. ORM mit Flask-SQLAlchemy 3.x
Vanilla JS (ES6+) Frontend
Modularer Aufbau in /static/js/editor/. Kein Build-Tool. Direkte script-Tags.
SVG Canvas
Zeichenflaeche ist ein HTML5 Canvas mit SVG-Symbol-Rendering. engine-render.js als Herzstuck.
DeepSeek API
KI-Integration. deepseek-chat Modell fuer Schaltplan-Generierung und VDE-Pruefung.
eventlet + SocketIO
Async-Server fuer Echtzeit-Collaboration. Cursor-Sharing, Presence-Anzeige.

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
2

Backend-Module

/opt/vbwork-ecad/
├── 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
3

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

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
5

Neues Symbol erstellen

SVG-Symbole werden in /app/static/symbols/ gespeichert und ueber symbol_index.json indiziert. Neue Symbole koennen per Datei oder API hinzugefuegt werden.

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.

6

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
7

Tests schreiben und ausfuehren

Das Projekt hat derzeit keine automatisierten Tests. Pytest ist konfiguriert (.pytest_cache vorhanden). Neue Features sollten mit Tests abgesichert werden.

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
8

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
9

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)
10

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!)
Secrets niemals in git committen. Datei .env ist in .gitignore. Fuer Produktiv-Secrets: .env_secrets verwenden, wird durch entrypoint.sh geladen.