docs(I2): plugin guide (API v2 + example), library spec (wcadlib JSON+ZIP), changelog 2.0, readme rewrite
This commit is contained in:
@@ -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
|
||||
@@ -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).
|
||||
|
||||
@@ -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": "<svg ...>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `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/<id>.svg" } ] }
|
||||
└── thumbs/
|
||||
└── <blockId>.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/<id>.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.
|
||||
@@ -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);
|
||||
```
|
||||
Reference in New Issue
Block a user