Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge

Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit
Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem,
dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en)
sowie Projektdokumentation und Probe-Harness.
This commit is contained in:
2026-06-30 20:52:27 +02:00
commit ca859c4aa4
157 changed files with 37921 additions and 0 deletions
+293
View File
@@ -0,0 +1,293 @@
# Design — Ressourcen & Grafik
> Teil der Standalone-Architektur — siehe [../../ARCHITECTURE.md](../../ARCHITECTURE.md).
> Bauteile: [elements.md](elements.md). Output/Pläne: [plans-output.md](plans-output.md).
Die **Stil-Schicht**: verwaltete Ressourcen-Bibliotheken (Vectorworks-Stil),
regelbasierte Overrides, Symbol-/Text-Bibliotheken und Detailgrad-Steuerung. Sie
wird **beim Rendern angewandt, nie in die Geometrie eingebacken** (ROADMAP §2b).
Dieses Dokument übersetzt DOSSIERs `styles.py`/`gestaltung.py`, `mass_style.py`,
`overrides.py`, `library.py`, `text_create.py`. Bezeichner englisch, Prosa
deutsch.
---
## 1. Resource Manager — verwaltete Bibliotheken
Drei Bibliotheken im `Project.resources`-Block; **alles verweist per id**, zentral
änderbar (ROADMAP §2d). Verweis-Kette: 2D-Objekte/Ebenen → LineStyle/Hatch;
Hatch → LineStyle; Component → Hatch (+3D-Material).
```ts
interface Resources {
lineStyles: LineStyle[];
hatches: Hatch[];
components: Component[];
}
interface LineStyle { // Line Manager
id; name;
weight: number; // Strichstärke in mm (≙ Rhino PlotWeight)
color: string; // hex
dash: number[]; // Strichmuster in mm ([] = durchgezogen)
}
interface Hatch { // Hatch Manager
id; name;
pattern: PatternId; // "solid" | "diagonal" | "insulation" | "concrete" | ...
scale: number; // Grundmaßstab des Musters
angle: number; // Grad
lineStyleId: string; // Linien der Schraffur → Line Manager
}
interface Component { // Component Manager (= DOSSIER-Material, erweitert)
id; name;
hatchId: string; // Schnitt-Schraffur → Hatch Manager
color3d: string; // 3D-Diffusfarbe
texture3d?: TextureRef; // optionale PBR-Textur (Phase 3)
pbr?: { roughness; metalness; opacity; ior }; // Material-Bibliothek (ROADMAP §11)
joinPriority: number; // Verschneidungs-Rang (DOSSIER _MATERIAL_PRIO als Daten)
}
```
**Migration vom heutigen Stand:** Der Spike hat `Material { color, planFill, hatch }`
und `Layer { materialId, thickness, priority }`. Ziel: `Material → Component`
(`color→color3d`, `planFill→` Fill aus `hatch.pattern==solid`+Farbe, `hatch`-Enum
`hatchId`), `Layer.priority → Component.joinPriority` (Priorität wandert vom
Layer zum Component, damit man sie nur einmal pflegt — siehe elements.md §1.3).
### 1.1 Manager-UI
`managers/ComponentManager.tsx`, `HatchManager.tsx`, `LineManager.tsx` — je eine
Liste mit CRUD + Vorschau (Three-Sphere für Component-3D, SVG-Swatch für
Hatch/Line). **Seeds** beim ersten Projekt (DOSSIER-Defaults):
- LineStyles: 0.13 / 0.18 / 0.25 / 0.35 / 0.50 mm (aus `DEFAULT_LAYER_SCHEMA`-lw).
- Hatches: `solid`, `diagonal`, `concrete`, `insulation` (Dämmung).
- Components: Stahlbeton (prio 800), Beton (800), Mauerwerk (600), Ziegel (550),
Holzständer (400), Dämmung (200), Putz (100) — exakt DOSSIER `_MATERIAL_PRIO`
(elements.md §1.3). Plus Glas (transparent), Holz-Türblatt (DOSSIER
`_OEFF_PIECE_DEFS`).
### 1.2 Render-Anwendung
- **3D:** Component → `MeshStandardMaterial` (`color3d`/`pbr`), pro `componentId`
gecacht (`viewport/scene.ts`).
- **Plan/Schnitt:** geschnittene Schicht → Polygon mit `fill` (Component-Farbe) +
`<pattern>` aus `hatchId`. Pattern als SVG `<pattern>` mit `patternTransform`
für Massstab (plans-output.md §3.2). Der Hatch nutzt seinen `lineStyleId` für
die Musterlinien.
---
## 2. Mehrschichtige Aufbauten ↔ Ressourcen
`WallType.layers[].componentId` / `SlabType.layers[].componentId` verweisen auf
Components. 3D und Plan lesen dieselben Schichten (elements.md §1). Die
**Prioritäts-Verschneidung** (Risiko #1) liest `Component.joinPriority`:
höhere Priorität läuft am Stoß durch (Backbone), niedrigere stößt seitlich an —
Algorithmus in elements.md §1.3 (Port DOSSIER `_t_junction_layer_overrides`).
---
## 3. Stile & Element-Override (Selektions-Attribute)
DOSSIERs GESTALTUNG-Panel (`styles.py`) setzt Farbe/Lineweight/Linetype/Hatch auf
die *Selektion*. Browser-Äquivalent — zwei Ebenen, in Render-Reihenfolge:
```
ByLayer (Ebenen-Default) → Element-Style (styleId) → Override-Regeln → gerendert
```
```ts
// Effektiver Stil eines Elements (resolve beim Rendern, nie persistiert)
interface EffectiveStyle { color; lineStyleId; hatchId?; }
function resolveStyle(project, el, doc): EffectiveStyle {
// 1) Default aus LayerCategory(categoryCode)
// 2) überschrieben durch el.styleId (Element-Override, optional)
// 3) überschrieben durch passende Override-Regeln (§4)
}
```
- **Wall-/Opening-Stil-Kataloge** (Presets, ROADMAP §11): benannte Sätze von
Default-Werten (Wandtyp + Farbe + lw; Öffnung mit Rahmen/Sims/…). 1-Klick-
Anwendung, globaler Stilwechsel. Speicherung: pro Projekt + cross-Projekt
(LocalStorage), Seed wie DOSSIER `_OEFF_DEFAULT_STYLES` (elements.md §2.1).
- **LoD-bewusste Stil-UI** (DOSSIER ROADMAP §11): das Stil-Panel zeigt nur
passende Controls je Geometrietyp (keine Füll-Optionen bei einer 3D-/Linien-
Auswahl). `panels/StylePanel.tsx` schaltet Felder nach `selection`-Typ.
- **Pipette:** Stil/Typ von einem Element auf ein anderes übernehmen (DOSSIER
`cmd/pipette`).
---
## 4. Regelbasierte Overrides (Engine)
Port `overrides.py` (ArchiCAD Graphical Overrides / Vectorworks
Datenvisualisierung). Im Browser **viel einfacher**, weil Overrides reine
**Render-Transformationen** sind — kein UserString-Backup/Restore nötig (DOSSIER
musste Originalwerte sichern, weil es echte Rhino-Objekte mutierte; wir mutieren
nichts).
```ts
interface OverrideConfig { enabled: boolean; rules: OverrideRule[]; activePresetId?: string; }
interface OverrideRule {
id; name; enabled: boolean;
conditions: Condition[]; conditionsLogic: "and" | "or";
actions: { color?: string; lineWeight?: number; lineStyleId?: string;
hatchId?: string; hatchScale?: number };
}
interface Condition {
type: "category" | "userField" | "name" | "elementType"; // ≙ layer_name/user_string/object_name
operator: "equals"|"notEquals"|"contains"|"startsWith"|"endsWith";
value: string;
key?: string; // nur für userField (z.B. "sia")
}
```
**Auswertung** (Port `_compose_overrides`):
```ts
function composeOverrides(el, project, cfg): Partial<Actions> {
// additive: Actions aller matchenden, aktiven Regeln kombinieren;
// bei Konflikt für dieselbe Property gewinnt die Regel WEITER OBEN (kleinerer Index).
}
```
- **Anwendung:** `resolveStyle` (§3) ruft `composeOverrides` — Override liegt
über Element-Style. Reines Read beim Rendern → kein `apply_all`/`restore_all`,
kein Backup, **keine reversibilität nötig**. Toggle `enabled` rendert neu.
- **Live:** Da abgeleitet, schlägt jede Modell-/Regel-Änderung sofort durch (kein
`install_listeners`/`AddRhinoObject`-Hook wie DOSSIER).
- **Presets & Templates** (cross-Projekt): Preset = Satz Regeln, Template =
einzelne Regel; LocalStorage statt `~/Library/.../override_presets.json`
(`save_preset`/`load_preset`/`list_rule_templates``resources/overridePresets.ts`).
- **SIA-416-Preset** (elements.md §7): vier Regeln `userField sia == HNF|NNF|VF|FF`
→ Farbe + Solid-Hatch, Port `_build_sia_preset_rules`. Aktivieren = Preset
`activePresetId` setzen.
```ts
// resources/overrides.ts
function composeOverrides(el, project, cfg): Partial<OverrideAction>
function setActivePreset(project, presetId): Project // immutabel
const PRESETS_NS = "cad.presets.overrides"; // LocalStorage
```
---
## 5. Symbol-Bibliothek
Port `library.py` + `cmd/symbol`. Wiederverwendbare 2D-Symbole (Möbel, Sanitär,
Bäume, Nordpfeil, Pflanzen) für den Plan.
```ts
interface Symbol {
id; name; category: string; // "furniture" | "sanitary" | "vegetation" | "annotation"
svgPath: string; // Pfad-/Gruppen-Markup im Symbol-Koordinatensystem (m)
defaultScale: number;
}
interface SymbolInstance extends ElementBase { // type:"draw2d", subtype:"symbol"
symbolId: string; at: Vec2; scale: number; angle: number;
}
```
- **Speicherung:** mitgelieferte Symbole als statische Assets (SVG); Nutzer-
Symbole im Projekt + cross-Projekt (LocalStorage). DOSSIER nutzt Block-
Definitionen; bei uns SVG-Definition + Instanz-Transform (`<use>`-artig).
- **Picker:** `panels/SymbolPicker.tsx` (≙ DOSSIER `SymbolPicker.jsx`) — Grid mit
Vorschau, Drag in den Plan; Instanz auf Ebene `60 Plangrafik` (bzw. `22 Möbel`).
- **Render:** `Primitive{ kind:"symbol", ... }` → SVG `<g transform>` mit dem
Symbol-Markup (plans-output.md §9).
---
## 6. Text & Rich-Text-Annotationen
Port `text_create.py` + `text_editor.py`. Formatierte Beschriftungen auf Canvas.
```ts
interface TextElement extends ElementBase { // type:"draw2d", subtype:"text"
at: Vec2; runs: RichRun[];
heightMm: number; // Paper-mm (rendern × Massstab) ODER Modell-m
heightMode: "paper" | "model"; // DOSSIER raum_txt_modus fix|masstab
font: string; align: "left"|"mid"|"right"; angle: number;
mask?: boolean; // Hintergrund-Maskierung (verdeckt Linien darunter)
frame?: boolean; // Rahmen um den Text
}
interface RichRun {
text: string;
bold?; italic?; super?; sub?; // Hoch-/Tiefstellung (Maß-Indizes)
}
interface TextStyle { id; name; font; heightMm; bold; italic; } // Text-Presets
```
- **Render:** `<text>` mit `<tspan>` pro Run; `super/sub` via `baseline-shift` +
kleinerer `font-size`; `mask` via weißem `<rect>` darunter; `frame` via `<rect>`.
- **Editor:** Inline-Rich-Text-Editor (contentEditable oder leichter Custom-Editor)
`RichRun[]`. Fonts aus einer kuratierten Web-Font-Liste + System-Fonts
(DOSSIER `_list_system_fonts` mit Preferred-Liste DM Mono/Krungthep/…); im
Browser via `document.fonts` / `queryLocalFonts()` (wo verfügbar) + gebündelte
Web-Fonts.
- **Massstabsbezug:** `heightMode:"paper"` → Texthöhe in mm, gerendert × Massstab
(plans-output.md §3) — Beschriftung bleibt bei jedem Massstab lesbar.
---
## 7. Detailgrad (Level of Detail)
Querschnittsthema (ROADMAP §2b „Modelldarstellungen"). Drei Stufen, Dokument-/
Snapshot-weiter Override mit Per-Element-Ausnahme — DOSSIER
`darstellung`/`aktive_darstellung`.
```ts
type DetailLevel = "coarse" | "medium" | "fine"; // einfach | standard | detail
// Auflösung (Port _resolve_oeff_darstellung):
function resolveDetail(el, doc): DetailLevel {
const v = el.detailLevel ?? "auto";
return v === "auto" ? doc.detailLevel : v; // doc-Level hat IMMER konkreten Wert
}
```
- **Dokument-Ebene:** `Project.detailLevel` (Default `coarse`/einfach = 1:100,
DOSSIER `_DARSTELLUNG_DEFAULT_GLOBAL`). In der TopBar global umschaltbar; ein
ViewSnapshot kann ihn pro Ansicht überschreiben (plans-output.md §5).
- **Wirkung:** jedes Bauteil-`generatePlan`/`build3d` liest den aufgelösten LoD und
zeichnet entsprechend (elements.md: Tür coarse=Lücke, fine=Glas/Schwenkbogen/
Sims). Kritisch für Mixed-Scale-Pläne (1:50 Detail neben 1:200 Übersicht).
- **Schnitt-Schraffur** koppelt an LoD: grob ggf. nur Umriss, fein voll schraffiert.
---
## 8. Section-Style (3D-Schnittflächen)
Port DOSSIER `_apply_section_style` (`layer_builder.py`). Wo die Schnittebene ein
Bauteil durchschneidet: Schnittfläche bekommt die **Component-Schraffur**, die
Schnittkante einen dicken Rand, optional eine Silhouette.
```ts
interface SectionStyle { // pro Component (oder Ebene) ableitbar
hatchId?: string; hatchScale; hatchAngle;
boundaryShow: boolean; boundaryLineStyleId; boundaryWidthScale;
fillBackground: boolean;
}
```
- **3D-Viewport:** Three.js hat keinen nativen „Schnittflächen-Cap". Cap-Geometrie
selbst erzeugen: Schnittpolygon der Cut-Plane mit den Breps → Fläche mit
Hatch-Material (oder Stencil-Cap-Technik). Phase 4.
- **2D-Schnitt (SVG):** `generateSection` (plans-output.md §4) liefert `cutFaces`
→ mit `SectionStyle.hatchId` füllen. Das ist der Hauptweg; der 3D-Cap ist Bonus.
---
## 9. Was Browser hier einfacher macht (vs. DOSSIER)
| DOSSIER-Aufwand | entfällt im Browser, weil … |
|---|---|
| UserString-Backup/Restore bei Overrides (`_backup_original`/`_restore_original`) | Overrides sind reine Render-Reads — nichts wird mutiert |
| Hatch-Curve-Link über Sticky (`gestaltung_curve_hatch`, Pending-TTL) | Schraffur ist eine Eigenschaft des Polygons, kein separates Objekt |
| Plotweight-Welt↔Bildschirm-Rescaling (`write/read_plotweight`) | Strichstärke ist in mm im SVG-Paper-Space (plans-output.md §3.2) |
| `install_listeners` für Live-Override-Reapply | reaktiver Store re-rendert automatisch |
| SectionStyle-API-Reflection über Rhino-Versionen | wir definieren das Rendering selbst (SVG/Three) |
| Cross-doc Presets als Dateien im User-Home | LocalStorage + Export/Import |
---
## 10. Umsetzungs-Reihenfolge (verweist auf ROADMAP-Phasen)
1. **Phase 1:** Component/Hatch/Line-Manager (§1) + `resolveStyle` (§3) +
LoD-Grundgerüst (§7) + LoD-bewusste Stil-UI.
2. **Phase 2:** Stil-Kataloge (Wände/Öffnungen), Material-Seeds mit `joinPriority`.
3. **Phase 3:** Overrides-Engine + SIA-Preset (§4), Symbol-Bibliothek (§5),
Rich-Text (§6), PBR-Material-Bibliothek; massstabsabhängige Hatch-/Linetype-
Skalierung (plans-output.md §3.2).
4. **Phase 4:** Section-Style 3D-Cap (§8).