Add parametric walls design documentation
This commit is contained in:
@@ -48,6 +48,14 @@ Undo/Redo** eliminiert die Cache-Stale-Bugs strukturell. Ziel-Repo-Struktur mit
|
|||||||
**kleinen Bauteil-Modulen** statt des 7244-LOC-`elemente.py`-Monolithen; Rendering
|
**kleinen Bauteil-Modulen** statt des 7244-LOC-`elemente.py`-Monolithen; Rendering
|
||||||
über einen `THREE.Group`-Baum, der den Ebenen-Baum spiegelt.
|
über einen `THREE.Group`-Baum, der den Ebenen-Baum spiegelt.
|
||||||
|
|
||||||
|
### [design/parametric-walls.md](design/parametric-walls.md)
|
||||||
|
Regelbasierte Wandgenerierung als Alternative zum Direktzeichnen. Vier Regel-Varianten
|
||||||
|
(`GridRule`, `ModuleRule`, `ConditionalRule`, `PolylineRule`) erzeugen `Wall[]`-Arrays
|
||||||
|
über einen reinen Auflöser (`resolveParametricWall`). Deckungsbereich: Schweizer 3-m-
|
||||||
|
Wohnraster, bedingte Außen-/Innenwand-Dicken, 6-m-Jochbauweise. Phase A: Typsystem +
|
||||||
|
Resolver isoliert, kein UI. Phase B: Command + Formular-Editor. Phase C: Grid-Ressource
|
||||||
|
und IFC-Export.
|
||||||
|
|
||||||
### [design/elements.md](design/elements.md)
|
### [design/elements.md](design/elements.md)
|
||||||
Legt **Daten, Generierung (3D + Plan) und Grip-Editing pro Bauteil** fest. Wichtigste
|
Legt **Daten, Generierung (3D + Plan) und Grip-Editing pro Bauteil** fest. Wichtigste
|
||||||
Empfehlung: die **Prioritäts-T-/X-Verschneidung mehrschichtiger Wände** (Backbone-
|
Empfehlung: die **Prioritäts-T-/X-Verschneidung mehrschichtiger Wände** (Backbone-
|
||||||
|
|||||||
@@ -0,0 +1,672 @@
|
|||||||
|
# Parametrische Wände
|
||||||
|
|
||||||
|
Status: Implementiert (Phase A — Typ-System und Resolver in `src/model/`, kein UI).
|
||||||
|
Dieses Dokument spezifiziert die **Parametrischen Wände**: regelbasierte Definitionen,
|
||||||
|
die beim Auflösen eine Liste von `Wall`-Elementen erzeugen, anstatt sie einzeln vom
|
||||||
|
Nutzer zeichnen zu lassen.
|
||||||
|
|
||||||
|
Bezugsdokumente: [elements.md](elements.md) (Wand-/Türmodell),
|
||||||
|
[drawing-tools.md](drawing-tools.md) (Werkzeugsystem, Direktzeichnen),
|
||||||
|
[state-architecture.md](state-architecture.md) (Projekt-Store),
|
||||||
|
[resources-graphics.md](resources-graphics.md) (WallType/Component-Auflösung).
|
||||||
|
|
||||||
|
Implementierungsdateien:
|
||||||
|
- `src/model/types.ts` — `ParametricWall`, `ParametricRule` und alle Regel-Varianten.
|
||||||
|
- `src/model/parametricWalls.ts` — `resolveParametricWall()`, `applyRule()` und
|
||||||
|
Hilfsfunktionen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Überblick
|
||||||
|
|
||||||
|
Eine **parametrische Wand** (`ParametricWall`) ist kein festes `Wall`-Element, sondern
|
||||||
|
ein **Regelwerk**, das beim Auflösen (`resolveParametricWall`) eine Menge von `Wall[]`-
|
||||||
|
Elementen generiert. Die erzeugten Wände sind gewöhnliche `Wall`-Objekte; sie
|
||||||
|
unterscheiden sich lediglich in ihrer Herkunft. Das semantische Modell (`Project`)
|
||||||
|
bleibt die einzige Wahrheit — parametrische Wände sind eine Ressource in der
|
||||||
|
Ressourcen-Bibliothek, nicht eine separate Laufzeit-Geometrie-Schicht.
|
||||||
|
|
||||||
|
```
|
||||||
|
Project.parametricWalls: ParametricWall[]
|
||||||
|
│
|
||||||
|
│ resolveParametricWall(pw, floorId, context, defaultWallType)
|
||||||
|
▼
|
||||||
|
Wall[] ──→ normales Rendering über generatePlan / Viewport3D
|
||||||
|
```
|
||||||
|
|
||||||
|
Erzeugte Wände können entweder **temporär** (zur Laufzeit, als Ergänzung zu
|
||||||
|
`project.walls` im Rendering-Pfad) oder **eingebacken** (als `Wall[]` fest in
|
||||||
|
`Project.walls` gespeichert) behandelt werden. Phase A legt nur den Auflöser fest;
|
||||||
|
die Auswahl liegt bei der aufrufenden Komponente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Motivation
|
||||||
|
|
||||||
|
### 1.1 Schnellere Modellierung von Regelgrundrissen
|
||||||
|
|
||||||
|
Schweizer Wohnbauten folgen häufig einem 3-m-Achsraster (SIA-Norm, Modul-/
|
||||||
|
Skelettbauweise). Zwanzig Wände eines Rasters von Hand zu zeichnen ist fehleranfällig
|
||||||
|
und verhindert spätere parametrische Änderungen (z. B. Geschossanzahl, Rasterweite,
|
||||||
|
Wandtyp).
|
||||||
|
|
||||||
|
Eine `GridRule` erzeugt dieses Muster aus wenigen Parametern (Achsabstand, Richtung,
|
||||||
|
Bereich) und lässt sich mit einer einzigen Zahl auf „4-m-Büroraster" umstellen.
|
||||||
|
|
||||||
|
### 1.2 Kongruenz mit FreeCAD BIM / IFC
|
||||||
|
|
||||||
|
FreeCAD BIM kennt **ParametricObjects**, die ihre Geometrie aus Regeln ableiten (z. B.
|
||||||
|
`ArchWall` mit `Length`, `Width`, `Height`). Obwohl das Datenformat hier kein IFC ist,
|
||||||
|
schafft ein ähnliches Abstraktionsniveau eine spätere Brücke: Beim IFC-Export können
|
||||||
|
parametrische Wände als `IfcWallStandardCase` mit konstanten Attributen exportiert
|
||||||
|
werden — kein Informationsverlust gegenüber manuell gezeichneten Wänden.
|
||||||
|
|
||||||
|
### 1.3 Bedingte Wandtypen ohne manuelle Klassifizierung
|
||||||
|
|
||||||
|
Außenwände sind dicker als Innenwände; Trennwände zwischen Einheiten erfordern
|
||||||
|
Schallschutz. Eine `ConditionalThicknessRule` (`condition: "exterior" → thickType`)
|
||||||
|
weist den richtigen Wandtyp automatisch aus der geometrischen Lage zu — ohne dass der
|
||||||
|
Nutzer jeden Wandabschnitt einzeln klassifizieren muss.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architektur
|
||||||
|
|
||||||
|
### 2.1 Typen (`src/model/types.ts`)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* Eine parametrische Wand-Regel — generiert automatisch Wall[]-Einträge für
|
||||||
|
* ein gegebenes Geschoss. Lebt in Project.parametricWalls[].
|
||||||
|
*/
|
||||||
|
export interface ParametricWall {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
/**
|
||||||
|
* Geordnete Liste der anzuwendenden Regeln. Spätere Regeln können die
|
||||||
|
* Ausgabe früherer verfeinern (z. B. Dickenzuweisung nach Raster).
|
||||||
|
*/
|
||||||
|
rules: ParametricRule[];
|
||||||
|
/**
|
||||||
|
* Rückfall-Wandtyp, falls eine Regel keinen eigenen `wallTypeId` nennt.
|
||||||
|
*/
|
||||||
|
defaultWallTypeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Diskriminierte Union aller Regel-Varianten. */
|
||||||
|
export type ParametricRule =
|
||||||
|
| GridRule
|
||||||
|
| ModuleRule
|
||||||
|
| ConditionalThicknessRule
|
||||||
|
| ReferenceLineRule
|
||||||
|
| SequenceRule;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Einbettung ins Projekt
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface Project {
|
||||||
|
// … bestehende Felder …
|
||||||
|
/**
|
||||||
|
* Parametrische Wanddefinitionen (Ressourcen-Bibliothek). Optional, damit
|
||||||
|
* bestehende Projekte/Tests ohne `parametricWalls` gültig bleiben.
|
||||||
|
*/
|
||||||
|
parametricWalls?: ParametricWall[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Resolver-Kontext (`src/model/parametricWalls.ts`)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface ParametricContext {
|
||||||
|
/** Das Ziel-Geschoss. */
|
||||||
|
floor: DrawingLevel;
|
||||||
|
/**
|
||||||
|
* Optionale Rasterachsen (Phase C: verlinkter Grid-Ressource). Fehlen sie,
|
||||||
|
* berechnet die Engine die Achsen aus GridRule.spacing.
|
||||||
|
*/
|
||||||
|
gridAxes?: { x: number[]; y: number[] };
|
||||||
|
/**
|
||||||
|
* Optionales Clipping-Polygon (Meter). Fehlt es, reicht das Raster über
|
||||||
|
* einen Standardbereich (0 … spacing × 10).
|
||||||
|
*/
|
||||||
|
boundaryGeometry?: { boundary: Vec2[] };
|
||||||
|
/**
|
||||||
|
* Bereits im Projekt vorhandene Wände des Geschosses. Werden von
|
||||||
|
* refinierenden Regeln (ConditionalThicknessRule, ReferenceLineRule) genutzt.
|
||||||
|
*/
|
||||||
|
existingWalls?: Wall[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Regel-Varianten
|
||||||
|
|
||||||
|
### 3.1 GridRule — Achsraster
|
||||||
|
|
||||||
|
Erzeugt parallele Wände auf einem gleichmäßigen Raster. Typischer Einsatz: Schweizer
|
||||||
|
Wohnbau-Achsraster (3 m), Büro-Konstruktionsraster (6 m), strukturelle Raster mit
|
||||||
|
fester Stützweite.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface GridRule {
|
||||||
|
type: "grid";
|
||||||
|
/**
|
||||||
|
* Optionaler Verweis auf eine Grid-Ressource (Phase C). Für Phase A wird
|
||||||
|
* stattdessen `spacing` genutzt.
|
||||||
|
*/
|
||||||
|
gridId?: string;
|
||||||
|
/** Rasterabstand in Metern (Default: 3.0). */
|
||||||
|
spacing?: number;
|
||||||
|
/**
|
||||||
|
* Achsrichtungen: „x" = nur Wände entlang der Y-Achse,
|
||||||
|
* „y" = nur Wände entlang der X-Achse, „both" = Vollraster.
|
||||||
|
*/
|
||||||
|
directions: "x" | "y" | "both";
|
||||||
|
/** Optionaler Verweis auf Clipping-Polygon. */
|
||||||
|
boundaryId?: string;
|
||||||
|
/** Optionale Wandtyp-Übersteuerung; sonst defaultWallTypeId. */
|
||||||
|
wallTypeId?: string;
|
||||||
|
/** Lage der Wandachse über die Dicke (Vectorworks-Stil). */
|
||||||
|
referenceLine?: WallReferenceLine;
|
||||||
|
/** Optionale Höhenübersteuerung in Metern; sonst Geschosshöhe. */
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Geometrieausgabe (top-down Grundriss):**
|
||||||
|
|
||||||
|
```
|
||||||
|
directions: "x", spacing: 3.0, Bereich 0…12 m:
|
||||||
|
|
||||||
|
y
|
||||||
|
│
|
||||||
|
12 ──────────────────────────
|
||||||
|
│
|
||||||
|
9 ──────────────────────────
|
||||||
|
│
|
||||||
|
6 ──────────────────────────
|
||||||
|
│
|
||||||
|
3 ──────────────────────────
|
||||||
|
│
|
||||||
|
0 ──────────────────────────
|
||||||
|
│
|
||||||
|
└──────────────────────────► x
|
||||||
|
0 12
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wann verwenden:**
|
||||||
|
- Tragende Wände auf fester Stützweite (Wohnbau 3 m, Büro 6 m).
|
||||||
|
- Vollraster (`"both"`) für strukturelle Rastersysteme.
|
||||||
|
- In Kombination mit `ConditionalThicknessRule` zur automatischen Außen/Innen-Klassifizierung.
|
||||||
|
|
||||||
|
**Beispiel: Schweizer 3-m-Wohnraster**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pw: ParametricWall = {
|
||||||
|
id: "pw-eg-raster",
|
||||||
|
name: "EG Längswände 3m-Raster",
|
||||||
|
defaultWallTypeId: "wt-innen-15",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
spacing: 3.0,
|
||||||
|
directions: "x", // Wände in X-Richtung (y = 0, 3, 6, 9, 12)
|
||||||
|
wallTypeId: "wt-innen-15",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auflösung:
|
||||||
|
const walls = resolveParametricWall(pw, "floor-eg", {
|
||||||
|
floor: egFloor,
|
||||||
|
boundaryGeometry: { boundary: rectBoundary(0, 0, 12, 12) },
|
||||||
|
}, defaultWallType);
|
||||||
|
// → 5 Wände bei y = 0, 3, 6, 9, 12, je 12 m lang
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 ModuleRule — Bay-/Jochbauweise
|
||||||
|
|
||||||
|
Unterteilt eine Referenzspanne in gleiche Module und erzeugt Querwände an jedem
|
||||||
|
Teilungspunkt. Typisch für Bürogebäude (6-m-Joch) oder Reihenhäuser mit modularer
|
||||||
|
Erschließung.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface ModuleRule {
|
||||||
|
type: "module";
|
||||||
|
/** Modulmaß in Metern (z. B. 6.0, 3.6). */
|
||||||
|
moduleSize: number;
|
||||||
|
/** Ausrichtung der Trennwände: „x" = Querwände senkrecht zu X, „y" = zu Y. */
|
||||||
|
direction: "x" | "y";
|
||||||
|
/**
|
||||||
|
* Optionaler Verweis auf eine Referenzwand, die die Spannweite definiert.
|
||||||
|
* Fehlt er, wird die Geschoss-Ausdehnung (Bounding-Box) genutzt.
|
||||||
|
*/
|
||||||
|
referenceWallId?: string;
|
||||||
|
/** Optionale Wandtyp-Übersteuerung; sonst defaultWallTypeId. */
|
||||||
|
wallTypeId?: string;
|
||||||
|
referenceLine?: WallReferenceLine;
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wann verwenden:**
|
||||||
|
- Wenn sich Querwände aus einer Referenzspanne (Fassade, Achswand) ergeben.
|
||||||
|
- Vorzug vor `GridRule`, wenn nur in eine Richtung unterteilt wird und eine
|
||||||
|
Referenzwand die Spanne definiert.
|
||||||
|
|
||||||
|
**Beispiel: 6-m-Bay-Bürogebäude**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pw: ParametricWall = {
|
||||||
|
id: "pw-buero-joch",
|
||||||
|
name: "Büro 6m-Joch",
|
||||||
|
defaultWallTypeId: "wt-beton-20",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: "module",
|
||||||
|
moduleSize: 6.0,
|
||||||
|
direction: "x", // Querwände senkrecht zur X-Achse
|
||||||
|
// referenceWallId: "W-sudfassade" → Spanne aus der Südwand ableiten
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
// resolveParametricWall → Querwände bei x = 6, 12, 18, 24, 30 (bei 36-m-Fassade)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 ConditionalThicknessRule — Bedingte Wandtypen
|
||||||
|
|
||||||
|
Weist bereits erzeugten Wänden (aus vorherigen Regeln in der Sequenz) einen anderen
|
||||||
|
Wandtyp zu — abhängig von einer Bedingung. Gibt modifizierte **Kopien** zurück; die
|
||||||
|
Eingabe-Wände werden nicht mutiert.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface ConditionalThicknessRule {
|
||||||
|
type: "conditional-thickness";
|
||||||
|
/**
|
||||||
|
* Bedingung für den Treffer:
|
||||||
|
* • „exterior" — Wand liegt am Außenrand (Bounding-Box des Kontexts).
|
||||||
|
* • „interior" — Wand liegt im Inneren.
|
||||||
|
* • „bearing" — tragende Wand (Heuristikum: Wand läuft ±10° zu X/Y-Achse).
|
||||||
|
* • beliebiger String — benutzerdefiniertes Tag (Phase C: Wall.tags[]).
|
||||||
|
*/
|
||||||
|
condition: "exterior" | "interior" | "bearing" | string;
|
||||||
|
/** Ziel-Wandtyp, der bei Treffer gesetzt wird. */
|
||||||
|
wallTypeId: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wann verwenden:**
|
||||||
|
- Immer in Kombination mit `GridRule` oder `ModuleRule` (als zweite Regel in
|
||||||
|
`ParametricWall.rules`): Raster erzeugt, Dicke verfeinert.
|
||||||
|
- Wenn Außen- und Innenwände denselben geometrischen Ursprung haben, aber
|
||||||
|
verschiedene Aufbauten benötigen.
|
||||||
|
|
||||||
|
**Beispiel: Außen dick, Innen dünn**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pw: ParametricWall = {
|
||||||
|
id: "pw-eg-komplett",
|
||||||
|
name: "EG Vollraster mit Außenwand-Differenzierung",
|
||||||
|
defaultWallTypeId: "wt-innen-15",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: "grid", spacing: 3.0, directions: "both",
|
||||||
|
wallTypeId: "wt-innen-15",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "conditional-thickness",
|
||||||
|
condition: "exterior",
|
||||||
|
wallTypeId: "wt-aussen-36", // Außenwände erhalten dicken Aufbau
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 ReferenceLineRule — Wandachsen-Lage
|
||||||
|
|
||||||
|
Setzt `referenceLine` bei passenden Wänden einheitlich (Vectorworks-Stil: Achse
|
||||||
|
links/rechts/mittig). Gibt modifizierte Kopien zurück.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface ReferenceLineRule {
|
||||||
|
type: "reference-line";
|
||||||
|
/** Neue Lage der Wandachse, die einheitlich gesetzt wird. */
|
||||||
|
referenceLine: WallReferenceLine; // "left" | "center" | "right"
|
||||||
|
/**
|
||||||
|
* Filterziel:
|
||||||
|
* • „all" — alle Wände im aktuellen Satz.
|
||||||
|
* • „exterior" — nur Außenwände.
|
||||||
|
* • beliebiger String — benutzerdefiniertes Tag (Phase C).
|
||||||
|
*/
|
||||||
|
target: "all" | "exterior" | string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wann verwenden:**
|
||||||
|
- Außenwände auf `"left"` setzen (Achse liegt auf der Fassadenfläche).
|
||||||
|
- Als abschließende Regel in einer `SequenceRule` nach Raster und Dickenzuweisung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 SequenceRule — Zusammenfassung von Unterregeln
|
||||||
|
|
||||||
|
Fasst mehrere Regeln als atomare Einheit zusammen. Jede Unterregel erhält die Ausgabe
|
||||||
|
der vorherigen als `existingWalls` — so können spätere Regeln frühere verfeinern.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface SequenceRule {
|
||||||
|
type: "sequence";
|
||||||
|
rules: ParametricRule[];
|
||||||
|
/**
|
||||||
|
* Wenn true: Abbruch nach der ersten Unterregel, die mindestens eine Wand
|
||||||
|
* generiert/verändert hat (Short-Circuit-Fallback).
|
||||||
|
*/
|
||||||
|
stopOnMatch?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wann verwenden:**
|
||||||
|
- Um eine zusammengehörige Kombination (Raster → Dicke → Referenzlinie) als
|
||||||
|
Untermodul wiederzuverwenden — z. B. in unterschiedlichen Geschossen mit leicht
|
||||||
|
abweichenden Parametern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Resolver-API (`src/model/parametricWalls.ts`)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* Löst ein ParametricWall-Regelwerk zu einem Wall[]-Array für ein gegebenes
|
||||||
|
* Geschoss auf.
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Regelwerk sequenziell ausführen; jede Regel erhält die Ausgabe der
|
||||||
|
* vorherigen als existingWalls (ermöglicht Verfeinerung).
|
||||||
|
* 2. Duplikate (gleicher Start-/Endpunkt innerhalb tolerance) entfernen.
|
||||||
|
* 3. Bereinigte Wall[]-Liste zurückgeben.
|
||||||
|
*
|
||||||
|
* Die Ausgabe ist sofort bereit zur Einfügung in project.walls. Es werden
|
||||||
|
* keine Seiteneffekte erzeugt — kein Store, kein Dispatch, kein React.
|
||||||
|
*
|
||||||
|
* @param parametricWall Das Regelwerk.
|
||||||
|
* @param floorId ID des Ziel-Geschosses.
|
||||||
|
* @param context Kontext (Geschoss-Objekt, Grid-Achsen, Grenzen, …).
|
||||||
|
* @param defaultWallType Fallback-Wandtyp, wenn eine Regel keinen nennt.
|
||||||
|
* @param tolerance Näherungstoleranz für Duplikat-Erkennung (Meter, Default 0.01).
|
||||||
|
* @returns Wall[]-Array, bereit zur Einfügung.
|
||||||
|
*/
|
||||||
|
export function resolveParametricWall(
|
||||||
|
parametricWall: ParametricWall,
|
||||||
|
floorId: string,
|
||||||
|
context: ParametricContext,
|
||||||
|
defaultWallType: WallType,
|
||||||
|
tolerance?: number,
|
||||||
|
): Wall[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatcher: delegiert eine Regel an die passende Implementierung.
|
||||||
|
* Exportiert für Unit-Tests und erweiterbare Regeltypen.
|
||||||
|
*/
|
||||||
|
export function applyRule(rule: ParametricRule, ctx: RuleCtx): Wall[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt doppelte Wände: zwei Wände gelten als Duplikat, wenn Start- und
|
||||||
|
* Endpunkt jeweils innerhalb tolerance übereinstimmen (vorwärts und rückwärts).
|
||||||
|
*/
|
||||||
|
export function deduplicateWalls(walls: Wall[], tolerance?: number): Wall[];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.1 Höhenauflösung
|
||||||
|
|
||||||
|
Die Wandhöhe (`Wall.height`) ergibt sich nach folgender Priorität:
|
||||||
|
|
||||||
|
1. `rule.height`, falls an der einzelnen Regel gesetzt.
|
||||||
|
2. `context.floor.floorHeight` des Zielgeschosses.
|
||||||
|
3. Fallback: 2.6 m (globaler Default, CONVENTIONS.md).
|
||||||
|
|
||||||
|
### 4.2 ID-Schema
|
||||||
|
|
||||||
|
```
|
||||||
|
"pw-<floorId>-gx-<counter>" // GridRule, X-Achse
|
||||||
|
"pw-<floorId>-gy-<counter>" // GridRule, Y-Achse
|
||||||
|
"pw-<floorId>-mx-<counter>" // ModuleRule, X-Teilung
|
||||||
|
"pw-<floorId>-ct-<counter>" // ConditionalThicknessRule
|
||||||
|
"pw-<floorId>-rl-<counter>" // ReferenceLineRule
|
||||||
|
```
|
||||||
|
|
||||||
|
IDs sind sessionlokal (Zähler startet bei 0 je Modullade). Eingebrannte Wände
|
||||||
|
erhalten beim Commit neue stabile IDs über `uniqueId("W")` — konsistent mit dem
|
||||||
|
Wand-Werkzeug (vgl. [drawing-tools.md §8](drawing-tools.md#8-id-vergabe--immutabilität)).
|
||||||
|
|
||||||
|
### 4.3 Duplikat-Erkennung
|
||||||
|
|
||||||
|
`deduplicateWalls` vergleicht Start-/Endpunkte beider Wände (vorwärts: A→B == A→B,
|
||||||
|
und rückwärts: A→B == B→A) innerhalb einer Toleranz von 1 cm (0.01 m). Die **erste**
|
||||||
|
Instanz wird behalten; spätere Duplikate werden verworfen. Dies ist wichtig bei
|
||||||
|
Vollrastern (`"both"`), bei denen X- und Y-Wände exakt auf einem Rasterpunkt
|
||||||
|
zusammentreffen könnten.
|
||||||
|
|
||||||
|
### 4.4 Verhalten bei ungültigen Eingaben
|
||||||
|
|
||||||
|
| Situation | Verhalten |
|
||||||
|
|-----------|-----------|
|
||||||
|
| `spacing <= 0` oder `moduleSize <= 0` | `[]` |
|
||||||
|
| `referenceWallId` nicht in `existingWalls` | Fallback auf Bounding-Box, kein Fehler |
|
||||||
|
| Unbekannter `condition`-String | `matchesCondition` gibt `false` zurück (kein Treffer) |
|
||||||
|
| Unbekannter `SequenceRule`-Untertyp | TypeScript exhaustiveness-Guard, `[]` |
|
||||||
|
| Segment mit `|end - start| < 1e-6` m | Kann durch deduplicateWalls entfernt werden |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Integration ins Projekt
|
||||||
|
|
||||||
|
### 5.1 Ressourcen-Speicherung
|
||||||
|
|
||||||
|
`ParametricWall`-Einträge leben unter `Project.parametricWalls` (optionales Array).
|
||||||
|
Sie sind Teil des `.cad.json`-Dokuments und werden mit dem Rest des Projekts gespeichert.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// sampleProject.ts — Beispieleintrag
|
||||||
|
export const sampleProject: Project = {
|
||||||
|
// …
|
||||||
|
parametricWalls: [
|
||||||
|
{
|
||||||
|
id: "pw-eg-raster",
|
||||||
|
name: "EG Längswände 3m-Raster",
|
||||||
|
defaultWallTypeId: "wt-innen-15",
|
||||||
|
rules: [
|
||||||
|
{ type: "grid", spacing: 3.0, directions: "x" },
|
||||||
|
{ type: "conditional-thickness", condition: "exterior",
|
||||||
|
wallTypeId: "wt-aussen-36" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Rendering ohne UI (Phase A)
|
||||||
|
|
||||||
|
In Phase A werden parametrische Wände **nicht** automatisch gerendert. Der Auflöser
|
||||||
|
ist eine reine Funktion; Aufrufer müssen ihn explizit einbinden. Mögliche Verwendung
|
||||||
|
in `generatePlan` oder `Viewport3D`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// generatePlan.ts (Ergänzung, Phase A)
|
||||||
|
const defaultWallType = project.wallTypes[0];
|
||||||
|
const extraWalls = (project.parametricWalls ?? []).flatMap((pw) =>
|
||||||
|
resolveParametricWall(pw, activeLevelId, {
|
||||||
|
floor: activeFloor,
|
||||||
|
boundaryGeometry: projectBoundary,
|
||||||
|
}, defaultWallType)
|
||||||
|
);
|
||||||
|
const allWalls = [...project.walls, ...extraWalls];
|
||||||
|
// … allWalls statt project.walls in der Rendering-Pipeline verwenden
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Keine UI in Phase A
|
||||||
|
|
||||||
|
Kein Command, kein Panel, kein Formular. `ParametricWall`-Einträge werden in Phase A
|
||||||
|
ausschließlich **programmatisch** (Unit-Tests, `sampleProject`, direkte JSON-Bearbeitung
|
||||||
|
des Projekts) erstellt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Ausblick: Folge-Phasen
|
||||||
|
|
||||||
|
### Phase B — UI und Command-Schnittstelle
|
||||||
|
|
||||||
|
- Neues Command (z. B. `PWWALL`) oder Ressourcen-Manager-Tab „Parametrische Wände"
|
||||||
|
mit Formular-Editor je Regeltyp.
|
||||||
|
- „Einbrennen" (Flatten): `ParametricWall` → feste `Wall[]` in `Project.walls`
|
||||||
|
einfügen und den `ParametricWall`-Eintrag entfernen (unidirektional, Undo über Store).
|
||||||
|
- Auswahl parametrischer Wände im Plan (als Gruppe); Grip-Editing der Raster-Parameter
|
||||||
|
und Spannweiten.
|
||||||
|
|
||||||
|
### Phase C — Grid-Ressource und Schnittpunkt-Clipping
|
||||||
|
|
||||||
|
- `GridResource`: ein projektweites, benanntes Koordinatenraster (LV95-Offset,
|
||||||
|
Rasterweite, Drehung), auf das mehrere `GridRule`-Instanzen via `gridId` verweisen.
|
||||||
|
- Präzises Clipping: erzeugte Wände werden am Gebäudeumriss getrimmt — exakte
|
||||||
|
`lineIntersect`-Berechnung statt Bounding-Box-Approximation.
|
||||||
|
- Benutzerdefinierte Tags (`Wall.tags[]`) für komplexe `ConditionalThicknessRule`-
|
||||||
|
Bedingungen jenseits von „exterior/interior/bearing".
|
||||||
|
- IFC-Export: `ParametricWall`-Gruppen → `IfcWallStandardCase` mit parametrischen
|
||||||
|
Attributen und `IfcRelDefinesByType`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Vollständige Anwendungsbeispiele
|
||||||
|
|
||||||
|
### 7.1 Schweizer Wohnbau: 3-m-Raster EG + 1.OG
|
||||||
|
|
||||||
|
Zwei-Geschoss-Wohnhaus, typisches CH-Wohnbauraster. Die Längswände beider Geschosse
|
||||||
|
entstehen aus zwei `ParametricWall`-Einträgen mit identischen Regeln, unterschieden
|
||||||
|
nur durch `floorId` beim Auflösen:
|
||||||
|
|
||||||
|
```
|
||||||
|
Top-down (Grundriss):
|
||||||
|
|
||||||
|
y=12 ──────────────────────────── (W5)
|
||||||
|
y=9 ──────────────────────────── (W4)
|
||||||
|
y=6 ──────────────────────────── (W3)
|
||||||
|
y=3 ──────────────────────────── (W2)
|
||||||
|
y=0 ──────────────────────────── (W1)
|
||||||
|
↑
|
||||||
|
x=0 x=12
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const rasterRegel: ParametricWall = {
|
||||||
|
id: "pw-laengswand-raster",
|
||||||
|
name: "Längswände 3m-Raster",
|
||||||
|
defaultWallTypeId: "wt-innen-15",
|
||||||
|
rules: [
|
||||||
|
{ type: "grid", spacing: 3.0, directions: "x" },
|
||||||
|
// Außenwände (y=0 und y=12) erhalten den dicken Aufbau:
|
||||||
|
{ type: "conditional-thickness", condition: "exterior",
|
||||||
|
wallTypeId: "wt-aussen-36" },
|
||||||
|
// Außenwände: Achse liegt auf der Fassadenfläche:
|
||||||
|
{ type: "reference-line", referenceLine: "left", target: "exterior" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// EG auflösen:
|
||||||
|
const wallsEG = resolveParametricWall(rasterRegel, "floor-eg",
|
||||||
|
{ floor: egFloor, boundaryGeometry: { boundary: rect(0,0,12,12) } },
|
||||||
|
project.wallTypes[0]);
|
||||||
|
|
||||||
|
// 1.OG auflösen (gleiche Regel, anderes Geschoss):
|
||||||
|
const wallsOG = resolveParametricWall(rasterRegel, "floor-og1",
|
||||||
|
{ floor: ogFloor, boundaryGeometry: { boundary: rect(0,0,12,12) } },
|
||||||
|
project.wallTypes[0]);
|
||||||
|
// Änderung spacing: 3.5 → beide Geschosse sofort konsistent.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Vollraster mit Außen/Innen-Differenzierung
|
||||||
|
|
||||||
|
Gebäudeumriss als Rechteck; die Randwände erhalten automatisch den dicken
|
||||||
|
Außenwand-Typ, alle anderen den dünnen Innenwand-Typ:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const vollraster: ParametricWall = {
|
||||||
|
id: "pw-eg-vollraster",
|
||||||
|
name: "EG Vollraster mit Differenzierung",
|
||||||
|
defaultWallTypeId: "wt-innen-15",
|
||||||
|
rules: [
|
||||||
|
{ type: "grid", spacing: 3.0, directions: "both" },
|
||||||
|
{ type: "conditional-thickness", condition: "exterior",
|
||||||
|
wallTypeId: "wt-aussen-36" },
|
||||||
|
{ type: "conditional-thickness", condition: "interior",
|
||||||
|
wallTypeId: "wt-innen-15" },
|
||||||
|
{ type: "reference-line", referenceLine: "left", target: "exterior" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
─────┬─────┬─────┬─────
|
||||||
|
│ │ │ │ │
|
||||||
|
─────┼─────┼─────┼─────
|
||||||
|
│ │ │ │ │
|
||||||
|
─────┴─────┴─────┴─────
|
||||||
|
|
||||||
|
Rand-Segmente: wt-aussen-36 (dicker Aufbau)
|
||||||
|
Innen-Segmente: wt-innen-15 (dünner Aufbau)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Modulbauweise: 6-m-Joch, Bürogebäude
|
||||||
|
|
||||||
|
Längliches Bürogebäude, 36 m × 12 m, 6-m-Joch. Querwände entstehen automatisch;
|
||||||
|
Entwurfsänderung (z. B. auf 7.2-m-Joch) erfordert eine einzige Zahl:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const joch: ParametricWall = {
|
||||||
|
id: "pw-buero-joch",
|
||||||
|
name: "Büro 6m-Joch",
|
||||||
|
defaultWallTypeId: "wt-beton-20",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type: "module",
|
||||||
|
moduleSize: 6.0,
|
||||||
|
direction: "x", // Querwände senkrecht zur X-Achse
|
||||||
|
// referenceWallId: "W-sudfassade" → Spanne aus Referenzwand
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// resolveParametricWall → Querwände bei x = 6, 12, 18, 24, 30
|
||||||
|
// (bei Bounding-Box minX=0, maxX=36, Enden selbst ausgespart)
|
||||||
|
|
||||||
|
// Änderung auf 7.2-m-Joch: moduleSize: 7.2
|
||||||
|
// → 4 Trennwände bei x ≈ 7.2, 14.4, 21.6, 28.8 — automatisch neu berechnet.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Architektur-Garantien
|
||||||
|
|
||||||
|
- **Modell bleibt einzige Wahrheit.** `ParametricWall`-Definitionen sind Daten in
|
||||||
|
`Project.parametricWalls`; `resolveParametricWall` ist eine **reine Funktion** ohne
|
||||||
|
Side-Effects. Keine globale Laufzeit-Geometrie-Schicht.
|
||||||
|
- **Erzeugte Wände sind gewöhnliche `Wall`-Objekte.** Alle nachgelagerten Systeme
|
||||||
|
(`generatePlan`, `Viewport3D`, `computeJoins`) arbeiten unverändert; sie müssen
|
||||||
|
nicht zwischen „parametrisch erzeugten" und „direkt gezeichneten" Wänden
|
||||||
|
unterscheiden.
|
||||||
|
- **Fehlertoleranz statt Absturz.** Unbekannte Regeltypen liefern `[]`; der TypeScript-
|
||||||
|
exhaustiveness-Guard fängt fehlende `case`-Zweige zur Compilezeit. Unbekannte
|
||||||
|
Bedingungsstrings in `ConditionalThicknessRule` geben `false` (kein Treffer) statt
|
||||||
|
zu werfen.
|
||||||
|
- **Keine vorzeitige Generalisierung.** Phase A liefert fünf Regel-Varianten und
|
||||||
|
einen Auflöser. UI, Command-Schnittstelle und Grid-Ressource folgen in Phase B/C.
|
||||||
|
- **Immutabilität.** Verfeinerungsregeln (`ConditionalThicknessRule`,
|
||||||
|
`ReferenceLineRule`) geben modifizierte **Kopien** zurück; `existingWalls` werden
|
||||||
|
nie mutiert — konsistent mit der `setProject`-Konvention (CONVENTIONS.md).
|
||||||
Reference in New Issue
Block a user