# Design — Ebenen-Darstellung (Layer Display Settings)

> Teil der Standalone-Architektur — siehe [../../ARCHITECTURE.md](../../ARCHITECTURE.md).
> Ressourcen/Stile: [resources-graphics.md](resources-graphics.md). Output/Pläne:
> [plans-output.md](plans-output.md). Kontextmenü/Inline-Editor:
> [context-menu.md](context-menu.md).

Dieses Dokument spezifiziert die **per-Ebene Darstellungseinstellungen** auf der
`LayerCategory` (Grafik-Kategorie) und den dazugehörigen Editor
„Ebeneneinstellungen…", der aus dem Ebenen-Kontextmenü geöffnet wird.

Heute trägt jede `LayerCategory` nur eine flache Strichstärke (`lw`), eine `color`
und eine optionale `hatch` (ein freier String, der nirgends aufgelöst wird). Das
reicht nicht: Eine Ebene soll — wie in Vectorworks/DOSSIER — einen vollständigen
**Stift (PEN)** und eine vollständige **Standard-Schraffur (HATCH)** definieren,
die beim Rendern angewandt werden. Bezeichner englisch, Prosa/UI-Text deutsch
(CONVENTIONS.md).

---

## 1. Zielbild

Jede Ebene definiert zwei Darstellungs-Aspekte, die in den Grundriss-Generator
einfließen:

- **PEN** — Linienstil der Ebene: `type` (durchgezogen / gestrichelt / …),
  `color` und `lw` (Strichstärke in mm Papier). Steuert alle Umriss-/Symbol-Linien
  der Elemente dieser Ebene (Wand-Umriss, Tür-Symbol, Referenzlinie).
- **HATCH** — Standard-Schraffur der Ebene: `pattern`, `scale`, `angle` und die
  `lineWeight` der Musterlinien. Wird angewandt, wo ein Element keine eigene
  Schraffur aus einem `Component` mitbringt (z. B. einschichtige/„grob"-Flächen,
  reine 2D-Zeichnungsobjekte einer Ebene).

Beides folgt dem Architektur-Prinzip: **Darstellung wird beim Rendern aufgelöst,
nie in die Geometrie eingebacken.**

---

## 2. Reference vs. Inline — Entscheidung

Es gibt drei Modelle, ein Datum für PEN/HATCH einer Ebene zu halten:

1. **Pure inline** — die Ebene trägt `{type,color,lw}` und `{pattern,scale,angle,
   lineWeight}` direkt. Einfach, aber: kein Wiederverwenden, kein zentrales
   Ändern; widerspricht der Ressourcen-Architektur (resources-graphics.md §1:
   „alles verweist per id, zentral änderbar").
2. **Pure reference** — die Ebene trägt nur `lineStyleId` / `hatchId`. Konsistent,
   zentral, aber unflexibel: Eine Ebene kann z. B. nicht „den Stil X, aber in
   ihrer eigenen Farbe" wollen, ohne einen Klon-Stil anzulegen.
3. **Reference + optionale per-Ebene Overrides** (EMPFOHLEN) — die Ebene
   **verweist** auf eine `LineStyle`- bzw. `HatchStyle`-Ressource und darf
   **einzelne Felder lokal überschreiben**. Das ist exakt das DOSSIER/Vectorworks-
   Muster: ein Stil als Basis, regelbasierte/lokale Overrides obendrauf
   (resources-graphics.md, `overrides.py`).

### Empfehlung: Reference + optionale Overrides

Begründung:

- **Zentrale Pflege bleibt erhalten:** Ändert man den Linienstil „Wand stark" im
  Line Manager, ziehen alle Ebenen nach, die ihn referenzieren und das jeweilige
  Feld nicht überschreiben.
- **Lokale Freiheit ohne Stil-Wildwuchs:** Eine Ebene kann punktuell `color` oder
  `lw` anpassen (häufigster Fall: gleiche Strichart, andere Farbe), ohne einen
  fast identischen Stil zu duplizieren.
- **Migrationsfähig:** Die heutige flache `{color, lw}` der Ebene wird zu reinen
  Overrides über einem neutralen Basis-Stil — verlustfrei (siehe §6).
- **Konsistent mit der bestehenden Kette:** `Component → Hatch → LineStyle`
  verweist bereits per id; Ebenen reihen sich nahtlos ein.

Die Overrides sind **sparse**: nur gesetzte Felder überschreiben. Ein leeres
Override-Objekt (oder `undefined`) bedeutet „komplett dem Stil folgen".

---

## 3. Datenmodell (TS)

### 3.1 LineStyle erweitern um `type`

`LineStyle` trägt heute schon `weight`, `color`, `dash`. Wir machen die
Strichart explizit benennbar (statt nur via `dash`-Array), damit der Editor ein
sauberes Dropdown anbietet und `dash` daraus ableiten kann.

```ts
/** Benannte Strichart eines Stifts (für UI-Dropdown). */
export type LineKind = "solid" | "dashed" | "dotted" | "dashdot";

/** mm-Strichmuster je Strichart (relativ zur Papier-mm). */
export const LINE_DASH: Record<LineKind, number[] | null> = {
  solid: null,
  dashed: [0.6, 0.4],
  dotted: [0.1, 0.25],
  dashdot: [0.6, 0.25, 0.1, 0.25],
};

export interface LineStyle {
  id: string;
  name: string;
  /** NEU: benannte Strichart; `dash` wird daraus abgeleitet, falls nicht gesetzt. */
  kind: LineKind;
  /** Strichstärke in Millimetern (≙ Rhino PlotWeight). */
  weight: number;
  color: string;
  /** Strichmuster in mm; `null` = durchgezogen. Optional — sonst aus `kind`. */
  dash: number[] | null;
}
```

> Hinweis: `kind` ist additiv; bestehende `LineStyle`-Daten setzen es per Migration
> aus `dash` (§6).

### 3.2 PEN- und HATCH-Override-Typen

```ts
/**
 * Per-Ebene Stift (PEN). Verweist auf einen LineStyle; einzelne Felder dürfen
 * lokal überschrieben werden. Alle Override-Felder optional (sparse).
 */
export interface LayerPen {
  /** Basis-Linienstil (Line Manager). */
  lineStyleId: string;
  /** Lokale Overrides — nur gesetzte Felder gewinnen. */
  override?: {
    kind?: LineKind;
    color?: string;
    /** Strichstärke in mm Papier. */
    lw?: number;
  };
}

/**
 * Per-Ebene Standard-Schraffur (HATCH). Verweist auf einen HatchStyle; einzelne
 * Felder dürfen lokal überschrieben werden. `enabled=false` = Ebene hat keine
 * Default-Schraffur (Umriss-only).
 */
export interface LayerHatch {
  /** Aktiv? false = keine Default-Schraffur dieser Ebene. */
  enabled: boolean;
  /** Basis-Schraffur (Hatch Manager). */
  hatchId: string;
  /** Lokale Overrides — nur gesetzte Felder gewinnen. */
  override?: {
    pattern?: HatchPattern;
    scale?: number;
    /** Drehung in Grad. */
    angle?: number;
    color?: string;
    /** Strichstärke der Musterlinien in mm Papier. */
    lineWeight?: number;
  };
}
```

### 3.3 LayerCategory erweitern

```ts
export interface LayerCategory {
  code: string;
  name: string;
  visible: boolean;
  locked: boolean;

  // ── NEU: vollständige Darstellung ──────────────────────────────────────────
  /** Stift der Ebene (PEN) — Linien aller Elemente dieser Ebene. */
  pen: LayerPen;
  /** Standard-Schraffur der Ebene (HATCH). */
  hatch: LayerHatch;

  /** Unterkategorien (Baum). */
  children?: LayerCategory[];

  // ── DEPRECATED (nur Übergang; siehe Migration §6) ──────────────────────────
  /** @deprecated → pen.override.color. */
  color?: string;
  /** @deprecated → pen.override.lw. */
  lw?: number;
}
```

`color` und `lw` bleiben als optionale, deprecatete Felder bestehen, bis alle
Lesepfade auf den Resolver (§4) umgestellt sind, und werden dann entfernt. Die
Panel-Swatch (`LayersPanel`) liest künftig die **aufgelöste** Stift-Farbe.

---

## 4. Resolver — vom Modell zur Render-Entscheidung

Der Resolver löst PEN/HATCH einer Ebene gegen die Ressourcen-Bibliotheken auf und
wendet die Overrides an. Er ist die **einzige** Stelle, an der „Stil + Override"
zusammenfließen; Generator und Panel rufen nur ihn.

### 4.1 Aufgelöste Render-Typen

`HatchRender` existiert bereits in `generatePlan.ts`. Wir ergänzen ein paralleles
`PenRender` und exportieren beide Resolver aus einem neuen Modul
`src/model/layerStyle.ts` (damit Panel und Generator teilen).

```ts
/** Aufgelöster Stift einer Ebene — alles, was die Linie zu zeichnen braucht. */
export interface PenRender {
  color: string;
  /** Strichstärke in mm Papier. */
  lw: number;
  /** Strichmuster in mm Papier; null = durchgezogen. */
  dash: number[] | null;
}

// HatchRender: bereits in generatePlan.ts definiert (pattern, scale, angle,
// color, lineWeight, dash). Wird nach layerStyle.ts gezogen und re-exportiert.
```

### 4.2 Resolver-Funktionen (Pseudocode)

```ts
function resolvePen(project: Project, layer: LayerCategory): PenRender {
  const ls = getLineStyle(project, layer.pen.lineStyleId); // wirft, falls fehlend
  const o = layer.pen.override ?? {};
  const kind = o.kind ?? ls.kind;
  return {
    color: o.color ?? ls.color,
    lw:    o.lw    ?? ls.weight,
    // Override-kind setzt das dash neu; sonst Stil-dash bzw. aus kind abgeleitet.
    dash:  o.kind ? LINE_DASH[o.kind] : (ls.dash ?? LINE_DASH[ls.kind]),
  };
}

function resolveLayerHatch(project: Project, layer: LayerCategory): HatchRender | null {
  if (!layer.hatch.enabled) return null;            // Ebene ohne Default-Schraffur
  const h = getHatch(project, layer.hatch.hatchId); // wirft, falls fehlend
  const o = layer.hatch.override ?? {};
  // Musterlinien-Stärke: Override > LineStyle der Schraffur > Default 0.13 mm.
  const baseLs = h.lineStyleId ? getLineStyle(project, h.lineStyleId) : null;
  return {
    pattern:    o.pattern    ?? h.pattern,
    scale:      o.scale      ?? h.scale,
    angle:      o.angle      ?? h.angle,
    color:      o.color      ?? h.color,
    lineWeight: o.lineWeight ?? baseLs?.weight ?? 0.13,
    dash:       baseLs?.dash ?? null,
  };
}
```

Beide bauen eine `Map<code, …>` über den ganzen Baum, analog zur heutigen
`categoryLwMap`:

```ts
export function penMap(project: Project): Map<string, PenRender> {
  const m = new Map<string, PenRender>();
  for (const c of flattenCategories(project.layers)) m.set(c.code, resolvePen(project, c));
  return m;
}
export function layerHatchMap(project: Project): Map<string, HatchRender | null> {
  const m = new Map<string, HatchRender | null>();
  for (const c of flattenCategories(project.layers))
    m.set(c.code, resolveLayerHatch(project, c));
  return m;
}
```

---

## 5. Einfluss auf `generatePlan`

Heute (generatePlan.ts):

- `categoryLwMap(project.layers)` liefert nur `lw` je Code; die Umriss-Strichstärke
  kommt daraus, **Farbe** der Umrisse ist fest `POCHE_STROKE`.
- Schraffur kommt ausschließlich aus dem `Component` der jeweiligen Schicht
  (`resolveHatch(project, comp.hatchId)`); die Ebenen-`hatch` wird **nicht** genutzt.

Änderungen (minimal-invasiv, additiv):

### 5.1 Pens ersetzen `lwByCode`

```ts
const pens = penMap(project);               // statt categoryLwMap
const layerHatches = layerHatchMap(project);
…
const pen = pens.get(wall.categoryCode) ?? FALLBACK_PEN; // {color, lw, dash}
```

`addWallPoche` und `addDoorSymbol` bekommen statt `wallLwMm: number` /
`doorLwMm: number` jeweils das ganze `pen: PenRender`:

- **Wand-Umrisslinie:** `stroke: pen.color` (statt fix `POCHE_STROKE`),
  `strokeWidthMm: pen.lw * OUTLINE_DETAIL_FACTOR[detail]`, `dash: pen.dash`.
  → Das `Primitive` „polygon" braucht ein optionales `dash?: number[] | null`
  (Schichtfugen bleiben durchgezogen; nur die Umriss-Kontur nutzt `pen.dash`).
- **Schichtfugen:** behalten `POCHE_STROKE` und ihre dünne `LAYER_LINE_MM`
  (interne Hilfslinien sind bewusst neutral, nicht stift-gefärbt).
- **Tür-Symbol / Referenzlinie:** `cls` bleibt, aber `weightMm` aus `pen.lw`,
  und die PlanView darf die Stift-Farbe nutzen (`door-leaf` etc. erhalten optional
  ein `stroke`-Feld am line/arc-Primitive; ansonsten greift die CSS-Klasse wie
  bisher).

### 5.2 Default-Schraffur der Ebene

Die Ebenen-Schraffur greift dort, wo **keine Component-Schraffur** vorliegt:

- **`detail === "grob"`** (eine Sammelfläche, heute `NO_HATCH`): statt `NO_HATCH`
  nun `layerHatches.get(wall.categoryCode) ?? NO_HATCH`. So bekommt die grobe
  Poché die Standard-Schraffur der Ebene (z. B. ein leichtes Diagonalmuster),
  falls die Ebene eine definiert; sonst bleibt sie ungeschraffiert.
- **mittel/fein, mehrschichtig:** unverändert — die Component-Schraffur je Schicht
  hat Vorrang (spezifischer als die Ebene). Die Ebenen-Schraffur ist der
  *Fallback*, nicht der Default-Override.
- **Reine 2D-Zeichnungsobjekte** (künftige `drawing`-Ebenen-Elemente ohne
  Component): nutzen direkt `resolveLayerHatch` als ihre Füllschraffur.

Auflöse-Reihenfolge der Schraffur einer gezeichneten Fläche:

```
Component.hatch  >  LayerCategory.hatch (enabled)  >  keine Schraffur
```

### 5.3 Geänderte Signaturen (Zusammenfassung)

```ts
// vorher: addWallPoche(out, project, wall, doors, cuts, greyed, detail, wallLwMm)
function addWallPoche(out, project, wall, doors, cuts, greyed, detail,
                      pen: PenRender, layerHatch: HatchRender | null): void

// vorher: addDoorSymbol(out, wall, door, greyed, detail, doorLwMm)
function addDoorSymbol(out, wall, door, greyed, detail, pen: PenRender): void
```

`Primitive` (polygon) erhält optional `dash?: number[] | null`; line/arc erhalten
optional `stroke?: string`, damit Pen-Farbe durchschlagen kann (CSS-Klasse bleibt
Default).

---

## 6. Editor „Ebeneneinstellungen…"

Geöffnet wie heute über `layerMenuItems → openLayerEditor(code)` →
`setEditor({ kind: "layer", code, x, y })`. Der bestehende `InlineEditor`-Rahmen
(dunkel, am Anker, Esc/Außenklick schließt) und die `EditorField`-Zeilen bleiben;
der Inhalt wächst von 3 Feldern auf zwei kompakte Abschnitte **PEN** und **HATCH**.

Da der Editor jetzt mehr Felder trägt, wird er als **kompakte Sektions-Form**
gestaltet (zwei Gruppen mit Trenn-Überschrift), gemäß CONVENTIONS.md UI-Konventionen
(saubere Form, keine wiederholten Beschriftungen, DOSSIER-Stil, alles via `t()`).

### 6.1 Aufbau

```
┌ Ebene 20 ───────────────── ×
│ Name        [ Wände            ]
│
│ ── Stift (PEN) ──────────────
│ Linienstil  [ Wand stark   ▾ ]   ← Dropdown über project.lineStyles
│ Strichart   [ durchgezogen ▾ ]   ← override.kind (leer = "vom Stil")
│ Farbe       [■]  [↺]             ← override.color; ↺ = Override entfernen
│ Stärke      [ 0.35 ] mm  [↺]     ← override.lw
│
│ ── Schraffur (HATCH) ────────
│ [✓] aktiv
│ Schraffur   [ Beton        ▾ ]   ← Dropdown über project.hatches
│ Muster      [ vom Stil     ▾ ]   ← override.pattern
│ Maßstab     [ 1.00 ]      [↺]
│ Drehung     [ 45 ] °      [↺]
│ Farbe       [■]           [↺]
│ Linienst.   [ 0.13 ] mm   [↺]
└──────────────────────────────
```

- **Override-Semantik im UI:** Jedes Override-Feld zeigt entweder „vom Stil"
  (Override leer → Platzhalter mit dem aufgelösten Stil-Wert als Hint) oder einen
  konkreten Wert. Ein kleiner **Reset-Knopf `↺`** je Override-Feld löscht das
  Override (setzt es zurück auf `undefined` → Feld folgt wieder dem Stil).
- **Live, kein Bestätigen:** wie der heutige Editor — jede Änderung ruft sofort
  `patchCategory(code, patch)`.
- **i18n:** alle Labels über `t()`. Neue Keys (Beispiele):
  `editor.pen`, `editor.lineStyle`, `editor.lineKind`, `editor.color`,
  `editor.lineWeight`, `editor.hatch`, `editor.hatchEnabled`, `editor.pattern`,
  `editor.scale`, `editor.rotation`, `editor.fromStyle`, `editor.resetOverride`.
  Strichart-/Muster-Werte: `lineKind.solid`, `lineKind.dashed`, …,
  `hatchPattern.solid`, `hatchPattern.diagonal`, … . Menü-Label bleibt
  `ctx.layerSettings`.

### 6.2 Patch-Helfer

`patchCategory(code, patch: Partial<LayerCategory>)` bleibt die Schnittstelle.
Für die verschachtelten Overrides nutzt der Editor schmale Helfer (im App-Scope),
die sparse mergen und leere Overrides auf `undefined` kollabieren:

```ts
function setPenOverride(cat: LayerCategory, patch: Partial<LayerPen["override"]>) {
  const next = pruneEmpty({ ...cat.pen.override, ...patch });
  patchCategory(cat.code, { pen: { ...cat.pen, override: next } });
}
function setHatchOverride(cat, patch) { /* analog für cat.hatch.override */ }
// pruneEmpty: entfernt undefined-Felder; gibt undefined zurück, wenn leer.
```

`setLineStyleId` / `setHatchId` setzen nur die Referenz; `hatch.enabled` ist ein
Checkbox-Patch.

### 6.3 „Eigenschaften kopieren / einfügen"

Der bestehende `layerClipboard` (heute `{ color, lw }`) wird auf die volle
Darstellung erweitert: `{ pen, hatch }` (die Override-tragenden Strukturen, ohne
`code/name/visible/locked`). „Kopieren" liest `{ pen, hatch }` der Quelle,
„Einfügen" patcht sie auf das Ziel. So überträgt sich der komplette Stift +
Schraffur einer Ebene auf eine andere.

---

## 7. Migration bestehender Beispieldaten

Bestehende Projekte/Sample-Daten haben `LayerCategory { color, lw, hatch?: string }`
und `LineStyle { weight, color, dash }` (ohne `kind`). Eine reine Lese-Zeit-
Migration (`migrateProject(project)`), idempotent, beim Laden:

1. **LineStyle.kind ableiten** — aus `dash`:
   ```
   dash == null || dash.length === 0  → "solid"
   sonst, wenn min(dash) sehr klein   → "dotted"   (heuristisch)
   sonst                              → "dashed"
   ```
   (Eine genaue Zuordnung ist nicht nötig; `dash` bleibt führend, `kind` ist nur
   für das Dropdown.)

2. **Neutralen Basis-Linienstil sicherstellen** — falls die Bibliothek noch keinen
   generischen „Standard"-Stift hat, einen `lineStyle` mit
   `{ id: "ls-default", name: "Standard", kind: "solid", weight: <Ebenen-lw>, color: "#000", dash: null }`
   anlegen. (Pro Ebene wird der Stift referenziert; die Ebenen-spezifischen
   `color`/`lw` wandern in das **Override**, nicht in den Stil — so bleibt der
   Stil wiederverwendbar.)

3. **Pro LayerCategory `pen` bauen:**
   ```ts
   pen = {
     lineStyleId: "ls-default",
     override: pruneEmpty({ color: cat.color, lw: cat.lw }),
   }
   ```
   Damit ist die Darstellung **pixelgenau wie vorher** (gleiche Farbe, gleiche lw),
   nur jetzt über die Resolver-Kette.

4. **Pro LayerCategory `hatch` bauen** — aus dem alten `hatch?: string`:
   - War `hatch` ein gültiger `HatchStyle.id` → `{ enabled: true, hatchId: hatch }`.
   - War es ein Pattern-Name oder leer/unbekannt → `{ enabled: false, hatchId:
     <erste Hatch-id der Bibliothek> }` (Referenz muss existieren, aber inaktiv).
   So entsteht **keine** unbeabsichtigte Schraffur (Default heute: keine).

5. **Deprecated-Felder belassen** für eine Übergangsphase; nach Umstellung aller
   Lesepfade (`generatePlan`, `LayersPanel`-Swatch, Clipboard) in einem zweiten
   Schritt `color`/`lw` aus `LayerCategory` und der alte `hatch: string` entfernen.

Migration ist **idempotent**: Liegt `pen`/`hatch` bereits vor, wird die Ebene
unverändert durchgereicht.

---

## 8. Build-Plan (phasiert)

**Phase 1 — Datenmodell & Resolver (keine UI-Sichtbarkeit).**
- `LineKind` + `LINE_DASH`, `LineStyle.kind`, `LayerPen`, `LayerHatch`,
  `LayerCategory.pen/hatch` in `types.ts`.
- `src/model/layerStyle.ts`: `PenRender`, `resolvePen`, `resolveLayerHatch`,
  `penMap`, `layerHatchMap`; `HatchRender` hierher ziehen + re-exportieren.
- `migrateProject()` (Schritte §7) + Aufruf beim Laden/Seed.
- `npx tsc -b` grün.

**Phase 2 — Generator umstellen.**
- `generatePlan` nutzt `penMap`/`layerHatchMap` statt `categoryLwMap`.
- `Primitive`-polygon `dash?`, line/arc `stroke?` ergänzen; `addWallPoche`/
  `addDoorSymbol`-Signaturen auf `PenRender` + `HatchRender|null`.
- Ebenen-Default-Schraffur in „grob" und für Schicht-lose Flächen verdrahten.
- Visuell prüfen via `node scripts/probe.mjs` (Geometrie unverändert, Farben/lw
  identisch zur Migration).

**Phase 3 — Panel.**
- `LayersPanel`-Swatch liest aufgelöste Stift-Farbe (`resolvePen(...).color`).

**Phase 4 — Editor.**
- `InlineEditor`-Inhalt für `kind: "layer"` auf die PEN/HATCH-Sektionen erweitern
  (§6), mit Dropdowns über `project.lineStyles` / `project.hatches`, Reset-Knöpfen,
  neuen i18n-Keys.
- `layerClipboard` auf `{ pen, hatch }` erweitern; Kopieren/Einfügen anpassen.

**Phase 5 — Aufräumen.**
- Deprecatete `color`/`lw`/`hatch: string` aus `LayerCategory` entfernen, sobald
  kein Lesepfad sie mehr nutzt; Sample-Daten direkt im neuen Format ablegen.

---

## 9. Offene Punkte / bewusst nicht jetzt

- **Pro-Geschoss-Overrides der Ebene** (eine Ebene anders je `DrawingLevel`):
  nicht in dieser Iteration; das Schema gilt geschossübergreifend (types.ts).
  Falls später nötig, als zweite Override-Ebene über demselben Resolver.
- **Regelbasierte Overrides** (resources-graphics.md, `overrides.py`): orthogonal;
  würden nach der Ebenen-Auflösung greifen.
- **Linienstil-Endkappen/Joins** und feinere Dash-Skalierung: bleiben in der
  PlanView (Darstellung), nicht im Modell.
