23 KiB
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 (Wand-/Türmodell), drawing-tools.md (Werkzeugsystem, Direktzeichnen), state-architecture.md (Projekt-Store), resources-graphics.md (WallType/Component-Auflösung).
Implementierungsdateien:
src/model/types.ts—ParametricWall,ParametricRuleund 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)
/**
* 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
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)
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.
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
ConditionalThicknessRulezur automatischen Außen/Innen-Klassifizierung.
Beispiel: Schweizer 3-m-Wohnraster
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.
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
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.
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
GridRuleoderModuleRule(als zweite Regel inParametricWall.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
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.
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
SequenceRulenach 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.
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)
/**
* 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:
rule.height, falls an der einzelnen Regel gesetzt.context.floor.floorHeightdes Zielgeschosses.- 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).
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 |
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.
// 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:
// 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→ festeWall[]inProject.wallseinfügen und denParametricWall-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 mehrereGridRule-Instanzen viagridIdverweisen.- Präzises Clipping: erzeugte Wände werden am Gebäudeumriss getrimmt — exakte
lineIntersect-Berechnung statt Bounding-Box-Approximation. - Benutzerdefinierte Tags (
Wall.tags[]) für komplexeConditionalThicknessRule- Bedingungen jenseits von „exterior/interior/bearing". - IFC-Export:
ParametricWall-Gruppen →IfcWallStandardCasemit parametrischen Attributen undIfcRelDefinesByType.
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
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:
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:
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 inProject.parametricWalls;resolveParametricWallist 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 fehlendecase-Zweige zur Compilezeit. Unbekannte Bedingungsstrings inConditionalThicknessRulegebenfalse(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;existingWallswerden nie mutiert — konsistent mit dersetProject-Konvention (CONVENTIONS.md).