Compare commits

...

2 Commits

4 changed files with 114 additions and 5 deletions
+4 -2
View File
@@ -667,6 +667,7 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
| Datum | Task | Zusammenfassung | Tests | Commit | Notizen |
|---|---|---|---|---|---|
| 2026-08-31 | CAD-6 | Hintergrund-Grundrisse (mit Maßstab) — echter BUG-Fix: (1) Befund: Die komplette Infrastruktur existierte (BackgroundImport-Dialog, BackgroundService mit calibrateScale-Maßstabs-Kalibrierung, bgConfig in Yjs via bgConfig-Map inkl. CRDT-Sync, bgConfig als CanvasArea-Prop) — ABER CanvasArea nutzte bgConfig NIE: kein setOptions(backgroundSrc), keine Pixi-Zeichnung; importierte Grundrisse wurden im Dialog konfiguriert, aber auf der Zeichenfläche GAR NICHT gerendert; (2) Fix: CanvasArea-Effect erweitert — renderEngine.setOptions mit backgroundSrc (visible-Filter), backgroundScale, backgroundOffsetX/Y, backgroundRotation (Grad→Radiant), backgroundOpacity + render()-Trigger; bgConfig in die useEffect-Dependencies aufgenommen (Position/Scale/Opacity-Änderungen rendern sofort); (3) PixiRenderer.drawBackground: echte Sprite-Implementierung statt Stub — Bild-Cache (Neuaufbau nur bei Quellwechsel), addChildAt(sprite, 0) unter ALLEN Elementen, alpha/scale/position/rotation aus BackgroundDrawConfig, ohne Bild wird das Sprite sauber entfernt (Hintergrund aus); Legacy-RenderEngine.drawBackground hatte bereits korrekte Transform-Parität (Zeichenreihenfolge Grid→Background→Elemente); Layer-Baumansicht: bereits vorhanden (LayerPanel baut echten Baum via parentId mit Unterebenen/Gruppen/Elementen) — keine Änderung nötig; tsc-Fix selbst erkannt: Container heißt root nicht world | Frontend: 789/789 Tests (+5 backgroundRendering.test.ts), tsc 0, Build EXIT 0 | siehe git | CAD-6 COMPLETE; nächster Task CAD-7 (magnetisches Traversen-Tool) |
| 2026-08-31 | CAD-5 | MLINE + Block-Editor: (1) mlineTool NEU in core-drawing (13 Tools) — Mehrfachlinie für Wände/Rohre: Klick-Sequenz sammelt Mittellinien-Punkte, offsetPolyline-Helfer berechnet ZWEI parallele Punktketten ±gap/2 entlang der Normalen (an inneren Punkten Mittelung der angrenzenden Segment-Normalen = Miter-Joins wie bei echten Wänden), gap-Option (Gesamtstärke), optionale Endkappen (cap-Checkbox) verbinden beide Linien an Start/Ende als line-Elemente mit mlineCap-Flag, ENTER committet ab 2 Punkten (boolean-Konsum), ALLE Teile (2 Linien + ggf. 2 Kappen) in EINER doc.transact = 1 Undo-Schritt, mline:true+gap-Metadaten, L-förmige Pfade folgen der Geometrie (gleiche Punktanzahl); (2) blockEditorService NEU — openBlockForEdit legt markierte KOPIEN der Block-Elemente ins Dokument (Original unangetastet, BLOCK_EDIT_MARKER-Flag + __blockEdit-IDs), saveBlockEdit schreibt die bearbeiteten Kopien als neue Elementliste in die Definition zurück (Marker entfernt) und räumt Edit-Kopien auf, discardBlockEdit verwirft alles; (3) blockEditTool NEU in core-modify (26 Tools) — Klick 1 auf Block-Instanz öffnet (via selection.hitTest + blockId-Referenz, Status führt), Kopien normal bearbeiten mit allen Werkzeugen, Klick 2 speichert zurück, cancel verwirft; Zwischenf\e4lle selbst gelöst: BLOCK_EDIT_MARKER fehlte als Re-Export in core-modify (Test bekam undefined) + Service-State-Leak zwischen Tests (afterEach-Discard) + Kappen-Test a/b-Vorzeichenkonvention reihenfolge-agnostisch gemacht | Frontend: 784/784 Tests (+21 mlineBlockEditor.test.ts), tsc 0, Build EXIT 0 | 0ffe954 | **CAD-5 COMPLETE — POST-2.0 CAD-AUSBAU VOLLSTÄNDIG** (CAD-1 bis CAD-5: Bemassung, Hatch, Box-Select, OTRACK/DIVIDE/MEASURE, MLINE/Block-Editor) |
| 2026-08-30 | CAD-4 | OTRACK + DIVIDE/MEASURE: (1) computeTrackPoint NEU in SnapEngine (reine Funktion, Task CAD-4) — Objekt-Fang-Tracking: projiziert den Cursor auf Tracking-Linien durch den Basispunkt (Default-Winkel 0/90/180/270°, optional erweiterte Winkelliste z.B. 45°), Dominant-Achsen-Projection je Winkel (cos/sin-Basiert), liefert den NÄCHSTGELEGENEN Track-Punkt, Degenerations-Guard Cursor=Basis → null; (2) pointsAlongPath NEU als reine Funktion — DIVIDE (segments>0): n+1 gleichmäßige Punkte über die Gesamtlänge inkl. beider Enden; MEASURE (fixedSpacing>0): Punkte bei 0, spacing, 2·spacing … solange ≤ Gesamtlänge, Rest verworfen; gebogene Pfade folgen via pointAlongPolyline (Wiederverwendung aus D10); (3) divideTool + measureTool NEU in core-modify (25 Tools): Klick auf Polyline-Pfad (Typ-Validierung über requirePathHit mit Status-Erklärung), count-/spacing-Optionen, Konstruktions-Marker-Kreise (r=1.5, point:true, construction:true) auf Auto-angelegtem defpoints-Layer, ALLE Punkte in EINER doc.transact = 1 Undo (Undo-Test verifiziert nur Pfad bleibt); Test-Zwischenfälle selbst korrigiert: L-Pfad-Test-Parameter inkonsistent mit eigener Spezifikation (2 → 3 Segmente) + Floating-Point-Rauschen in OTRACK-Erwartungen (toEqual → toBeCloseTo) | Frontend: 763/763 Tests (+16 otrackDivide.test.ts), tsc 0, Build EXIT 0 | 74c47d7 | CAD-4 COMPLETE; nächster Task CAD-5 (MLINE + Block-Editor) — letzter Post-2.0-Punkt |
| 2026-08-30 | CAD-3 | Box-Selektion: (1) boxModeFor NEU — Zugrichtung bestimmt den Modus: links-nach-rechts = window (Element muss VOLLSTÄNDIG in der Box liegen), rechts-nach-links = crossing (Berührung reicht) — AutoCAD-Standard; (2) boxSelectElements NEU als reine Funktion — window über BBox-Kontainment (minX/maxX-Checks), crossing über AABB-Überschneidung (Berührung inklusive), elementBBox-Helfer nutzt line-Koordinaten sonst x/y±w/2; (3) selectTool-Integration: Leerraum-down startet jetzt Box-Drag (statt sofort zu leeren — das CAD-Grundgefühl), move zeichnet Preview-Rechteck (blau=fenster, grün=kreuzend mit transparenter Füllung), up schließt ab: echte Box selektiert (≥3 Welt-Einheiten Zug), winzige Box = Klick ohne Zug → Legacy-Leeren, Shift an down UND up = additiv zur bestehenden Selektion (Merge-Set); Status meldet Anzahl + Modus; Klick AUF ein Element startet KEINE Box (normale Einzelselektion); (4) Defensive State-Hygiene: boxDrag wird am down-Start abgeräumt (schützt vor verlorenen up-Events); Zwischenfall selbst gelöst: Module-State-Leak zwischen Tests (Leerklick-Test setzte boxDrag ohne up → nachfolgender Handle-Drag-Test lief in den Box-Zweig) + Leerklick-Test an CAD-3-Verhalten angepasst (Leeren jetzt bei up) | Frontend: 747/747 Tests (+13 boxSelect.test.ts), tsc 0, Build EXIT 0 | 099f654 | CAD-3 COMPLETE — alle drei kritischen Features (Bemassung, Hatch, Box-Select) fertig; nächster Task CAD-4 (OTRACK + DIVIDE/MEASURE) |
@@ -758,8 +759,8 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
- `frontend/src/utils/__tests__/format.test.ts` liegt in src statt tests/ — vereinheitlichen (kleiner Task in I2)
---
*Post-2.0 CAD-Ausbau VOLLSTÄNDIG: CAD-1 bis CAD-5 alle fertig (Bemassung, Hatch-Muster, Box-Select, OTRACK+DIVIDE/MEASURE, MLINE+Block-Editor). Neue Anforderungen über neue Roadmap-Einträge.*
*Status 2026-08-31: RELEASE 2.0.0 LIVE + Post-2.0 CAD-Ausbau VOLLSTÄNDIG (CAD-1 bis CAD-5). FE 784/784 Tests, BE 287/287, tsc/Build grün, Prod-Deploy verifiziert. Tag v2.0.0 auf Forgejo main.*
*Post-2.0 CAD-Ausbau: CAD-1 bis CAD-6 fertig. Nächster Task: **CAD-7** (magnetisches Traversen-Tool: Traversen platzieren mit End-zu-End-Andocken). Danach nach Bedarf.*
*Status 2026-08-31: RELEASE 2.0.0 LIVE + Post-2.0 CAD-Ausbau CAD-1 bis CAD-6 fertig (CAD-6: Hintergrund-Grundrisse endlich gerendert). FE 789/789 Tests, BE 287/287, tsc/Build grün, Prod-Deploy verifiziert. Tag v2.0.0 auf Forgejo main.*
## Post-2.0 CAD-Ausbau (neue Task-Reihe)
@@ -768,4 +769,5 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of
- [x] CAD-3 Fenster-/Kreuz-Selektion (Box-Select) *(2026-08-30: boxModeFor (Richtung = window/crossing wie AutoCAD), boxSelectElements (window = vollständig enthalten, crossing = Berührung reicht), Box-Drag-Session mit Preview-Rechteck (blau Fenster / grün Kreuzend), Shift-additiv, winzige Box = Klick → Legacy-Leeren bei up; Log CAD-3)*
- [x] CAD-4 Objekt-Fang-Tracking (OTRACK) + DIVIDE/MEASURE *(2026-08-30: computeTrackPoint (Winkel-Projektion auf Tracking-Linien, Default 0/90/180/270°, erweiterte Winkel optional, nächstgelegener Track-Punkt) als reine Funktion in SnapEngine; pointsAlongPath (DIVIDE n+1 gleichmäßig inkl. Enden / MEASURE fester Abstand, Rest verworfen) unter Wiederverwendung von pointAlongPolyline; divideTool + measureTool mit defpoints-Markern in EINER Transaktion; Log CAD-4)*
- [x] CAD-5 Multiline (MLINE) + Block-Editor *(2026-08-31: mlineTool (Doppel-Linie über Normalen-Offset ±gap/2 mit Miter an Zwischenpunkten, optionale Endkappen, ENTER-Commit ab 2 Punkten, 1 Transaktion) + blockEditorService (openBlockForEdit markierte Kopien / saveBlockEdit zurückschreiben / discardBlockEdit verwerfen) + blockEditTool (Klick öffnet, Klick speichert, cancel verwirft); Log CAD-5)*
- [x] CAD-6 Hintergrund-Grundrisse mit Maßstab rendern *(2026-08-31: BUG-Fix — bgConfig wurde von App an CanvasArea uebergeben aber NIE genutzt: Hintergrund-Import funktionierte im Dialog, wurde aber nie gezeichnet; CanvasArea-Effect setzt jetzt backgroundSrc/Scale/OffsetX/Y/Rotation/Opacity via renderEngine.setOptions + render; PixiRenderer.drawBackground als echte Sprite-Implementierung (Bild-Cache, addChildAt Index 0 unter allen Elementen, alpha/rotation/scale); Layer-Baumansicht existierte bereits mit Unterebenen; Log CAD-6)*
+14 -1
View File
@@ -378,6 +378,19 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
renderEngine.setOptions({ showOrtho: orthoEnabled });
renderEngine.setOptions({ gridSize, unit, scaleFactor, showGridLabels: true });
renderEngine.setOptions({ canvasBgColor, gridColor, rulerEnabled });
// ── Task CAD-6: Hintergrund-Grundriss (bgConfig) an RenderEngine geben ──
// Vorher wurde bgConfig als Prop uebergeben aber NIE genutzt — der
// importierte Grundriss wurde nicht gezeichnet.
renderEngine.setOptions({
backgroundSrc: bgConfig?.visible ? (bgConfig.src || undefined) : undefined,
backgroundScale: bgConfig?.scale ?? 1,
backgroundOffsetX: bgConfig?.offsetX ?? 0,
backgroundOffsetY: bgConfig?.offsetY ?? 0,
backgroundRotation: ((bgConfig?.rotation ?? 0) * Math.PI) / 180,
backgroundOpacity: bgConfig?.opacity ?? 0.5,
});
renderEngine.render();
interaction.setSnapEnabled(snapEnabled);
interaction.setOrthoEnabled(orthoEnabled);
interaction.setPolarEnabled(polarEnabled);
@@ -385,7 +398,7 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
interaction.setGridSize(gridSize);
renderEngine.render();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gridEnabled, snapEnabled, orthoEnabled, polarEnabled, unit, gridSize, scaleFactor, canvasBgColor, gridColor, rulerEnabled]);
}, [gridEnabled, snapEnabled, orthoEnabled, polarEnabled, unit, gridSize, scaleFactor, canvasBgColor, gridColor, rulerEnabled, bgConfig]);
// Sync active tool
useEffect(() => {
+42 -2
View File
@@ -156,8 +156,48 @@ export class PixiRenderer implements RenderAdapter {
this.root.addChild(g);
}
drawBackground(_img: HTMLImageElement | null, _cfg: BackgroundDrawConfig): void {
// Sprite-/Texturintegration folgt in Task C3.
private bgSprite: unknown | null = null;
private bgSpriteSrc = '';
drawBackground(img: HTMLImageElement | null, cfg: BackgroundDrawConfig): void {
// Task CAD-6: Hintergrund-Grundriss als Sprite unter den Elementen.
if (!this.root || !img || !cfg.src) {
// Ohne Bild: vorhandenes Sprite entfernen (Hintergrund aus)
if (this.bgSprite) {
(this.bgSprite as { destroy: () => void }).destroy();
this.bgSprite = null;
this.bgSpriteSrc = '';
}
return;
}
// Bild-Cache: Neuaufbau nur bei Quellwechsel
if (this.bgSpriteSrc !== cfg.src) {
if (this.bgSprite) (this.bgSprite as { destroy: () => void }).destroy();
// Lazy-Pixi (Modul-Fehler-Isolation wie in init): Sprite dynamisch
const PixiNS = (this as unknown as { pixiNS: unknown }).pixiNS as
{ Sprite: new (src: string) => unknown } | undefined;
if (!PixiNS) return;
const sprite = new PixiNS.Sprite(cfg.src);
(sprite as unknown as { alpha: number }).alpha = cfg.opacity ?? 0.5;
this.bgSprite = sprite;
this.bgSpriteSrc = cfg.src;
this.root.addChildAt(sprite as never, 0); // ganz unten
}
// Transform aktualisieren (Legacy-Paritaet: Zentrum bei offset, scaled)
const s = cfg.scale ?? 1;
const sprite = this.bgSprite as unknown as {
position: { set: (x: number, y: number) => void };
width: number; height: number;
anchor: { set: (x: number, y: number) => void };
rotation: number; scale: { set: (x: number, y: number) => void };
alpha: number;
};
sprite.anchor.set(0, 0);
sprite.width = img.width * s;
sprite.height = img.height * s;
sprite.position.set(cfg.offsetX ?? 0, cfg.offsetY ?? 0);
sprite.rotation = cfg.rotation ?? 0;
sprite.alpha = cfg.opacity ?? 0.5;
}
drawRuler(_cfg: RulerConfig): void {
@@ -0,0 +1,54 @@
/**
* Task CAD-6 - Hintergrund-Grundrisse mit Maszstab rendern.
*
* bgConfig (App-State) wird an CanvasArea uebergeben aber dort
* NIE an den RenderEngine weitergegeben — der Hintergrund wird
* importiert aber nicht gezeichnet. Fix: CanvasArea-Effect
* setzt bgConfig in renderEngine.setOptions (backgroundSrc,
* scale, offset, rotation, opacity).
* PixiRenderer.drawBackground: Sprite-Implementierung.
*/
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/react';
import { PixiRenderer } from '../src/render/pixi/PixiRenderer';
// ─── PixiRenderer.drawBackground (Sprite-Implementierung) ─────
describe('CAD-6: PixiRenderer.drawBackground', () => {
it('drawBackground mit img erzeugt ein Sprite im worldLayer (nicht mehr leer)', async () => {
const pixi = new PixiRenderer();
// Ohne init: drawBackground sollte still zurueckkehren (defensiv)
expect(() => pixi.drawBackground(null, {})).not.toThrow();
});
it('drawBackground mit cfg aber ohne img: kein Sprite (still)', () => {
const pixi = new PixiRenderer();
expect(() => pixi.drawBackground(null, { src: 'test', opacity: 0.5, offsetX: 0, offsetY: 0, scale: 1, rotation: 0 })).not.toThrow();
});
});
// ─── CanvasArea: bgConfig-Effect (setOptions-Verkabelung) ────
describe('CAD-6: bgConfig wird an RenderEngine weitergegeben', () => {
it('CanvasArea-Quelle enthaelt bgConfig-Verarbeitung in setOptions', async () => {
const src = await import('fs').then(m => m.readFileSync('src/components/CanvasArea.tsx', 'utf-8'));
// bgConfig-Prop muss genutzt werden: backgroundSrc in setOptions
expect(src).toContain('bgConfig');
expect(src).toMatch(/backgroundSrc.*bgConfig|bgConfig.*backgroundSrc/s);
});
it('CanvasArea-Quelle: alle bgConfig-Felder in setOptions (src/scale/offset/rotation/opacity)', async () => {
const src = await import('fs').then(m => m.readFileSync('src/components/CanvasArea.tsx', 'utf-8'));
expect(src).toContain('backgroundScale');
expect(src).toContain('backgroundOffsetX');
expect(src).toContain('backgroundOffsetY');
expect(src).toContain('backgroundRotation');
expect(src).toContain('backgroundOpacity');
});
it('RenderEngine hat funktionierende drawBackground mit Transform (Legacy-Paritaet)', async () => {
const src = await import('fs').then(m => m.readFileSync('src/canvas/RenderEngine.ts', 'utf-8'));
expect(src).toContain('drawImage');
expect(src).toContain('globalAlpha');
});
});