From 5b38af78f04f94543f1fb6867f9f04239d37563c Mon Sep 17 00:00:00 2001 From: mistangl Date: Thu, 16 Apr 2026 13:34:05 +0200 Subject: [PATCH] erste Fassung von Client und Server rein --- .gitignore | 4 + bin/client.bat | 74 ++++++++++++++++++ bin/install_npm.bat | 18 +++++ bin/install_npm.sh | 19 +++++ bin/server.bat | 74 ++++++++++++++++++ bin/setenv.bat | 15 ++++ bin/setenv.sh | 13 +++ bin/start.bat | 21 +++++ bin/stop.bat | 17 ++++ client/index.html | 12 +++ client/package.json | 23 ++++++ client/src/App.tsx | 10 +++ client/src/main.tsx | 9 +++ client/tsconfig.json | 17 ++++ client/vite.config.ts | 16 ++++ doc/architektur.md | 152 ++++++++++++++++++++++++++++++++++++ server/catalog/symbols.json | 26 ++++++ server/main.py | 66 ++++++++++++++++ server/requirements.txt | 3 + 19 files changed, 589 insertions(+) create mode 100644 bin/client.bat create mode 100644 bin/install_npm.bat create mode 100644 bin/install_npm.sh create mode 100644 bin/server.bat create mode 100644 bin/start.bat create mode 100644 bin/stop.bat create mode 100644 client/index.html create mode 100644 client/package.json create mode 100644 client/src/App.tsx create mode 100644 client/src/main.tsx create mode 100644 client/tsconfig.json create mode 100644 client/vite.config.ts create mode 100644 doc/architektur.md create mode 100644 server/catalog/symbols.json create mode 100644 server/main.py create mode 100644 server/requirements.txt diff --git a/.gitignore b/.gitignore index 61a9253..1c58ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,7 @@ ipython_config.py /work /log /results + +# Node.js (client) +node_modules/ +client/dist/ diff --git a/bin/client.bat b/bin/client.bat new file mode 100644 index 0000000..4065004 --- /dev/null +++ b/bin/client.bat @@ -0,0 +1,74 @@ +@echo off + +REM ================================================================ +REM PLANTPLAN - Client (Vite Dev-Server) verwalten +REM Verwendung: client.bat [start|stop|reload|status] +REM ================================================================ + +call "%~dp0setenv.bat" + +if "%1"=="" goto usage + +if /i "%1"=="start" goto start +if /i "%1"=="stop" goto stop +if /i "%1"=="reload" goto reload +if /i "%1"=="status" goto status +goto usage + +:start +echo Starting PlantPlan Client (Vite) ... + +REM Pruefen ob Client bereits laeuft +tasklist /FI "WINDOWTITLE eq PlantPlan-Client" 2>nul | find /i "cmd.exe" >nul +if not errorlevel 1 ( + echo Client laeuft bereits! + goto end +) + +REM Pruefen ob node_modules existiert +if not exist "%PV_CLIENT%\node_modules" ( + echo FEHLER: node_modules nicht gefunden! Bitte zuerst install_npm.bat ausfuehren. + goto end +) + +start "PlantPlan-Client" cmd /k "cd /d "%PV_CLIENT%" && npm run dev" +echo Client gestartet auf %PV_CLIENT_URL% +goto end + +:stop +echo Stopping PlantPlan Client ... +taskkill /FI "WINDOWTITLE eq PlantPlan-Client" /T /F >nul 2>&1 +if errorlevel 1 ( + echo Client war nicht gestartet. +) else ( + echo Client gestoppt. +) +goto end + +:reload +echo Reloading PlantPlan Client ... +call :stop +timeout /t 2 /nobreak >nul +call :start +goto end + +:status +tasklist /FI "WINDOWTITLE eq PlantPlan-Client" 2>nul | find /i "cmd.exe" >nul +if not errorlevel 1 ( + echo Client: LAEUFT +) else ( + echo Client: GESTOPPT +) +goto end + +:usage +echo. +echo Verwendung: client.bat [start^|stop^|reload^|status] +echo. +echo start - Client starten (Vite Dev-Server) +echo stop - Client stoppen +echo reload - Client neu starten +echo status - Pruefen ob Client laeuft +echo. + +:end diff --git a/bin/install_npm.bat b/bin/install_npm.bat new file mode 100644 index 0000000..fc74a36 --- /dev/null +++ b/bin/install_npm.bat @@ -0,0 +1,18 @@ +@echo off +REM ================================================================ +REM PLANTPLAN - Node.js Packages fuer Client installieren +REM ================================================================ +REM Fuehrt npm install im client/ Verzeichnis aus +REM ================================================================ + +call "%~dp0setenv.bat" + +if not exist "%PV_CLIENT%\node_modules" ( + echo Installiere Node.js Packages fuer Client... + cd /d "%PV_CLIENT%" + npm install + echo Erfolgreich. +) else ( + echo Node.js Packages bereits installiert! + echo Zum Aktualisieren: cd client ^& npm install +) diff --git a/bin/install_npm.sh b/bin/install_npm.sh new file mode 100644 index 0000000..cd0df0d --- /dev/null +++ b/bin/install_npm.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# ================================================================ +# PLANTPLAN - Node.js Packages fuer Client installieren +# ================================================================ +# Fuehrt npm install im client/ Verzeichnis aus +# ================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/setenv.sh" + +if [ ! -d "$PV_CLIENT/node_modules" ]; then + echo "Installiere Node.js Packages fuer Client..." + cd "$PV_CLIENT" + npm install + echo "Erfolgreich." +else + echo "Node.js Packages bereits installiert!" + echo "Zum Aktualisieren: cd client && npm install" +fi diff --git a/bin/server.bat b/bin/server.bat new file mode 100644 index 0000000..7796877 --- /dev/null +++ b/bin/server.bat @@ -0,0 +1,74 @@ +@echo off + +REM ================================================================ +REM PLANTPLAN - Server (FastAPI/Uvicorn) verwalten +REM Verwendung: server.bat [start|stop|reload|status] +REM ================================================================ + +call "%~dp0setenv.bat" + +if "%1"=="" goto usage + +if /i "%1"=="start" goto start +if /i "%1"=="stop" goto stop +if /i "%1"=="reload" goto reload +if /i "%1"=="status" goto status +goto usage + +:start +echo Starting PlantPlan Server (uvicorn) ... + +REM Pruefen ob Server bereits laeuft +tasklist /FI "WINDOWTITLE eq PlantPlan-Server" 2>nul | find /i "cmd.exe" >nul +if not errorlevel 1 ( + echo Server laeuft bereits! + goto end +) + +REM Pruefen ob venv existiert +if not exist "%PROJECT%\.venv\Scripts\activate.bat" ( + echo FEHLER: .venv nicht gefunden! Bitte zuerst install_py.bat ausfuehren. + goto end +) + +start "PlantPlan-Server" cmd /k "cd /d "%PV_SERVER%" && "%PROJECT%\.venv\Scripts\activate.bat" && uvicorn main:app --reload --host %PV_SERVER_HOST% --port %PV_SERVER_PORT%" +echo Server gestartet auf %PV_SERVER_URL% +goto end + +:stop +echo Stopping PlantPlan Server ... +taskkill /FI "WINDOWTITLE eq PlantPlan-Server" /T /F >nul 2>&1 +if errorlevel 1 ( + echo Server war nicht gestartet. +) else ( + echo Server gestoppt. +) +goto end + +:reload +echo Reloading PlantPlan Server ... +call :stop +timeout /t 2 /nobreak >nul +call :start +goto end + +:status +tasklist /FI "WINDOWTITLE eq PlantPlan-Server" 2>nul | find /i "cmd.exe" >nul +if not errorlevel 1 ( + echo Server: LAEUFT +) else ( + echo Server: GESTOPPT +) +goto end + +:usage +echo. +echo Verwendung: server.bat [start^|stop^|reload^|status] +echo. +echo start - Server starten (uvicorn mit --reload) +echo stop - Server stoppen +echo reload - Server neu starten +echo status - Pruefen ob Server laeuft +echo. + +:end diff --git a/bin/setenv.bat b/bin/setenv.bat index f3f3207..6640f9d 100644 --- a/bin/setenv.bat +++ b/bin/setenv.bat @@ -20,6 +20,15 @@ set "PV_LOG=%PROJECT%\log" set "PV_TESTS=%PROJECT%\tests" set "PV_RESULTS=%PROJECT%\results" set "PV_EXAMPLES=%PROJECT%\examples" +set "PV_CLIENT=%PROJECT%\client" +set "PV_SERVER=%PROJECT%\server" + +REM Netzwerk-Konfiguration +set "PV_SERVER_HOST=127.0.0.1" +set "PV_SERVER_PORT=8000" +set "PV_CLIENT_PORT=5173" +set "PV_SERVER_URL=http://%PV_SERVER_HOST%:%PV_SERVER_PORT%" +set "PV_CLIENT_URL=http://localhost:%PV_CLIENT_PORT%" REM Python-Pfad erweitern (nur wenn noch nicht vorhanden) echo %PYTHONPATH% | find /i "%PV_LIB%" >nul @@ -35,6 +44,8 @@ if not exist "%PV_DATA%" mkdir "%PV_DATA%" if not exist "%PV_LOG%" mkdir "%PV_LOG%" if not exist "%PV_RESULTS%" mkdir "%PV_RESULTS%" if not exist "%PV_EXAMPLES%" mkdir "%PV_EXAMPLES%" +if not exist "%PV_CLIENT%" mkdir "%PV_CLIENT%" +if not exist "%PV_SERVER%" mkdir "%PV_SERVER%" REM Umgebungsvariablen anzeigen echo. @@ -47,6 +58,10 @@ echo PV_DATA = %PV_DATA% echo PV_RESULTS = %PV_RESULTS% echo PV_LOG = %PV_LOG% echo PV_EXAMPLES = %PV_EXAMPLES% +echo PV_CLIENT = %PV_CLIENT% +echo PV_SERVER = %PV_SERVER% +echo PV_SERVER_URL = %PV_SERVER_URL% +echo PV_CLIENT_URL = %PV_CLIENT_URL% echo PYTHONPATH = %PYTHONPATH% echo ================================================================ echo. diff --git a/bin/setenv.sh b/bin/setenv.sh index a946a2d..3294dda 100644 --- a/bin/setenv.sh +++ b/bin/setenv.sh @@ -19,8 +19,17 @@ export PV_CFG="$PROJECT/cfg" export PV_LOG="$PROJECT/log" export PV_TESTS="$PROJECT/tests" export PV_RESULTS="$PROJECT/results" +export PV_CLIENT="$PROJECT/client" +export PV_SERVER="$PROJECT/server" export PV_EXAMPLES="$PROJECT/examples" +# Netzwerk-Konfiguration +export PV_SERVER_HOST="127.0.0.1" +export PV_SERVER_PORT="8000" +export PV_CLIENT_PORT="5173" +export PV_SERVER_URL="http://$PV_SERVER_HOST:$PV_SERVER_PORT" +export PV_CLIENT_URL="http://localhost:$PV_CLIENT_PORT" + # Python-Pfad erweitern (nur wenn noch nicht vorhanden) if [[ ":$PYTHONPATH:" != *":$PV_LIB:"* ]]; then export PYTHONPATH="$PV_LIB:$PYTHONPATH" @@ -40,7 +49,11 @@ echo "PV_LIB = $PV_LIB" echo "PV_DATA = $PV_DATA" echo "PV_RESULTS = $PV_RESULTS" echo "PV_LOG = $PV_LOG" +echo "PV_CLIENT = $PV_CLIENT" +echo "PV_SERVER = $PV_SERVER" echo "PV_EXAMPLES = $PV_EXAMPLES" +echo "PV_SERVER_URL = $PV_SERVER_URL" +echo "PV_CLIENT_URL = $PV_CLIENT_URL" echo "PYTHONPATH = $PYTHONPATH" echo "================================================================" echo "" diff --git a/bin/start.bat b/bin/start.bat new file mode 100644 index 0000000..c2e7cc0 --- /dev/null +++ b/bin/start.bat @@ -0,0 +1,21 @@ +@echo off + +REM ================================================================ +REM PLANTPLAN - Server und Client gemeinsam starten +REM ================================================================ + +call "%~dp0setenv.bat" + +echo ================================================================ +echo PlantPlan - Starte Server und Client ... +echo ================================================================ +echo. + +call "%~dp0server.bat" start +call "%~dp0client.bat" start + +echo. +echo ================================================================ +echo Server: %PV_SERVER_URL% +echo Client: %PV_CLIENT_URL% +echo ================================================================ diff --git a/bin/stop.bat b/bin/stop.bat new file mode 100644 index 0000000..d602b55 --- /dev/null +++ b/bin/stop.bat @@ -0,0 +1,17 @@ +@echo off + +REM ================================================================ +REM PLANTPLAN - Server und Client gemeinsam stoppen +REM ================================================================ + +echo ================================================================ +echo PlantPlan - Stoppe Server und Client ... +echo ================================================================ +echo. + +call "%~dp0server.bat" stop +call "%~dp0client.bat" stop + +echo. +echo Alle Prozesse gestoppt. +echo ================================================================ diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..f0db3b7 --- /dev/null +++ b/client/index.html @@ -0,0 +1,12 @@ + + + + + + PlantPlan + + +
+ + + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..e1d7b96 --- /dev/null +++ b/client/package.json @@ -0,0 +1,23 @@ +{ + "name": "plantplan-client", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tldraw": "^2.9.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.7.0", + "vite": "^6.0.0" + } +} diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..c6ba5af --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,10 @@ +import { Tldraw } from 'tldraw' +import 'tldraw/tldraw.css' + +export default function App() { + return ( +
+ +
+ ) +} diff --git a/client/src/main.tsx b/client/src/main.tsx new file mode 100644 index 0000000..feac8ee --- /dev/null +++ b/client/src/main.tsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..4c77361 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true + }, + "include": ["src"] +} diff --git a/client/vite.config.ts b/client/vite.config.ts new file mode 100644 index 0000000..eba15f7 --- /dev/null +++ b/client/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, + }, +}) diff --git a/doc/architektur.md b/doc/architektur.md new file mode 100644 index 0000000..e23fa3f --- /dev/null +++ b/doc/architektur.md @@ -0,0 +1,152 @@ +# PlantPlan – Architektur + +**Datum:** 16. April 2026 + +--- + +## 1. Überblick + +PlantPlan ist eine hybride Webanwendung für technische Gleisplan-Skizzen. Das iPad dient als Eingabegerät (PWA im Safari), ein Python-Backend übernimmt Bibliotheksverwaltung, Normalisierung und Export. + +``` +┌─────────────────────────┐ ┌─────────────────────────┐ +│ client/ │ │ server/ │ +│ (tldraw + React + Vite) │ HTTP │ (FastAPI + Python) │ +│ │────────▶│ │ +│ - Canvas & Zeichnung │ JSON │ - Symbolbibliothek │ +│ - Custom Shapes │◀────────│ - Normalisierung │ +│ - Snap-to-Grid │ │ - JSON/SVG Export │ +└─────────────────────────┘ └─────────────────────────┘ +``` + +--- + +## 2. Projektstruktur + +``` +plantplan/ +├── bin/ Skripte (setenv, install, activate) +├── cfg/ Konfigurationsdateien +├── client/ Frontend – wird auf Server deployed +│ ├── src/ +│ │ ├── main.tsx React Entry Point +│ │ └── App.tsx tldraw Canvas +│ ├── index.html +│ ├── package.json +│ ├── tsconfig.json +│ └── vite.config.ts Dev-Proxy /api → localhost:8000 +├── server/ Backend – wird auf Server kopiert +│ ├── catalog/ +│ │ └── symbols.json Symbolbibliothek +│ ├── main.py FastAPI Anwendung +│ └── requirements.txt Python-Abhängigkeiten +├── doc/ Dokumentation +├── lib/ Python-Bibliotheken (eigene Module) +├── tests/ Tests +└── examples/ Beispieldaten +``` + +--- + +## 3. Client (Frontend) + +| Aspekt | Detail | +|---|---| +| Framework | React 18 + TypeScript | +| Canvas | tldraw v2 | +| Bundler | Vite | +| Dev-Server | `localhost:5173` | +| Build-Output | `client/dist/` (statische Dateien) | + +Der Vite Dev-Server leitet `/api/*`-Requests per Proxy an das lokale Backend weiter. Im Produktivbetrieb liefert nginx die statischen Dateien aus und proxied API-Calls. + +--- + +## 4. Server (Backend) + +| Aspekt | Detail | +|---|---| +| Framework | FastAPI | +| ASGI-Server | Uvicorn | +| Python | >= 3.11 | +| Dev-Server | `localhost:8000` | + +### API-Endpunkte + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/symbols` | Symbolbibliothek als JSON ausliefern | +| POST | `/normalize` | Koordinaten auf Grid snappen | +| POST | `/export` | Finales JSON/SVG-Projektfile erzeugen | + +### Datenmodell (Request) + +```json +{ + "shapes": [ + { + "id": "shape:abc", + "type": "weiche", + "x": 100, + "y": 200, + "props": { "radius": 300, "direction": "left" } + } + ], + "grid_size": 10 +} +``` + +--- + +## 5. Lokale Entwicklung + +Beide Komponenten laufen parallel auf einer Maschine: + +``` +Terminal 1 (Server): + cd server + pip install -r requirements.txt + uvicorn main:app --reload + +Terminal 2 (Client): + cd client + npm install + npm run dev +``` + +Kein externer Server nötig. Der Vite-Proxy sorgt dafür, dass Frontend und Backend nahtlos kommunizieren. + +--- + +## 6. Deployment (Hetzner) + +Beim Deployment werden zwei Teile auf den Server kopiert: + +| Was | Wohin | Wie | +|---|---|---| +| `client/dist/` (nach `npm run build`) | nginx Document Root | Statische Dateien | +| `server/` | Python-Umgebung | uvicorn hinter nginx reverse proxy | + +``` +nginx +├── / → client/dist/ (statische Dateien) +└── /api/* → proxy_pass http://127.0.0.1:8000 +``` + +--- + +## 7. Umgebungsvariablen (setenv.bat / setenv.sh) + +| Variable | Pfad | +|---|---| +| `PROJECT` | Projekt-Root | +| `PV_CLIENT` | `client/` | +| `PV_SERVER` | `server/` | +| `PV_BIN` | `bin/` | +| `PV_LIB` | `lib/` | +| `PV_CFG` | `cfg/` | +| `PV_DATA` | `data/` | +| `PV_LOG` | `log/` | +| `PV_RESULTS` | `results/` | +| `PV_EXAMPLES` | `examples/` | +| `PV_TESTS` | `tests/` | diff --git a/server/catalog/symbols.json b/server/catalog/symbols.json new file mode 100644 index 0000000..f076b3a --- /dev/null +++ b/server/catalog/symbols.json @@ -0,0 +1,26 @@ +{ + "symbols": [ + { + "id": "weiche_links_r300", + "label": "Weiche links R300", + "type": "weiche", + "radius": 300, + "direction": "left", + "svg_path": "M 0,0 L 100,0 Q 150,0 200,50", + "snap_points": [[0,0], [100,0], [200,50]], + "ports": ["in", "out_gerade", "out_abzweig"] + }, + { + "id": "kreisel_standard", + "label": "Kreisel", + "type": "kreisel", + "geometry": { + "circle_top_r": 40, + "circle_bottom_r": 40, + "straight_length": 80 + }, + "svg_path": "M -40,0 A 40,40 0 1,1 40,0 A 40,40 0 1,1 -40,0 M -40,160 A 40,40 0 1,1 40,160 A 40,40 0 1,1 -40,160 M -40,0 L -40,160 M 40,0 L 40,160", + "snap_points": [[0,0], [0,160]] + } + ] +} diff --git a/server/main.py b/server/main.py new file mode 100644 index 0000000..c4ab825 --- /dev/null +++ b/server/main.py @@ -0,0 +1,66 @@ +""" +PlantPlan FastAPI Backend +- Symbolbibliothek ausliefern +- Koordinaten normalisieren +- JSON/SVG Export +""" + +import json +from pathlib import Path +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel + +app = FastAPI(title="PlantPlan API") + +# CORS: Erlaubt Zugriff vom lokalen Frontend Dev-Server +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173"], + allow_methods=["*"], + allow_headers=["*"], +) + +CATALOG_DIR = Path(__file__).parent / "catalog" + + +# --- Models --- + +class SketchPayload(BaseModel): + shapes: list[dict] + grid_size: int = 10 + + +# --- Endpoints --- + +@app.get("/symbols") +async def get_symbols(): + """Symbolbibliothek als JSON ausliefern.""" + catalog_file = CATALOG_DIR / "symbols.json" + with open(catalog_file, encoding="utf-8") as f: + return json.load(f) + + +@app.post("/normalize") +async def normalize(payload: SketchPayload): + """Koordinaten auf Grid snappen und SVG-Pfade normalisieren.""" + cleaned = [] + for shape in payload.shapes: + s = dict(shape) + if "x" in s: + s["x"] = round(s["x"] / payload.grid_size) * payload.grid_size + if "y" in s: + s["y"] = round(s["y"] / payload.grid_size) * payload.grid_size + cleaned.append(s) + return {"shapes": cleaned} + + +@app.post("/export") +async def export_sketch(payload: SketchPayload): + """Finales Projektfile als JSON exportieren.""" + return { + "json": { + "version": "1.0", + "shapes": payload.shapes, + } + } diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..9f2591f --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,3 @@ +fastapi>=0.115.0 +uvicorn[standard]>=0.34.0 +pydantic>=2.0.0