Files
Gartenvisualisierung/README.md
T
admGitAICDSandClaude Opus 5 fefa8e8c9a
Deploy App / deploy (push) Successful in 28s
Memo vom 11.09.2026 umgesetzt
Haus
- Oeffnungen aus den Fassadenplaenen von EHLE abgegriffen. Zum Garten steht
  im Erdgeschoss eine einzige, mittige Fensterfront (x -8.82 bis -4.67),
  daneben das Lamellenband vor dem gedeckten Sitzplatz; im Obergeschoss zwei
  Fenster mit zwei schmalen Lamellen dazwischen.
- Der gedeckte Sitzplatz ist nur noch gegen Westen offen, so wie es die
  Suedwestfassade als "Aussensitzplatz" zeigt. Gegen den Garten steht dort
  eine geschlossene Wand - bisher schaute man durch die Fassade hinein.
- Hauszugang an der Nordostfassade statt an der Nordwestfassade: Tuerfluegel
  und feststehende Verglasung, zusammen 3.15 m, darueber das Vordach. Das
  Vordach kennt jetzt seine Fassadenseite.
- Die Fassadennamen im Datenmodell waren vertauscht; jetzt mit Tabelle im
  README festgehalten.

Garten
- Sichtschutz: die gruenen Punkte im Kiesbett sind Ziergraeser, keine
  Polster. Neue Halmgeometrie - schmales, sich verjuengendes Band, das nach
  oben ueberhaengt, rund 1.10 m. Die Bodenleuchten sind jetzt Strahler und
  leuchten Stelen und Graeser von unten an.
- Baenkli: die Fuesse standen quer zur Sitzplatte, weil der Versatz in
  Welt-Z statt entlang der gedrehten Achse gerechnet wurde.
- Obere Ebene bis an das Kraeuterbeet eben auf -0.02. Zwischen Grillfeld und
  Kraeuterbeet stieg der Rasen um gut 30 cm an.
- Unter dem Spielhaus steht der Betonklotz aus dem Plan (graues Rechteck
  1.01 x 2.70 m); die offene Plattform daneben bleibt auf Stuetzen.
- Aus dem Gartenhaus wird ein Gartenschrank: 1.80 x 0.50 m, 2.00 m hoch, mit
  dem Ruecken in der Ecke und den Doppeltueren zum Garten.
- Neben dem Gelaender der Osttreppe lag Rasen; dort liegt jetzt derselbe
  Belag wie auf dem Weg, bis an den Treppenkopf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:01:39 +02:00

332 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gartenvisualisierung Egglihaldenstrasse 9a
Begehbare 3D-Visualisierung des geplanten Gartens von Jasmin und Damian
Kellenberger, Egglihaldenstrasse 9a, 9030 Abtwil SG.
Live: **https://garten.aicds.ch**
Die gesamte Geometrie ist **massstäblich aus dem Gartenplan** abgeleitet – nicht
nachmodelliert. Ein Node-Werkzeug liest die Vektordaten direkt aus dem Plan-PDF,
rechnet sie in Meter um und erzeugt daraus das Szenenmodell.
---
## Grundlagen
| Quelle | Inhalt |
|---|---|
| `Info Dateien/Gartenplan 1 …pdf` | Garten Design AG, 14.08.2026, Massstab 1:100, A1 |
| `Info Dateien/20210427 - Projektpläne Haus 9a.pdf` | EHLE Architekten AG, Grundrisse, Fassaden, Umgebung |
| `Info Dateien/WhatsApp Image …jpeg` | Fotos des Ist-Zustands (Fassade, Beläge, Nachbarschaft) |
Die DWG wird nicht benötigt – das PDF enthält dieselbe Vektorgeometrie.
### Koordinatensystem
Alles rechnet im **Site-Frame**, in Metern:
- **X** – Hausachse ENE, Peilung 61.1615° gegen Nord
- **Z** – senkrecht dazu, SSE, Peilung 151.1615°
- **Y** – Höhe über dem Fixpunkt `0.00` = **651.17 m ü. M.** (OK Fertigboden EG)
Der Nordpfeil des Plans zeigt exakt zur Blattoberkante, das Zeichnungsraster
liegt um −28.8385° gedreht dazu. Der Massstab ist über das Plattenraster
verifiziert: die Keramikplatten messen sich auf 0.600 m genau.
### Höhen
| Bezug | relativ | absolut |
|---|---|---|
| Feuerwehrzufahrt, Nordwestecke | −2.08 | 648.09 m |
| Nachbargarten Zollet (Haus 9, OK FB EG) | −1.70 | 649.47 m |
| Hauptsitzplatz Travertin (untere Ebene) | −0.33 | 650.84 m |
| Rasen und Kiesfeld (obere Ebene) | −0.02 | 651.15 m |
| Fixpunkt Hauseingang | 0.00 | 651.17 m |
| Ostseite beim Apfelbaum | +0.63 | 651.80 m |
| OK Fertigboden Obergeschoss | +2.90 | 654.07 m |
| OK Attika Haus 9a | +6.15 | 657.32 m |
---
## Datenpipeline
```bash
npm run data # extract-plan + build-site
```
1. **`tools/extract-plan.mjs`** – öffnet das Plan-PDF mit MuPDF (WASM) und
protokolliert alle Zeichenoperationen mit. Ergebnis: rund 5400 Polygone,
234 Pflanzenkreise, 229 Beschriftungen und 46 Höhenkoten, transformiert in
den Site-Frame. → `tools/out/plan-raw.json`
2. **`tools/authoring.mjs`** – handgepflegte Ergänzungen: Zuordnung der
Planfarben zu Belagsarten, Mauerachsen und -höhen, Gebäudekubatur,
Geländezonen, Standorte. Alles kommentiert und auf Plan oder
Architekturpläne zurückgeführt.
3. **`tools/build-site.mjs`** – klassiert die Flächen, leitet Pflanzen und
Leuchten ab, interpoliert das Geländeraster (0.25 m) aus den Höhenkoten je
Zone und schreibt `public/data/site.json` (rund 110 kB).
Zur Kontrolle: `node tools/debug-svg.mjs` zeichnet die extrahierte Geometrie als
SVG-Draufsicht, `node tools/inspect-colors.mjs` listet die Planfarben mit den
nächstgelegenen Beschriftungen.
### Die beiden Ebenen
Der Garten liegt auf zwei Niveaus, und der Sprung dazwischen ist ein Bauteil:
| | |
|---|---|
| Untere Ebene – Hauptsitzplatz Travertin | **−0.33 m** |
| Travertinsitzmauer, Ansichtshöhe | **0.31 m** |
| Obere Ebene – Rasen, Kiesfeld, Flammengrill | **−0.02 m** |
| Gedeckter Sitzplatz | −0.02 m |
| Zwei schwebende Travertinstufen dazwischen | −0.17 / −0.31 m |
Gegen Osten steigt der Garten laut Plan weiter an (0.26 bis 0.63 m); dort bleibt
die aus den Koten ausgeglichene Ebene stehen. Die beiden Hauptniveaus stehen als
`LEVELS` in `tools/authoring.mjs` und werden nicht geschätzt – Flächen, deren
Schwerpunkt darin liegt, bekommen exakt dieses Niveau.
### Der Sichtschutz
Er ist bewusst **nicht durchgehend**. Der Plan zeichnet ihn als Wechsel von
aufrechten **Travertinstelen** (50 × 6 cm, in Dreiergruppen) und
**Eibengruppen**, alle zusammen in einem **Dekorkiesbett**. Die 28 Stelen kommen
mit Position und Ausrichtung direkt aus dem Plan; die Eiben werden nur innerhalb
einer Gruppe zu einem Heckenkörper verkettet, damit die Lücken für die Stelen
offen bleiben.
Höhen nach Abschirmrichtung:
- gegen den Südnachbarn, entlang der langen Fläche: **1.80 m**
- gegen Zollet und die Feuerwehrzufahrt, untere Ebene: **1.50 m**
### Ebene Beläge statt gewelltem Gelände
Gebaute Flächen sind eben. Wo kein festes Niveau vorgegeben ist, wird aus den
Höhenkoten eine Ausgleichsebene bestimmt – Gefälle auf 4 % begrenzt, am
Schwerpunkt der Koten aufgehängt. Jede Kote gehört dabei zu genau **einer**
Fläche, nämlich der kleinsten, die sie enthält; sonst zieht etwa die Kote einer
Stufe die darunterliegende Terrasse mit hoch und aus zwei sauberen Ebenen wird
eine schiefe.
Das Geländeraster wird in diesen Feldern auf die Ebene gezwungen und dort
**nicht** geglättet. Im Rendering bekommt jeder Belag eine senkrechte Sichtkante
zum angrenzenden Gelände – erst dadurch wirkt eine Terrasse gebaut und nicht
aufgemalt. Wachsende Flächen (Rasen, Schotterwiese, Beet) folgen dem Gelände.
### Bauteile statt hochgezogener Flächen
Der Plan nennt vier verschiedene Konstruktionen, und sie sehen auch verschieden
aus (`src/scene/walls.ts`):
- **Travertinmauer / Sitzmauern** – Schichtmauerwerk aus einzelnen Blöcken mit
versetzten Stossfugen und überstehender Abdeckplatte.
- **Winkelelemente aus Beton** – Fertigteile von 1 m Breite, einzeln versetzt,
mit Stossfugen, nach unten dicker werdendem Stiel und Fussplatte.
- **Betonmauer Nachbar** – Ortbeton in Schaltafelfeldern mit abgefaster Krone.
- **Lamellenwand** – senkrechte Bretter im Rhythmus des Plans (10 cm breit,
35 cm tief, 90 cm Achsabstand) vor dem gedeckten Sitzplatz.
- **Holzscheiterwand** – Stahlrahmen, gefüllt mit gestapelten Scheitern.
- **Treppen mit Rampe, Geländer und Rinnen** – die linearen Bauteile aus
`LINEAR` in `tools/authoring.mjs`, gerendert in `src/scene/structures.ts`:
Blockstufen aus Travertin, Stahlrohrgeländer mit Hand- und Knieholm entlang
des Gefälles, Entwässerungsrinnen mit bündigem Rost.
- **Gartentor** – Stabgitterflügel von 100 cm mit zwei Pfosten und senkrechten
Stäben, nicht als geschlossenes Blatt.
- **Spielhaus** – ein Stelzenhaus. Der Plan zeichnet zwei Rechtecke: das Podest
1.81 × 2.70 m mit der Kote 1.47 (rund 0.9 m über dem Gelände) und darauf an
der Ostseite die Hütte 1.01 × 2.70 m. Der westliche Teil bleibt offene
Plattform mit Brüstung und Leiter.
Die **Eibengruppen** entstehen nicht aus Einzelkugeln: die Pflanzstandorte des
Plans werden gruppenweise verkettet, begradigt, an scharfen Knicken getrennt und
als geschnittener Heckenkörper mit gerundetem Querschnitt aufgezogen – siehe
Abschnitt „Der Sichtschutz". Ebenso die Ligusterhecke (170 cm, 75 cm breit).
Die **Travertinplatten 40 × 60 × 3 cm** liegen laut Plan einzeln im Dekorkies –
30 Platten, exakt an den Planpositionen, als erhabene Platten auf dem Kiesbett.
Die **Rasenrandplatten** sind im Plan ein durchgehendes Band von 6 cm Breite
zwischen Rasen und Kiesbett des Sichtschutzes. Sie werden nicht als Belag,
sondern als hochkant stehende Randplatte mit sichtbarer Kante aufgebaut
(Belagstyp `randplatte`).
Am **Gartentor** liegen ebenfalls zwei schwebende Travertinstufen: der Plan
zeichnet dort ein Feld von 0.80 × 1.60 m mit den Koten −0.13 (Podest am Tor)
und −0.32 (Anschluss an den Hauptsitzplatz).
Der Plan zeigt **zwei** orange markierte Felder von 1.50 × 0.46 m in der
Sichtschutzlinie; die Beschriftung „Holzscheiterwand 150 cm x 200 cm" steht
zwischen ihnen und gilt für beide.
Materialien und Ausbildung folgen den **Referenzbildern im Plan-PDF**: heller,
geschichteter Travertin für Mauern, Stelen und Platten, die schwebenden Stufen
als auskragende Platten mit Schattenfuge, der Flammengrill als anthrazitfarbener
Stahlkubus mit Holzfach und runder Feueröffnung. Die Bilder lassen sich mit
`node tools/plan-bilder.mjs` aus dem PDF ziehen.
### Ebene Ebenen, geneigte Wege
`LEVELS` in `tools/authoring.mjs` gibt einzelnen Bereichen ihre Höhe fest vor,
statt sie aus streuenden Koten zu schätzen. Zwei Formen:
- **`level` + `flatten`** – waagrecht und zwingend. Die obere Ebene mit Rasen
und Kiesfeld liegt eben auf −0.02. Ohne diese Vorgabe zieht die Interpolation
den Rasen hügelig hoch, weil die Koten 0.09 bis 0.26 schon im angehobenen
Kiesbett des Sichtschutzes liegen und nicht mehr im Rasen.
- **`plane`** – eine geneigte Ebene, für die Wege. Sie sind Rampen, keine
waagrechten Felder: Ostweg rund 12 %, Weg an der Südgrenze rund 3 %,
Feuerwehrzufahrt rund 13.6 % (Plankoten −2.08 und −1.08). Der
Architekturplan (`Erdgeschoss mit Umgebung 1:100`) beschriftet dieselben
Gefälle mit „Steigung 9.9 %“ und „Steigung 13.0 %“.
Ausserhalb aller Zonen wird die Interpolation der nächstgelegenen Zone
fortgesetzt, nicht ihr konstanter Ersatzwert – sonst steht der Belag als
schwebende Platte über einer Geländekante.
### Das Haus nach den Fassadenplänen
Die Öffnungen sind aus den Fassadenplänen von EHLE Architekten (1:100)
abgegriffen. Die Pläne benennen die Fassaden nach Himmelsrichtung, das
Datenmodell nach Achsrichtung:
| Plan | Site | Lage |
| --- | --- | --- |
| Südostfassade | `south`, z +0.96 | lang, zum Garten |
| Nordostfassade | `east`, x +0.28 | kurz, Hauszugang |
| Südwestfassade | `west`, x −13.82 | kurz, zur Terrasse |
| Nordwestfassade | `north`, z −6.87 | lang, zum Nachbarn – im Plansatz nicht gezeichnet, bleibt geschlossen |
Zum Garten steht im Erdgeschoss **eine** mittige Fensterfront, daneben das
Lamellenband vor dem gedeckten Sitzplatz; im Obergeschoss zwei Fenster mit zwei
schmalen Lamellen dazwischen. Der gedeckte Sitzplatz ist **nur gegen Westen
offen** („Aussensitzplatz“ in der Südwestfassade) – gegen den Garten steht dort
eine geschlossene Wand. Ohne sie schaut man von aussen durch die Fassade in den
Sitzplatz hinein.
### Treppen begehen und springen
Stufen und Treppen stecken nicht im Höhenraster, sondern sind eigene Körper.
Damit man sie begehen kann, rechnet `main.ts` ihre Trittflächen in die
Höhenabfrage des Rundgangs ein (`treppeTritte` in `src/scene/structures.ts`);
die schwebenden Stufen sind entsprechend **kein** Hindernis mehr. Dazu kommt
der Sprung mit der Leertaste – rund 55 cm, genug für jede Mauerkrone.
### Warum Zonen statt eines einzigen Geländes
Das Grundstück fällt über rund 3.5 m ab, die Sprünge liegen aber nicht im
Gelände, sondern in den Stützbauten: Travertinmauer zum Nachbargrundstück,
Winkelelemente aus Beton entlang der Feuerwehrzufahrt, Betonmauer im Süden.
Deshalb wird je Zone getrennt interpoliert – über die Zonengrenze hinweg nie.
Die Mauern erhalten ihr Kronen- und Fussprofil aus dem Gelände beider Seiten.
---
## Anwendung
```bash
npm install
npm run dev # http://localhost:5173
npm run build # dist/, rund 640 kB
```
Stack: Vite + TypeScript + three.js. **Keine externen Assets** – sämtliche
Texturen (Travertin, Keramik, Tegula, Rasen, Kies, Beton, Holzschalung,
Scheiterwand) werden zur Laufzeit prozedural auf ein Canvas gezeichnet. Die UVs
sind weltbezogen, damit die Plattenraster über Flächengrenzen hinweg durchlaufen
– so wie auf dem Plan.
### Bedienung
| | |
|---|---|
| Rundgang | Klicken, dann `W` `A` `S` `D`, `Shift` zum Rennen, `Esc` gibt die Maus frei |
| Handy | links Joystick, rechts wischen zum Umsehen |
| Übersicht | ziehen zum Drehen, Mausrad zum Zoomen |
| Tageszeit | Regler oben rechts, Sonnenstand astronomisch für Abtwil SG gerechnet |
Der Rundgang folgt dem Gelände, steigt Stufen bis 45 cm und wird von Mauern,
Gebäuden, Hecken und Objekten aufgehalten. Ab Dämmerung schalten die 17 Leuchten
des Plans automatisch ein.
### Aufbau
```
src/
site.ts Datenmodell und Höhen-Sampler
scene/textures.ts prozedurale Beläge und Materialien
scene/geometry.ts Polygone auf das Gelände legen, Mauern extrudieren
scene/terrain.ts Geländeraster und Umland
scene/surfaces.ts Beläge
scene/walls.ts Mauern als Bauteile: Schichtmauerwerk, Winkelelemente, Lamellen
scene/structures.ts Stelen, Objekte, Leuchten
scene/vegetation.ts Gehölze, Hecken, Gräser, Stauden
scene/building.ts Wohnhaus 9a und Nachbargebäude
scene/environment.ts Himmel, Sonnenstand, Licht
controls/walker.ts Begehen mit Kollision und Geländeführung
ui/hud.ts Bedienoberfläche
```
---
## Deployment
Push auf `main` löst den Gitea-Workflow aus; die App landet unter
`/opt/aicds/apps/Gartenvisualisierung/` und wird per `docker compose` gebaut.
Traefik-Router `garten`, Host `garten.aicds.ch`, Netzwerk `agent-net`.
```bash
docker compose config # Prüfung vor dem Push
docker compose up -d --build
```
Das Image enthält nur `dist/` hinter nginx – die Planunterlagen und Fotos aus
`Info Dateien/` werden über `.dockerignore` ausgeschlossen und gelangen nie in
den Webcontainer.
---
## Was bewusst interpretiert ist
Der Plan ist ein Situationsplan; einiges steht nur als Beschriftung darin. Diese
Annahmen stecken in `tools/authoring.mjs` und sind dort einzeln kommentiert:
- **Mauerhöhen** aus den Höhenkoten beidseits abgeleitet; die gelbe Kote `25`
bei der Sitzmauer als 25 cm Aufkantung gelesen, `35` bei den Heidelbeeren
analog.
- **Lamellenwand zum Sitzen** als senkrechte Holzlamellen im Planrhythmus
(10 cm breit, 35 cm tief, rund 90 cm Achsabstand), passend zum Foto des
Ist-Zustands.
- **Wohnhaus** in Kubatur und Öffnungen nach Gartenplan und Fassadenplänen,
Materialisierung nach den Fotos: vertikale Holzschalung graubraun, heller
Betonsockel, Flachdach mit extensiver Begrünung.
- **Nachbarhaus Zollet** als einfaches Kontextvolumen, 1.70 m tiefer liegend.
- **Solitärgehölze** mit den Kronenradien des Plans; Wuchshöhen als
Erfahrungswerte.
- **Gartenhaus, Spielhaus, Flammengrill, Bänkli** in Lage und Grundfläche nach
Plan, in der Ausformulierung frei.
---
## Stolperstein beim Deployment
Der erste Rollout lieferte trotz laufendem Container und korrekten Traefik-Labels
ein 404 vom Proxy. Ursache war der HEALTHCHECK im Dockerfile: busybox-`wget`
löst `localhost` zuerst nach `::1` auf, nginx lauschte aber nur auf IPv4 – der
Check schlug dauerhaft fehl und der Container blieb auf `health: starting`.
**Traefik nimmt Container, die nicht `healthy` sind, gar nicht in die
Routing-Tabelle auf.** Deshalb entstand kein Router, kein Zertifikat und damit
ein 404 mit dem Traefik-Default-Zertifikat – ein Fehlerbild, das leicht nach
einem Proxy- oder DNS-Problem aussieht.
Behoben durch `http://127.0.0.1/` im Healthcheck und ein zusätzliches
`listen [::]:80;` in der nginx-Konfiguration. Der Deploy-Workflow wartet
seither auf `healthy` und prüft den Router, bevor er den Lauf abschliesst.
Merksatz für weitere AICDS-Apps: ein Container mit fehlschlagendem Healthcheck
wird von Traefik stillschweigend ignoriert.