diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..723fd28 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog + +## 2.0.0 (2026-08-29) + +Vollständige Neuentwicklung der Kern-Architektur (Phasen A–H der Roadmap). + +### Architektur +- **Plugin-System v2 (A1–A4):** ToolExtensionV2/PanelExtension/LibraryProviderExtension-Registry mit Interaktions-Dispatcher, CADDocument-Fassade über Yjs mit echtem Y.UndoManager (1 Aktion = 1 Undo-Schritt), Undo-fähige transact-Bündelung, V2HistoryPanel. Alle 30+ Werkzeuge als V2-Plugins migriert. +- **PixiJS-Renderer (C1–C4):** WebGL-Rendering als Default (Opt-out), inkrementelles Element-Rendering über Map-Diff, Snap-Marks + Selection-Handles im Overlay, MockAdapter für headless Tests. +- **Kernel:** React-freies `kernel/**` (Regel), documentService-Singleton, featureFlags. + +### CAD-Werkzeuge (D1–D12, vollständig) +- Ellipse (3-Klick, Rotation aus Zugrichtung, Bogen via Winkel-Optionen) + Spline (CV-basiert, Catmull-Rom durch alle Kontrollpunkte) +- Point/XLine/Ray auf Auto-angelegtem `defpoints`-Layer mit `construction:true`-Tag +- MTEXT (mehrzeilig, optionaler Rahmen in derselben Transaktion) +- Stretch, Break, Join, Explode, Align, Chamfer, Lengthen, PolylineEditor (verifiziert, Phase D complete) +- GripEditing: Vertex-/Center-Grips (Linien-Endpunkte, Polyline-Vertices, Kreis-Zentrum) mit Drag in einer Transaktion; BBox-Corner-Skalierung bleibt parallel +- Arrays: rect, polar, **path** (Kopien gleichmäßig entlang Pfadlänge) +- Snaps erweitert: Quadrant (4 Kreis-Quadranten), Perpendicular (Fußpunkt vom Referenzpunkt), Tangent (beide Tangentenpunkte via acos), fromOffset-Helfer +- BlockAttribute: `BlockAttrDef` auf Definitionen, `attrValues` auf Instanzen, Rendering als Text-Primitives, PropertiesPanel-Formular + +### Event-Bestuhlung (E1–E7, vollständig) +- Sitzreihen/-blöcke mit Drag-Interaktion, ArcRows (Stühle tangential rotiert), Vorlagenkatalog (Kino, Bankett, Stehtisch, Podest), CSV-Sitzlistent-Export, Gäste-Verwaltung mit Stuhl-Zuweisung +- **Neu:** Vorhang (wellige Polyline), Scheinwerfer (Kegel-Polygon mit Winkel + Farbe), Absperrung (Kettenglieder), Bühnenhöhen-Attribut + +### Bibliothek & Import/Export (F1–F8, vollständig) +- Globale Block-Bibliothek: Ordner, Suche, Favoriten, Thumbnails, Grid-Ansicht, Drag&Drop in die Zeichenfläche +- Export/Import als JSON (`.wcadlib`) **und** echtes ZIP-Paket (manifest.json + blocks.json + thumbs/) +- **DXF-Import erweitert:** ELLIPSE, SPLINE (Grad ≤ 3, de-Boor-Approximation), MTEXT, POINT, ATTDEF, MINSERT-Arrays, strukturierter skippedEntities-Fehlerreport +- **DXF-Export erweitert:** BLOCKS+INSERTS mit Namens-Sanitizing, ATTDEF/ATTRIB/SEQEND, AC1009 (R12) + AC1015, R12-korrektes POLYLINE+VERTEX+SEQEND; Roundtrip-Tests deckten und fixten 3 latente Konventions-Bugs (Circle-Zentrum in Writer und Parser, ARC-Winkel-Radian→Grad) +- **SVG-Import als echte Geometrie:** voller Path-Parser (M/L/H/V/C/S/Q/T/A/Z, absolut+relativ, Bézier-Flattening, Arc-Endpoint→Center), rect/circle-Konventions-Fixes, Ellipse als Polygon +- Builtin-Kataloge: Event/Architektur/Landschaft (je 15 Blöcke) über LibraryProvider, User- und Global-Library angebunden + +### Layouts & Plot (G1–G5, vollständig) +- Layout-Datenmodell in Yjs (undo-getrackt), LayoutBar mit Viewport-Minimap, zoom-to +- Titelblock mit attributierten Texten (Projekt/Datum/Maßstab/Autor), auto-insert als Layout-Frame +- **PDF-Export je Layout:** A4 landscape im Ausgabemaßstab, Linienbreiten + Dash-Patterns aus Layern, monochrome-Option, Titelblock-Mitzeichnung +- **Browser-Print:** @media print mit A4 landscape, UI-Chrome ausgeblendet, Print-Service mit Cleanup + +### Modi & Rollen (H1–H5, vollständig) +- simple/pro-Modus (localStorage-persistiert): simple zeigt nur basic-Tools, CommandLine pro-only, Topbar-Toggle +- Projekt-Templates: 6 Branchen-Presets (Halle/OpenAir/Saal/Messe/Büro/Garten) mit Grid/Layern/Plugin-Seed +- **Rollenschutz:** library-admin (an admin-Rolle gebunden) für alle 8 schreibenden Global-Library-Routen; Lesen/Export für alle authentifizierten Nutzer +- i18n-Grundlage: strings.ts de/en-Maps mit t()-Funktion, Persistenz, useT-Hook; LayoutBar + Modus umgestellt +- Flächenberechnung: Shoelace-polygonArea, formatArea (quadratische Skalierung), measure-area-Tool, PropertiesPanel-Anzeige + +### Behobene Fehlerklassen +- **BUG-1-Klasse komplett geschlossen:** alle Date.now()-Auto-IDs im Backend durch UUID-Slice ersetzt (deterministische Tests mit eingefrorener Zeit beweisen die Kollisionsfreiheit) +- Circle-Center-Konvention in DXF-Writer, DXF-Parser und SVG-Import vereinheitlicht (Zentrum statt min-corner) +- ARC-Winkel-Konvertierung dxf-parser (intern Radiant) → App (Grad) gefixt + +### Tests +- **Frontend: 697 Tests** (alle grün), **Backend: 287 Tests** (alle grün) +- Volle tsc-Typechecks und Production-Builds grün bei jedem Task diff --git a/README.md b/README.md index 4c63718..8a93024 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,74 @@ -# Web CAD Neu +# WebCAD -Web-basiertes 2D-CAD für Event-Bestuhlungspläne. +Web-basiertes 2D-CAD mit Kollaboration, Plugin-System und Fokus auf +Veranstaltungsplanung (Bestuhlung, Bühnen, Layouts). + +## Features + +- **Zeichnen:** Linien, Rechtecke, Kreise, Bögen, Ellipsen, Splines, Polylinien, Text, MTEXT, Bemaßungen, Hatch +- **Modifizieren:** Move/Copy/Rotate/Scale/Mirror, Trim/Extend/Fillet/Chamfer, Stretch/Break/Join/Explode/Align/Lengthen, Polyline-Editor, Grips (Vertex/Center), Arrays (rect/polar/path) +- **Event-Tools:** Stühle, Sitzreihen (Drag), Sitzblöcke, ArcRows, Tische, Bühnen (mit Höhen-Attribut), Vorhang, Scheinwerfer, Absperrungen, Vorlagenkatalog, Gäste-Verwaltung mit Sitzplatz-Zuweisung, CSV-Sitzlistent-Export +- **Snaps:** Endpoint, Midpoint, Center, Intersection, Quadrant, Perpendicular, Tangent, Grid, Polar-Tracking +- **Bibliothek:** Globale Block-Bibliothek mit Ordnern, Suche, Favoriten, Thumbnails; Builtin-Kataloge (Event/Architektur/Landschaft); Export/Import als .wcadlib (JSON oder ZIP) +- **Import/Export:** DXF (Import mit ELLIPSE/SPLINE/MTEXT/POINT/ATTDEF + Fehlerreport; Export R12/2000 mit BLOCKS/ATTR), SVG (echte Geometrie-Konvertierung mit Path-Parser), PDF (je Layout, maßstabsgetreu), PNG, JSON +- **Layouts & Plot:** Layout-Verwaltung mit Viewport-Minimap, Titelblock mit Attributen, PDF-Export, Browser-Print +- **Kollaboration:** Yjs CRDT, kollaborativ korrektes Undo/Redo (nur eigene Operationen) +- **Modi:** simple (Grundwerkzeuge) / pro (Vollpalette), localStorage-persistiert +- **Templates:** 6 Branchen-Presets (Halle, OpenAir, Saal, Messe, Büro, Garten) +- **i18n:** de/en (Grundlage, erweiterbar über `src/i18n/strings.ts`) +- **Rollen:** admin (library-admin, globaler Schreibzugriff) / planer (lesen + eigene Zeichnungen) ## Stack -- Frontend: React + TypeScript + Vite + HTML Canvas -- Backend: Node.js + TypeScript + Fastify + SQLite -- Collaboration: Yjs CRDT + +- **Frontend:** React 18 + TypeScript + Vite + PixiJS v8 (WebGL-Renderer, Canvas2D-Fallback) + zustandsfreier Service-Layer +- **Backend:** Node.js + TypeScript + Fastify 5 + SQLite (better-sqlite3) +- **Kollaboration:** Yjs + y-websocket +- **Tests:** Vitest (697 FE + 287 BE), Playwright E2E, tsc-Strict-Mode ## Entwicklung + ```bash -npm install -npm run dev +# Backend + cd backend && npm install && npm run dev # http://localhost:3001 + +# Frontend +cd frontend && npm install && npm run dev # http://localhost:5173 +``` + +### Tests + +```bash +cd frontend && npm test # 697 Vitest-Tests +cd backend && npm test # 287 Vitest-Tests + +cd frontend && npx tsc --noEmit # Typecheck +cd frontend && npm run build # Production-Build ``` -- Frontend: http://localhost:5173 -- Backend: http://localhost:3001 ## Docker + ```bash docker-compose up --build ``` + +## Plugin-Entwicklung + +Siehe [docs/PLUGIN_GUIDE.md](docs/PLUGIN_GUIDE.md) – Plugin-API v2 mit +Werkzeugen, Panels und Bibliotheks-Providern, inkl. Minimal-Beispiel. + +## Bibliotheks-Pakete (.wcadlib) + +Siehe [docs/LIBRARY_SPEC.md](docs/LIBRARY_SPEC.md) – JSON- und ZIP-Format, +Validierungsregeln, Endpoints und CAD-Element-Konventionen. + +## Architektur-Regeln + +- `kernel/**` importiert niemals React +- `render/pixi/**` kennt keine React-Components +- React kennt den Renderer nur über CanvasArea +- Eine Werkzeug-Aktion = eine `doc.transact()` = ein Undo-Schritt + +## Roadmap & Status + +Alle funktionalen Phasen (A–H) sind abgeschlossen. Aktueller Stand und +Task-Log: [ROADMAP.md](ROADMAP.md). diff --git a/docs/LIBRARY_SPEC.md b/docs/LIBRARY_SPEC.md new file mode 100644 index 0000000..6239e35 --- /dev/null +++ b/docs/LIBRARY_SPEC.md @@ -0,0 +1,80 @@ +# .wcadlib – Block-Bibliotheks-Paket-Spezifikation + +WebCAD-Block-Bibliotheken können als `.wcadlib`-Paket geteilt werden. +Es gibt zwei Formate: + +## Format 1: JSON-Paket (F1) + +Ein einzelnes JSON-File mit folgender Struktur: + +```json +{ + "format": "wcadlib", + "version": 1, + "exported_at": "2026-08-29T00:00:00.000Z", + "folders": [ + { "id": "f1", "name": "Veranstaltungstechnik", "parent_id": null } + ], + "blocks": [ + { + "name": "Stuhl-Reihe", + "folder_id": "f1", + "block_data": "[{...CADElement-Array als JSON-String...]}", + "svg_data": "" + } + ] +} +``` + +- `block_data` = JSON-String eines **CADElement-Arrays** +- `svg_data` = eigenständiges SVG (Thumbnail), optional +- Ordner-Hierarchie via `parent_id` (Import ordnet IDs neu zu) + +**Endpoints:** `GET /api/global-blocks/export`, `POST /api/global-blocks/import` +(schreibend: nur library-admin/Rolle admin, siehe H3) + +## Format 2: ZIP-Paket (F7) + +Ein echtes ZIP-Archiv mit fester Struktur: + +``` +library.wcadlib.zip +├── manifest.json { "format": "wcadlib-zip", "version": 1, +│ "exported_at": "...", "block_count": N, "folder_count": M } +├── blocks.json { "folders": [...], "blocks": [ +│ { "name", "folder_id", "block_data", +│ "thumbnail": "thumbs/.svg" } ] } +└── thumbs/ + └── .svg Thumbnails, Referenz aus blocks.json +``` + +- `manifest.format` MUSS `wcadlib-zip` sein (sonst 400) +- `blocks.json` MUSS vorhanden sein (sonst 400); `blocks`-Array Pflicht +- `thumbnail` verweist relativ auf `thumbs/.svg` + +**Endpoints:** +- `GET /api/global-blocks/export-zip` → `application/zip` +- `POST /api/global-blocks/import-zip` (Body = ZIP als application/zip; + schreibend: nur library-admin) + +## Validierungsregeln (Import) + +| Fehler | HTTP | +|---|---| +| Kein valides ZIP (Format 2) | 400 | +| blocks.json fehlt / invalides JSON | 400 | +| manifest.format unbekannt | 400 | +| Kein JSON/blocks-Array (Format 1) | 400 | +| Nicht authentifiziert | 401 | +| Rolle != admin (schreibend) | 403 | + +## CAD-Element-Konventionen (block_data) + +- `rect`: `x/y` = **Zentrum** der BBox +- `circle`/`arc`: `el.x/el.y` = **Zentrum**, Winkel in Grad (0° = 3 Uhr, CCW) +- `line`: `properties.x1/y1/x2/y2` +- `polyline`/`polygon`: `properties.points` +- `block_instance`: `properties.blockId` + optional `attrValues` (D12) + +Beim Drop auf die Zeichenfläche werden Element-IDs neu generiert und +die Position per Drop-Punkt übersetzt – der Bestand bleibt unverändert. diff --git a/docs/PLUGIN_GUIDE.md b/docs/PLUGIN_GUIDE.md new file mode 100644 index 0000000..6dbff95 --- /dev/null +++ b/docs/PLUGIN_GUIDE.md @@ -0,0 +1,139 @@ +# Plugin Guide – WebCAD Plugin API v2 + +WebCAD Plugin API v2 erlaubt die Erweiterung des CAD um Werkzeuge, +Panels und Block-Bibliotheken – ohne Kerncode anzufassen. + +## Plugin-Typ (PluginV2) + +```ts +import type { PluginV2, ToolExtensionV2 } from './plugins'; + +export const myPlugin: PluginV2 = { + manifest: { + id: 'my-tools', + name: 'Meine Werkzeuge', + version: '1.0.0', + author: 'Du', + description: 'Beschreibung', + category: 'tools', // tools|elements|library|import-export|theme|other + enabledByDefault: true, + }, + tools: [/* ToolExtensionV2[] */], + panels: [/* PanelExtension[] (optional) */], + libraryProviders: [/* LibraryProviderExtension[] (optional) */], + onInit?() {}, onActivate?() {}, onDeactivate?() {}, onDestroy?() {}, +}; +``` + +## Werkzeug (ToolExtensionV2) + +Ein Werkzeug besteht aus Manifest, Options-Schema und Handlern: + +```ts +export const myTool: ToolExtensionV2 = { + manifest: { + id: 'my-tool', + label: 'Mein Werkzeug', + icon: 'M', + ribbonTab: 'canvas', // canvas|insert|format + tags: ['basic'], // basic = sichtbar im simple-Mode, ['pro'] nur pro-Mode + description: 'Was es tut', + }, + optionsSchema: [ + { key: 'width', label: 'Breite', type: 'number', min: 1, max: 500 }, + { key: 'color', label: 'Farbe', type: 'color' }, + { key: 'style', label: 'Stil', type: 'select', + options: [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }] }, + { key: 'frame', label: 'Rahmen', type: 'checkbox' }, + ], + handlers: { + down(e, ctx) { + // e.world = Weltkoordinaten, e.shift, e.ctrl + // ctx.doc = CADDocument (CRUD + transact + undo) + // ctx.setStatus('...'), ctx.setPreview?.(element) + const c = ctx as FullToolContext; + if (!c.doc) return; + c.doc.transact(() => c.doc!.addElement({ /* CADElement */ })); + }, + move(e, ctx) { /* Live-Preview */ }, + up(e, ctx) { /* Commit */ }, + key(k, ctx) { /* return true = konsumiert (z. B. Enter) */ }, + cancel(ctx) { /* ESC/Rechtsklick: Session-State zurücksetzen */ }, + }, +}; +``` + +### Kernregeln + +1. **Eine Aktion = eine `doc.transact(() => ...)`** = genau ein Undo-Schritt. +2. **Session-State als Modul-Member** (nicht in `options` – diese sind geteilter UI-State). +3. **`cancel` setzt den Session-State immer zurück.** +4. **Koordinaten-Konventionen:** rect `x/y` = Zentrum der BBox; circle/arc `el.x/el.y` = Zentrum; Winkel in Grad (0° = 3 Uhr, CCW). +5. **Undo-fähig schreiben** nur über `doc.addElement/updateElement/deleteElements`. + +## Bibliotheks-Provider (LibraryProviderExtension) + +```ts +const myProvider: LibraryProviderExtension = { + id: 'my-catalog', + label: 'Mein Katalog', + async listFolders() { return [{ id: 'f1', label: 'Ordner', parentId: null }]; }, + async listBlocks(folderId) { return [/* LibBlock[] */]; }, + async getBlock(id) { return /* LibBlock */; }, +}; +``` + +`LibBlock.payload` = JSON-String eines CADElement-Arrays → Drag&Drop +in die Zeichenfläche funktioniert automatisch über den bestehenden +Drop-Kanal. Thumbnail = eigenständiges SVG. + +## Registrierung + +Built-in-Plugins werden in `src/plugins/index.ts` per +`pluginRegistry.registerV2(plugin)` registriert; `enabledByDefault` +macht sie aktiv (alternativ per `pluginRegistry.enableV2(id)`). + +## Beispiel: Minimal-Plugin + +```ts +// plugins/builtin/stamp/index.ts +import type { PluginV2, ToolExtensionV2 } from '../../types'; + +const stampTool: ToolExtensionV2 = { + manifest: { + id: 'stamp', label: 'Stempel', icon: '\u25c6', + ribbonTab: 'insert', tags: ['basic'], + description: 'Platziert ein Quadrat', + }, + optionsSchema: [{ key: 'size', label: 'Größe', type: 'number', min: 10, max: 200 }], + handlers: { + down(e, ctx) { + const c = ctx as any; + if (!c.doc) return; + const size = (ctx.options.size as number | undefined) ?? 50; + c.doc.transact(() => c.doc.addElement({ + id: `stamp_${Date.now()}`, + type: 'rect', layerId: 'layer-0', + x: e.world.x, y: e.world.y, + width: size, height: size, + properties: {}, + })); + ctx.setStatus('Stempel platziert'); + }, + }, +}; + +export const stampPlugin: PluginV2 = { + manifest: { + id: 'stamp', name: 'Stempel', version: '1.0.0', author: 'WebCAD', + description: 'Beispielplugin', category: 'elements', enabledByDefault: true, + }, + tools: [stampTool], +}; +``` + +Registrieren in `src/plugins/index.ts`: + +```ts +pluginRegistry.registerV2(stampPlugin); +```