Compare commits
231 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9133c0961d | |||
| b640bbe606 | |||
| 0b56d777bc | |||
| 1c09b6e7c2 | |||
| 4b77047916 | |||
| 670823e98d | |||
| 71e75b99f0 | |||
| 3a2cef3886 | |||
| d01cb82a89 | |||
| 8d688b982e | |||
| 5b60136a19 | |||
| 39ddd9b501 | |||
| 9c911e6d43 | |||
| cdc71621c4 | |||
| b9b75f7ad1 | |||
| e82d4b084c | |||
| 12441d36fb | |||
| 2c8ad8fe34 | |||
| 3697b7e2d8 | |||
| a750e87517 | |||
| 456bf29984 | |||
| c40764a25f | |||
| ff68ec3a5c | |||
| 72a40ccaaf | |||
| fe22cbf0df | |||
| 4e3b074f5c | |||
| 033cabe80f | |||
| 456ebc8098 | |||
| 13c3294f4d | |||
| 85011cb8ef | |||
| 3a206060e3 | |||
| 9d6e86d4c0 | |||
| ace0dc62a8 | |||
| fe9f396750 | |||
| 00733d8180 | |||
| 077e774303 | |||
| bd2b12bfb7 | |||
| 5130a050ff | |||
| e454eab1a8 | |||
| 67195d7dd5 | |||
| dd76ec89fd | |||
| 376aa67566 | |||
| 4ac99d37cb | |||
| 45e19b7293 | |||
| c794feef9e | |||
| c5b5ca600c | |||
| 4ef73c9198 | |||
| 4b93ac9cbb | |||
| 05bc5aa6e1 | |||
| d36c84689f | |||
| 8f9123ce0d | |||
| a44572bf7b | |||
| 79ea6b0b3b | |||
| 83da278ed2 | |||
| 33c2c6c22e | |||
| b7551d4930 | |||
| 25daec6be9 | |||
| 5229174551 | |||
| df0e5e432a | |||
| 715e950d7c | |||
| dbf78a9d75 | |||
| 26fbd11b73 | |||
| 6e5998ce72 | |||
| b65c676643 | |||
| ae18766b01 | |||
| c7128fe669 | |||
| b37c9f467b | |||
| 08b0b23a68 | |||
| 4a26c34db1 | |||
| 903dc19cec | |||
| 09c5178c85 | |||
| 8750c8f547 | |||
| 028a3637b4 | |||
| 74eecf7a73 | |||
| 0ca3b1dd57 | |||
| ecefe61611 | |||
| 2d96a864da | |||
| dec431579e | |||
| 9520191bde | |||
| 9136cad0ee | |||
| c7e080f639 | |||
| aa4205bf0b | |||
| 9a80d9cf42 | |||
| ea86cf7456 | |||
| 05cfade483 | |||
| 87a30b7061 | |||
| 23dddf893e | |||
| 45ded83294 | |||
| 77f32f14ce | |||
| 07042ebc75 | |||
| ecf262ef07 | |||
| 9acce9d7cd | |||
| cf8b638f33 | |||
| 20cf870c4c | |||
| e095b51d15 | |||
| 65d6e843fb | |||
| c79d15dda4 | |||
| 6035b8a8c2 | |||
| 6c04935711 | |||
| 8c96a5dfb9 | |||
| 8547d38e9a | |||
| 41d8fafa4e | |||
| 0d3a0a081f | |||
| d1df9d4d5c | |||
| e11743d6e7 | |||
| c54a2f916c | |||
| fde27f6838 | |||
| 00c90857ad | |||
| 018fef56bf | |||
| 1db661f19a | |||
| 6ea562ceda | |||
| e137353b95 | |||
| 16d32223a4 | |||
| 8f4fac633f | |||
| d4065c5a41 | |||
| d0b94a75ec | |||
| 8b6b9291a1 | |||
| 7e8764b21b | |||
| efc9dd87ce | |||
| f724c088e0 | |||
| 44942e6976 | |||
| e0d71691e1 | |||
| 9f6d4e8858 | |||
| 19d002d403 | |||
| 2c9633438d | |||
| 19fcafbcab | |||
| 4aed168760 | |||
| 2eeafb9aca | |||
| 7d3e0943e1 | |||
| d0f26cd874 | |||
| 777e02c927 | |||
| c34b7e5771 | |||
| b55ab9eb8f | |||
| bd13495923 | |||
| 683242ba78 | |||
| e54bb318bb | |||
| a302d512d9 | |||
| f09ba0e49f | |||
| 07475cbe6c | |||
| 87ef7988d1 | |||
| f3966f99e9 | |||
| f3639cfb15 | |||
| 6288f755a8 | |||
| 724db0f9bf | |||
| c2d6893a59 | |||
| c6d2641664 | |||
| 3d4c4985a9 | |||
| 912f0bc16d | |||
| ac7af9b9ef | |||
| 400ec890b0 | |||
| 558a24cf98 | |||
| e29f5a2a7c | |||
| b03614c35b | |||
| 1086225f7b | |||
| ca1ed1b0d2 | |||
| 04c7334cc4 | |||
| 93b70fa95a | |||
| fae4f6fcb6 | |||
| d65d84183a | |||
| 1f6da09b2b | |||
| 5fd0161bc7 | |||
| f4e70902d0 | |||
| af0b044fca | |||
| 692dd3f719 | |||
| 4d721a62f7 | |||
| b028fc6b31 | |||
| 581ddccf1a | |||
| 1fd688896c | |||
| f9a5e763fd | |||
| ec2568276d | |||
| 2b6b99059d | |||
| 658536029b | |||
| 675c68de55 | |||
| e9dc423a12 | |||
| 6aaef8f46a | |||
| eaf6d8ed7b | |||
| d30f79e162 | |||
| bde1f3f8d1 | |||
| 2dd7fe339d | |||
| 3c4d983a7d | |||
| 094d8b4504 | |||
| 330c01bbbd | |||
| d2503c1191 | |||
| e3f460c9f8 | |||
| 28b5492def | |||
| 4a62dbf83c | |||
| 8a79f6cbaa | |||
| 9371b65b3b | |||
| e8f5e7272d | |||
| f95df75ea1 | |||
| 918f60c498 | |||
| 23410f9b9e | |||
| 98994c96aa | |||
| 0e484746de | |||
| 87d12b976f | |||
| 519735c782 | |||
| 47f1c24bb3 | |||
| 1bbcc7038f | |||
| e6466a5027 | |||
| 1ab8c7e496 | |||
| 0e839b50d6 | |||
| 623b243306 | |||
| 6a7bbeec2f | |||
| cbf54e6b65 | |||
| 543d06adb5 | |||
| a3b25da96d | |||
| 2a2b3b294c | |||
| 926dedca40 | |||
| 4311a7dfbd | |||
| 31ac91b2a7 | |||
| 0189eed5ac | |||
| 45f9d0c381 | |||
| faa84e98af | |||
| e82f0b6de1 | |||
| 128c04a5d0 | |||
| 19f99382f6 | |||
| ec181998ca | |||
| c400a96575 | |||
| b4c3c2de4a | |||
| 788f4d58ca | |||
| 229169cea1 | |||
| f4cd16b7ac | |||
| e7ea1eeddd | |||
| c96239a794 | |||
| 260320af2e | |||
| f08ef13fe8 | |||
| 3d2d4d6321 | |||
| cfe5249440 | |||
| b9731a4979 | |||
| ce3b594403 | |||
| 94a5af6b6f |
+1
-1
@@ -57,7 +57,7 @@ Rendern angewandt, nie in die Geometrie eingebacken.
|
||||
|
||||
## Arbeitsweise (für Beiträge)
|
||||
|
||||
- Substanzielle, mehrstufige Arbeit an **Subagenten** delegieren, wo möglich.
|
||||
- Substanzielle, mehrstufige Arbeit schrittweise in isolierten Schritten angehen.
|
||||
- Änderungen verifizieren: `npx tsc -b`, `npm run build`, und Screenshot via
|
||||
`node scripts/probe.mjs` (schreibt `scripts/probe.png`) bzw. `probe-ff*.mjs` für
|
||||
Firefox-Fälle. Screenshot ansehen und Geometrie visuell prüfen.
|
||||
|
||||
+65
-794
@@ -1,820 +1,91 @@
|
||||
# HANDOVER — Browser-BIM (cad), Standalone-Port von DOSSIER
|
||||
# HANDOVER — Stand 2026-07-04 (Schraffur/Linien-Epic + Folgethemen)
|
||||
|
||||
> Für die nächste Instanz. Stand: 2026-06-29. Lies zuerst
|
||||
> `CONVENTIONS.md`, `ROADMAP.md` und die Projektnotizen (siehe unten).
|
||||
Übergabe an eine frische Instanz. Alles unten Gelistete ist committet, sofern nicht anders vermerkt. Ältere Handover-Stände (< 2026-07-04) siehe git-Historie. Zuerst `CONVENTIONS.md` lesen.
|
||||
|
||||
## >>> COMMIT-REGEL (verbindlich, IMMER beachten) <<<
|
||||
> **➡️ Aufgaben-Queue steht in `PENDENZEN.md`** (Single Source of Truth, priorisierte Checkliste + Arbeitsprotokoll). HANDOVER = nur noch Kontext/Konventionen/Environment/Zielmodelle, KEIN Backlog mehr. Neue Pendenzen/Rückfragen dort eintragen, nicht hier.
|
||||
|
||||
Dieses Repo darf **keinerlei Hinweise auf KI-Werkzeuge** enthalten — weder im
|
||||
Code/Doku noch in der Git-Historie.
|
||||
## ⚠️ NACHTRAG 2026-07-04 abends — Unterbruch, 3 Slices verloren
|
||||
|
||||
- **Commit-Messages:** sachlich, in der Sprache des Projekts. **NIEMALS**
|
||||
`Co-Authored-By:`-Trailer, „Generated with …"-Zeilen, Tool-/Modellnamen oder
|
||||
sonstige Urheber-Hinweise auf einen Assistenten.
|
||||
- **Dateien/Kommentare:** keine Erwähnung von Assistenten, Modellen oder Agenten.
|
||||
Wer hier weiterarbeitet, schreibt so, als wäre es Handarbeit des Teams.
|
||||
- Vor jedem Push kurz prüfen: `git log` und `git diff` frei von solchen Spuren.
|
||||
> **STAND-UPDATE (Session 4, neues Gerät):** **Environment behoben** — Toolchain vorhanden (`node`/`npm`/`cargo` unter `/usr/bin`, `wasm-pack` via `npx` 0.15.0; es war die Flatpak-Sandbox); fehlendes Rust-Target per `sudo dnf install rust-std-static-wasm32-unknown-unknown` (+ lld) nachinstalliert; beide WASM-Engines gebaut → App läuft. **Slice 1 (Locked-Iso `cb8fae5`) + Slice 2 (Joins Phase 1c `c5a344d`) committet.** **Slice 3 (Öffnungen als Boolean-Löcher + Deckentrim-nur-3D) ✅ committet `1407c68`:** Fenster/Türen = 1 Wandkörper pro Schicht-Band mit rechteckigen `holes` (render3d stanzt sie per achsparalleler Rechteck-Gitter-Zerlegung + 4 Laibungsquads/Loch), Segment-Boxen weg; `holes`/`openings` schliessen sich im Emitter aus; Schnitt-Pfad (layered=false) segmentiert unverändert weiter. Deckentrim `trimWallTopForCeilings` gilt nur noch im 3D-Pfad, Schnitt-Pfad bekommt volle Wandhöhe. Alle Datei-Edits waren gelandet — unabhängig verifiziert: cargo test 56 (inkl. beider Pflicht-Tests versetzt-überlappende Fenster + Tür-Loch-bis-Boden), vitest 230, build:engine3d + tsc sauber, Trace clean. **Damit sind alle drei verlorenen 3b-Slices rekonstruiert.** **Memory-Direktiven `priority-based-joins.md` + `openings-as-booleans.md` wiederhergestellt.**
|
||||
>
|
||||
> **⚠️ PUSH BLOCKIERT:** keine Git-Credentials auf diesem Gerät (kein Helper, keine `~/.git-credentials`, kein SSH-Key; Remote HTTPS `git.openbureau.ch/karim/DOSSIER-STANDALONE`). `cb8fae5` + `c5a344d` (+ folgender Öffnungen-Commit) sind **lokal committet, NICHT gepusht** (`ahead of origin/master`). Sobald Gitea-PAT (repo-write) oder SSH-Key da ist: `git push origin master`. Bis dahin holt das andere Gerät die Arbeit nicht.
|
||||
>
|
||||
> Der folgende Block bleibt als historischer Kontext/Vorlage für den offenen Slice 3 stehen.
|
||||
|
||||
## >>> KOORDINATION & MEMORY (bei mehreren Instanzen/Agents) <<<
|
||||
Die Arbeit wurde unerwartet unterbrochen — mehrere Tasks liefen parallel. Diese Übergabe wurde nachträglich rekonstruiert. **Der tatsächliche HEAD-Commit ist `03f0c40`** (Nordstern-3D Live-Schnittebene) — alles danach existierte nur im Arbeitsbaum der alten Maschine und ist auf **diesem** Checkout (neues Gerät, `git status` sauber bis auf `package-lock.json`) **nicht vorhanden**. Diese drei Slices waren in echter Arbeit (nicht nur geplant) und gelten als verloren — neu beauftragen, nicht nur „prüfen ob gelandet":
|
||||
|
||||
**Mehrere Hauptinstanzen gleichzeitig:**
|
||||
- Git: nur **eine** Instanz committet/pusht auf `master`, ODER jede arbeitet auf
|
||||
eigenem Branch und merged kontrolliert. `App.tsx`/`types.ts` ist der serielle
|
||||
Flaschenhals — verschiedene Features ≠ dieselbe Datei.
|
||||
- Dieser HANDOVER ist der **Koordinationskanal** zwischen Instanzen (liegt im
|
||||
Repo, wird mitgepusht). Stand hier kurz festhalten, bevor du übergibst.
|
||||
1. **Locked-Iso-Fix** (`src/viewport/Wasm3DViewport.tsx`, `OrbitState` um `ortho: boolean` + `orthoHalfHeight: number` erweitern, `orbitCamera()`/Preset-Effect/Pan/Zoom entsprechend anpassen) — Bug: Iso-Ansicht kippt bei der ersten Kamerabewegung sofort in Perspektive statt orthografisch (parallel) zu bleiben. War fertig gebaut und quantitativ verifiziert (Parallelogramm-Kantenvergleich vor/nach Orbit, 225/225 Tests grün), aber **nicht mehr committet**, bevor der Unterbruch kam. Kleinster, unabhängigster der drei Fixes — zuerst neu machen.
|
||||
2. **Joins Phase 1c — Durchgangswand echt aufbrechen** (`spanCutouts` in `src/model/joins.ts` + `src/plan/generatePlan.ts` + Rust-Parität `src-tauri/geometry/src/lib.rs`): Nutzer-Befund nach Phase 1b war, dass der Innenputz der DURCHGANGSWAND am T-Stoss nur überdeckt wird (Zeichenreihenfolge), nicht geometrisch ausgeschnitten — Fugen-/Nahflächenlinien laufen noch durch den Backstein-Durchstoss, es „liest" noch nicht wie ein einziger Join. Das war die **explizite Priorität des Nutzers** für diese Session. Die Umsetzung war weit fortgeschritten (schrieb bereits in `joins.ts`), dann abgebrochen. **Nicht committet, nicht im Baum.** Vollständige Aufgabenbeschreibung mit allen Datei:Zeile-Ankern steht im Transkript bei „Agent:Phase 1c: Durchgangswand aufbrechen" — 1:1 als Vorlage für den Neu-Auftrag wiederverwenden.
|
||||
3. **Öffnungen als echte Boolean-Löcher** (`RWall.holes`, `src/plan/toWalls3d.ts`, `src-tauri/render3d/src/mesh.rs`, additiv `types.rs`): EIN Wandkörper mit rechteckigen Löchern statt der heutigen Pfeiler/Brüstung/Sturz-Ersatzkörper (sichtbare Segment-Nähte, keine echten Löcher). Pflicht-Testfall (Nutzer-Direktive, Kernmotivation des Slices): zwei Fenster in derselben Wand mit überlappendem Achsen-Intervall, aber unterschiedlicher Höhe (z. B. u=[1.0,2.0]/z=[0.9,2.1] und u=[1.5,2.5]/z=[0.3,0.7]) — die alte Segment-Zerlegung kann das nicht abbilden, echte Löcher schon. Zusatz-Auftrag im selben Slice: `trimWallTopForCeilings` (Phase-3-Deckentrim, Commit `e4b8df6`) darf **nur** im 3D-Viewer-Pfad (`layeredWalls:true`) gelten, **nicht** im Schnitt-Pfad (`layeredWalls:false`) — der Schnitt braucht die volle, ungetrimmte Wandhöhe für seine eigene schichtweise Prioritäts-Subtraktion (`subtractDominantBands`), sonst zeigt der Schnitt die Wände fälschlich abgeschnitten. Fortschritt bei Abbruch **unklar** (letztes Status-Update deutete auf „noch in der Kartierungsphase" hin) — vermutlich am wenigsten weit von den dreien; komplett neu beauftragen. Vollständiger Auftragstext im Transkript bei „Agent:Oeffnungen als Boolean-Loecher" + die zwei Zusatz-Direktiven (Decken-Trim-Fix, Pflicht-Testfall versetzte Fenster) kurz danach.
|
||||
|
||||
**Subagents:**
|
||||
- Bekommen **kein** Memory automatisch — nur was im Prompt steht. Regeln (v. a.
|
||||
die COMMIT-REGEL oben) explizit mitgeben, sonst kennt der Agent sie nicht.
|
||||
- Agents **nicht** committen und **nicht** ins Memory schreiben lassen. Sie
|
||||
liefern Diffs/Dateien/Ergebnisse zurück; die Hauptinstanz committet und pflegt
|
||||
das Gedächtnis.
|
||||
**Zwei Engine-Direktiven vom alten Stand fehlen** (`priority-based-joins.md`, `openings-as-booleans.md`): die in Punkt 2/3 beschriebenen Zielverhalten waren gesichert (u. a. „Trennlinien an Merge-Kontakten verschwinden immer", „Öffnungen dürfen nie wieder zu Segmenten vereinfacht werden"). Beim Neu-Beauftragen diese Direktiven wieder explizit festhalten.
|
||||
|
||||
**Memory (`~/.claude/...`, außerhalb des Repos — leakt nie hierher):**
|
||||
- In den Kontext geladen wird nur der schlanke **Index** (eine Zeile je Eintrag);
|
||||
einzelne Fakten erscheinen nur bei Relevanz. Größe ist daher selten ein Problem.
|
||||
- **Keine automatische Bereinigung.** Gepflegt wird beim Schreiben (Duplikate
|
||||
aktualisieren statt anlegen, Überholtes löschen) oder auf Ansage.
|
||||
- Nur **beständige** Fakten ablegen, ein Fakt pro Datei, Index-Zeile knapp. Bei
|
||||
parallelen Schreibvorgängen ist der Index (MEMORY.md) die Contention-Stelle —
|
||||
vor dem Edit frisch lesen (der „modified since read"-Guard verhindert blindes
|
||||
Überschreiben).
|
||||
**Environment-Lücke auf diesem Gerät:** `node`/`npm`/`cargo`/`wasm-pack` waren zum Zeitpunkt dieser Übergabe **nicht auf dem PATH** (geprüft: kein Treffer, auch nicht unter der VSCodium-Flatpak-Datenlage). **Vermutliche Ursache: VSCodium lief als Flatpak** — Flatpak-Sandboxing blendet system-installierte Toolchains (Node/Rust unter `/usr/...` oder `~/.cargo`) typischerweise aus, auch wenn sie auf dem Host tatsächlich installiert sind. Der Nutzer wechselt gerade auf **natives VSCodium** (kein Flatpak mehr) — das könnte die Lücke von selbst beheben, MUSS aber nach dem Wechsel neu geprüft werden (`which node npm cargo wasm-pack`), bevor man von "Toolchain fehlt" auf "Toolchain neu installieren" schließt. Erst wenn nach dem Wechsel auf natives VSCodium immer noch nichts gefunden wird, wirklich neu installieren (Node LTS + Rust via rustup + `wasm-pack`, `npm install`, einmal `cargo build --manifest-path src-tauri/render3d/Cargo.toml` durchlaufen lassen). Ohne Toolchain ist die in `CONVENTIONS.md` vorgeschriebene Verifikation (`tsc`/`vitest`/`cargo test`/`wasm-pack`) nicht möglich — das zuerst klären, bevor einer der drei Slices neu beauftragt wird, sonst baut ein Agent blind ohne Verifikation.
|
||||
|
||||
## >>> AUFGABE FÜR NEUE INSTANZ: 2D-Plan auf WebGL-GPU-Renderer (ZUERST) <<<
|
||||
**Aufräum-Hinweis:** `Edit MEMORY md Added 1.txt` (lag unversioniert im Repo-Root) und die lokale `package-lock.json`-Änderung waren dirty/untracked. Die txt-Datei gehört nicht ins Repo — außerhalb verschieben oder löschen. `package-lock.json` prüfen (`git diff package-lock.json`) und nur committen, falls die Dependency-Änderung beabsichtigt ist.
|
||||
|
||||
**Warum:** Tauri-Linux-Webview = **WebKitGTK**, das SVG/2D auf der **CPU (Cairo)** rastert.
|
||||
User-GPU (AMD RX 7800 XT) langweilt sich; bei **144 Hz** (~6,9 ms/Frame) ruckelt Pan/Zoom/
|
||||
**Objekt-Bewegen** sichtbar. three.js/WebGL (3D) läuft GPU-beschleunigt → NUR der 2D-SVG-Pfad
|
||||
ist CPU-gebunden. Entscheidung mit User: 2D-Plan **von SVG auf WebGL2 (GPU)** umbauen.
|
||||
### Empfohlene Reihenfolge zum Wiederaufsetzen
|
||||
1. Nach dem Wechsel auf natives VSCodium zuerst `which node npm cargo wasm-pack` prüfen — evtl. war es nur die Flatpak-Sandbox, die die Toolchain versteckt hat. Nur falls immer noch nichts gefunden wird: Node + Rust + wasm-pack installieren + `npm install`. Blocker für alles Weitere.
|
||||
2. Locked-Iso-Fix neu bauen (klein, unabhängig, Spezifikation im Transkript vollständig vorhanden).
|
||||
3. Joins Phase 1c (Nutzer-Priorität dieser Session) — Direktive zuerst wieder ins Memory schreiben, dann Agent mit dem Auftragstext aus dem Transkript beauftragen.
|
||||
4. Öffnungen als Boolean-Löcher + Deckentrim-nur-3D-Zusatz (dieselbe Rust-Crate wie Phase 1c — nacheinander, nicht parallel, wegen Datei-Overlap in `mesh.rs`/`toWalls3d.ts`).
|
||||
5. Repo aufräumen (txt-Datei raus, `package-lock.json` klären), danach diesen Abschnitt auf „erledigt" aktualisieren.
|
||||
|
||||
**Sofortlösung fürs Vorführen/Entwickeln — Chromium-App-Shell:** `npm run shell`
|
||||
(`scripts/chromium-shell.sh`) startet bei Bedarf den Vite-Dev-Server (Port 5187, Polling per
|
||||
`curl`) und öffnet ihn dann in einem randlosen Chromium-Fenster (`--app=…`, eigenes Profil unter
|
||||
`~/.cache/cad-chromium-shell`), statt im WebKitGTK-Webview von `tauri:dev`. Chromium rendert
|
||||
dieselbe Seite GPU-beschleunigt und spürbar flüssiger. Kein Ersatz für den nativen wgpu-Pfad
|
||||
(`render2d`/`render3d`), aber ein schneller Weg zu einer ruckelfreien Oberfläche, solange der
|
||||
Electron/CEF-Shell (siehe Notizen zu „Render path decision") noch aussteht. Läuft rein im
|
||||
Browser ohne Tauri-Backend — `src/compute/index.ts` guardet den einzigen `invoke()`-Aufruf
|
||||
bereits per dynamischem Import mit TS-Fallback.
|
||||
## NACHTRAG Session 3 (2026-07-04 mittags) — Prioritaets-Joins + Schnitt-Sanierung
|
||||
|
||||
**Architektur (mit User abgestimmt):**
|
||||
- **Hybrid:** WebGL2-Canvas rendert die schwere Geometrie (Poché-Polygone, Linien, Schraffuren,
|
||||
Kreise/Bögen als Segmente). **Dünne SVG-Ebene DARÜBER** behält Text (Raumstempel/Labels),
|
||||
Griffe, Snap-Marker, Werkzeug-Vorschau (wenige Elemente → billig, DOM-Hit-Test + scharfer Text bleibt).
|
||||
- **Raw WebGL2, KEINE neue npm-Dependency** (User will nicht „abhängiger" werden; nativ).
|
||||
- **Geometrie pro Plan EINMAL tessellieren** (Cache), Pan/Zoom = nur Transform-Matrix-Uniform →
|
||||
keine Re-Tessellation, GPU-buttrig. Objekt-Bewegen: Plan ändert sich → re-tessellieren (leichter als SVG-Reconcile+Cairo).
|
||||
- **Linienbreite konstant in Screen-px** (wie heutiges `non-scaling-stroke`): im Vertex-Shader
|
||||
expandieren (Position world → clip, dann Normalen-Offset in clip via Viewport-px). Miter an Ecken
|
||||
später; Spike: Quads pro Segment.
|
||||
- **SVG-Renderer als Fallback hinter einem Flag behalten**, bis GL-Parität erreicht → kein Risiko.
|
||||
- Vorgehen: **erst Spike** (Linien+gefüllte Polygone + GPU-Pan/Zoom), Glätte bei 144 Hz messen
|
||||
(dev :5187 läuft; `tauri:dev`-Fenster läuft), DANN Schraffur/Feinschliff/Text-Overlay/Hit-Test.
|
||||
**Prioritäts-Join-Epic (Kern-Direktive, siehe unten „Verschneidungs-Zielmodell"):** Joins werden nach `Component.joinPriority` materialbewusst aufgelöst — konsistent in Grundriss, Schnitt UND 3D. Gelandet:
|
||||
- `e1698b7` **Wand-T-Verbindungen** (T-Knoten 3 Enden + Mittelspannen-Stoss) in `computeJoins`; generatePlan wendet die Cuts unverändert an. `0ddf99d` Rust-Parität (geometry-Crate). `d74dda1` Sample: Querwand **W9** (neuer Wandtyp `iw` Innenputz/Backstein/Innenputz) als T-Demo, Tür/Fenster beiseite gerückt.
|
||||
- `fc0a76f` **Phase 1 materialbewusst**: `resolveJoinPriority` (merge/coexist/trim), `WallCuts.layerCuts` (per-Schicht-Cuts + L-Seitenlinien), Backstein-Kern verschmilzt, Putz bricht als L. `bf97d50` **Phase 1b**: Kern sticht nur durch den Nah-Putz und stoppt an der Rückgrat-Nahfläche (mergeCut bei `offT + sign*(tT/2 − nearPlaster)`), KEINE Achs-Überlappung; Naht entfällt über stroke:none/noStrokeEdges/Zeichenreihenfolge. Rust jeweils synchron.
|
||||
- `e4b8df6` **Phase 3 (3D)**: Wand wird an der Deckenunterkante getrimmt, wenn eine Decke höherer joinPriority bündig aufliegt (`trimWallTopForCeilings` in toWalls3d, BBox-Footprint-Näherung dokumentiert) → kein Z-Fighting Wand/Decke.
|
||||
|
||||
**Fixpunkte im Code:**
|
||||
- `src/plan/PlanView.tsx` (~2500 Z.): `toScreen()` = Modell-m → viewBox-Einheiten, FIXER Ursprung,
|
||||
`PX_PER_M=90`; `view={x,y,w,h}` treibt `<svg viewBox>`; Pan = setView, Zoom = setView(onWheel).
|
||||
BEREITS optimiert: `primitiveEls`/`drawingRunEls` `useMemo` (Pan/Zoom 312→0 Re-Renders) +
|
||||
rAF-Coalescing beim Objekt-Drag (`flushDragMove`/`enqueueDragMove`, 40→1 Update/Frame). Diese
|
||||
bleiben nützlich; der GL-Renderer ersetzt die SVG-Primitiv-Ebene, NICHT die Interaktions-Logik/Griffe.
|
||||
- `src/plan/generatePlan.ts`: `export type Primitive` (Z.133) — kinds: `polygon`{pts,fill,stroke,
|
||||
strokeWidthMm,hatch,*Id}, `line`{a,b,weightMm?,strokeWidthMm?,dash?,color,cls,greyed,drawingId?},
|
||||
`arc`{center,r,…}, `text`{at,doc,extraLines,basePt,color} (Text → SVG-Overlay, NICHT WebGL).
|
||||
- Koordinaten/Breiten-Logik im SVG-Renderer: `PrimitiveShape` (~Z.2280) + `DrawingRunShape` — dort
|
||||
steht, wie Haarlinie/mm/`paperScale`/`non-scaling-stroke` die Strichstärke bestimmen (für GL nachbilden).
|
||||
- Hit-Test/Auswahl/Griffe laufen weiter über die bestehenden PlanView-Pfade (modellbasiert); GL ist nur Anzeige.
|
||||
- **Vektor-PDF/-Export bleibt** (kommt aus `generatePlan`/DXF, nicht aus der Anzeigefläche) — NICHT anfassen.
|
||||
**Schnitt-Sanierung (Regressionen aus der 3D-Schicht-Umstellung `de7a26b` behoben):**
|
||||
- `da39f0c` per-Schicht-3D-Bänder nutzten die RECHTE Normale statt `leftNormal` → Schichten in 3D+Schnitt gespiegelt (Backstein aussen). Eine Zeile in `pushSegment` gedreht.
|
||||
- `6c47355` Schnitt bekam durch die per-Schicht-Boxen segments×layers Cut-Polygone → Owner-Index-Versatz → einheitliches Diagonal-Muster. Fix: `projectToModel3d(project, { layeredWalls:false })` für `computeSection` (Voll-Box pro Wand); 3D-Viewer behält Default true.
|
||||
- `5ed3549` **Alle Schraffuren monochrom**: HATCH_INK `#0f0f0f` / HATCH_PAPER `#f0f0f0` (resolveHatch erzwingt Ink; Albedo-Fallback im Schnitt entfernt). Print-Mono separat/unangetastet.
|
||||
- `c36a7e0` Wandrelative Muster drehen im Schnitt mit der Wand (`SECTION_WALL_AXIS_ANGLE_DEG=90` an resolveHatch) → Dämmung horizontal quer zur Dicke.
|
||||
|
||||
**Gates:** `npx tsc -b` 0, `npm run build` grün, Trace-Scan sauber (keine KI-Spuren, COMMIT-REGEL oben).
|
||||
Kein Commit ohne Ansage. Verifizieren wie üblich per Puppeteer gegen :5187 (Render korrekt + Glätte messen).
|
||||
**Sonstiges:** `1855717` Renderer-Umschalter aus der Statusleiste in die **Settings**, Default **Nordstern** (WebGL2 nur explizit/Fallback). `32497bc` TopBar: Zoom-Cluster von Grid auf Flex (Phantom-Zeile +6px weg, jetzt bündig mit Zweizeilern), Bar 56→60px.
|
||||
|
||||
**GESAMT-TODO-LISTE (Stand 2026-07-01 Nacht) — im Hinterkopf behalten:**
|
||||
ERLEDIGT+verifiziert: U/I/O/P-Kopiermodi · Backlog#1 Transform→CommandLine · Backlog#4 3D-Zeichnen +
|
||||
Engine-Confirm-Bugfix · Raumstempel#2 (Feld-Modell) · Plan-Perf (Memo + rAF-Coalesce) · Tauri-M1-PoC
|
||||
(geometry-Crate 4/4 + Parität + `tauri:dev`-Boot). OFFEN: **(A) 2D-WebGL-Renderer [DIESE AUFGABE]** ·
|
||||
(B) Tauri-M1 Rest: Compute-Boundary in Render-Pfad einhängen (generatePlan sync → precompute+cache) +
|
||||
nächste Ops (kernel2d→DXF/DWG→detectRooms) · (C) three.js→wgpu (M2, nach 2D-WebGL) · Parametric Walls
|
||||
Phase B (andere Instanz) · Wand-Referenzlinien-Option unter Bezugspunkt · Viewport/Editier-Welle
|
||||
(Treppen-Griffe, Öffnung-entlang-Wand, 2D auf Zeichenebenen, runde Linienenden, schattiert-Politur) ·
|
||||
Smart-Join/Split + nahtfreie Wandverschneidung · Text/Stempel-Annotation (Frame+Rotation) · Welle B
|
||||
(3D-Schnitt+Stencil-Capping) · Welle C (HLR verdrahten) · Overrides/Ausschnitte/Multi-Page-Layouts.
|
||||
**IN FLIGHT bei Checkpoint:** (1) **3D-Live-Schnitt** (Opus-Agent): Clip-Plane-Uniform + discard in MESH/GRID_WGSL, `section_fill.rs` Cut-Caps aus `cut_section` mit prozeduraler 45°-Schraffur (CAP_WGSL), `set_section_plane` (web.rs cached walls/slabs), Viewport-Toggle; WASM-Rebuild nötig. (2) **Joins Phase 2** (Opus-Agent): Merge-Regel (gleiche Prio+Komponente) in der Schnitt-Dominanz `subtractDominantBands`. — `git status` prüfen, verifizieren, committen.
|
||||
|
||||
## >>> STAND 2026-07-01 (Tauri-PoC — App-frei, gelandet) <<<
|
||||
**NÄCHSTER Engine-Slice (Nutzer-Direktive, queued):** **Öffnungen als echte Boolean-Löcher** — Fenster/Türen sind heute Pfeiler/Brüstung/Sturz-Ersatzkörper (keine CSG, Triangulator lochfrei) → sichtbare Segment-Nähte. Ziel: EIN Wandkörper, Löcher via earcut-with-holes + Laibungsflächen; behebt auch die dokumentierte mesh↔cut_section-Öffnungs-Inkonsistenz. Läuft im selben Crate wie der 3D-Schnitt → erst danach starten.
|
||||
|
||||
**Tauri v2 + Rust-Compute-Boundary — Proof-of-Concept steht (3 App-freie Agents).**
|
||||
Alle Gates grün, nichts an `App.tsx`/`src/model/` angefasst (nur NEUE Dateien +
|
||||
additive Config). Ref: `docs/design/tauri-migration-plan.md` / `tauri-architecture.md`.
|
||||
**Verschneidungs-Zielmodell (Nutzer, verbindlich):** gleiche Komponente+Priorität = verschmelzen (Naht weg); schwächere Schicht bricht auf und schliesst als L; höhere Priorität schneidet bauteilübergreifend (Decke>Wand); Joins IMMER in allen drei Sichten identisch. Schraffur-Modell: Schnitt-Schraffur vs. Oberflächen-Schraffur, aktuell ALLE monochrom fg #0f0f0f auf bg #f0f0f0.
|
||||
|
||||
- **`src-tauri/` (Rust, neu):** Cargo-**Workspace** aus zwei Crates:
|
||||
- `src-tauri/geometry/` — **serde-only** Lib, Port von `computeJoins` (aus
|
||||
`src/model/joins.ts`) inkl. Vektor-Helfer. Input flach: `WallInput{ id,start,end,
|
||||
thickness,referenceOffset }`; Output `WallCuts{ wallId,startCut,endCut }` (Line=
|
||||
point+dir). **`cargo test` grün: 4/4** (L-Ecke=geteilte Gehrung, freies Ende,
|
||||
T-Stoss, kollinear → alle korrekt). Braucht KEIN webkit → hier testbar.
|
||||
- `src-tauri/` App-Crate (`cad-tauri`) — Tauri-v2-Shell (`lib.rs` mit
|
||||
`#[tauri::command] compute_joins` + `run()`, `main.rs`, `build.rs`,
|
||||
`tauri.conf.json` devUrl **:5187**, `icons/icon.png`). **`cargo check` GRÜN** —
|
||||
wider Erwarten NICHT von webkit blockiert: tauri v2 bindet `webkit2gtk-4.1`
|
||||
(vorhanden); nur `-6.0` fehlte. `src-tauri/target` + `gen/` sind Build-Artefakte
|
||||
(gitignored).
|
||||
- **`src/compute/index.ts` (neu, Boundary):** einzige Stelle für schwere Ops.
|
||||
`computeJoins(project,walls)` flacht ab → `invoke("compute_joins")` (geguardeter
|
||||
dynamischer Import von `@tauri-apps/api/core`, Build bleibt grün OHNE das Paket)
|
||||
→ bei null/Fehler `console.warn` + TS-Fallback (`joins.ts`). Zusätzlich
|
||||
Durchreicher `detectRooms` (roomBoundary.ts) + `parseShapeFromDwg`(=`parseDwg`);
|
||||
`computeKernel2D` bewusst ausgelassen (kein Einzel-Entry, ~30 Primitive).
|
||||
- **`package.json`/`vite.config.ts`:** `@tauri-apps/cli`+`api` (v2) installiert,
|
||||
Scripts `tauri`/`tauri:dev`/`tauri:build`; vite Port 5187 `strictPort`,
|
||||
`clearScreen:false`, `envPrefix` — rein additiv.
|
||||
## NACHTRAG Session 2 (2026-07-04 nachmittags/abends) — TopBar-Redesign + 3D-Editierbarkeit
|
||||
|
||||
**NÄCHSTE SCHRITTE (Tauri):** (1) **Echte TS↔Rust-Numerik-Parität** end-to-end prüfen
|
||||
— erst sinnvoll, wenn die Boundary in einen Caller verdrahtet ist (aktuell bewusst
|
||||
NICHT verdrahtet, weil das `generatePlan`/`App` async machen würde). (2) `computeJoins`
|
||||
in den Wand-Render-Pfad einhängen (async-Welle). (3) `tauri:dev` end-to-end auf einer
|
||||
Maschine MIT `webkit2gtk` (hier nur `cargo check`, kein Fenster-Boot getestet).
|
||||
(4) Nächste Ops laut Plan: `kernel2d` → DXF/DWG → `detectRooms`.
|
||||
**TopBar/UI gelandet:** DOSSIER-Wortmarke + Ressourcen-Icon links (Rhino-Stil), Massstab/Zoom-Cluster bereinigt (Duplikat weg, Zoom-Aktionen unter Massstab-Dropdown gestapelt, gleiche Breite, Ring bei „am Massstab" statt Flächen-Aufhellung), PDF/DXF raus → **Datei-Burger-Menü** rechts (Speichern/Öffnen als echter Projekt-JSON-Download/-Upload, Import, Export). **Einstellungs-Fenster** (`SettingsDialog.tsx`): Accent-Palette (`src/theme/accents.ts`), Auswahlrahmen-Farbe (Default Yuyake #CEB188) + Snap-Farbe (vorläufig **Sora #5FA1C9** gewählt — „aki"-Rückfrage bleibt offen, per Picker änderbar), Projekt-MüM-Feld (`project.referenceElevationMasl`, nur gespeichert, Draping folgt). **Custom Fenstersteuerung** (_/□/X) für Electron (`electron-preload.cjs` + IPC, `WindowControls.tsx`, TopBar = Drag-Region). **Dark-Theme ist jetzt fester Standard** (vorher an OS-`prefers-color-scheme` gekoppelt → zeigte fälschlich Light; Light bleibt Opt-in via `:root[data-theme="light"]`). Alle TopBar-Pillen auf dieselbe stets-dunkle Kontextmenü-Fläche vereinheitlicht; Panel-Köpfe flach (kein hellerer Balken) + feste 40px-Höhe. **Undo/Redo** (`historySlice.ts`, Ctrl+Z/Y, Coalescing für Drags). **15 gebündelte OFL-Schriften** (`public/fonts/`, `src/text/fonts.css`) statt Systemfonts. **Farbfelder**: Hex separat editierbar (`ColorHexField.tsx`). **Text-Zeilenhöhe-Regler** ersetzt „+ Text" (Text wird eigenes Werkzeug, AUDIT B1). **Linienstil** im Attribute-Panel editierbar. 2D-Zoom-Grenzen stark erweitert (20000×/0.005×).
|
||||
|
||||
## >>> STAND 2026-07-01 (Parametric Walls Phase A — ISOLIERT) <<<
|
||||
**3D-Editierbarkeit gelandet (Nordstern/render3d, NICHT three.js — Free=three.js-Viewer, Premium=editierbarer Nordstern, siehe Memory):**
|
||||
- **R1** `85a721f` Boden-Referenzraster auf y=baseElevation des aktiven Geschosses, ein-/ausschaltbar (Overlay-Button, `viewSlice.grid3dVisible`; eigene LineList-Pipeline `grid.rs`, `set_ground_grid`). Kamera: Links=Orbit, **Mitte/Rechts=Pan** (vorher Pan nur auf Shift+Mitte versteckt), Rad=Zoom-to-Cursor.
|
||||
- **R2** `6ef57b6` View-Styles **wireframe** + **hidden-line** + Fill-Light (`set_render_style`, `edges.rs` = Feature-Edge-Extraktion mit Crease-Dedup, Hidden-Line via Depth-Bias-Flächen + Kanten obenauf). `textured` → vorerst wie shaded (keine Textur-Pipeline).
|
||||
- **R3** `ac0d9ef` **Klick-Auswahl** im 3D (`raycast3d.ts`: OBB-Wand-/Prisma-Slab-Schnitt → Store-Selektion → Attribute+Objekt-Info-Panel). `toWalls3d.pickGeometry()` mit wallId/ceilingId.
|
||||
- **R4** `fabf97a` **Auswahl-Highlight**: orange Outline, immer sichtbar (No-Depth-Linien-Pipeline `set_highlight_lines`, `selectionHighlightLines()`).
|
||||
- **R5** `02bd261` **Materialfarben** im 3D: Wände/Decken in Farbe der dicksten Schicht statt Einheitsgrau.
|
||||
- **weiter:** `8bc472a` 2D **z-Anordnen** (Kontextmenü nach vorn/hinten, `reorderDrawings`); `de7a26b` **per-Schicht Wand-Bänder** (jede Materiallage als eigener farbiger Quader, wallId bleibt → Pick/Highlight ganze Wand); `e42c336` **AUDIT A6 Bauteil-Schedule-CSV** (Datei-Menü, `exportSchedule.ts`); `7dd324d` **Glasscheiben in Fenstern** (A5-Teil, RMesh in jeder Fensteröffnung).
|
||||
- Alle Runden **visuell im Browser verifiziert** (Playwright + WebGPU-Flags: `--enable-unsafe-webgpu --enable-features=Vulkan --use-gl=angle --use-angle=vulkan --no-sandbox`, URL `?engine=wasm`, Iso-View klicken).
|
||||
|
||||
**Neue Instanz (Opus) — Parallelisierung mit Agents (Sonnet):**
|
||||
- **Branch:** `feature/parametric-walls` (isoliert, keine App.tsx-Änderungen)
|
||||
- **Phase A FERTIG (alle Agents grün):**
|
||||
- **Agent 1:** Types (`ParametricWall`, 5 `ParametricRule` Varianten) + Engine
|
||||
(`resolveParametricWall()`, 5 rule handlers, helpers) in `src/model/parametricWalls.ts`
|
||||
- **Agent 2:** 50 Unit-Tests (Vitest), alle grün; `npm test -- parametricWalls.test.ts`
|
||||
- **Agent 3:** Design-Doku `docs/design/parametric-walls.md` + `docs/README.md` aktualisiert
|
||||
- Verification: `tsc -b` ✓, `npm run build` ✓ (577 modules, 3.72s)
|
||||
- **Phase B (UI-Integration) — für nächste Instanz mit Agents:**
|
||||
- Agent 1: TopBar ParametricWall-Picker + UI-Komponente
|
||||
- Agent 2: Command `pw` (`cmds/parametricWall.ts`)
|
||||
- Agent 3: ResourceManager-Integration (ParametricWall CRUD)
|
||||
- Dann merge → `master`, Screenshot-Verifikation
|
||||
- **Koordination:** andere Instanz committet parallel (Text, Raum, HLR) auf `master`,
|
||||
keine Konflikte (nur `types.ts`+`src/model/` erweitert, nicht `App.tsx`).
|
||||
**3D-REST (engine-schwer, bewusst NICHT blind gemacht — mit Nutzer angehen):**
|
||||
- **Schnitt-Schraffuren** (2D-Poché/Schraffur auf 3D-Schnittflächen; `section.rs`, ~66KB, komplex).
|
||||
- **Textur-/PBR-Pipeline** (Sampler/Bindings/UV in wgpu; dann `textured`-Style + `Component.texture3d`/Material echt rendern).
|
||||
- **Wand-Schicht-Bänder** in 3D (Option B aus R5: jede Materiallage als eigener extrudierter Teilquader statt nur repräsentativer Farbe — pro-Schicht-Farben liegen in `RWall.layers` schon vor).
|
||||
- **Ortho-Ray-Picking** (aktuell perspektivischer Pick-Strahl; für Front/Top/Side-Presets vor der ersten Navigation leicht ungenau).
|
||||
- **3D-Griffe/Editieren** (Wand-Endpunkte im 3D ziehen etc. — bisher nur Auswahl + Panel-Edit).
|
||||
|
||||
**Backlog #4 ERLEDIGT (Zeichnen im 3D-View — vorherige Instanz):** Werkzeug-Gate `toolsEnabled`
|
||||
(`App.tsx` ~1877) von `viewType === "grundriss"` auf `activeLevel.kind ===
|
||||
"floor"` erweitert → Werkzeuge sind jetzt AUCH in der Perspektive aktiv. Die
|
||||
gesamte Zeichen-Pipeline war bereits verdrahtet (`Viewport3D.workplaneModel()`
|
||||
raycastet den Cursor auf die Geschoss-OKFF; `onWorkplanePoint`/`onWorkplaneConfirm`
|
||||
speisen dieselbe Engine wie der Grundriss). Ende-zu-Ende per Puppeteer verifiziert:
|
||||
in iso-Ansicht Linie mit 2 Klicks gezeichnet → committet (`draw2d` 0→1, Prompt
|
||||
zurück auf „Befehl:"); Picks liefern gültige Modell-Vec2 (raycast korrekt).
|
||||
- **BUG-FIX unterwegs (Engine-Confirm, `src/commands/engine.ts` `confirm()`):**
|
||||
Beim Testen von #4 fiel ein VORBESTEHENDER Bug auf (reproduziert IDENTISCH im
|
||||
Grundriss, nicht durch #4 verursacht): mehrteilige Befehle (Wand/Polylinie)
|
||||
liessen sich per Enter/Rechtsklick NIE abschliessen. Ursache: `confirm()` nahm
|
||||
IMMER den Tab-Feld-Zweig (`fieldResultPoint`), sobald der Schritt Felder hatte —
|
||||
auch OHNE gelocktes Feld → es wurde ein Cursor-Punkt gefüttert (bei Wand: Null-
|
||||
Strecke, ignoriert) statt `onConfirm` (das den Zug committet) aufzurufen. Fix:
|
||||
Feld-Zweig nur noch bei tatsächlich GELOCKTEM Feld (`Object.keys(this.locks).length
|
||||
> 0`); ohne Lock fällt `confirm()` auf `onConfirm` durch. Verifiziert: Wand
|
||||
schliesst jetzt per Rechtsklick im Grundriss (91→98) UND in 3D (99→106); Tab-Feld-
|
||||
Fluss „Länge 4 tippen → Enter lockt → leeres Enter setzt Punkt" unverändert;
|
||||
Linie-Auto-Commit (2 Klicks) unverändert. Gegatet: `tsc -b` 0, `build` grün, keine
|
||||
Konsolenfehler. KEIN Commit.
|
||||
WASM-Workflow: `src/engine/pkg3d/` ist gitignore't → nach Rust-Änderung `npm run build:engine3d`, nur die `.rs` committen. DOSSIER-Audit-Details jetzt in `docs/design/dossier-feature-audit.md` (A1–A6/B1–B4/C/D/E) + `ROADMAP.md` §11.
|
||||
|
||||
- **Backlog #1 ERLEDIGT (Befehlsleiste-Transform vereinheitlicht):** Die laufende
|
||||
Auswahl-Transformation (nur noch `rotate`/Taste `d` mit U/I/O/P; move/mirror
|
||||
laufen längst über die Engine) speist jetzt die `CommandLine` statt der
|
||||
schwebenden `TransformBar`. Dritter Zweig in der `<CommandLine>`-Ternäre parallel
|
||||
zu `gripEdit` (`gripEdit ? … : activeTransform ? … : engineView…`): Prompt =
|
||||
`transform.hint.${op}.${…}`, Inline-Optionen = U/I/O/P (`transformOptions()`,
|
||||
aktiver Modus hervorgehoben via neuem `active?`-Feld an `CommandLineOption` +
|
||||
`.cmdline-option.active` in styles.css, `×count` bei array/distribute), Live-/
|
||||
lockbares **Winkel**-Feld beim Drehen bzw. **Distanz** beim Bewegen
|
||||
(`transformFields()` aus neuem `transformDragInfoRef`), getippte Zahl committet
|
||||
über `submitTransformValue()`. `d` fokussiert die CommandLine (wie `m`/`s`).
|
||||
`src/ui/TransformBar.tsx` gelöscht; verwaiste `.transform-bar`/`.tf-*`-CSS +
|
||||
`transform.bar`-Key harmlos stehen gelassen. Gegatet: `tsc -b` 0 Fehler, `build`
|
||||
grün, Trace-Scan sauber, Puppeteer-Probe end-to-end (nach `d`: „Drehzentrum
|
||||
klicken" + U/I/O/P; nach Zentrum+Bezug: „Zielwinkel klicken" + Live-Winkelfeld;
|
||||
keine Konsolenfehler). KEIN Commit (Konvention der Vorsessions beibehalten).
|
||||
## Konventionen (ZWINGEND — zuerst lesen)
|
||||
- **Keine Fremd-Tool-Spuren** im Repo: vor jedem Commit Trace-Scan (`git diff | grep -iE "co-authored|generated with"`) → muss leer sein. Commits deutsch, sachlich, KEINE Co-Authored-By-Trailer.
|
||||
- **NICHT ANFASSEN (fremde WIP):** `.gitignore`, `public/assets/materials/manifest.json`, `src/materials/library.ts`, `scripts/fetch-materials.mjs` — bleiben dauerhaft dirty, nie stagen.
|
||||
- **Verifikation:** `tsc --noEmit` clean + `vitest run` + trace-scan vor jedem Commit. Nur Task-Dateien stagen (Kollisionen: parallele Tasks disjunkte Datei-Lanes geben; i18n exklusiv einem Task).
|
||||
- **Engine = „Nordstern"** (render2d/render3d/WASM). Referenz DOSSIER-Rhino: `https://git.openbureau.ch/karim/dossier` (Alias git.kgva.ch), geklont `/tmp/dossier-ref`.
|
||||
|
||||
## >>> STAND 2026-07-01 (Abend) — Raum + Topbar-Umbau + Backlog — ZUERST lesen <<<
|
||||
## In dieser Session gelandet (Auswahl Commits)
|
||||
Schraffur/Linien-Epic komplett: Typmodell (vector/image Hatch, dash/zigzag/custom Line, Component Vordergrund/Hintergrund), ResourceManager **Master-Detail** (Schraffuren/Linien/Wandstile/Deckenstile, `9695748`), Rendering neuer Typen (Bild-`<pattern>`, random modellraum-verankert, Zickzack, Custom-Motiv-Editor `src/ui/MotifEditor.tsx`), modulares Linien-Segment-System (Strich/Punkt/Lücke), Attribut Vordergrund/Hintergrund + **By-Layer/By-Object-Quellen** (Nach Ebene/Bauteil/eigener Wert für Vordergrund/Hintergrund/Strichstärke/Schraffur, `d02781e`).
|
||||
Weiter: Gehrung spitze Winkel (2D+Schnitt+Rust), TopBar/Footer-Reorg, Zoom-% am Massstab, Tool-Shortcuts 1..0, Verschiebe-Dreieck flacher + Snap, Statusleiste „Nordstern", ResourceManager als **floating nicht-modales Fenster**, **swisstopo** Gebäude(extrudiert)+Terrain-Mesh in 3D (`f22970f`), Schnitt-Wandschicht-Orientierung (`7569524`), **Schnitt-Boolean-Dominanz** nach joinPriority (`23d81be`).
|
||||
|
||||
Alle unten genannten Agenten GRÜN gelandet und gegatet (`tsc -b` 0 Fehler,
|
||||
`npm run build` grün, Trace-Scan sauber, Boot-Probe ohne Konsolenfehler). Dev-
|
||||
Server läuft/hält auf :5187. KEIN Commit gemacht (Commit-Regel oben beachten).
|
||||
## Backlog & Rückfragen → `PENDENZEN.md`
|
||||
Der frühere „Offener Backlog"/„Offene Rückfragen"/„IN FLIGHT"-Abschnitt ist vollständig nach `PENDENZEN.md` migriert (Single Source of Truth). Dort steht auch das Arbeitsprotokoll.
|
||||
|
||||
**Diese Session gelandet:**
|
||||
- **Rich-Text-Kern (komplett):** `src/text/richText.ts` (Marks/RichTextDoc/TextRange,
|
||||
toggleMark/applyMark/isMarkActive/DEFAULT_PRESETS/serialize…), `src/text/renderHtml.ts`
|
||||
(docToHtml/docToLines/docToSvgText), `src/text/RichTextEditor.tsx` (contentEditable,
|
||||
Toolbar B/I/U/S + Grösse/Farbe/Presets).
|
||||
- **Raum-Slice (SIA-416), Agent gelandet:** `Room` in `model/types.ts` (id, floorId,
|
||||
categoryCode "60", siaCategory HNF/NNF/VF/FF/KGF, boundary, stampAnchor?, stampDoc?);
|
||||
`cmds/room.ts` (Modus inside=click-inside via `roomFromPointInside`, manual=Polylinie);
|
||||
Plan: neues `text`-Primitive + Füllpolygon + Stempel via `docToLines` + Live-Fläche;
|
||||
`panels/RoomBalancePanel.tsx` (SIA-Bilanz + CSV). **App-State für Stempel:**
|
||||
`selectedRoomId`/`selectedRoom.stampDoc`, `projectSlice.setRoomStampDoc(roomId,doc)`,
|
||||
`moveRoomStamp`, `setRoomStampAnchor`, `stampEditorRoomId`. Stempel-Editor = kleines
|
||||
schwebendes Fenster (kein Footer).
|
||||
- **DOSSIER-Referenz studiert → Spec `docs/design/topbar-dossier.md`** (verbindlich für
|
||||
Topbar-Arbeit; Referenz-Klon lag unter `/tmp/DOSSIER`). Kernbefund: Text-Styling ist im
|
||||
Original eine FEST in der Oberleiste sitzende Text-Gruppe (Stil/Font/Grösse + B/I/U +
|
||||
L/C/R + „+Text"), Akzent-Glow bei Auswahl; Raumstempel-Typografie darüber; Text-INHALT
|
||||
in eigenem kleinem Fenster; Zoom/Massstab als 2×2-Cluster (kombinierte Stat-Pille).
|
||||
- **Topbar-Umbau (Agent gelandet), `src/ui/TopBar.tsx` + `styles.css` + `App.tsx` +
|
||||
`ui/TextEditorDialog.tsx` (neu) + i18n:** Zoom-Doppelung entfernt; 2×2-Massstab/Zoom-
|
||||
Cluster; einheitliche Segmentpillen (neue `Segment`-Komponente); **Text-Gruppe** in der
|
||||
Topbar (`textTarget`-Prop = `null | {doc,range,apply}`, aus selektiertem Raum abgeleitet,
|
||||
`apply`→`setRoomStampDoc`; ohne range wirkt Format auf ganzes Doc); Doppelklick öffnet
|
||||
`TextEditorDialog`. Verwaiste alte CSS-Regeln (`.tb-zoom*`, `.stamp-editor*`) bewusst
|
||||
stehen gelassen (harmlos). „+ Text"-Button verdrahtet, aber DEAKTIVIERT (noch kein
|
||||
Text-Annotations-Werkzeug).
|
||||
|
||||
**Backlog / nächste Schritte (Priorität dieser Session, siehe auch Todo-Liste):**
|
||||
1. **Befehlsleiste-Transform vereinheitlichen:** laufende Transformation (move/mirror/rotate)
|
||||
soll die `CommandLine` speisen (Prompt „Basispunkt/Zielpunkt" + U/I/O/P als Inline-
|
||||
Optionen) statt der separaten schwebenden `TransformBar`; freies Befehl-Tippen während der
|
||||
Geste unterdrücken, Zahleneingabe (Distanz/Winkel) via `fields` bleibt. Muster: parallel
|
||||
zum bestehenden `gripEdit`-Zweig bei `<CommandLine>` (~App.tsx:2793). U/I/O/P-Routing
|
||||
(App.tsx ~1543–1548) NICHT kaputt machen — nur zusätzlich in die CommandLine spiegeln.
|
||||
2. **Strukturierter Raumstempel (DOSSIER `RaumProperties`):** Stempel ist KEIN Freitext,
|
||||
sondern ein FELD-Modell: Raumnummer · Raumname · Raumname-Zeile2 (in Listen als ein Name
|
||||
zusammengezogen) · Bodenfläche (an/aus + Präfix) · Fensterfläche (an/aus + Präfix) ·
|
||||
Nutzung HNF/… (an/aus). Zeilen-Layout-Editor (welches Feld auf welche Zeile) + Typografie
|
||||
PRO Feld-Einheit (Raumname grösser, andere kleiner, eine Zeile kursiv). Ersetzt den
|
||||
Freitext-`stampDoc`-Zwischenstand; die Topbar-Text-Gruppe formatiert dann die gewählte
|
||||
Feld-Einheit (löst auch die „ganzes Doc statt Selektion"-Einschränkung).
|
||||
3. **Wand-Optionen:** Referenzlinien-Option UNTER den Bezugspunkt setzen, gleiche Optik/
|
||||
Anordnung wie die übrigen Optionen (`cmds/wall.ts` / ToolsPanel-Optionsreihenfolge).
|
||||
4. **Zeichnen im 3D-View:** Cursor per Raycast auf die Arbeitsebene (okff des Geschosses)
|
||||
projizieren → Weltkoordinaten als `engine.move/pick` einspeisen; `toolsEnabled`-Gate
|
||||
(App.tsx ~1834, heute `viewType==="grundriss"`) auch für Perspektive öffnen.
|
||||
5. **Viewport/Editier-Welle:** Treppen-Griffe (App.tsx grips-memo hat keinen stair-Fall),
|
||||
Öffnung entlang Host-Wand verschieben (kein opening-Handler im Body-Move; `moveOpeningBy`
|
||||
fehlt), 2D-Tools auf Zeichnungsebenen (`toolsEnabled` erweitern + Bau-Tool-Buttons grauen),
|
||||
runde Linienenden (`stroke-linecap:round` auf offene Linien), „schattiert"-Politur
|
||||
(ACES-Tonemapping+sRGB, HemisphereLight/RoomEnvironment-PMREM, MeshLambert→MeshStandard in
|
||||
`Viewport3D.tsx` ~416–449).
|
||||
6. **Smart-Join/Split + NAHT-FREIE Wandverschneidung** (Poché-Union an Stössen, keine sichtbare
|
||||
innere Naht, Gehrung/T-Stoss). 7. **Text/Stempel-Annotation** (aktiviert „+Text";
|
||||
Frame+Rotation). 8. **Welle B** (3D-Schnittebene + Stencil-Capping + Schnittschraffur).
|
||||
9. **Welle C** (HLR verdrahten; `src/section/hlr.ts` bereit). 10. Overrides/Ausschnitte/
|
||||
Multi-Page-Layouts.
|
||||
|
||||
**Kollisions-Regel bestätigt:** `App.tsx`, `types.ts`, `generatePlan.ts`, `Viewport3D.tsx`,
|
||||
`registry.ts`, `PlanView.tsx`, i18n, `styles.css` sind Hotspots → nur EIN App.tsx-Agent pro
|
||||
Welle; parallele Tracks nur auf genuin App-freien neuen Modulen.
|
||||
|
||||
## >>> STAND 2026-07-01 (Bug-Batch Runde 1) — ZUERST lesen <<<
|
||||
|
||||
Runde-1-Agenten alle GRÜN gelandet (`tsc -b` + build + Probe verifiziert):
|
||||
- **3D-Editier-Kamera-Bug (kritisch, `src/viewport/Viewport3D.tsx`):** Ursache = schwerer
|
||||
Szenen-Aufbau-Effekt mit `project`-Dependency → jede Griff-Mutation baute Szene neu auf und
|
||||
`applyView3d` resettete die Kamera auf Preset (→ „springt in Top-View", Rechts/Links-Pan-Illusion).
|
||||
Fix: `camStateRef`-Snapshot (Pos/Target/up/ortho-Zoom/aktive Kam) im Render-Loop fortgeschrieben,
|
||||
bei Neuaufbau WIEDERHERGESTELLT statt `applyView3d`; `applyView3d` nur beim ersten Aufbau;
|
||||
`view3d`-Effekt guarded via `gripDragRef`; Top-View `enableRotate=false`; 2D-Drawings bekommen
|
||||
`move`-Griff (Anker=center/at) → in 3D verschiebbar. Kamera pos/target numerisch identisch vor/nach
|
||||
Drag/Löschen bewiesen.
|
||||
- **Print-Linienstärke invertiert → korrekt (`src/plan/PlanView.tsx`, `styles.css`):** Print zeichnet jetzt
|
||||
echte Papier-mm in viewBox-Einheiten OHNE `non-scaling-stroke`: `strokeVb=(mm·N/1000)·PX_PER_M`,
|
||||
N=`paperScale` stabil (nur bei `applyScale`/erstem Messen gesetzt). Rein=dicker, raus=dünner;
|
||||
1:100 vs 1:10 = exakt ×10. Display/Haarlinie unverändert. **Ecken-Miter:** CSS `stroke-linejoin:miter`
|
||||
+ `buildDrawingRuns` bündelt zusammenhängende `line`-Primitive gleicher drawingId zu einem
|
||||
`polyline`/`polygon` (Ringschluss erkannt) → gehrte Ecken statt Butt-Cap-Stufe. Primitive-Modell
|
||||
(und Print-Export) unverändert.
|
||||
- **ambientCG-Live-Bibliothek (`src/materials/ambientcg.ts` neu, `ResourceManager.tsx`):** komplette CC0-Lib
|
||||
live durchsuchbar (Suche/Kategorie/Auflösung 1K-4K), jszip-Entpackung → Blob-URLs, NormalGL bevorzugt.
|
||||
**CORS:** Such-JSON + `/get` brauchen Proxy → Vite-Dev-Proxy `/ambientcg` in `vite.config.ts`
|
||||
(`VITE_AMBIENTCG_PROXY` für Prod); optionale Prod-Route `openbureau-core/api/src/routes/ambientcg.js`
|
||||
(lesend, gemountet VOR Auth-Gate). Thumbnails direkt (ACAO:*). Offline → Schnellauswahl-Fallback.
|
||||
|
||||
**Runde 2 FERTIG (2026-07-01, alle GRÜN + Probe-verifiziert):**
|
||||
- **2a** [App/engine/editors/registry, neu `cmds/mirror.ts`+`cmds/join.ts`]: Snapping erster Punkt gefixt
|
||||
(Modifier shift/ctrl wurden im Command-Pfad verschluckt → jetzt via `EngineMods` durch `move/pick`→`host.snap`
|
||||
gefädelt); Segment-Move für offene Polylinien/Linien (`EdgeGrip.free`, ganzer 2D-Delta, Nachbar-Segmente
|
||||
folgen über geteilte Vertices); Kürzel↔Befehl vereinheitlicht (`M`→engine.start("move"), `S`→"mirror",
|
||||
`Ctrl+J`→"join", identisch zum Tippen; `Command.autoRun` für nicht-interaktive Befehle; `CommandSelection.drawingIds`
|
||||
für Mehrfachauswahl); **Mirror-Befehl** (2-Punkt-Achse, Vorschau, `commitTransform mirror/copy`).
|
||||
- **2b** [cmds/{polyline,rect,wall}]: Polylinie **offen/geschlossen** (Toggle `m`, Ring-Fill); Rechteck **2pt/3pt/Zentrum**
|
||||
(3pt = gedreht → closed polyline); **Wandstärke-Tab-Feld** nur bei einschichtigen Wandtypen (baut on-commit
|
||||
einen einschichtigen Wandtyp der Zieldicke, Original bleibt).
|
||||
- **Swisstopo/OSM-Kontext-Importer** [io/{lv95,geoContext,swissTopo,osm}, ui/ContextImportDialog, SitePanel]:
|
||||
„Standort importieren" — geocode (geo.admin SearchServer, LV95 direkt), swisstopo-Gebäude
|
||||
(`MapServer/identify` layer `ch.swisstopo.vec25-gebaeude`), OSM-Overpass (Gebäude/Strassen/Wasser/Grün).
|
||||
LV95↔WGS84 (Bern round-trip 0.38 m) + Origin-Shift. CORS: geo.admin direkt, Overpass via neuer read-only-Route
|
||||
`openbureau-core/api/src/routes/geoproxy.js` (vor Auth-Gate, host-allowlist) wenn `VITE_GEO_PROXY` gesetzt.
|
||||
|
||||
**TIER-1 FORTSCHRITT (2026-07-01):**
|
||||
- **DXF-Vektor-Export FERTIG** [src/export/{dxfWriter,exportDxf}, ui/ExportDxfDialog, TopBar+App-Button]:
|
||||
hand-rollierter R2000/AC1015-ASCII-Writer, `$INSUNITS=6` (Meter), 23 Layer aus Kategorien (ACI+True-Color+lw),
|
||||
LINE/LWPOLYLINE(closed)/CIRCLE/ARC/TEXT; Quelle = `generatePlan()` in Meter-Modellraum. 5 m-Wand→5.345 m Span
|
||||
verifiziert. DWG NICHT geshippt (libredwg-web nur lesend — ehrlich weggelassen).
|
||||
- **Decke (Ceiling) FERTIG** [geometry/ceiling, cmds/ceiling(alias d/decke), types.ts `Ceiling`, projectSlice/selectionSlice,
|
||||
generatePlan `addCeilingPoche`, PlanView-Hittest, Viewport3D `addCeilingMesh`(Extrude+Material+Modi), ToolsPanel-Button+
|
||||
Deckentyp-Picker, ObjectInfoPanel `CeilingSection`, host.ts, App-Grips/Selection]. Wand-Muster: floorId + categoryCode "30"
|
||||
+ wallTypeId/Component + VerticalAnchor(OK=Geschoss-Top) + optionale Dickenüberschreibung; statt Achse/Band ein
|
||||
closed `outline: Vec2[]`. Plan = Umriss-Polygon (Fill+Hatch + schwere Outline) VOR den Wänden gezeichnet. Grips Vertex+Edge+Body.
|
||||
- ⚠️ MERKE: **App.tsx ist der Serialisierungs-Engpass** — pro Welle darf NUR EIN Agent App.tsx editieren (DXF+Decke
|
||||
liefen versehentlich gleichzeitig darauf; Merge hat diesmal geklappt, aber nicht drauf verlassen).
|
||||
|
||||
- **Öffnung (Fenster/Tür) FERTIG** [geometry/opening, cmds/opening(alias f/fenster, t/tuer/tür, oe), types.ts `Opening`,
|
||||
generatePlan(`wallGaps`), PlanView, Viewport3D(`addWallMeshes` segmentiert + Sturz/Brüstung, `addOpeningMeshes` Rahmen/Glas/Flügel),
|
||||
slices, App, ToolsPanel(Fenster+Tür-Buttons), ObjectInfoPanel(editierbar)]. **Host-Sync:** Öffnung speichert KEINE
|
||||
Weltkoordinaten — alles wird pro Render aus `wall.start/end` neu abgeleitet → Wand verschieben bewegt Öffnung automatisch.
|
||||
Plan-Symbole je Detailgrad (Tür: Blatt→+Schwenkbogen→+Anschlag; Fenster: 1→+Rahmen→+2 Glaslinien).
|
||||
- **SIA-416-Engine FERTIG** [geometry/{roomArea,roomBoundary} — pure, isoliert, 34/34 Tests]: Raumerkennung
|
||||
(planar-arrangement+half-edge face-tracing, `detectRooms`/`roomFromPointInside`), Fläche/Umfang/Zentroid,
|
||||
SIA-Hierarchie (GF→KGF+NGF; NGF→NF(HNF/NNF)/VF/FF), `balance()`, `roomsToCsv`. Bereit für den Raum-Slice.
|
||||
|
||||
- **Treppe FERTIG** [geometry/stair, cmds/stair(alias treppe/tp — `tr`=trim!), types.ts `Stair`, model/wall `stairVerticalExtent`,
|
||||
generatePlan `addStairSymbol`(Schnittbruchlinie+Pfeil), Viewport3D `addStairMeshes`(Stufenprofil), slices/App/Panels/i18n].
|
||||
gerade/L/Wendel (Wendel = vereinfachtes Keil-Modell), SIA-17/29, geschoss-übergreifend (totalRise=Geschosshöhe).
|
||||
- **HLR-Spike FERTIG (Welle-C-Fundament)** [src/section/{hlr,occt,occt-wasm.d}.ts, docs/welle-c-hlr-spike/, package.json
|
||||
+opencascade.js, vite.config aliases]. **API-Pfad = `HLRAppli_ReflectLines`** (NICHT HLRBRep_Algo — im Prebuilt nicht
|
||||
konstruierbar). Lazy via dynamic import, Haupt-Bundle unberührt. 62 MB WASM → später Custom-Build auf 5-15 MB.
|
||||
|
||||
**SESSION-LIMIT-STOP (2026-07-01 ~11:00, reset 13:00 Europe/Zurich):** Raum- + Rich-Text-Agenten wurden vom
|
||||
Session-Limit abgeschnitten. Repo ist trotzdem GRÜN & stabil (tsc+build grün, dev :5187 läuft). Genauer Stand:
|
||||
- **Raum: NICHT begonnen** — kein `cmds/room.ts`, kein `Room`-Typ, keine Verdrahtung. Sauber neu starten. SIA-Engine
|
||||
(`geometry/roomArea.ts`+`roomBoundary.ts`) liegt fertig & getestet bereit zum Import.
|
||||
- **Rich-Text: FAST FERTIG** — `src/text/richText.ts` (Doc-Modell + Marks + plainText/applyMark/toggleMark/serialize/Presets)
|
||||
FERTIG. `src/text/renderHtml.ts` (docToHtml für Editor + docToLines/docToSvgText für SVG-Plan, gemeinsame Mark→Stil-Map)
|
||||
JETZT FERTIG & kompiliert (im Hauptthread nachgezogen, da Agent am Limit war). FEHLT nur noch: `src/text/RichTextEditor.tsx`
|
||||
(WYSIWYG-Komponente mit Toolbar bold/italic/underline/strike/Grösse/Farbe + Presets, controlled: value/onChange).
|
||||
- Zwei Helfer-Subagenten ("Map/Extract …wiring in App.tsx") liefen ebenfalls ins Limit; App.tsx ist laut tsc+build INTAKT.
|
||||
|
||||
**RESUME NACH RESET (Reihenfolge):** (1) Rich-Text-Kern fertig (renderHtml + Editor-Komponente) — App-frei, isoliert.
|
||||
(2) Raum-Slice (nutzt SIA-Engine, click-inside, Stempel, Bilanz+CSV) — App-Slice. (3) Text/Stempel-Slice (nutzt Rich-Text-Kern).
|
||||
(4) Smart-Join/Split. (5) Welle B (3D-Schnitt+Stencil-Capping) → Welle C (HLR verdrahten, `src/section/hlr.ts` bereit).
|
||||
(6) Overrides-Engine, Ausschnitte, Multi-Page-Layouts. REGEL: nur EIN App.tsx-Agent pro Welle; Parallel-Track nur wenn App-frei.
|
||||
|
||||
---
|
||||
|
||||
## >>> STAND 2026-06-30 (Fortsetzung, spät) <<<
|
||||
|
||||
Fortsetzungs-Lauf (UI + Import + Wand-Attribute + Selektion/Editieren). Alles
|
||||
unten ist `npx tsc -b` + `npm run build` GRÜN und per Screenshot/Probe verifiziert,
|
||||
außer „LÄUFT" markiert. Viel wurde an **Subagenten** delegiert (Dateien landen
|
||||
auf der Platte, unabhängig von Benachrichtigungen).
|
||||
|
||||
**Fertig & verifiziert (diese Fortsetzung):**
|
||||
- **Vektor-PDF-Export des Grundrisses (KERN-ZIEL — fertig & verifiziert):**
|
||||
`src/export/planToPrintSvg.ts` (Druck-SVG aus `generatePlan`, mm-Mapping im Maßstab,
|
||||
echte mm-Linienstärken/ISO-Stifte, Schraffuren als Vektorlinien) + `src/export/exportPdf.ts`
|
||||
(`jspdf`+`svg2pdf.js` → echtes Vektor-PDF, A4/A3 ×Hoch/Quer, Schriftfeld). TopBar-Button
|
||||
„PDF" + `src/ui/ExportPdfDialog.tsx`. Verifiziert: 522 Pfade, 0 Rasterbilder, maßstabsgenau
|
||||
(5,345×4,345 m → 53,45×43,45 mm @1:100). `scripts/probe-pdf.mjs`.
|
||||
- **2D-Boolean (Union/Differenz/Schnitt):** `src/editors/booleanOps.ts` (polygon-clipping),
|
||||
Kontextmenü „Vereinigen/Subtrahieren/Schneiden" + Ctrl/Cmd+Shift+U/D/I; geschlossene
|
||||
Drawing2D, Differenz=erstes Element minus Rest, Loch→Außenring gefüllt + Innenringe als
|
||||
Umriss. Verifiziert (Union 8/Differenz 6/Schnitt 4 Ecken). `scripts/probe-boolean.mjs`.
|
||||
- **3D-1 — ERSTELLEN in 3D (Meilenstein):** der 3D-Viewport ist Erstellungs-Fläche;
|
||||
dieselben Befehle (line/wall/rect/polyline) erzeugen Elemente direkt in 3D. Raycast auf
|
||||
Geschoss-Arbeitsebene (`THREE.Plane` y=gridElevation) → Modell-`Vec2` (model.x=P.x,
|
||||
model.y=P.z, aus `addDrawing2DLines` abgeleitet) → dieselbe `CommandEngine`
|
||||
(`move/pick/confirm`). Live-3D-Draft (Rubber-Band + Snap-Marker, eigene `draftGroup`),
|
||||
OrbitControls bei aktivem Befehl umgeschaltet, `pxPerMeter`-Fallback 90. Viewport3D-Props
|
||||
`commandActive`/`onWorkplanePoint`/`onWorkplaneConfirm`/`draft`. Verifiziert (Wand in 3D →
|
||||
in 3D + Plan sichtbar). `scripts/probe-3d-create-*.png`.
|
||||
- **3D-2 — EDITIEREN in 3D (Meilenstein):** 3D-Griffe (eigene `gripGroup`) bei genau 1
|
||||
Auswahl + `!commandActive`. Wand: Endpunkt-Kugeln (Achse), Höhen-Griff (OK via
|
||||
`updateWall top:{mode:"custom",z}`), Verschiebe-Griff; Drawing2D: Vertices. Drag raycastet
|
||||
auf Arbeitsebene bzw. vertikale Ebene → DIESELBEN Store-Actions wie Plan-Griffe
|
||||
(`moveGripOf`/`moveElementByOf`/`updateWall`) → Plan+3D konsistent. Laufender Drag in
|
||||
`gripDragRef` (übersteht den Szenen-Neuaufbau pro Move). Verifiziert (Endpunkt + Höhe in 3D
|
||||
editiert, im Plan deckungsgleich). `scripts/probe-3d-edit-*.png`. **Offen (→ Editier-Welle 3):
|
||||
3D-Snap beim Drag + Griffe „immer oben" (Tiefentest).**
|
||||
- **Zwei-Ton-Dark-Theme + KEIN Petrol-Grün mehr:** `src/styles.css` Dark-Tokens neutral
|
||||
(`--bg #0e0e0e`, `--panel #1d1d1d`, `--accent #4d4d4d` …); Kontextmenü-Tokens
|
||||
`--ctx-hover/-text` neutral; grüner Fokus-Ring entfernt.
|
||||
- **Topbar DOSSIER-Stil:** Icon-Grid 4-oben/3-unten (Grundriss integriert), Kombis
|
||||
gestapelt, kompakte Selects. `src/ui/TopBar.tsx`.
|
||||
- **Themed Dropdowns** statt nativer `<select>`: `src/ui/Dropdown.tsx` (Wert- + Aktions-
|
||||
Modus, Popover im Kontextmenü-Stil, kein blaues OS-Menü). In TopBar verdrahtet.
|
||||
- **Kontextmenü/Dropdown-Politur:** Material-Symbols-Icon-Font in `index.html` geladen
|
||||
+ `.material-symbols-outlined`-Regel in styles.css (sonst Roh-Ligaturtext); Größen
|
||||
runterskaliert (`.ctx-menu` 11.5px, Icons 13–14px, weniger Padding, Radius 10).
|
||||
- **Geschoss-Z-Raster:** `Viewport3D` Bodenraster auf `gridElevation` (= aktives
|
||||
Geschoss `baseElevation`).
|
||||
- **DXF/DWG-Import als DIALOG + Drag&Drop:** `src/ui/ImportDialog.tsx`,
|
||||
`src/io/dxfToDrawings.ts`; Ziel-Zeichnungsebene wählen / neue „Zeichnung" anlegen;
|
||||
Layer→Kategorie-Handling; App-weiter Drop-Handler.
|
||||
- **DWG in-app parsen:** `@mlightcad/libredwg-web` (WASM, installiert), `src/io/dwgParser.ts`
|
||||
(lazy, mappt DwgDatabase→{meshes,contours}), `vite.config.ts`-Aliase für WASM
|
||||
(`virtual:libredwg-glue` + `?url`), `src/io/libredwg-web.d.ts`. Gegen echte DWGs getestet
|
||||
(11.668 Konturen). Fallback→ODA-Hinweis bei Lib-Fehler.
|
||||
- **Wand-Objekt-Info-Attribute** (additiv, abwärtskompatibel): Modell `Wall.referenceLine`
|
||||
(`left|center|right`, default center) + `Wall.bottom/top: VerticalAnchor`
|
||||
(`{mode:"floor",floorId,offset?}|{mode:"custom",z}`) in `src/model/types.ts`; Resolver
|
||||
`src/model/wall.ts` (`wallReferenceOffset`, `wallVerticalExtent`, `nextFloorAbove`);
|
||||
angewandt in `generatePlan`, `model/joins.ts`, `Viewport3D`. Panel
|
||||
`src/panels/ObjectInfoPanel.tsx`; `src/state/selectionInfo.ts` `Selection.wall: WallInfo`;
|
||||
Host-Setter + `projectSlice` `updateWall`/`setWallThickness`. Einschichtig = 1-Layer-WallType.
|
||||
- **Panel-Text:** `.attr-*`/`.objinfo-*` Untertitel fett + Kontrast (`--label` statt `--muted`).
|
||||
- **Selektion-Überarbeitung:** `selectedDrawingId`→`selectedDrawingIds[]`
|
||||
(`selectionSlice`); Marquee wählt **Linien** (`marqueeHitDrawings`), **Ctrl/Cmd+A**,
|
||||
**Multi-Delete**, **Multi-Highlight** (`.plan-sel-draw`), **Shift-Klick-Mehrfachauswahl**
|
||||
(`PlanSelection.shift` → `onPlanSelect` toggelt).
|
||||
|
||||
- **Editier-Welle 2 — Split/Join/Segment-Löschen (FERTIG & verifiziert):** Ctrl+S Split
|
||||
(Rechteck→zwei geschlossene; nur bis Schnittpunkt), Ctrl+J Join (koinzidente Enden),
|
||||
Alt+Klick=Segment löschen (Schere). `kernel2d.ts` (`splitPolylineAtParam`/
|
||||
`splitClosedByChord`/`removeSegment`/`splitAtIntersections`/`joinChains`; Tests 16–23,
|
||||
25 ok), `src/editors/splitJoin.ts`, App-Keydown (Ctrl/Cmd+S/J, `preventDefault`) +
|
||||
`onSegmentCut`, PlanView Alt-Klick. Verifiziert (Rechteck→2 geschlossene, Join→1
|
||||
Polylinie, Alt→Segment weg). `scripts/probe-splitjoin.mjs`. Wände bleiben no-op.
|
||||
- **Editier-Welle 3 + Eingabe-Vereinheitlichung + Snap-Feinschliff (FERTIG):** 3D-Drag-Snap
|
||||
(gleiche `computeSnap`) · Griffe/Marker immer-oben · koinzidente Endpunkt-Griffe ziehen
|
||||
gemeinsam (Viewport3D). **GUI-Werkzeug** (line/polyline/rect) → `engine.start` +
|
||||
Befehlsfeld-Fokus; **Shift = H/V-Ortho** beim Griff-Drag (2D+3D, `applyAngleConstraint`
|
||||
aus `tools/snapping`). OFFEN: „Wand" noch Legacy (Wand-als-Command); Editieren läuft noch
|
||||
AD-HOC, nicht durch die Engine → Tab-Wert + Referenz-Tracking offen. Snap-Marker
|
||||
vereinheitlicht: 2D Endpunkt=Kreis/Mittelpunkt=Strich, 3D-Snap=`#ffcc44`, Marker kleiner;
|
||||
2D-Vorschaulinie grau-Volllinie.
|
||||
- **DWG-Parser erweitert (FIX „keine Geometrie gefunden") + Trim (FERTIG):** `io/dwgParser.ts`
|
||||
neu — ARC/CIRCLE/ELLIPSE/SPLINE + **INSERT/Block-Expansion** (rekursiv, aus
|
||||
`db.tables.BLOCK_RECORD.entries`, volle 2D-Affin-Transform) + Diagnose-Histogramm
|
||||
(`DxfImportResult.diagnostics`, in ImportDialog). Real-DWG 37 INSERT → 67k Konturen.
|
||||
**Trim** (`commands/cmds/trim.ts`, Alias `tr`, `kernel2d.trimPolyline`) — Quick-Trim bis
|
||||
Schnittpunkt. Beide verifiziert.
|
||||
- **Material-Bibliothek (Daten, FERTIG):** 12 CC0-Materialien von ambientCG →
|
||||
`public/assets/materials/<id>/{color,normal,roughness,displacement,ao}.jpg` + `manifest.json`
|
||||
+ `src/materials/library.ts` (PBR).
|
||||
- **Welle A — 3D-Materialien (FERTIG & verifiziert):** `Component.material` (`ComponentMaterial`:
|
||||
PBR-Map-URLs + `sizeM` Kachelgröße), `src/materials/runtime.ts` (`MaterialRuntime` → gecachte
|
||||
`MeshStandardMaterial`, Kacheln in Welt-Metern via Extrude-UVs), `renderMode` „textured"
|
||||
(TopBar-Darstellung), Material-Picker (ambientCG-Grid) + Upload + Kachelgröße im
|
||||
`ResourceManager` (Bauteile-Tab). Backstein mit Normal-Map-Tiefe verifiziert.
|
||||
`scripts/probe-materials.mjs`. Schnittschraffur-Slot frei für Welle B.
|
||||
- **Editier-Eingabe + Topbar-Politur (FERTIG):** Befehlszeile zeigt **Live-Länge/-Winkel**
|
||||
beim Zeichnen (`Command.fieldValues`), **Cursor-HUD entfernt**; **Seiten-Verschieben
|
||||
propagiert verbundene Enden** (2D+3D); **Tab beim Vertex-Editieren** setzt Wert
|
||||
(`gripEdit`-Controller in App — noch ad-hoc, NICHT durch die Engine). Topbar: Rest
|
||||
iconisiert (Referenzlinien/Linien-Modus/Ressourcen), Linien-Modus → Dropdown, Aktions-
|
||||
Buttons als Dropdown-Pill-Familie, **Detailgrad+Massstab gestapelt + 1:N/Zoom% kompakt**
|
||||
(`.tb-zoomstack`).
|
||||
- **Wand-als-Command (FERTIG & verifiziert):** `commands/cmds/wall.ts` (chainend, Achs+Band-
|
||||
Draft, Tab-Felder; Wandfelder aus `CommandContext.activeWallTypeId`/`level`) + Registry
|
||||
(Aliase `w`/`wand`) + App-Kopplung (`TOOL_COMMAND.wall`, Legacy-Wand umgeleitet). Wände in
|
||||
2D+3D. `scripts/probe-wall-cmd.mjs`.
|
||||
|
||||
**OFFENE WELLEN (nächste Schritte):** Referenzobjekt-Achs-Tracking + Editieren VOLL durch die
|
||||
Engine (heute `gripEdit` ad-hoc); **Welle B** (3D-Clip/Schnittebene + Stencil-Capping +
|
||||
Schnittschraffur — nutzt das Material-System aus Welle A); **Welle C** (3D→2D-Schnitt/
|
||||
Perspektive via HLR, opencascade.js-Spike); **Swisstopo-Importer** (Ort+Radius → Gebäude+
|
||||
Gelände, geo.admin STAC/Geocode, CORS prüfen); Bögen/Kreise-Trim; Wandstärke-Tab bei
|
||||
einschichtigen Wänden.
|
||||
|
||||
**Offene Wellen (Todo-Spiegel — Todo-Liste lebt im Kontext):**
|
||||
1. Editier-Welle 3: **koinzidente Endpunkt-Griffe gemeinsam ziehen** + verbundene Enden
|
||||
propagieren beim Seiten-Verschieben (topologisch, das Tiefste).
|
||||
2. **GUI-Werkzeug ↔ Befehlszeile koppeln**: Werkzeug-Klick startet denselben Command →
|
||||
Werte unten eintippbar; danach **Cursor-Wert entfernen/optional**.
|
||||
3. **Selektions-Direkt-Edit**: Länge/Winkel des gewählten Elements im Befehlsfeld (Tab-Zyklus).
|
||||
4. **Swisstopo/openbureau-Importer-Dialog** (Ort+Radius). Fluss steht: Geocode
|
||||
`api3.geo.admin.ch/.../SearchServer` (sr=2056→LV95) → ±Radius → WGS84-bbox → STAC
|
||||
`data.geo.admin.ch/api/stac/v1`, Collections `ch.swisstopo.swissalti3d` +
|
||||
`ch.swisstopo.swissbuildings3d_3_0` → Download → TIN/Mesh→Kontext. LV95↔WGS84 nötig;
|
||||
**Browser-CORS** das Risiko. Referenz: DOSSIER `rhino/swisstopo.py`.
|
||||
5. **3D→2D-Dokument**: live + statisch (MVP-Kantenprojektion → HLR) für Vektor-PDF.
|
||||
Hinweis: `generatePlan` liefert SCHON Vektoren (SVG) → Plan-PDF bereits vektoriell.
|
||||
6. **Topbar-Rest iconisieren** (Zoom/Referenzlinien/Linien-Modus/Ressourcen).
|
||||
7. **Wand als Command** (+ Wandstärke-Tab nur bei einschichtig) + **Trim**.
|
||||
|
||||
**Resume-Checkliste:** 1. CONVENTIONS.md + diesen HANDOVER + Memory lesen. 2. `npx tsc -b` &
|
||||
`npm run build` (grün?). 3. `npm run dev -- --port 5187 --strictPort` (User schaut :5187).
|
||||
4. Split/Join-Stand prüfen (git diff, Probe). 5. Arbeitsweise: **autonom, keine
|
||||
Rückfragen** (User hat Vollrechte), substanzielle Arbeit an Subagenten delegieren,
|
||||
jede Änderung per `tsc`+`build`+Screenshot verifizieren. `node scripts/probe.mjs`.
|
||||
|
||||
---
|
||||
|
||||
## >>> STAND 2026-06-30 (Über-Nacht-Run) — älter <<<
|
||||
Großer autonomer Build-Lauf. Alles unten ist tsc + build GRÜN und per Screenshot
|
||||
verifiziert (sofern nicht „läuft" markiert). Befehlssystem-Bauplan:
|
||||
`docs/design/rhino-command-system.md`.
|
||||
|
||||
**Fertig & verifiziert:**
|
||||
- **Rhino-Befehlssystem (Tier 0 + Tier 1 Zeichnen):** `src/commands/` — `types.ts`
|
||||
(Command-Interface), `engine.ts` (State-Machine + lastCommand + Eingabe-Routing),
|
||||
`parseInput.ts` (`5,3`·`r5,3`·`5<45`·`<45`·nackte Zahl), `registry.ts` (+Aliase
|
||||
`l/pl/rec/c`), `cmds/{line,polyline,rect,circle}.ts`. UI: `src/ui/CommandLine.tsx`
|
||||
(über der Statusleiste, Tab fokussiert). Befehle: **Line, Polyline (Close/Undo),
|
||||
Rectangle, Circle** — alle mit getippten Koordinaten, per Probe gezeichnet.
|
||||
Koexistenz mit Alt-Tools (Werkzeugleiste). `scripts/probe-command-line.mjs`,
|
||||
`probe-draw-commands.mjs`.
|
||||
- **Tab-Feld-Zyklus** (Rhino-Präzision §2.7): beim Zeichnen Tab durch Felder
|
||||
(Linie/Polylinie: Länge→Winkel; Rechteck: Breite→Höhe; Kreis: Radius). Zahl lockt
|
||||
das aktive Feld, ungelockte folgen der Maus. Additive optionale Command-Methoden
|
||||
`fields()`/`pointFromFields()` + Engine-Feldzustand + CommandLine-Boxen. Verifiziert
|
||||
exakt (3 m/45°, Rechteck 4×2). `scripts/probe-tab-fields.mjs`.
|
||||
- **Tier-1-Editierbefehle:** **Move** (Auswahl verschieben), **Copy** (wiederholend
|
||||
duplizieren), **Offset** (parallele Kurve via `kernel2d.offsetPolyline`, persistente
|
||||
Distanz, Seite per Klick) — `cmds/{move,copy,offset}.ts`, Aliase `m/cp/o`. `CommandContext`
|
||||
um `selection` erweitert (App `host.context()` füllt sie aus dem Store). Offset verifiziert
|
||||
(paralleler Versatz exakt 0.5 m). `scripts/probe-offset2.mjs`.
|
||||
- **2D-Geometrie-Kernel** `src/geometry/kernel2d.ts` — Offset/Trim/Extend/Fillet +
|
||||
Segment-/Linien-/Kreis-Schnitt + Fläche/Wicklung. 16/16 Unit-Checks
|
||||
(`scripts/test-kernel2d.ts`, via `npx esbuild … | node`). Basis für Tier-1-Editierbefehle.
|
||||
- **Kontext/Gelände Phase 1** (paralleler Strang): `src/io/dxfParser.ts` (DXF: 3DFACE/
|
||||
MESH/POLYLINE→Mesh, LWPOLYLINE/LINE→Konturen; DWG bewusst nur via ODA→DXF-Hinweis),
|
||||
`src/model/terrain.ts` (`generateTerrainFromContours` → TIN via delaunator),
|
||||
`src/state/siteSlice.ts` (`addContextObject`/`generateTerrain`/…), `Project.context`
|
||||
(ContextObject = ImportedMesh|ContourSet|TerrainMesh, „dumme" Kontext-Geometrie, NICHT
|
||||
semantisch), `Viewport3D` rendert Kontext/Terrain. Deps `dxf-parser`+`delaunator`.
|
||||
- **Echte Isometrie (Orthographic-Kamera):** `Viewport3D` schaltet front/top/side/iso auf
|
||||
OrthographicCamera (parallele Projektion), Perspektive bleibt perspektivisch. Verifiziert.
|
||||
- **Plan-Tinte-Fix:** 2D-Linien waren unsichtbar (CSS `.draw2d{stroke:var(--ink)}` hell auf
|
||||
hellem Papier). CSS-stroke entfernt + dunkler Fallback in generatePlan → 2D dunkel sichtbar.
|
||||
- **Bugfixes:** 2D-Füllungen (rect/polyline rendern Fill+Schraffur+`fillColor`); Kanten-Griff-
|
||||
Dreiecke sitzen auf der Linie + inkrementelles Dragging (keine Akkumulation); Plan-View
|
||||
wandert nicht mehr mit (fixer Welt-Ursprung, Reset nur bei Geschoss-Wechsel via `resetKey`).
|
||||
|
||||
**LÄUFT gerade (Agent):**
|
||||
- **Tab-Feld-Zyklus** (Nutzer-Wunsch): beim Zeichnen Tab durch Felder (Länge→Winkel→
|
||||
Breite/Höhe/Radius), Zahl lockt Feld, Rest folgt Maus. Additive optionale Command-Methoden
|
||||
`fields()`/`pointFromFields()` + Engine-Feldzustand + CommandLine-Boxen.
|
||||
|
||||
**NÄCHSTE SCHRITTE (Reihenfolge):**
|
||||
1. Tab-Feld-Zyklus verifizieren (Agent-Gate).
|
||||
2. **Tier-1-Editierbefehle:** Move/Copy/Offset(nutzt kernel2d)/Trim/Split/Join/Explode.
|
||||
Undo/Redo-Stack im Store erwägen (Commits laufen über `setProject`).
|
||||
3. **Terrain-Integration:** Konturen im Plan (generatePlan/PlanView), Import-/Terrain-Befehle
|
||||
+ Site-Panel (`src/panels/`) + i18n + DWG→DXF-Hinweis-UI; `.dxf`-File-Picker → `parseDxf`.
|
||||
4. **Wand-Stärke-Feld:** Wand ist noch ein Alt-Tool — als Command portieren, dann Tab-Feld
|
||||
`thickness` NUR bei einschichtigen/freien Wänden (mehrschichtige Typen: Stärke vorgegeben).
|
||||
5. Tier 2 (Rotate/Scale/Mirror/Arc/Fillet/Array/Group/Gumball2D), Tier 3 (ExtrudeCrv/Box/
|
||||
PushPull + analytische Wand-Öffnungen, §3.0 Bauplan).
|
||||
|
||||
**Gotchas (frisch gelernt):**
|
||||
- Ein Prozess-Neustart killt laufende Hintergrund-Agenten (Status `failed`); nach
|
||||
jedem Wiederaufnehmen `src/commands/`-Existenz + `npx tsc -b` prüfen, Abgestürztes neu starten.
|
||||
- Inline `{...s, feld}` als Tupel-Return löst TS-Excess-Property-Check aus → erst typisierte
|
||||
Variable (siehe cmds/line.ts-Muster).
|
||||
- Ein Subagent ist bei diesem Befehls-Build geflaket (spawnte Research statt zu bauen) →
|
||||
verzahnte/quer schneidende Arbeit an einen fähigen Subagenten delegieren oder selbst machen.
|
||||
|
||||
---
|
||||
|
||||
## Was das ist
|
||||
Standalone-**Browser-BIM** (React+TS+Vite+Three.js) für Wohnbau — die Browser-Variante
|
||||
des Rhino-Plugins **DOSSIER** (Referenz-Repo, public/klonbar: https://git.kgva.ch/karim/DOSSIER;
|
||||
falls weg: neu klonen nach `scratchpad/DOSSIER`). Prinzip: **ein semantisches Modell →
|
||||
alle Sichten (Plan/3D/Schnitt) abgeleitet**, Darstellung erst beim Rendern.
|
||||
|
||||
## Aktueller Stand (tsc + build GRÜN)
|
||||
Funktioniert & verifiziert: semantisches Modell · **mehrschichtige Wände** mit
|
||||
**L-Ecken-Gehrung** · Tür mit Öffnung + Schwenkbogen · **Dokumentmodell** (Zeichnungsebenen
|
||||
= Geschosse+Schnitte/Ansichten/Zeichnungen; Ebenen = Kategorie-Baum, DOSSIER-Codes 1:1) ·
|
||||
**3D zeigt EG+OG gestapelt** · **Resource-Manager** (Component/Hatch/Line, als Fenster-Overlay) ·
|
||||
**Panel-System** (Docks links/rechts, Tabs, 5 Anzeige-Modi) · **Top-Bar + Footer** (Massstab
|
||||
echt 1:N, Detailgrad, Render-Modus Schattiert/Draht/Kanten, Referenzlinien, Cursor X/Y live) ·
|
||||
**Maus:** Mitte=Pan/Orbit, Links=Auswahl(+Marquee), Rechts=eigenes Kontextmenü · **Native-App**
|
||||
(kein Browser-Rechtsklick/Textauswahl) · **i18n** (`src/i18n/`, de+en, `t('key')`) ·
|
||||
**aktive Zeichenwerkzeuge** (Wand/Linie/Polylinie/Rechteck + Snapping endpoint/midpoint/
|
||||
intersection/onEdge/grid/ortho mit Fang-Menü, Live-Vorschau+HUD — `src/tools/`, Phase 1+2).
|
||||
|
||||
### Gerade fertig & verifiziert (2026-06-29)
|
||||
**Aktive Zeichenwerkzeuge — Phase 1 + 2** (`docs/design/drawing-tools.md`): Tool-System in
|
||||
**`src/tools/`** (types · snapping · tools/registry) + Verdrahtung in PlanView/App/TopBar/
|
||||
generatePlan. Per probe verifiziert (`scripts/probe-tools.mjs`, `probe-line.mjs`, `probe-phase2.mjs`):
|
||||
- **Werkzeugleiste** (TopBar): Auswahl/Wand/Linie/**Polylinie/Rechteck** + Wandtyp-Dropdown +
|
||||
**Fang-Menü** (Häkchen je Snap-Art, Rasterweite, Winkelraster — `position:fixed`, da Topbar clippt).
|
||||
Nur im Grundriss aktiv.
|
||||
- **Wand-Werkzeug**: Achs-Polylinie → je Segment ein `Wall`; Live-Band-Vorschau + HUD (Länge·Winkel);
|
||||
Rechts-/Doppelklick/Enter committet; **Gehrung automatisch aus `computeJoins`** (mehrschichtiger L-Stoss).
|
||||
- **2D-Werkzeuge**: Linie (2-Klick), **Polylinie** (Klick auf Start schließt, Doppel-/Rechtsklick beendet
|
||||
offen), **Rechteck** (zwei Ecken) → `Drawing2D` (line/polyline/rect); in `generatePlan` abgeleitet
|
||||
(`addDrawing2D`, `color` am line-Primitiv, Bounds erweitert).
|
||||
- **Snapping**: endpoint · midpoint · intersection · onEdge(Lot) · grid · ortho(Shift); Prioritäts-
|
||||
gewichtet (`PRIORITY` in snapping.ts); Bildschirm-Marker je Art (Quadrat/Dreieck/✕/Raute/Punkt) +
|
||||
Ortho-Hilfslinie; Ctrl = Fang aus. Einstellbar über das Fang-Menü (`snap` State in App).
|
||||
- **Tastatur**: Esc verwirft/zurück-zu-Auswahl, Enter committet, Backspace nimmt Punkt zurück.
|
||||
- Modell: `Drawing2D` + `Project.drawings2d` (types.ts), `Element` erweitert. i18n de+en (`tool.*`,`snap.*`).
|
||||
- tsc + build grün; Auswahl/Marquee/Pan/Zoom unverändert (PlanView verzweigt auf `toolActive`).
|
||||
|
||||
**Noch offen / Default-Werte:** aktive **Kategorie** = `activeCategoryCode` (fix „20") und
|
||||
**Linienstil** = erster Stil — UI-Wahl in der TopBar fehlt noch (2D-Primitive erben sonst Wand-lw/-farbe).
|
||||
|
||||
**Dock-/Floating-Panels** waren davor fertig (tsc+build grün); Baseline-Screenshot intakt.
|
||||
|
||||
### Ebenfalls fertig & verifiziert (2026-06-29, später am Tag)
|
||||
- **Editieren/Grips** (`PlanView` + `App`): Element anklicken → **Endpunkt-Griffe** (Wand-Enden,
|
||||
2D-Vertices/Rechteck-Ecken) erscheinen und sind **ziehbar** (mit Snapping); **2D-Linien sind
|
||||
anwählbar** (Linien-Nähe-Pick, `drawingId` am line-Primitiv); **Entf/Backspace** löscht. Wand-
|
||||
Gehrung folgt live. `moveGrip`/`drawingVertices` in App.
|
||||
- **Shift = Ortho** beim Griff-Ziehen (Bezug = Nachbar-Vertex; H/V bzw. Winkelraster).
|
||||
- **Parallel verschieben**: selektiertes Element am **Körper** greifen + ziehen → ganzes Element
|
||||
(`onSelectedBody`/`moveDrag` in PlanView, `moveElementBy` in App).
|
||||
- **Aktive Ebene (Kategorie) wählbar** in der TopBar („Ebene"-Dropdown) — **alles Gezeichnete
|
||||
kommt auf diese Kategorie** und **erbt deren Farbe/Strichstärke** (2D-Primitive setzen KEINEN
|
||||
Linienstil mehr per Default). Statusleiste zeigt die aktive Ebene. `activeCategoryCode` State.
|
||||
- **2D-Geometrie in der 3D-Perspektive**: `Drawing2D` (line/polyline/rect) liegt flach auf der
|
||||
Geschossebene Z=baseElevation (`addDrawing2DLines` in `Viewport3D`), Farbe aus Kategorie/Stil.
|
||||
(Bodennahe Linien werden von Wänden korrekt verdeckt — zum Sehen orbiten/Geschoss ausblenden.)
|
||||
- Probes: `probe-grips.mjs`, `probe-3d2d.mjs`, `probe-edit2.mjs`. tsc + build grün.
|
||||
|
||||
### Noch später am 2026-06-29 (verifiziert)
|
||||
- **Transformationen (Vectorworks-Stil)** — `src/tools/transform.ts` + `src/ui/TransformBar.tsx` +
|
||||
App. Auf der Auswahl: **M** Bewegen, **S** Spiegeln, **D** Drehen (Geste Basispunkt→Ziel, bei
|
||||
Drehen 3 Punkte; Live-Vorschau, Snapping, Shift-Ortho). **Modusleiste U/I/O/P**: U bewegen ·
|
||||
I Kopie · O N Kopien (Anzahl-Prompt) · P verteilen. Probe `probe-transform.mjs`/`probe-array.mjs`
|
||||
(move/copy/array verifiziert). Routing via `toolInputActive`-Prop in PlanView.
|
||||
- **Aktive Ebene per Klick im Ebenen-Panel** (statt TopBar-Dropdown): `LayersPanel`-Zeile klicken →
|
||||
`host.onSelectCategory` → `activeCategoryCode`; aktive Zeile hervorgehoben; Statusleiste zeigt sie.
|
||||
TopBar-Ebene-Dropdown entfernt. (probe-layer.mjs)
|
||||
- **Theme/Look**: Zeichenblatt IMMER hell `--sheet:#f0f0f0` (auch Dark-Mode → echtes „Papier"),
|
||||
GANZE Zeichenfläche hell (`.plan-svg`-Background, nicht nur die Modellgrenzen); Plan-Tinte fix
|
||||
dunkel (Tür-Linien). Dark-Theme etwas abgedunkelt (Panels #1c1c1c). **3D-Orbit ohne Nachlauf**
|
||||
(`enableDamping=false` — kontrollierter).
|
||||
|
||||
### Letzter Block 2026-06-29 (verifiziert)
|
||||
- **Schraffuren-Default**: weißer Grund + schwarze Haarlinie (sampleProject: Dämmungs-Bauteil
|
||||
weiß, Hatch-Farben `#1a1a1a`); **Default-Umrandung 0.18 mm** (`WALL_FALLBACK_MM`, addCategory).
|
||||
- **Stiftstärken-Vorgabe** `PEN_WEIGHTS` (0.02…2.0) als `<datalist>` im Linienstil-Editor.
|
||||
- **Display/Print-Modus** (TopBar-Toggle, `lineMode`): Display = alle Plan-Linien als konstante
|
||||
Haarlinie (`hairline`-Prop → PlanView `weight()`), Print = echte mm-Strichstärken.
|
||||
- **`.lin`/`.pat`-Import**: Parser `src/io/linParser.ts`/`patParser.ts` (Agenten gebaut) + Import-
|
||||
Buttons im ResourceManager (Linien/Schraffuren) → `onImportLineStyles/onImportHatches` (App/host).
|
||||
.lin voll (Dash); **.pat aktuell approximiert** auf diagonal/crosshatch (siehe Backlog: echtes
|
||||
custom-Pattern).
|
||||
- **Werkzeug-Palette** (`src/panels/ToolsPanel.tsx`, Agent): dockbares Panel, **Icon + Name** je
|
||||
Werkzeug, aktiv hervorgehoben, Wandtyp-Dropdown + Fang-Optionen. Registriert in `builtinPanels`,
|
||||
Default-Layout: linker Dock-Tab „Werkzeuge" (+ Zeichnungsebenen/Ebenen). **`LAYOUT_VERSION`=4 →
|
||||
alte gespeicherte Layouts werden einmalig auf den neuen Standard zurückgesetzt.**
|
||||
- Probes: `probe-display.mjs`, `probe-import.mjs`, `probe-tools-panel.mjs`. tsc + build grün.
|
||||
|
||||
## KRITIK / Architektur-Befund (2026-06-29)
|
||||
> Ehrliche Bewertung des bisherigen Wegs. Für die nächste Instanz als Entscheidungsgrundlage.
|
||||
|
||||
**Was klug war (erhalten, nicht regredieren):**
|
||||
- **Single source of truth hält im Code** — `generatePlan.ts` *und* `Viewport3D.tsx` nutzen
|
||||
dieselbe `computeJoins`/`clippedBand` aus `src/model/`. Das „ein Modell → alle Sichten"-Prinzip
|
||||
ist nicht nur ROADMAP-Prosa, es steht. Beim Refactor diese Trennung Modell↔Sicht bewahren.
|
||||
- **Grundriss analytisch** aus Parametern statt Mesh-Schnitt (Weg A) — richtig.
|
||||
- **Bewusst leichter Stack** (Three.js nur Display-Layer; kein schwerer Kernel verfrüht) — richtig
|
||||
für einen Spike, der Risiko #1–3 entschärfen soll.
|
||||
- i18n via `t()` wird eingehalten; `tsc -b` ist grün.
|
||||
|
||||
**Hauptkritik (zu beheben):**
|
||||
1. **God-Component gegen eigene Regel.** `App.tsx` ist **~2461 Zeilen mit ~29 `useState`**.
|
||||
CONVENTIONS.md fordert wörtlich „dünner Shell, keine Geschäftslogik". Das ist verletzt.
|
||||
2. **Kein Store.** `src/state/` existiert NICHT, obwohl CONVENTIONS.md Store+Slices
|
||||
(project/selection/view/layout) vorschreibt. Zustand = lokale Hooks → Prop-Drilling.
|
||||
- Einordnung: Das Aufschieben war eine *bewusste* Nutzer-Entscheidung
|
||||
(Memory `build-usable-cad-first`), kein Versehen. **Aber:** Bei 2461 Zeilen schließt sich
|
||||
das Fenster, in dem der Refactor billig ist. Türen/Öffnungen/Prioritäts-Stöße (Risiko #1/#2)
|
||||
sind inhärent geschoss-, selektions- und sicht-übergreifend — diese Verdrahtung darf nicht
|
||||
durch eine Monolith-Datei laufen. **Empfehlung: Store ziehen VOR der nächsten Feature-Welle.**
|
||||
|
||||
**Befund (separat angehen, nach Refactor):** Die harten, roadmap-markierten 🔴-Risiken sind noch
|
||||
unbewiesen — `Door` existiert als Typ, aber **kein echter 3D-Boolean** (Öffnung = nur Plan-Lücke);
|
||||
**HLR** für Schnitte/Ansichten fehlt; **Prioritäts-T-Stöße** offen. Das einfache Drittel ist bewiesen,
|
||||
das schwere aufgeschoben. Die Frage „ist es wirklich CAD?" entscheidet sich erst, wenn diese landen.
|
||||
|
||||
## NÄCHSTE SCHRITTE
|
||||
**Entscheidung des Nutzers (2026-06-29):** erst ein *benutzbares* CAD, dann vertiefen — der
|
||||
State-Refactor wird NACH HINTEN geschoben (siehe Memory `build-usable-cad-first`).
|
||||
> ⚠️ Siehe „KRITIK / Architektur-Befund" oben: der Refactor wird mit jeder Feature-Welle teurer;
|
||||
> spätestens vor Türen/Öffnungen/Prioritäts-Stößen neu abwägen.
|
||||
|
||||
### ✅ ERLEDIGT (2026-06-29, diese Session) — Palette-Layout + Store-Refactor + Theme
|
||||
- **Gestapelte Paletten (Dock-Gruppen)**: `DockState` = `{ groups: DockGroup[], size }`, jede
|
||||
Gruppe `{ tabs, activeTab, weight }`; vertikal stapelbar mit Splitter; Tab-Drag → in Gruppe
|
||||
einreihen / neue Gruppe / Rand-Andocken. `LAYOUT_VERSION=6`. (`types.ts`/`layout.ts`/`Dock.tsx`/
|
||||
`TabStrip.tsx`/`panelDrag.tsx`/`App.tsx`.) Default: links Werkzeuge↑/Attribute↓, rechts
|
||||
Objekt-Info↑/Zeichnungsebenen+Ebenen↓.
|
||||
- **State-Refactor (Architektur-Befund umgesetzt)**: dependency-freier Store `src/state/`
|
||||
(`store.ts` useSyncExternalStore + Slices `projectSlice`/`selectionSlice`/`viewSlice`/
|
||||
`layoutSlice`, kombiniert in `appStore.ts`). App.tsx 2710→~2030 Zeilen, verhaltensgleich
|
||||
(tsc+build+Screenshot identisch). **Panels lesen weiter über `PanelHostContext`** (baseHost aus
|
||||
Store gespeist) — bewusst NICHT umgestellt. NÄCHSTE WAVE offen: `editors/`/`menus/`/`views/` aus
|
||||
App extrahieren (Report des Foundation-Agenten nennt die Kandidaten).
|
||||
- **Selektions-Kontrakt** (`src/state/selectionInfo.ts` + host.ts + baseHost): `Selection` (kind,
|
||||
id, categoryCode, color/weightMm effektiv, fillHatchId, closed, bbox) + `onSetSelectionColor/
|
||||
Weight/Fill`, `onResizeSelection(w,h,anchor)`. Modell: `Wall.color?`, `Drawing2D.weightMm?`
|
||||
ergänzt; generatePlan wendet beide an. Wand = Weight/Fill bewusst No-op (erbt aus Ebene).
|
||||
- **Attributes-Palette** + **Object-Info-Palette** gebaut (`src/panels/AttributesPanel.tsx`,
|
||||
`ObjectInfoPanel.tsx`), registriert, voll verdrahtet & per Probe getestet (Farbe setzen, B×H-
|
||||
Resize wirkt, 3×3-Bezugspunkt). Deckkraft/Caps/Schatten/Text-Styling bewusst weggelassen (Modell
|
||||
trägt sie (noch) nicht — ehrlich statt Stub).
|
||||
- **Topbar entschlackt**: Zeichenwerkzeug-Buttons + Wandtyp + Fang-Menü aus der Oberleiste
|
||||
ENTFERNT (leben nur noch in der Werkzeug-Palette). `SnapMenu`/`SNAP_TOGGLES` aus `TopBar.tsx` raus.
|
||||
- **Dark-Theme vertieft** (`styles.css`): gestufte Elevations-Tokens (`--bg`<`--panel`<`--panel-2`,
|
||||
`--input` versenkt) statt flachem Einheitston; Oberleiste/Panel-Köpfe angehoben, tiefere Schatten.
|
||||
`--sheet` bleibt hell. Siehe Memory `ui-depth-dark`.
|
||||
- **CSS-Altlast behoben**: ein `*/` in einem Kommentar (`.nav-*/.res-*`) hatte die ganze `.dock`-
|
||||
Regel verschluckt (`display:flex` nie aktiv) — gefixt.
|
||||
|
||||
### Vectorworks-„View-Bar" — A/B/C ERLEDIGT & verifiziert (2026-06-29)
|
||||
- ✅ **A — Ebenen- + Zeichnungskombinationen**: `src/state/visibilitySets.ts` (localStorage
|
||||
`cad.layercombos.<name>` / `cad.drawingcombos.<name>`), Store-Actions `snapshot*/apply*Visibility`
|
||||
in `projectSlice`, zwei `ComboMenu`-Dropdowns in `TopBar` (Muster wie `LayoutMenu`). Round-Trip
|
||||
per Probe verifiziert. (Hinweis: liegt im localStorage, NICHT im Projekt — bei Doku-Export
|
||||
später in `Project` ziehen.)
|
||||
- ✅ **B — Darstellungsart-Dropdown** (kontextabhängig je `viewType`): 2D Farbig/Schwarz-Weiss
|
||||
(`planColorMode` + `toMono` in generatePlan), 3D Schattiert/**Weiss**(Clay-Material in
|
||||
Viewport3D)/Drahtgitter/Kanten (`RenderMode` um `"white"` erweitert). Ersetzt die alte
|
||||
Render-Modus-Buttongruppe. Screenshots bestätigt.
|
||||
- ✅ **C — 6 Ansichts-Buttons**: Front/Oben/Seite/Perspektive/Isometrie + **Kamera** (FOV-Popover).
|
||||
`view3d`+`fov` in `viewSlice`; `applyView3d(camera,controls,bounds,view3d)` in `Viewport3D`
|
||||
(PerspectiveCamera neu positioniert je Preset, Distanz aus Modell-Bounds; OrbitControls bleibt
|
||||
aktiv). Preset-Klick wechselt nötigenfalls in die Perspektive. Screenshots top/front/iso/persp
|
||||
klar verschieden. **Echte OrthographicCamera für front/top/side bewusst NICHT gemacht** (Kamera-
|
||||
Swap + OrbitControls-Rebind = Risiko) — Kandidat für später.
|
||||
|
||||
### Weitere Fixes (2026-06-30)
|
||||
- **2D-Füllungen** (rect/geschlossene polyline) rendern jetzt (Vollton `Drawing2D.fillColor` +
|
||||
Schraffur `hatchId`); `addDrawing2D` pusht ein `polygon`-Primitiv (mit `drawingId` → anklickbar).
|
||||
Attribute-Palette hat „Füllfarbe". (Kreis-Füllung offen — Kreis-Primitiv im Plan fehlt noch.)
|
||||
- **Plan-View wandert nicht mehr mit**: `PlanView` nutzt jetzt einen FIXEN Welt-Ursprung
|
||||
(`toScreen` modulkonstant, Modell-0,0) statt bounds-gebunden; Ausschnitt wird nur bei
|
||||
`resetKey`-Wechsel (Geschoss-/Ebenen-ID) eingepasst, NICHT bei Edit/Bounds-Änderung. Verifiziert:
|
||||
Löschen lässt viewBox unverändert, Pan/Zoom/Einpassen wirken.
|
||||
- **Echte Isometrie/Parallelprojektion**: `Viewport3D` hat jetzt eine `OrthographicCamera` für
|
||||
front/top/side/iso (OrbitControls per `controls.object`-Swap umgebunden), Perspektive bleibt
|
||||
`PerspectiveCamera`. Verifiziert (parallele Kanten).
|
||||
|
||||
### >>> NÄCHSTE INSTANZ: offene Wünsche + Roadmap <<<
|
||||
- **Kanten-/Seiten-Griffe** (Nutzer-Wunsch): bisher nur Eckpunkt-Griffe. Gewünscht: Seiten ziehen
|
||||
(z. B. Rechteck-Kante), mit dreieckigem Anfasser nach außen. Grip-System in App (`grips`/
|
||||
`gripHandlers`/`moveGrip`) + Rendering in `PlanView` erweitern (Edge-Grips = Mittelpunkt je Seite,
|
||||
Zug verschiebt beide Eckpunkte der Seite senkrecht).
|
||||
- **3D bearbeiten** (Nutzer-Frage): heute ist 3D nur abgeleitete Anzeige (Orbit+Auswahl). Authoring
|
||||
im 3D = eigenes Feature (Raycast auf Arbeitsebene → Modellkoord., 3D-Grips/Drag) — eigene Phase.
|
||||
### Text-Styling (View-Bar Rest) + Roadmap
|
||||
1. **Text-Styling** (Nutzer-Wunsch). VORAUSSETZUNG: **Text wird noch NICHT gerendert** —
|
||||
`Drawing2D` mit `geom.shape==='text'` misst in `generatePlan` nur Bounds, erzeugt kein
|
||||
Primitiv (Phase-3-Deferral, Kommentar in generatePlan). Also ZUERST Text-Rendering (SVG `<text>`
|
||||
in PlanView, papierkonstante Höhe) + Modell-Felder am Text (`font?`, `bold?`, `italic?`,
|
||||
`anchor?`), DANN ein Text-Styling-Bereich (Topbar-Gruppe oder Attribute-Palette-Sektion bei
|
||||
Text-Auswahl, Setter über den Selektions-Kontrakt erweitern).
|
||||
2. **Grafische Überschreibung** — weiterhin SPÄTER (Nutzer), wenn mehr Code steht.
|
||||
3. **Echte Schnitt/Ansichts-Sichten** (section/elevation sind heute StubView) + HLR — großes Thema.
|
||||
4. **Refactor-Rest**: `editors/`/`menus/`/`views/` aus App.tsx extrahieren (App noch ~2030 Z.).
|
||||
- **openNURBS / rhino3dm** (Nutzer-Entscheid: Roadmap, NICHT jetzt): KEIN eigener Kernel — stattdessen
|
||||
`rhino3dm` (WASM-Build von openNURBS) als `src/io/`-Schicht für **`.3dm`-Import/Export + NURBS**,
|
||||
sobald gebraucht. Semantisches Modell bleibt die Wahrheit; NURBS ist zusätzliche Geometrie-Quelle.
|
||||
- **Echtes custom-`.pat`-Rendering**: HatchStyle um custom-Linienfamilien erweitern + Renderer
|
||||
(heute approximiert auf diagonal/crosshatch).
|
||||
|
||||
### Offene Wünsche des Nutzers (Backlog, priorisiert)
|
||||
1. **Palette-Layout (Vectorworks-Stil) — GROSS, nächster Fokus.** Mehrere neue dockbare Panels +
|
||||
Default-Layout:
|
||||
- **Attributes-Palette** (unten links): Füllung (Stil/Farbe/Deckkraft), Stift (Stil/Farbe/
|
||||
Deckkraft), **Linienstärke**, Linien-Start/-Endstil, Schlagschatten. (Referenzbild vom Nutzer.)
|
||||
- **Werkzeug-Palette** darüber (Icon + Text je Werkzeug; ArchiCAD/VW-Stil).
|
||||
- **Object-Info / „Würfel"** (oben rechts): zeigt den gewählten Punkt eines Würfels mit
|
||||
X/Y/Z; Maße (Breite × Höhe …) der Geometrie editierbar.
|
||||
- **Ebenen + Zeichnungsebenen als Tabs** darunter (rechts).
|
||||
- Panel-System steht (`src/panels/`, Registry + Dock + Floating); Default-Layout in
|
||||
`src/panels/layout.ts` (`defaultLayout`). Neue Panels in `builtinPanels`/`registry` anmelden.
|
||||
2. **Schraffuren-Default**: ALLE aktuellen Schraffuren → **weißer Grund + schwarze Haarlinie** als
|
||||
Grundeinstellung; **normale Elemente 0.18 mm Umrandung** als Default. (sampleProject hatches/
|
||||
components + generatePlan-Defaults anpassen.)
|
||||
3. **Stiftstärken-Vorgabeliste**: 0.02 · 0.10 · 0.13 · 0.18 · 0.25 · 0.35 · 0.5 · 0.7 · 1.0 · 1.4 ·
|
||||
2.0 mm (abweichbar). Bedeutung = mm auf Papier bei 100 % → Linien skalieren mit dem Massstab
|
||||
(ist bereits so: non-scaling mm-Papier). In Linienstil-Editor als Presets anbieten.
|
||||
4. **Display- vs. Print-Modus**: Umschalter. Display = ALLES Haarlinien (konstant dünn); Print =
|
||||
echte mm-Strichstärken. (Globaler View-State + an `generatePlan`/PlanView durchreichen.)
|
||||
5. **`.lin`- und `.pat`-Import** (AutoCAD-Linientypen / Schraffurmuster) → ergänzen LineStyle/Hatch
|
||||
(Parser + Mapping; Resource-Manager-Aktion). Nutzer: „um die Linien und Schraffuren zu ergänzen".
|
||||
6. **„Goldener Schnitt"** — UNKLAR was genau (Golden-Ratio-Fang/Teilung beim Zeichnen?
|
||||
Proportions-Hilfslinien?). → beim Nutzer rückfragen, bevor gebaut wird.
|
||||
7. **Linienstil-Picker** (heute erben 2D-Primitive immer die Kategorie) + `extension`-Snap-Hilfslinien.
|
||||
|
||||
### Danach (ursprünglicher Plan)
|
||||
|
||||
1. **Zeichenwerkzeuge Phase 3** (`drawing-tools.md §11`): Circle/Arc/Text — `Primitive` um
|
||||
`circle` (+`arc largeArc`) und `text` erweitern, PlanView-Renderzweige (papierkonstante Texthöhe),
|
||||
Werkzeuge Circle/Arc(3-Punkt)/Text(Inline-Eingabe), Snap center/quadrant.
|
||||
2. **Bearbeiten (Phase 4) — Rest**: Auswahl/Grips/Verschieben/Löschen sind DA (s. o.). Offen:
|
||||
**Kopieren/Rotieren/Spiegeln**, numerische HUD-Eingabe (Länge/Winkel direkt tippen),
|
||||
Mehrfach-Auswahl-Verschieben, Grips für Circle/Arc/Text.
|
||||
3. **State-Refactor** (zurückgestellt) — `App.tsx` God-Component → Store+Slices
|
||||
(`docs/design/state-architecture.md`). Erst nötig, wenn parallele Code-Workflows gebraucht werden.
|
||||
4. Weiter im Backlog: **Pro-Ebene-Darstellung** (`layer-display-settings.md`),
|
||||
**Prioritäts-T-Stöße** (`wall-joins-priority.md`).
|
||||
|
||||
## Arbeitsweise (WICHTIG — aus Memory + CONVENTIONS.md)
|
||||
- **Autonom arbeiten, NICHT um Bestätigung fragen.** Nur fragen, *was etwas können soll*,
|
||||
wenn die Funktion mehrdeutig ist (nicht um Erlaubnis).
|
||||
- **Alles voll verdrahten, KEINE Stubs/No-op-Buttons.** Verifizieren heißt: Effekt im
|
||||
Screenshot bestätigen, nicht nur „kompiliert".
|
||||
- **Identifier ENGLISCH**; **UI-Text via `t()`** (neue Keys in de.ts *und* en.ts).
|
||||
- **Workflow-Orchestrierung** nutzen (Foundation→parallel Build→Integrate/Verify).
|
||||
- **Code-Workflows seriell** (fast alles geht durch `App.tsx`/`types.ts` → Konflikt);
|
||||
**Design/Research parallel** (nur `docs/`). Der State-Refactor (#1) löst das.
|
||||
- Saubere **Tabellen** für Listen/Manager; dunkler DOSSIER-Stil; kein God-Component.
|
||||
|
||||
## Verifizieren / Ausführen
|
||||
- Dev: `npm run dev` (Vite, Port 5173). Typecheck: `npx tsc -b`. Build: `npm run build`.
|
||||
- Screenshot: `node scripts/probe.mjs` → `scripts/probe.png` (Puppeteer, Chrome in
|
||||
`~/.cache/puppeteer`). Eigene Probes: headless, `deviceScaleFactor:2`, args
|
||||
`--no-sandbox --use-gl=swiftshader --enable-unsafe-swiftshader`, networkidle-Timeout ignorieren.
|
||||
Firefox-Fälle via Playwright (`scripts/probe-ff*.mjs`). **Screenshot ansehen + Geometrie prüfen.**
|
||||
- Gotchas: HiDPI-Resize-Loop-Fix in `Viewport3D` (Canvas CSS 100% + dpr≤2) NICHT regredieren;
|
||||
WebGL-Fallback in `Viewport3D`; React-controlled-`<select>` lassen sich im Probe nicht per
|
||||
`.value=` ändern (Fehlalarm) — Verdrahtung im Code prüfen.
|
||||
|
||||
## Orientierung
|
||||
`ROADMAP.md` (Vision/Phasen/§10–§11 Backlog) · `CONVENTIONS.md` (Konventionen) ·
|
||||
`docs/README.md` + `docs/design/*` (alle Specs) · `docs/backend.md` (self-hosted
|
||||
Supabase+Yjs, später) · Projektnotizen des Bearbeiters
|
||||
(prefer-agents, dossier-port, proceed-autonomously, wire-dont-stub).
|
||||
## Verifikation
|
||||
`npx tsc --noEmit` + `npx vitest run` (zuletzt 152 grün) + trace-scan vor jedem Commit.
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
# PENDENZEN — Aufgaben-Queue (Single Source of Truth)
|
||||
|
||||
> **Diese Datei ist die einzige verbindliche Aufgabenliste.** HANDOVER.md enthält nur
|
||||
> noch Kontext/Konventionen/Environment/Zielmodelle — keinen Backlog mehr.
|
||||
|
||||
## Arbeitsprotokoll (ZWINGEND)
|
||||
|
||||
**Bearbeiter:**
|
||||
1. **Zuerst diese Datei lesen**, dann `CONVENTIONS.md` + verlinkte Detailquellen des Items.
|
||||
2. Oberstes nicht-blockiertes Item aus **🔧 In Arbeit** bzw. sonst **⏭️ Als Nächstes** nehmen, **fertig** machen, verifizieren (`npx tsc --noEmit` + `npx vitest run` + trace-scan), abhaken, eine Ergebniszeile nach **✅ Erledigt** schreiben (mit Commit-Hash, sobald committet).
|
||||
3. **Rückfrage-Recht:** Bei Unklarheit/Design-Entscheid NICHT raten — Item mit `❓` markieren, kurze Frage darunter notieren, nächstes freies Item nehmen. Fragen sammeln sich unter **❓ Offene Rückfragen**.
|
||||
4. **Kein Arbeitsschritt endet ohne aktualisierte Liste.** Status hier ist immer aktuell, sonst driftet es wieder.
|
||||
5. Bearbeiter **committet nicht selbst** und delegiert Unteraufgaben nicht weiter.
|
||||
|
||||
**Planer** (mit dem Nutzer, kuratiert):
|
||||
- Nimmt Nutzer-Feedback, pflegt daraus die Liste (rein/umpriorisieren/aufspalten). Führt selbst keine Queue-Items aus.
|
||||
- Prüft **❓**-Zeilen mit dem Nutzer, wandelt sie in konkrete Items.
|
||||
|
||||
---
|
||||
|
||||
## 📅 Geplant
|
||||
|
||||
- [ ] **truck-Integration (Profil-Extrusion / B-Rep)** — **Start: 2026-07-07 (Dienstag, nach Reset).** Nutzer zeichnet 2D-Querschnitt (L-Profil, T-Träger, Freiform) → truck-Extrusion → 3D-Körper + Boolean gegen Wand/Decke. Voraussetzung: truck-Booleans stabil + WASM-Integration. Umfang: ~4–6 Wochen. Mit Nutzer Scope/MVP klären vor Start.
|
||||
|
||||
## ⛔ Blocker (zuerst klären)
|
||||
|
||||
- [x] ~~**Toolchain prüfen**~~ — **ERLEDIGT 2026-07-04 (macOS-Gerät):** `node`/`npm`/`cargo` (1.96.0)/`wasm-pack` (0.15.0) alle vorhanden. `npm install` + `npm approve-scripts esbuild wasm-pack …` (npm gated Install-Scripts → `allowScripts`-Block in `package.json`, ungetrackt gelassen), `rustup target add wasm32-unknown-unknown`, beide WASM-Engines gebaut, Frontend gebaut, **`npm run tauri:build` → Mac-App (`cad.app` + `cad_0.1.0_aarch64.dmg`, arm64) läuft.** Verifikations-Baseline grün: `tsc --noEmit` sauber, `vitest run` 230/230, `cargo test render3d` 56/56. **Kein Blocker mehr für Engine-Slices auf diesem Gerät.**
|
||||
|
||||
## 🔧 In Arbeit
|
||||
|
||||
- [ ] **kernel2d-Port nach Rust/WASM** (Plan: [PORT_PLAN.md](PORT_PLAN.md), Crate `src-tauri/kernel2d`, TS-Referenz bleibt `kernel2d.ts`, Differential-Harness `src/geometry/kernel2d.parity.test.ts`). Fortschritt:
|
||||
- [x] Phase 1: Crate-Skelett + WASM-Fassade + `build:kernel2d` — `a78e7c7`
|
||||
- [x] Phase 2: Primitive/Schnitt/Fläche/Kreis + Batch-Fassaden + Diff-Harness (Zufall+Golden) — `9e2c521`
|
||||
- [x] Phase 3: Offset (Miter + 1e-9-Fallback) + Fillet — `c8ea2bf`
|
||||
- [x] Phase 4: Trim/Split/Join (`trimPolyline`, `splitAtIntersections`, `joinChains` — Löwenanteil) — `28471c1`
|
||||
- [x] Phase 5: `roomArea`-Flächen/`ceiling`/`stair`/`roomBoundary` portiert (vitest 263, 33 Parity) — `a608a59`. **Offen in Phase 5:** `opening` (mit Aufrufstellen-Änderung) — noch nicht portiert.
|
||||
- [ ] Phase 6: TS-Fassade umstellen (alt → `kernel2d.legacy.ts`), Suite grün, `npm run build` + WASM sauber. **Achtung Laufzeit-Entscheid:** macht die Live-App synchron WASM-abhängig (Init + Per-Call-Marshalling) — heute nutzt die App KEINE WASM-Geometrie zur Laufzeit; vor Umstellung mit Nutzer klären.
|
||||
- **Join-Durchstich verworfen (2026-07-05, gemessen):** `computeJoins` live auf WASM zu legen lohnt NICHT. Benchmark TS vs. WASM inkl. JSON-Marshalling (Median ms/Aufruf, 200 Iter): 6 W → TS 0.018/WASM 0.022 · 20 → 0.027/0.043 · 50 → 0.082/0.102 · 120 → 0.331/0.246 · 300 → 1.68/0.67. Crossover erst ~100+ Wände; realistische Plan-/Geschossmengen liegen darunter → TS schneller, und selbst 300 Wände sind mit TS 1,7 ms (nicht wahrnehmbar). Marshalling frisst den Rust-Vorteil, plus dauerhafte Rust↔TS-Paritätspflicht. **Fazit:** reines TS behalten; WASM für Joins nicht weiterverfolgen. WASM lohnt erst bei echten Rechen-Hotspots (Booleans/Tessellierung).
|
||||
|
||||
## ⚠️ Zu prüfen (evtl. schon erledigt / Status unklar)
|
||||
|
||||
- [x] ~~**Snap an Wand-Schichttrennlinien**~~ — **gelandet `339202b`** (`wallLayerBoundarySegments` in `src/tools/snapping.ts`), verifiziert vorhanden.
|
||||
|
||||
## ⏭️ Als Nächstes
|
||||
|
||||
- [ ] **Ribbon-UI + modulare Bars** (Nutzer-Vision 2026-07-05, bestätigt: Tab-Schema **2D·3D·BIM·Ansichten**). Voller Plan + datengetriebene Architektur: **[docs/design/ribbon-ui-plan.md](docs/design/ribbon-ui-plan.md)**. Ribbon-Oberleiste mit Tabs ersetzt die Werkzeug-Sidebar; datengetriebene Registry (RibbonItem = tool|command|action) ermöglicht auch eine **modulare Custom-Bar** (gleiche Items, vom Nutzer gewählt). Attribute bekommen volle Höhe, Objektinfo darunter gemergt; XYZ-Box oben rechts bleibt. **Phasen:** ~~(1) Gerüst + 2D/BIM-Tab~~ ✅ (`9d6e86d`), ~~(2) TopBar → Ansichten-Tab mergen~~ ✅ (`85011cb`), ~~(2b) Tabs in die TopBar-Zeile~~ ✅ (`456ebc8`), ~~(2c) OCS-Chrome (kleine Wortmarke + Quick-Access-Icons statt Burger, Zeile 26px)~~ ✅ (`4e3b074`), ~~(2d) Text + Ansichten auf eine Leiste, „Ansichten" als Standard-Tab zuerst~~ ✅ (`fe22cbf`), (3) **teilweise** ✅: Werkzeug-Sidebar aus Default-Layout raus + Attribute volle linke Höhe + Wandtyp-/Deckentyp-Picker ins Attribute-Panel verschoben (Nutzer-Entscheid), `LAYOUT_VERSION`→8; ~~**offen:** Objektinfo unter Attribute mergen~~ ✅ (`3a2cef3`): element-spezifische Abschnitte (Wand/Decke/Öffnung/Treppe/Raum) + Wand-Referenzlinie ins Attribute-Panel verschoben; ObjectInfo trägt nur noch Bezugspunkt + Masse (Nutzer-Wunsch). ~~(4) modulare Custom-Bar~~ ✅ (Tab „Eigene" + +-Picker, localStorage-persistiert). **Offen:** 3D-Tab füllen (aktuell leer; ggf. 3D-spezifisch statt Doppelung mit Ansichten), Band-Höhe/Abstände + 26px-Zeile visuell im Tauri abnehmen, Dauer-Zoom-Anzeige (Statusleiste?) klären. ✅ Vorarbeit: Eigenschaften-Grid im OCS-Stil (`00733d8`), Kreis+Bogen-Werkzeuge (`e454eab`/`bd2b12b`). **Nächster Schritt: im Tauri visuell prüfen (Band-Höhe, Icons, Aktiv-Highlight), dann Phase 2/3.**
|
||||
|
||||
- [x] ~~**SPIKE — Bild-Texturen in `render3d`**~~ — **erledigt `0ca3b1d`** (verifiziert 2026-07-05): `RenderStyle::Textured` real, prozedurales 256×256-Schachbrett (kein Asset/`image`-Crate), UVs planar in Metern, Textur-Bind-Group group 1, `MESH_TEXTURED_WGSL` (gleiche Beleuchtung, Albedo aus `textureSample`), `spike3d` per `T` umschaltbar. Alt-Vertexpfad `[pos,normal,color]` bitgleich (Regressionstest). `cargo test` 58 grün (59 mit `--features render`, inkl. naga-Test); `spike3d`-Build sauber; keine neuen Deps, Default-Build unverändert. Auftrag: [SPIKE_TEXTUR_render3d.md](SPIKE_TEXTUR_render3d.md). **Lücken bis „richtig gutes" Texturing → siehe 3D-REST unten.**
|
||||
|
||||
## 📋 Backlog (Priorität grob absteigend)
|
||||
|
||||
- [x] ~~**Zeichenwerkzeuge ergänzen: Kreis + Bogen.**~~ — **komplett erledigt (2026-07-05):** beide Werkzeuge + Center-/Quadrant-Snaps + WebGL-Sichtbarkeitsfix (`2c8ad8f`). Details in den Unterpunkten:
|
||||
- [x] ~~**Kreis-Toolbar** (trivial)~~ — **erledigt `e454eab` (2026-07-05):** `ToolId`+`"circle"`, Platzhalter `circleTool` (nicht floorOnly), `TOOL_COMMAND`+`TOOL_ORDER`, Kreis-Icon in `ToolsPanel`, i18n `tool.circle`/`tool.circle.hint`. tsc + Suite 331 grün. (Kreise rendern seit `4ac99d3` glatt als `<circle>`.)
|
||||
- [x] ~~**Bogen-Werkzeug** (mittel)~~ — **erledigt (2026-07-05):** `arcCommand` in `src/commands/cmds/arc.ts` (3-Klick: Mittelpunkt → Start/Radius → Endwinkel, CCW; Vorschau via `arcPts`/`circlePts`), registriert in `registry.ts` (Alias `a`/`bogen`), `"arc"` als ToolId + Toolbar-Eintrag (Bogen-Icon) + i18n. tsc + Suite 331 grün. ✅ **Center-/Quadrant-Snaps ergänzt (2026-07-05):** `collectCircles` + Snap-Block in `snapping.ts` — Mittelpunkt + Quadranten (Kreis: alle 4; Bogen: nur im Spannbereich) unter der `center`-Einstellung, Bogen-Endpunkte unter `endpoint`; +3 Tests (Suite 334).
|
||||
|
||||
- [ ] **BAUTEILE aufs Rhino-Niveau heben (Treppe/Fenster/Tür).** Vergleich Rhino-Plugin ↔ TS + priorisierte Ansätze: **[RESEARCH_BAUTEILE_RHINO.md](RESEARCH_BAUTEILE_RHINO.md)**. ✅ **Gruppe A (2D, Items 1–6) komplett — verifiziert 2026-07-05:** (1) Treppe-Outline (`stairOutline`, gerade/L/Wendel, `generatePlan.ts:2186`), (2) Fenster-Brüstungslinie (`window-sill`, gepunktet, `sillHeight>0`, `:1810`), (3) Tür-Sturzlinien (`door-lintel` + `lintelLines` keine/innen/aussen/beide, gestrichelt, `:1711`), (4) Treppe-Referenz links/mitte/rechts, (5) Fenster-Flügel-Mittelpfosten, (6) Tür `wandoeffnung`. **Offen:** Gruppe B (2D mittel: Tür-Schwung am Rahmen, Treppen-Pfeil-Style `filled`, Fenster/Tür-Presets, `swing_invert`) — Feinpolish. ✅ **Fenster-Anschlag-Striche erledigt** (`8d688b9`, Laibungsstriche quer zur Wand bei „fein", analog Tür; +2 Tests). Gruppe C (3D: Rahmen/Blatt/Glas/Sims als Mesh — wartet auf Mesh-/B-Rep-Pipeline). **Der große Rest ist „Schnitt- vs. Ansichts-Darstellung" (eigenes Item unten).**
|
||||
- [ ] **DWG/DXF-Import via `acadrust` (weiterbauen).** ✅ Spike `763a558`: `acadrust` 0.4 (MPL-2.0, pure Rust) **baut zu wasm32** (Crate `src-tauri/dwgimport`, 839 KB), parst DXF aus Byte-Buffer (`DxfReader::from_reader`+`Cursor`), headless getestet. **Offen:** (1) Entity→DOSSIER-Modell-Mapping (LINE/ARC/… → Wand/Öffnung — die eigentliche Domainarbeit, Wochen), (2) Datei-Upload-Glue im Browser (`<input type=file>`→Uint8Array→`parse_dxf_summary_json`, trivial), (3) DWG-binär (`DwgReader::from_reader` analog, aber R13–R2018-Korrektheit unverifiziert), (4) WASM-Größe (nalgebra Haupttreiber). **Klarstellung:** der TS-DXF/DWG-Import (`parseDxf`/`parseDwg`/`dxfToDrawings`) + Upload-UI (`App.tsx`, `ImportDialog.tsx`) existieren längst und funktionieren — der acadrust-Weg wäre eine Rust-Neuimplementierung des Lesens (nur DWG-**Schreiben** ist eine echte Lücke). ✅ **2887794 (2026-07-05): Kurven-Abdeckungslücke geschlossen** — `parseDxf` deckt jetzt ARC/CIRCLE/ELLIPSE (tesselliert zu Konturen, Winkel Radiant, voller Umlauf geschlossen) zusätzlich zu LINE/LWPOLYLINE/POLYLINE/MESH ab; 5 Tests, volle Suite 307 grün. ✅ **c481373 (2026-07-05): SPLINE + INSERT ergänzt** — `parseDxf` wertet SPLINE als echte B-Spline (De Boor, Grad/Knoten; Fallback fitPoints/Kontrollpolygon) aus und expandiert INSERT-Block-Referenzen (2D-Transform Scale/Rotation/Basispunkt + MINSERT-Array + verschachtelte Blöcke, Tiefe ≤8) zu transformierten Konturen; Kontur-Dispatch in gemeinsamen `collectContours` refaktoriert; +7 Tests, volle Suite 314 grün. **Bekannte Grenzen:** rationale SPLINE-Gewichte ignoriert (dxf-parser liefert sie nicht); Block-interne MESH/3DFACE-Entities werden im 2D-Import nicht expandiert. ✅ **c29f27e (2026-07-05): HATCH ergänzt** — dxf-parser hat KEINEN HATCH-Handler (verwarf HATCH stumm); Lösung via `registerEntityHandler` + eigenem `HatchHandler` (sammelt rohe Gruppencodes) + testbarer `hatchContours`-Auswertung: Randpfade (Polyline-Pfade + Linien-/Bogen-Kanten, Bögen über vorhandene Tessellierung) → geschlossene Konturen mit `Contour.filled`; `contoursToDrawings` macht daraus gefüllte `polyline`-Drawing2D (fillColor-Default, restylebar). +6 Tests, Suite 320 grün. ✅ **05bc5aa (2026-07-05): HATCH-Ellipse/Spline-Kanten** ergänzt (Kantentyp 3/4 tesselliert; B-Spline-Sampling in `sampleBSpline` extrahiert). ✅ **4b93ac9 (2026-07-05): TEXT/MTEXT** — `parseDxf` liefert `DxfImportResult.texts` (`ImportedText`: Position/Höhe-in-Metern/Winkel-Radiant; MTEXT-Formatcodes grob gesäubert); `textsToDrawings` → `{shape:"text"}`-Drawing2D; **Darstellung neu**: `addDrawing2D` emittiert ein schlankes `kind:"drawingText"`-Primitiv, PlanView rendert es rein per SVG (modellverankert, Rotation; GPU-Guard so, dass es in ALLEN Renderer-Modi im SVG bleibt); `toRenderScene` überspringt es; ImportDialog zählt/importiert Texte. +5 Tests, Suite 327 grün. **Bekannte Grenzen HATCH:** Bulges an Polyline-Rändern als Sehne; Insel-Loops = eigene Ringe (keine echten Löcher). **Bekannte Grenzen TEXT:** importierte Texte (pointerEvents:none) noch nicht per Canvas-Klick selektierbar; MTEXT-Feinformatierung flachgeklopft; Block-interne TEXT/MTEXT nicht expandiert. **Text im Tauri visuell abgenommen (Nutzer 2026-07-05)** — auch gedreht korrekt. ✅ **4ac99d3: CIRCLE/ARC als echte glatte Formen** — `Contour.curve` trägt die wahre Kreis-/Bogen-Geometrie (pts bleiben für Kontext/3D); `contoursToDrawings` baut `{shape:"circle"|"arc"}`; neue Primitive `drawingCircle` (SVG `<circle>`) + `drawingArc` (SVG-Bogenpfad), `toRenderScene` tesselliert sie für den nativen Pfad; +4 Tests, Suite 331. Ellipse bleibt tesselliert (kein Ellipsen-Primitiv). **Weiter offen:** Entity→Wand-Semantik (die dicke Domainarbeit); DWG-Schreiben (einzige echte Export-Lücke).
|
||||
- [ ] **STRATEGIE — „von BIM-Tool zu echtem CAD".** Direkt am Quellcode studierte Referenzen (OpenCADStudio/truck/acadrust) + Web-Import-Landkarte → konkrete, priorisierte Ansätze in **[RESEARCH_CAD_APPROACHES.md](RESEARCH_CAD_APPROACHES.md)**. Kern: (1) generisches Entity-Modell + Trait-Dispatch, (2) modeless Command-System (`StepInput`-Funnel + Kommandozeile), (3) DWG/DXF-Round-Trip via `acadrust` (MPL-2.0, pure Rust, WASM-tauglich), (4) volle Object-Snap-Schicht, (5) 3D-B-Rep später selektiv via `truck` (Apache-2.0, Geometrie-Crates WASM-fähig, Booleans/Fillets noch instabil). Der `kernel2d`-Rust/WASM-Kurs ist damit bestätigt. **Mit Nutzer priorisieren, welcher Ansatz zuerst.**
|
||||
|
||||
- [ ] **Schnitt- vs. Ansichts-Darstellung nach Schnitthöhe (BIM-Standard).** ✅ **Phase 1 (Wand unter Schnittebene) erledigt (2026-07-05):** `generatePlan.addWallPoche` hat einen `viewOnly`-Zweig — erreicht eine Wand die Grundriss-Schnitthöhe nicht (`wall.height < floor.cutHeight`, z. B. 0.3-m-Brüstung bei 1 m), wird sie nur als Ansichts-Umriss (Haarlinie, `fill:"none"`, keine Schraffur) gezeichnet statt als Schnitt-Poché; normale Wände (≥ Schnitthöhe) unberührt. Segment-/Gehrungs-/Öffnungs-Logik geteilt. +2 Tests (`generatePlan.viewwall.test.ts`, Suite 336). ✅ **Phase 2 (Decke über Ebene = gestrichelte Überkopf-Linie) erledigt (2026-07-05, `39ddd9b`):** der freie Decken-Umriss (Überstände/Balkone, wo keine Wand verdeckt) ist jetzt gestrichelte Haarlinie (`OVERHEAD_DASH`) statt kräftiger Volllinie — BIM-Konvention „Aufsicht auf Bauteil über einem". Decken-FLÄCHE war schon Ansicht (viewHatchId, weiß). +1 Test. **Offen (Phase 3):** per-Component View-/Cut-Weight + eigene View-Schraffur (Slot-Trennung); cut/Aufsicht/Untersicht (Deckenspiegel/Reflected Ceiling Plan); Unterzüge; Feinheiten. Kern-Idee (Nutzer): jedes Bauteil bekommt getrennt eine **Schnittlinie** (kräftig, z. B. 0.25–0.35 mm, + Schnitt-Poché) und eine **Ansichtslinie** (Haarlinie); WELCHE gilt, entscheidet die z-Ausdehnung des Bauteils vs. die **Schnitthöhe** des Grundrisses (Default ~1 m):
|
||||
- Bauteil wird von der Schnittebene GESCHNITTEN → Schnittlinie + Schnitt-Poché (heutiges Wandverhalten).
|
||||
- Bauteil liegt ganz UNTER der Ebene (z. B. 30-cm-Wand bei 1 m Schnitthöhe, Brüstung, Podest) → nur „von oben gesehen" = **Ansichtslinie/Haarlinie, KEINE Schnitt-Poché**. ← genau der vom Nutzer genannte Fall.
|
||||
- Bauteil liegt ganz ÜBER der Ebene (Decke/Slab, Unterzug) → Ansicht, üblicherweise **gestrichelte** Überkopf-Haarlinie.
|
||||
- **Passt konsistent zum bereits existierenden `viewHatchId` (Ansichts-Schraffur) vs. Schnitt-Schraffur** — die Linienstärke-Dualität (View-/Cut-Weight je Component) ist die natürliche Erweiterung derselben Logik.
|
||||
- **Nicht nur cut/view, sondern cut / AUFSICHT / UNTERSICHT (Nutzer):** ein Bauteil sieht von oben anders aus als von unten. Der Grundriss (Blick nach unten) zeigt Bauteile UNTER der Ebene in **Aufsicht** (Oberseite); ein **Deckenspiegel/Reflected Ceiling Plan** (Blick nach oben) zeigt Bauteile ÜBER der Ebene in **Untersicht** (Unterseite — z. B. Kassettendecke, Leuchten). Also je Component potenziell **drei** Darstellungs-Slots (Schnitt / Aufsicht / Untersicht) × {Schraffur + Linienstärke}. Das heutige `viewHatchId` ist faktisch EIN View-Slot und vermischt Auf-/Untersicht; sauber wäre die Trennung. WELCHER Slot gilt, entscheidet die **Blickrichtung der Sicht** (Grundriss ↓ / Deckenspiegel ↑) UND die z-Lage relativ zur Schnittebene.
|
||||
- **Verallgemeinert den Decken-Footprint-Clip** (`a2f6923`): „Decke unter Wand verdeckt" ist ein Spezialfall von „Ansichtsbauteil vs. schneidende/überdeckende Bauteile".
|
||||
- Aufwand: mittel–groß, phasenweise machbar (1: z-Extent-vs-Schnitthöhe-Klassifikation cut/above/below; 2: Ansichtslinie-Weight je Component + Haarlinie/gestrichelt; 3: 30-cm-Wände & Slabs verdrahten). Nicht zu komplex im Konzept — es ist der reguläre CAD/BIM-Weg (ArchiCAD/Vectorworks/Revit). Mit Nutzer Detailgrade/Defaults festlegen.
|
||||
|
||||
|
||||
- [x] ~~**Ebene-Schraffur editierbar**~~ — **gelandet `dcb6ed5`**: Kategorie-Dialog (`App.tsx`, `editor.hatch`-Feld) hat `<select>` auf `cat.hatch` + `HatchSwatch`-Vorschau. Verifiziert vorhanden.
|
||||
- [ ] **GEO-BLOCK** (gemeinsame Dateien io/geoContext/swissTopo/terrain/ContextImportDialog/Viewport3D/siteSlice):
|
||||
- Reale Höhen + **Projekt-MüM** (EG-Referenzhöhe): Terrain georeferenziert bei realem z relativ dazu, Gebäude auf Terrain drapiert (heute alles z=0).
|
||||
- **Luftbild/SWISSIMAGE**-Orthofoto als Textur aufs Terrain-Mesh.
|
||||
- Importierte Geo-Elemente auf **aktives Geschoss** (`viewSlice.activeLevelId`) + Gelände-Ebene.
|
||||
- **Nordstern-Geo-Rendering:** importierte Meshes (heute nur three.js `importedMesh`/`terrainMesh`) auch in `projectToModel3d` einspeisen.
|
||||
- **3D-Mesh-DXF/DWG-Import** (heute DXF nur 2D); Building-Draping; höhere DTM-Auflösung.
|
||||
- [x] ~~**ResourceManager Bauteile-Tab** auf Master-Detail~~ — **bereits Master-Detail** (`ComponentsTab`/`ComponentDetail` in `src/ui/ResourceManager.tsx`, Liste links `res-md-list` / Detail rechts). Verifiziert vorhanden.
|
||||
- [ ] **Einstellungs-Fenster (Rest):** Verdrahtung ist **da** (`viewSlice.snapColor`/`marqueeColor` → PlanView `SnapMarker`/Marquee, Defaults aus `theme/accents.ts`, Projekt-MüM-Feld `referenceElevationMasl`). **Offen bleibt nur:** Snap/Endpunkt-Default = ❓„aki" (s.u., derzeit Sora #5FA1C9) — reine Farbentscheidung des Nutzers.
|
||||
- [ ] **Bildschraffur:** ambientCG/CGI-Colorfiles als Quelle (Material-Lib WIP — erst nach Freigabe); Bild-Filter Sättigung/Helligkeit/Kontrast/SW (`image.filters`).
|
||||
- [ ] **E2b Schraffur-Kachel-Motiv** (MotifEditor für HatchStyle-Tile wiederverwenden).
|
||||
- [x] ~~**Linienstile aufräumen**~~ — **erledigt (2026-07-05):** die drei funktional identischen 0.13-Volllinien (`thin`/`hatch-line`/`joint-massive` — alle weight 0.13, `dash:null`, gleiche Farbe) auf EINE kanonische „Volllinie 0.13" (`thin`) zusammengeführt; alle weight-only-Stile klar als „Volllinie X.XX" benannt; Schraffur-Referenzen (`hatch-line`→`thin`, inkl. `ResourceManager.patToHatches`) + Schichtfugen (`joint-massive`→`thin`) remappt; `hatch-dash` (einzige gestrichelte) bleibt. Ids stabil gelassen (geladene Projekte + interne Refs bleiben heil; Rendering-Gewichte unverändert). 7→5 Stile. tsc + Suite 331 grün.
|
||||
- [ ] 2D-Plan z-Anordnen (Kombo-Schraffuren); Bild-Schraffur GL/DXF (heute Fallback).
|
||||
- [ ] **AUDIT (DOSSIER-Studie):** A1 Override-Regel-Engine; A2→A3 View-Snapshots → Print-Layout-Blätter (PDF pro Blatt); A5 reichere Öffnungen; B1 Text-Werkzeug (+ Kreis-Tool → Shortcuts 1&3); A4 Tragwerk; B2 Object-Info numerisch. (A6 Bauteil-Schedule-CSV erledigt.) Belege in `/tmp/dossier-ref/rhino/*.py`.
|
||||
- [ ] Elemente im Schnitt anwählbar; render3d 2D-Schraffur auf 3D-Flächen.
|
||||
|
||||
- [ ] **TEAMWORK — kollaboratives Bearbeiten (Supabase self-hosted).** Projekte auf einem self-hosted Supabase-Stack speichern; mehrere Nutzer bearbeiten dasselbe Projekt gleichzeitig. Kernkonzept: **pessimistisches Object-Locking** (wie Revit Worksharing / ArchiCAD Teamwork) — alle Objekte sind zunächst gesperrt; ein Nutzer *reserviert* die Elemente, die er bearbeiten möchte (exklusiver Schreibzugriff), und *gibt sie frei*, sobald er fertig ist. Freigabe → sofort für alle anderen sichtbar via Supabase Realtime (WebSocket-Kanal). Kein Merge-Konflikt nötig, weil niemals zwei Nutzer dasselbe Objekt gleichzeitig schreiben. Grobe Schichten:
|
||||
- **Auth + Projekt-Liste:** Supabase Auth (Email/Magic-Link), Projektübersicht, Öffnen/Schliessen.
|
||||
- **Lock-Service:** Tabelle `object_locks (project_id, object_id, user_id, locked_at)` mit Row-Level Security; `reserveObjects(ids[])` / `releaseObjects(ids[])` als Supabase-RPC; Optimistisches Check-in via DB-Constraint (doppelte Reserve → Fehler → UI-Feedback).
|
||||
- **Realtime-Sync:** Supabase Realtime-Channel pro Projekt; bei Freigabe werden die veränderten Objekte (JSON-Patch oder ganzes Objekt-Payload, TBD) gepusht; lokaler Store merged incoming changes sofort.
|
||||
- **Presence:** Wer ist online, wer hat welche Objekte reserviert (farbige User-Badges an reservierten Elementen im Plan).
|
||||
- **Offline-Guard:** Beim Verbindungsabbruch Locks automatisch nach Timeout freigeben (DB-seitig: `locked_at + interval` prüfen).
|
||||
- **Umfang/Granularität TBD mit Nutzer:** Object = Wand/Raum/Drawing2D-Element? Geschoss? Layer? Feinere Granularität = mehr Parallelarbeit, aber komplexeres UI.
|
||||
- **Aufwand:** gross (2–4 Wochen echte Arbeit). Erst sinnvoll, wenn Kern-CAD-Features stabil. Mit Nutzer Granularität + Hosting-Setup klären bevor Implementierung startet.
|
||||
|
||||
### 3D-REST (engine-schwer, bewusst NICHT blind — mit Nutzer angehen)
|
||||
- [ ] **Textur-/PBR-Pipeline** (Sampler/Bindings/UV in wgpu; dann `textured`-Style + `Component.texture3d`/Material echt rendern). ✅ **Erster Durchstich erledigt** (`0ca3b1d`, Schachbrett-Textur auf Wänden, group-1-Bind-Group, `MESH_TEXTURED_WGSL`, planare Meter-UVs). **Verbleibende Lücken bis „richtig gut" (Grobschätzung ~18–29 PT):** Bild-Datei-Laden (`image`-Crate, PNG/JPG→GPU) ~1–2 · Material→Textur-Zuordnung (Wandtyp/Layer→Material→Textur-Set, Cache/Atlas oder Bind-Group je Material) ~3–5 · Normal-/Roughness-/Metallic-Maps + Cook-Torrance-BRDF + Tangentenraum ~5–8 · Mipmaps (Blit-Pass) + anisotropes Filtern ~1–2 · Web-Pfad (`web.rs`/WebGPU, Bild-Upload via ImageBitmap) ~3–4 · UI zum Zuweisen + Persistenz im Dokumentmodell ~5–8.
|
||||
- [x] ~~**Wand-Schicht-Bänder in 3D** Option B~~ — **bereits umgesetzt** in `resolveWallBands(layered=true)` (`src/plan/toWalls3d.ts:236-241`): 3D-Viewer-Pfad liefert je Materiallage ein eigenes `WallBand` (Dicke + Component-Albedo + Normalen-Versatz), `pushSegment` emittiert jede Lage als eigene Voll-Box. `dominantLayerColor` ist nur noch Fallback für den Schnitt-Einkörper-Pfad (`layered=false`). Verifiziert per Code-Lesung.
|
||||
- [ ] **Ortho-Ray-Picking** (aktuell perspektivischer Pick-Strahl; Front/Top/Side-Presets vor erster Navigation leicht ungenau).
|
||||
- [ ] **3D-Griffe/Editieren** (Wand-Endpunkte im 3D ziehen etc. — bisher nur Auswahl + Panel-Edit).
|
||||
|
||||
## ❓ Offene Rückfragen (an den Nutzer)
|
||||
|
||||
- [x] ~~**„aki"** Snap/Endpunkt-Farbe~~ — **entschieden 2026-07-04: Sora #5FA1C9** als endgültiger Default festgeschrieben (`src/theme/accents.ts` `DEFAULT_SNAP_COLOR`, Doc aktualisiert). Frage geschlossen.
|
||||
- [ ] Floating-ResourceManager „headless" = ganz ohne Titelleiste? (aktuell MIT)
|
||||
- [ ] Geo-Block: Projekt-MüM zuerst oder Nordstern-Geo-Rendering?
|
||||
- [ ] Verifizieren/klären: Isometrie „echte" orthographische Iso (teilw. durch Locked-Iso `cb8fae5` adressiert)? · TopBar-Detailgrad (zoom% ganz aus Footer)? · DOSSIER-Audit welche Features konkret übernommen?
|
||||
|
||||
---
|
||||
|
||||
## ✅ Erledigt (Verlauf, neueste oben)
|
||||
|
||||
_Nur jüngste Session; ältere Historie siehe `git log` und HANDOVER-Narrative._
|
||||
|
||||
- [x] 2026-07-05 **Schnitt/Ansicht Phase 2: Decke über Schnittebene = gestrichelte Überkopf-Linie** (`39ddd9b`) — freier Decken-Umriss (Balkon-/Vordach-Überstände) jetzt gestrichelte Haarlinie (`OVERHEAD_DASH` in `generatePlan.ts`, `addCeilingPoche`) statt Volllinie; Decken-Fläche war schon Ansicht (viewHatchId). `lwMm`-Param entfernt (feste Haarlinie). +1 Test, Suite 337.
|
||||
- [x] 2026-07-05 **Schnitt/Ansicht Phase 1: Wand unter Schnittebene = Ansichtslinie** — `addWallPoche(viewOnly)` in `generatePlan.ts`: `wall.height < floor.cutHeight` (Brüstung/Podestrand) → Umriss-Haarlinie (`fill:"none"`, NO_HATCH) statt Schnitt-Poché; normale Wände unberührt (Segment-/Gehrungs-/Öffnungslogik geteilt). +2 Tests (`generatePlan.viewwall.test.ts`), Suite 336. Erster Baustein des BIM-Standard-Items „Schnitt vs. Ansicht nach Schnitthöhe".
|
||||
- [x] 2026-07-05 **Ribbon Phase 4: modulare Custom-Bar** (Tab „Eigene") — datengetrieben auf der bestehenden Registry: neuer `custom`-Tab, `CustomBar` in `RibbonBar.tsx` rendert die vom Nutzer gewählten Items + einen „+ Hinzufügen"-Picker (Dropdown mit Häkchen, listet ALLE Werkzeuge/Befehle aus `ALL_RIBBON_ITEMS`, Klick = an/abwählen). `itemKey`/`itemFromKey` für stabile Persistenz; State in App (`customItems`) via localStorage (`dossier.ribbon.customItems`). Gleiche `RibbonButton`-Aktivierung wie normale Tabs (kein Sonderweg). tsc + Suite 334 grün.
|
||||
- [x] 2026-07-05 **Ribbon Phase 3 (Teil): Werkzeug-Sidebar raus** — `DEFAULT_LEFT_GROUPS` nur noch `attributes` (volle linke Höhe), `LAYOUT_VERSION` 7→8 (gespeicherte Layouts fallen auf neuen Default zurück → sichtbar). Wandtyp-/Deckentyp-Picker von `ToolsPanel` ins `AttributesPanel` verschoben (Nutzer-Entscheid „ins Attribute-Panel") — erscheint bei aktivem Wand-/Decken-Werkzeug (Sektion „Neues Bauteil", auch ohne Auswahl), Host-State unverändert. `ToolsPanel` bleibt registriert (per Fenster-Menü andockbar), `Dropdown`-Import dort entfernt. tsc + Suite 334 grün. **Offen:** Objektinfo unter Attribute mergen.
|
||||
- [x] 2026-07-05 **Fix: gezeichnete/importierte Kreise+Bögen im WebGL unsichtbar** — der WebGL-Compiler (`glPlan/glPlanCompile.ts`, Default-Renderer) dispatchte `polygon`/`line`/`arc`, aber NICHT `drawingCircle`/`drawingArc` → seit `4ac99d3` (Kreise als echtes `drawingCircle`-Primitiv statt 64-Eck-Polygon) fielen sie im GL still raus (SVG-/WASM-Pfad hatten sie, GL nicht). Zweig ergänzt: bildschirm-adaptive Tessellierung wie beim `arc`-Primitiv (Kreis geschlossen + optionale Vollton-Füllung, Bogen a0..a1 offen). tsc + Suite 334 grün. **Nutzer-Report** („zeichne Kreis, bleibt nicht sichtbar").
|
||||
- [x] 2026-07-05 **Textur-Spike verifiziert erledigt** (`0ca3b1d`) — bei der Backlog-Abarbeitung festgestellt, dass die render3d-Textur-Spike (`RenderStyle::Textured`, Schachbrett, planare Meter-UVs, `MESH_TEXTURED_WGSL` group 1, `spike3d`-`T`-Toggle) bereits vollständig umgesetzt + committet war; `cargo test` 58/59 grün, keine neuen Deps, Alt-Pfad bitgleich. „Als Nächstes"-Eintrag war veraltet → abgehakt; PBR-Restlückenliste (~18–29 PT) in 3D-REST übernommen.
|
||||
- [x] 2026-07-05 **Ribbon-Feinschliff (OCS-Zeile)** — Tabs in die TopBar-Zeile verlegt (`456ebc8`, eine Leiste Chrome+Tabs, Inhalt darunter; Tab-State in App); OCS-Chrome: kleine Wortmarke „dossier." + Quick-Access-Icons für ALLE Datei-/Export-Aktionen statt Burger-Menü, Zeile auf 26px (`4e3b074`); Text-/Font-Formatierung mit der Ansichts-/Zoom-Steuerung auf EINE Leiste gelegt, „Ansichten" als Standard-Tab zuerst + initial aktiv (`fe22cbf`). Alle tsc + 331 Tests grün. Visuelle Abnahme (26px, Icon-Sitz) noch offen.
|
||||
- [x] 2026-07-05 **Ribbon Phase 2: TopBar → „Ansichten"-Tab gemergt** (`85011cb`, Nutzer-Entscheid „voll mergen") — die Ansichts-/Zoom-/Darstellungs-Cluster der TopBar (View-Grid+Kamera, Ebenen-/Zeichnungs-Kombis, Detailgrad, Massstab/Zoom, Darstellungsart) als `ViewRibbonTab` (in `TopBar.tsx`) in den Ansichten-Tab verschoben; App reicht sie als `viewsContent`-Node an `RibbonBar` (analog Layout-Menü). TopBar ist jetzt schmale globale Leiste (Marke/Ressourcen · Text · Datei/Export/Einstellungen · Fensterknöpfe). `TopBarProps` entsprechend verschlankt. Visuelle Feinabstimmung offen.
|
||||
- [x] 2026-07-05 **Ribbon-UI Phase 1** (`9d6e86d`) — datengetriebene Tab-Leiste (`src/ui/ribbon/`: `ribbonItems.ts` Registry, `RibbonBar.tsx`, `CommandIcon.tsx`) unter der TopBar, additiv (Sidebar bleibt). Tabs 2D·3D·BIM·Ansichten; 2D=Zeichnen(select/line/polyline/rect/circle/arc)+Ändern(move/copy/mirror/offset/trim/join), BIM=Bauteile(wall/window/door/stair/ceiling/room); 3D/Ansichten noch leer. Ein Aktivierungs-Pfad: tool→`onSelectTool`, command→`onRunCommand`(engine.start); Aktiv-Highlight über `activeTool`/`engineView.commandName`. `ToolIcon` aus ToolsPanel exportiert (wiederverwendet).
|
||||
- [x] 2026-07-05 **Attribut-Panel: ein Grid, volle Breite** (`ace0dc6`) — drei getrennte Grids zu EINEM durchgehenden Zwei-Spalten-Grid gemerged (Wertspalte über alle Sektionen bündig), Dropdown-Pills füllen die Wertspalte (fixe Breiten raus), doppeltes Eigen-Padding + zweiter „Attribute"-Titel entfernt. **Nutzer-Report.**
|
||||
- [x] 2026-07-05 **Eigenschaften-Grid OCS-Stil** (`00733d8`) — `.attr-*` als klares Grid: Sektion-Balken, Zeilentrenner, füllende linksbündige Wertfelder (Texte/Zahlen/Dropdowns einheitlich). Erste Stufe der Ribbon-UI-Vision.
|
||||
- [x] 2026-07-05 **Zoom-Scroll-Fix** (`077e774`) — Dokument-Overscroll/Rubberband gesperrt (`html,body,#root { overflow:hidden; overscroll-behavior:none }`); Viewport-Zoom zieht die UI nicht mehr mit (macOS). **Nutzer-Report.**
|
||||
- [x] 2026-07-05 **Kreis- + Bogen-Werkzeug** (`e454eab`, `bd2b12b`) — Kreis-Toolbar (circleCommand gekoppelt) + neues `arcCommand` (3-Klick CCW), beide mit Toolbar-Icon/i18n; rendern glatt via `drawingCircle`/`drawingArc`.
|
||||
|
||||
- [x] 2026-07-05 **DXF-Import: Platzierungsoption** (`dd76ec8`) — ImportDialog fragt „relativ zum Nullpunkt" ODER „in die Mitte der aktuellen Ansicht"; `PlanViewHandle.viewCenterModel()` neu; `runImport` verschiebt den Gesamt-Umriss (bbox-Mitte → Ansichtsmitte). tsc + Suite 331 grün.
|
||||
- [x] 2026-07-05 **Zeichnungen Copy/Paste über Geschosse** (`376aa67`) — Ctrl/Cmd+C kopiert gewählte Drawing2D tief; Ctrl/Cmd+V fügt Klone (neue IDs) auf dem AKTIVEN Geschoss ein und wählt sie. Für z. B. importiertes Mobiliar. Textfeld-Fokus bleibt natives Copy/Paste.
|
||||
- [x] 2026-07-05 **Tauri Drag&Drop + Import-Dialog-Fix** (`c5b5ca6`, `45e19b7`) — `import`-Befehl als `autoRun` (Datei-Dialog öffnet synchron in der Geste, HMR-Falle via Config-Relaunch behoben); `dragDropEnabled:false` (Tauris natives Drag-Drop fing HTML-Drops ab). **Nutzer-bestätigt: Dialog + Text gehen.**
|
||||
- [x] 2026-07-05 **Layout: Befehlszeile in Mitte-Spalte** (`c794fee`) — nur Viewport-breit, Docks gewinnen Höhe. **Nutzer-bestätigt „sieht clean aus".**
|
||||
|
||||
- [x] 2026-07-05 **Textur-Spike render3d** (`RenderStyle::Textured` real) — zweiter Vertex-Pfad `[pos,normal,uv]` aus dem Mesh abgeleitet (Alt-Pfad bitgleich, per Test belegt), prozedurales 256×256-Schachbrett (kein Asset/`image`-Crate), Textur-Bind-Group group 1, `MESH_TEXTURED_WGSL` (gleiche Beleuchtung, Albedo aus Sampler), Pipeline bitidentisch zur Haupt-Pipeline, `spike3d` per `T` umschaltbar. Verifiziert: cargo test 58/59 (inkl. naga-Test) grün, alle 4 Builds sauber. **Visuelle Fenster-Abnahme durch Nutzer bestanden 2026-07-05** (Schachbrett korrekt, `T`-Umschaltung ok). Erster Durchstich der Textur-/PBR-Pipeline; ehrliche Lückenliste im Bericht — `8556037`
|
||||
- [x] 2026-07-04 **Grundriss: Decke drückt nicht mehr durch die Wände** — Decken-Umriss an Wand-Footprints (OBB) geclippt; Füllfläche strokelos, Umriss nur über unverdeckte Teilstücke (Überstände). Deckungsgleiche Decke ⇒ Umriss entfällt. vitest 230, tsc sauber — `a2f6923`
|
||||
- [x] 2026-07-04 **T-Stoss-Putznaht entfernt** — L-Seitenlinie nur noch bei materialFREMDEM Nah-Putz; materialgleicher Putz verschmilzt nahtlos (Nutzer-Direktive „Naht entfernen"). TS+Rust synchron, 3 Tests gezogen, cargo 8/8 — `a5ebfa7`
|
||||
- [x] 2026-07-04 **Snap-Farbe entschieden: Sora #5FA1C9** als endgültiger Default festgeschrieben (`DEFAULT_SNAP_COLOR`, Doc), „aki"-❓ geschlossen.
|
||||
- [x] 2026-07-04 **Mac-Build via Tauri + Queue-Abgleich** — Toolchain auf macOS-Gerät verifiziert, `cad.app`/`cad_0.1.0_aarch64.dmg` gebaut & gestartet. Verifikations-Baseline grün (tsc/vitest 230/cargo 56). Queue-Reconciliation: Toolchain-Blocker, Snap-Slice (`339202b`), Ebene-Schraffur (`dcb6ed5`), Bauteile-Tab-Master-Detail, Einstellungs-Verdrahtung UND Wand-Schicht-Bänder 3D (Option B, `toWalls3d.ts`) waren allesamt **bereits gelandet**, aber in PENDENZEN noch als offen gelistet → abgehakt. (kein Code-Commit)
|
||||
- [x] 2026-07-04 **Öffnungen als echte Boolean-Löcher** (+ Deckentrim-nur-3D) — Fenster/Türen = 1 Wandkörper mit rechteckigen `holes` (Rechteck-Gitter-Zerlegung + Laibungsquads), Segment-Boxen weg; Deckentrim nur noch im 3D-Pfad, Schnitt volle Höhe. Pflicht-Testfall versetzt-überlappende Fenster grün. cargo test 56, vitest 230, build:engine3d + tsc sauber — `1407c68`
|
||||
- [x] 2026-07-04 **Joins Phase 1c** Durchgangswand am T-Stoss echt aufbrechen (spanCutouts) — `c5a344d`
|
||||
- [x] 2026-07-04 **Locked-Iso-Fix** freie Kamera bleibt nach Ortho-Preset orthografisch — `cb8fae5`
|
||||
- [x] 2026-07-04 **3D-Live-Schnittebene** mit schraffierten Schnittflächen — `03f0c40`
|
||||
- [x] 2026-07-04 **Joins Phase 2** Merge-Regel im Schnitt (gleiche Komponente verschmilzt) — `82d354f`
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
# PORT_PLAN — kernel2d nach Rust/WASM (hinter identischem TS-Interface)
|
||||
|
||||
Stand: 2026-07-04. **Dieser Plan ist der geforderte erste Schritt — noch kein Code.**
|
||||
Ziel: `src/geometry/kernel2d.ts` (+ reine Geometrie aus room/ceiling/opening/stair) in
|
||||
ein Rust-Crate portieren, zu WASM bauen, hinter einer TS-Fassade mit *exakt gleichen*
|
||||
Signaturen einhängen. TS-Kernel bleibt als Referenz (`kernel2d.legacy.ts`).
|
||||
Differential-Test: Rust-WASM == TS-Legacy auf identischen Eingaben (epsilon je Funktion).
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope-Inventar (was wirklich portiert wird)
|
||||
|
||||
**Externe JS-Geometrie-Libs: KEINE.** Der Kernel ist handgeschriebene f64-Mathematik,
|
||||
einzige Abhängigkeit sind die Vektor-Helfer aus `src/model/geometry.ts`
|
||||
(`add/sub/scale/len/normalize/leftNormal/cross/dot/lineIntersect`) — trivial mit zu
|
||||
portieren. `polygon-clipping`/`delaunator` werden im Kernel **nicht** genutzt.
|
||||
|
||||
### Voll im Scope (reine Geometrie)
|
||||
- **`kernel2d.ts`** — komplett: Primitive, Schnitt, Offset, Trim/Split/Join, Kreis, Fläche/Orientierung, Fillet.
|
||||
- **`roomBoundary.ts`** — komplett: `detectRooms`, `roomFromPointInside(Faces)`, `pointInPolygon`, `WallSegment`/`WallFace`/`DetectRoomsOptions` (generische Geometrie-Typen, `thickness: number`).
|
||||
- **`ceiling.ts`** — komplett: `normalizeOutline`, `isValidOutline`, `ceilingArea`, `outlineBBox`, `outlineCentroid`, `pointInOutline` (generische Polygon-Utilities; Name irreführend, keine Decken-Semantik).
|
||||
|
||||
### Teilweise im Scope
|
||||
- **`roomArea.ts`** — NUR `signedArea`, `polygonArea`, `perimeter`, `centroid`. Alles ab `SiaCategory` (`siaLabel`, `evaluateRoom`, `balance`, `roomsToCsv`) ist SIA-416-Domänenlogik/CSV → **bleibt TS**.
|
||||
- **`stair.ts`** — portierbar, aber `stairGeometry` nimmt `Stair`. „Scheinkopplung": es werden nur geometrische Felder gelesen (`shape/start/dir/runLength/width/stepCount/…`), `totalRise` kommt bereits aufgelöst vom Aufrufer. Port via Rust-Struct `StairParams` (strukturgleich, null Model-Semantik).
|
||||
|
||||
### ⚠️ Scope-Spannung: `opening.ts` (im Auftrag genannt, aber stark model-gekoppelt)
|
||||
7 von 9 Exporten binden `Wall`/`Opening` direkt ein; `getWallType`/`wallTypeThickness`/`wallReferenceOffset`/`wallVerticalExtent` lösen gegen `Project` auf.
|
||||
- **Portierbar mit abgeflachter Signatur** (Vec2/number statt Wall/Opening): `wallAxisLength`, `openingInterval`, `wallAxisFrame`, `openingJambs`, `openingCenter`, `doorSymbol`, `openingGapQuad`/`windowSymbol` (letztere brauchen `thickness`/`refOff` als `number`-Parameter).
|
||||
- **Bleibt TS** (braucht `Project`/Geschoss-Auflösung): `openingVerticalExtent` (via `wallVerticalExtent` → `Project.drawingLevels`).
|
||||
- **→ Entscheidung nötig** (siehe §7): Der Auftrag verbietet Änderungen an Aufrufstellen außer dem Import-Pfad. Ein Port von `opening` würde die *Signaturen* ändern (Wall→Vec2/number) und damit die Aufrufstellen brechen — das widerspricht „keine Änderung an Aufrufstellen". Empfehlung: **opening in Phase 1 ausklammern**, nur den echt reinen Kern (kernel2d/roomBoundary/ceiling/roomArea-Flächen/stair) portieren.
|
||||
|
||||
---
|
||||
|
||||
## 2. Crate-vs-Portieren — pro Operation
|
||||
|
||||
Akzeptanzkriterium ist **Differential-Parität gegen die naive TS-Routine**, nicht „geometrisch besser". Jedes Fremd-Crate mit anderem Algorithmus bricht die Parität per Konstruktion → Default = **PORTIEREN** (TS-Routinen sind 5–40 Zeilen f64, 1:1 übersetzbar).
|
||||
|
||||
| Operation | geprüftes Crate | Entscheidung | Grund |
|
||||
|---|---|---|---|
|
||||
| `offsetPolyline`/`offsetSegment` | cavalier_contours 0.6 | **PORTIEREN** | Crate liefert **Arcs (bulge)** statt `Vec2[]`, **heilt Selbstschnitte**, gibt mehrere Polylinien — TS heilt bewusst NICHT. Semantik nicht angleichbar. |
|
||||
| Segment/Line-Schnitt | geo / robust | **PORTIEREN** | Cramer-Formel; jede andere denom-/Epsilon-Politik driftet in Parallel-Grenzfällen. |
|
||||
| Trim/Split/`segmentPolylineHits` | geo `Relate` | **PORTIEREN** | Projektspezifisch (t-Dedup `1e-6`, Pick-nächster-Bogen, Wrap-Logik). |
|
||||
| Kreis-Schnitte | — | **PORTIEREN** | Quadratik mit projekt-EPS-Disc-Klemmung. |
|
||||
| `signedArea`/`isCCW` | geo `Area` | **PORTIEREN** | Shoelace-Summierung in **identischer Vertex-Reihenfolge** (f64 nicht assoziativ). |
|
||||
| `filletCorner` | — | **PORTIEREN** | `acos/atan2/tan(θ/2)`-Kette + projekt-Cutoffs; liefert projekt-spezifische `Fillet`-Struktur. |
|
||||
| `detectRooms` / `joinChains` | geo / i_overlay | **PORTIEREN** | Topologie-/reihenfolgeabhängig; andere Kantendurchlauf-Reihenfolge → andere (gleich gültige) Ringe → Parität bricht. |
|
||||
| point-in-polygon | robust | **PORTIEREN** (robust nur intern, optional) | Siehe §3. |
|
||||
|
||||
Fremd-Crates (cavalier_contours/geo/i_overlay) wären für einen späteren *Tier-2-Rewrite* (echter Arc-Offset, Boolean-Ops) wertvoll — das ist ein **anderes Produkt**, nicht dieser paritätserhaltende Port.
|
||||
|
||||
---
|
||||
|
||||
## 3. `robust`-Prädikate vs. Differential-Parität (Spannung auflösen)
|
||||
|
||||
TS testet Orientierung überall via naives `cross()` gegen `EPS`. `robust::orient2d` liefert das **exakte** Vorzeichen — weicht von naiv **nur in der nahe-degenerierten Zone** ab (fast-parallele Segmente, Null-Fläche-Polygone, Punkt-auf-Kante). Genau dort schlägt der Diff-Test am ehesten an. Man kann nicht gleichzeitig „bit-Parität gegen naiv" und „robuste Prädikate" im *selben* Vergleich haben.
|
||||
|
||||
**Entscheidung:**
|
||||
1. **v1 portiert die naiven `cross`-Vergleiche 1:1** (KEIN `robust`) → Zufalls-Diff-Test wird bit-nah grün. Robustheit kommt aus denselben f64-Formeln + demselben `EPS` wie TS.
|
||||
2. **Additiv, getrennt:** `robust` nur *intern* in `detectRooms`/point-in-polygon hinter optionalem Feature `robust-predicates`, flankiert von **Golden-Cases, die die KORREKTE (robuste) Antwort asserten** (nicht TS-Parität). Diese Fälle sind aus dem Zufalls-Diff-Test ausgenommen.
|
||||
3. Zwei Testklassen, nie gemischt: **Zufalls-Parität = naiv**, **Golden-Korrektheit = robust**.
|
||||
|
||||
---
|
||||
|
||||
## 4. Crate-Setup & Vite (exakter Klon von `src-tauri/geometry`)
|
||||
|
||||
Ort: **`src-tauri/kernel2d`** (analog render2d/render3d/geometry; das gesamte Tooling ist auf `src-tauri/<crate>` + `../../src/engine/pkg<X>` verdrahtet). *Namens-Hinweis:* Auftrag sagt `crates/kernel2d` — siehe §7.
|
||||
|
||||
- `Cargo.toml`: `crate-type = ["cdylib","rlib"]`; Features `default=[]`, `web=[wasm-bindgen, serde_json, console_error_panic_hook]`, `robust-predicates=[robust]` (additiv, nicht in web-Default).
|
||||
- `src-tauri/Cargo.toml`: `exclude = [..., "kernel2d"]` erweitern (sonst „multiple workspace roots").
|
||||
- `src/lib.rs`: reiner f64-Rechenkern feature-frei (`cargo test`-bar, headless) + `#[cfg(feature="web")]` JSON-**Batch**-Fassade pro Operation (`offset_polylines_json`, `intersect_batch_json`, `fillet_batch_json`, `circle_intersect_batch_json`, `detect_rooms_json`), Muster `compute_joins_json`.
|
||||
- `examples/parity.rs`: Klon von `geometry/examples/parity.rs` (Batch-JSON stdin→stdout) — nativer Diff-Kanal.
|
||||
- `package.json`: `"build:kernel2d": "wasm-pack build src-tauri/kernel2d --release --target web --out-dir ../../src/engine/pkgKernel2d --out-name kernel2d --no-default-features --features web"`.
|
||||
- **Vite: kein Config-Eintrag nötig** — `--target web`-Pakete werden als normales ES-Modul importiert, Vite bündelt `kernel2d_bg.wasm` automatisch (`new URL(..., import.meta.url)`). `pkgKernel2d/` ist git-ignoriert (self-`.gitignore = *`) → vor `vitest`/`build` muss `build:kernel2d` laufen (CI-Schritt).
|
||||
- **TS-Fassade** `src/geometry/kernel2d.ts` wird dünner Wrapper (init-WASM, JSON-Marshalling, gleiche Exports); Alt-Impl → `src/geometry/kernel2d.legacy.ts` (nicht löschen, ist die Diff-Referenz).
|
||||
|
||||
---
|
||||
|
||||
## 5. Differential-Test-Harness
|
||||
|
||||
- **Grobkörnige WASM-Grenze:** eine Batch-Funktion je Operation (N Polylinien rein, N Ergebnisse raus). Keine Per-Punkt-Calls (jeder Call marshallt einen String = O(n)-Kopie).
|
||||
- **Diff-Kanal:** Der Auftrag verlangt **Rust-WASM** vs. TS-Legacy → primär WASM (via `initSync`). Zusätzlich `cargo run --example parity` (nativ) als schneller Sekundär-Kanal (bit-identisch zu WASM für `+ - * / sqrt`; **Ausnahme** `atan2/acos/tan` in Fillet: libm nativ ≠ wasm um letzte ULP → Winkel-Epsilon).
|
||||
- **vitest-Init synchron:** `initSync({ module: readFileSync(pkgKernel2d/kernel2d_bg.wasm) })` (kein `fetch`), einmal in `beforeAll`.
|
||||
- **Zufallsgeneratoren:** seed-basiert (Seed im Testnamen), Polylinien 3–20 Vertices, Koordinaten `[-100,100] m`, `closed`/`d` zufällig; zusätzlich Cluster nahe `0` und `1e-6..1e-3`, um Toleranzschwellen zu treffen.
|
||||
- **Vergleichsreihenfolge:** zuerst **Struktur exakt** (Array-Längen, closed-Flags, Punktzahl, null/nicht-null), dann Werte mit op-Epsilon. Struktur ist der schärfste Paritäts-Wächter.
|
||||
|
||||
### Epsilon pro Funktion
|
||||
| Größe | Toleranz | Grund |
|
||||
|---|---|---|
|
||||
| Punktkoordinaten (Offset/Trim/Split/Kreis) | `abs 1e-9` | Bestehender Paritätstest nutzt `1e-9` und besteht bit-nah. |
|
||||
| Fläche (`signedArea`) | `rel 1e-9·max(1,\|A\|)` | Shoelace ∝ coord² → absolute ULP wächst mit Flächengröße; relativ skaliert korrekt. |
|
||||
| Winkel (`filletCorner`) | `abs 1e-7 rad` | `atan2/acos` libm-abhängig (nativ↔wasm ULP-Drift); 1e-7 rad ≈ 5.7e-6°, weit unter Zeichenrelevanz. |
|
||||
| Parameter t/s | `abs 1e-9` | TS dedupliziert erst ab `1e-6` → kleinere Diffs ändern nie die Struktur. |
|
||||
| Struktur | **exakt** | Kein Epsilon. Hier bricht ein Fremd-Crate. |
|
||||
|
||||
### Golden-Cases (explizit, aus Zufallstest teils ausgenommen)
|
||||
kollineare Tripel · Null-Länge-Segmente (Dublett-Vertex) · Offset-Selbstschnitt (enges U, großes d — hier bräche cavalier_contours) · spitze Fillet-Winkel (θ→0) · fast-paralleler Schnitt (denom knapp <>EPS → **Korrektheits-Golden/robust**) · konzentrische/tangentiale Kreise · Punkt exakt auf Polygonkante (**Korrektheits-Golden**).
|
||||
|
||||
---
|
||||
|
||||
## 6. Kritische Paritäts-Details (MÜSSEN exakt repliziert werden)
|
||||
- `len` = `Math.hypot` → **`f64::hypot`** (nicht `(x²+y²).sqrt()`).
|
||||
- `normalize` Null-Guard: `len || 1` → `if l==0.0 {1.0} else {l}` (Ergebnis `{0,0}`, kein NaN).
|
||||
- **Zwei Epsilons:** `EPS=1e-7` (kernel2d) UND hartkodiert **`1e-9`** in `lineIntersect` (Offset-Miter-Fallback hängt daran — geometrischer Sprung, nicht epsilon).
|
||||
- Dedup-Schwelle `1e-6`, Fillet-Kollinearität `1e-4` (nicht EPS).
|
||||
- **Stabile Sortierung** (JS `Array.sort` ist stabil): Rust `sort_by`, nicht `sort_unstable_by`.
|
||||
- Term-Reihenfolge in `cross`, `signedArea`-Summierung, Kreis-Diskriminante `B*B−4*A*C` exakt beibehalten (f64 nicht assoziativ; kein Kahan/Reorder).
|
||||
- Modulo-Indizierung `(i+len-1)%len` (usize-Unterlauf vermeiden).
|
||||
- `joinChains`: greedy `i<j`-erster-Treffer-dann-Neustart exakt nachbilden (reihenfolgeabhängiges Ergebnis).
|
||||
|
||||
---
|
||||
|
||||
## 7. Offene Entscheidungen (vor Coding klären)
|
||||
1. **`opening` im Scope?** Portieren würde Signaturen (Wall→Vec2/number) und damit Aufrufstellen ändern — widerspricht „keine Änderung an Aufrufstellen außer Import-Pfad". **Empfehlung: opening in Phase 1 ausklammern.**
|
||||
2. **Crate-Ort:** Auftrag `crates/kernel2d` vs. Repo-Konvention `src-tauri/kernel2d` (analog render2d/render3d). **Empfehlung: `src-tauri/kernel2d`** (Tooling passt out-of-the-box).
|
||||
3. **Diff-Kanal:** Auftrag verlangt Rust-**WASM**; nativer `parity`-Kanal ist schneller (kein wasm-pack in CI) und bit-identisch außer Fillet-Transzendente. **Empfehlung: WASM primär (Auftragskonform) + nativ sekundär.**
|
||||
|
||||
## 8. Phasen (Reihenfolge)
|
||||
1. Crate-Skelett `src-tauri/kernel2d` + Build-Script + Workspace-exclude + leere WASM-Fassade → `build:kernel2d` grün.
|
||||
2. Primitive + Schnitt + Fläche + Kreis portieren (trivial–mittel) + Batch-Fassade + Diff-Test-Harness (Zufall+Golden) → grün.
|
||||
3. Offset (Miter+1e-9-Fallback) + Fillet portieren → Golden für Selbstschnitt/spitze Winkel grün.
|
||||
4. Trim/Split/Join (`trimPolyline`, `splitAtIntersections`, `joinChains` — der Löwenanteil) → Struktur-Golden grün.
|
||||
5. `roomBoundary` (`detectRooms`) + `ceiling` + `roomArea`-Flächen + `stair` (StairParams).
|
||||
6. TS-Fassade umstellen (Alt → `.legacy.ts`), Import-Pfade der Aufrufstellen unverändert lassen, bestehende Suite grün, `npm run build` + WASM-Build sauber.
|
||||
@@ -1,54 +1,75 @@
|
||||
# DOSSIER Standalone
|
||||
|
||||
Browser-BIM für Wohnbau. Ein Werkzeug, um ein Wohnhaus aus semantischen
|
||||
Bauteilen zu modellieren und daraus saubere, normgerechte 2D-Pläne zu ziehen —
|
||||
ohne Revit, ohne Installation, im Browser.
|
||||
Eigenständiges BIM-Werkzeug für Wohnbau. Modelliert ein Wohnhaus aus
|
||||
semantischen Bauteilen und zieht daraus saubere, normgerechte 2D-Pläne —
|
||||
ohne Revit.
|
||||
|
||||
Das ist die eigenständige Browser-Variante des Rhino-Plugins
|
||||
Das ist die eigenständige Standalone-Variante des Rhino-Plugins
|
||||
[DOSSIER](https://git.kgva.ch/karim/DOSSIER): dieselbe Denkweise (Geschosse,
|
||||
Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts-Verschneidung), aber als
|
||||
React/Three.js-App statt als Rhino-Aufsatz.
|
||||
React/Three.js-App in einer Electron-Desktop-Shell statt als Rhino-Aufsatz.
|
||||
Kein Browser-Tab-Produkt: eigenes Fenster, eigene Rendering-Engine
|
||||
(Rust/WASM/WebGPU), gebaut für flüssiges CAD-Arbeiten statt für den Webview
|
||||
kleinster gemeinsamer Nenner.
|
||||
|
||||
## Grundgedanke
|
||||
|
||||
Es gibt **ein semantisches Modell** als einzige Wahrheit. Jede Ansicht — 3D,
|
||||
Grundriss, Schnitt, Ansicht — wird daraus **abgeleitet**. Darstellung
|
||||
Grundriss, Schnitt, Ansicht, PDF — wird daraus **abgeleitet**. Darstellung
|
||||
(Detailgrad, Linienstärken, Schraffuren) wird erst beim Rendern angewandt, nie
|
||||
in die Geometrie eingebacken. Wer eine Wand verschiebt, verschiebt sie überall.
|
||||
|
||||
Konkret heißt das auch: Der Grundriss entsteht nicht aus einem zerschnittenen
|
||||
3D-Mesh, sondern **analytisch aus den Bauteil-Parametern** (Wandachsen + Dicken →
|
||||
Linien, Öffnungen → Lücken + Symbol). Schnitte und Ansichten brauchen später den
|
||||
zweiten Weg — echte 3D-Projektion mit verdeckten Kanten.
|
||||
Linien, Öffnungen → Lücken + Symbol). PDF-Export ist derselbe Plan, nur mit
|
||||
echten mm-Stiftstärken statt Bildschirm-Hairlines — kein zweites Rendering.
|
||||
Schnitte und Ansichten brauchen den zweiten Weg — echte 3D-Projektion mit
|
||||
verdeckten Kanten (HLR) —, dessen Machbarkeit per Spike bewiesen, aber noch
|
||||
nicht ans UI angebunden ist.
|
||||
|
||||
## Stand heute
|
||||
|
||||
Ehrlich eingeordnet: aus dem Risiko-Spike ist ein **benutzbares 2D-CAD mit
|
||||
abgeleiteter 3D-Sicht** geworden. Der einfachere Teil steht und ist per
|
||||
Screenshot verifiziert; die schwierigen BIM-Kernrisiken sind bewusst noch offen.
|
||||
abgeleiteter 3D-Sicht, Vektor-PDF/DXF-Export und einer parametrischen
|
||||
Wand-Engine** geworden. Der einfachere Teil steht und ist per Screenshot/Probe
|
||||
verifiziert; die schwierigen BIM-Kernrisiken sind bewusst noch offen.
|
||||
|
||||
**Funktioniert:**
|
||||
- Semantisches Modell mit **mehrschichtigen Wänden** und automatischer
|
||||
**L-Ecken-Gehrung** (`computeJoins`); dieselbe Logik speist Plan *und* 3D.
|
||||
- **Parametrische Wände** (`ParametricWall`-Regelwerke: Grid/Modul/Sequenz/
|
||||
Referenzlinie/bedingte Dicke) lösen sich zu konkreten `Wall`-Objekten auf,
|
||||
statt jede Wand einzeln von Hand zu ziehen.
|
||||
- **Dokumentmodell wie in DOSSIER:** Zeichnungsebenen (Geschosse + Schnitte/
|
||||
Ansichten/Zeichnungen) × Kategorie-Ebenen (Code-Baum 1:1, `20 Wände`, `30 Decken` …).
|
||||
- **Zeichnen & Editieren** im Grundriss: Wand, Linie, Polylinie, Rechteck, Kreis;
|
||||
Snapping (Endpunkt/Mittelpunkt/Schnittpunkt/Lot/Raster/Ortho), Grips, Move/Copy/
|
||||
Offset, Spiegeln/Drehen/Array.
|
||||
- **Zeichnen & Editieren** im Grundriss: Wand, Linie, Polylinie, Rechteck, Kreis,
|
||||
Öffnungen, Treppen, Decken, Raumstempel; Snapping (Endpunkt/Mittelpunkt/
|
||||
Schnittpunkt/Lot/Raster/Ortho), Grips, Move/Copy/Offset, Spiegeln/Drehen/Array,
|
||||
Trim/Split/Join.
|
||||
- **Rhino-artiges Befehlssystem** mit getippten Koordinaten (`5,3` · `r5,3` ·
|
||||
`5<45`) und Tab-Feld-Zyklus (Länge → Winkel …).
|
||||
- **Vektor-Export:** PDF (A4/A3, Titelblock, echte mm-Stiftstärken nach ISO-Pen-
|
||||
Steps, keine Rasterbilder) und DXF — beide aus derselben `Plan`-Struktur wie
|
||||
der Bildschirm.
|
||||
- **PBR-Material-Bibliothek** (ambientCG-Import, `manifest.json`) für die
|
||||
3D-Ansicht.
|
||||
- **Resource Manager** (Component / Hatch / Line) — alles per id referenziert,
|
||||
zentral änderbar.
|
||||
- **Panel-System** (dockbar, stapelbar, floatend), Top-Bar + Statusleiste
|
||||
(echter Maßstab 1:N, Cursor X/Y), eigenes Kontextmenü, **i18n** (de/en).
|
||||
- **Import:** DXF (Konturen/Mesh) → Terrain-TIN; `.lin`/`.pat` für Linien/Schraffuren.
|
||||
- **Import:** DXF/DWG (Konturen/Mesh) → Terrain-TIN, Swisstopo/LV95-Geokontext,
|
||||
OSM-Kontextimport; `.lin`/`.pat` für Linien/Schraffuren.
|
||||
|
||||
**Bewusst noch offen** (die eigentlich harten Teile):
|
||||
- Echte **3D-Booleans** für Öffnungen — Türen/Fenster sind im Plan eine Lücke,
|
||||
noch kein geschnittenes Volumen.
|
||||
- **HLR** (verdeckte Kanten) für Schnitte und Ansichten — diese Views sind Stubs.
|
||||
- **HLR** (verdeckte Kanten) für Schnitte und Ansichten: Machbarkeit per
|
||||
OCCT-WASM-Spike bewiesen (`docs/welle-c-hlr-spike/`, `src/section/hlr.ts`),
|
||||
aber noch nicht ans UI/Dokumentmodell verdrahtet — Views sind noch Stubs.
|
||||
- **Prioritäts-T-/X-Stöße** mehrschichtiger Wände (Beton läuft durch, Putz
|
||||
verbindet seitlich) — das berüchtigte Risiko #1.
|
||||
- **Multi-Page-Layouts/Ausschnitte** (mehrere Viewports pro Blatt) — PDF-Export
|
||||
ist noch single-sheet.
|
||||
|
||||
Details und die Begründungen stehen im
|
||||
[HANDOVER](HANDOVER.md) (laufendes Arbeitsprotokoll) und in der
|
||||
@@ -61,43 +82,57 @@ Geometrie-Kernel verfrüht eingezogen.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Shell | Electron (eigenes randloses App-Fenster, kein Browser-Tab) |
|
||||
| Frontend | React + TypeScript + Vite |
|
||||
| 3D | Three.js |
|
||||
| 2D-Plan | eigener SVG-Renderer |
|
||||
| 3D | Three.js, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render3d`) |
|
||||
| 2D-Plan | eigener SVG-Renderer, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render2d`) |
|
||||
| Geometrie | eigener 2D-Kernel (`src/geometry/kernel2d.ts`), `delaunator` fürs Terrain |
|
||||
| Import | `dxf-parser`, eigene `.lin`/`.pat`-Parser |
|
||||
| Export | `jspdf`/`svg2pdf.js` (Vektor-PDF), eigener DXF-Writer |
|
||||
| Import | `dxf-parser`, `@mlightcad/libredwg-web` (DWG), eigene `.lin`/`.pat`-Parser |
|
||||
| Schnitt/HLR-Spike | `opencascade.js` (OCCT-WASM) — isoliert, noch nicht verdrahtet |
|
||||
|
||||
Geplant, aber noch nicht eingezogen: `rhino3dm` (NURBS / `.3dm`), web-ifc (IFC),
|
||||
OpenCascade/Manifold (exakte Booleans + HLR). Siehe ROADMAP §4.
|
||||
Manifold (exakte 3D-Booleans). OCCT-WASM ist für HLR bereits als Spike da (s.o.),
|
||||
aber noch nicht an Dokumentmodell/UI angebunden. Siehe ROADMAP §4.
|
||||
|
||||
## Entwicklung
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # Vite, http://localhost:5173
|
||||
npm run dev # Vite, http://localhost:5187
|
||||
npm run electron # Electron-Fenster (Dev-Server + randlose App-Shell)
|
||||
npx tsc -b # Typecheck
|
||||
npm run build # tsc -b && vite build
|
||||
npm test # vitest run
|
||||
```
|
||||
|
||||
Verifiziert wird visuell: `node scripts/probe.mjs` rendert die App headless und
|
||||
schreibt `scripts/probe.png` — Screenshot ansehen, Geometrie prüfen. Für
|
||||
Firefox-Fälle gibt es `scripts/probe-ff*.mjs` (Playwright).
|
||||
schreibt `scripts/probe.png` — Screenshot ansehen, Geometrie prüfen. Weitere
|
||||
`scripts/probe-*.mjs` decken einzelne Features ab (PDF, Trim, Materialien,
|
||||
Theme, Boolean-Ops …); für Firefox-Fälle gibt es `scripts/probe-ff*.mjs`
|
||||
(Playwright).
|
||||
|
||||
## Aufbau
|
||||
|
||||
```
|
||||
src/
|
||||
model/ semantisches Modell + Ableitungen (types, geometry, joins, terrain)
|
||||
geometry/ 2D-Kernel (offset/trim/fillet/intersect)
|
||||
model/ semantisches Modell + Ableitungen (types, parametricWalls, roomStamp, joins, terrain)
|
||||
geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling, opening, roomArea/-Boundary, stair)
|
||||
commands/ Rhino-artiges Befehlssystem (engine, parser, registry, cmds/)
|
||||
tools/ interaktive Zeichenwerkzeuge + Snapping + Transformationen
|
||||
plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG)
|
||||
plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2-Renderer)
|
||||
viewport/ Viewport3D (Three.js)
|
||||
section/ HLR-Spike (OCCT-WASM) — Schnitte/Ansichten, noch nicht verdrahtet
|
||||
export/ PDF- und DXF-Export aus derselben Plan-Struktur wie der Bildschirm
|
||||
materials/ PBR-Material-Bibliothek (ambientCG-Import, Runtime)
|
||||
text/ Rich-Text (Beschriftungen, Textobjekte)
|
||||
panels/ dockbares Panel-System + die einzelnen Paletten
|
||||
state/ Store + Slices (project/selection/view/layout)
|
||||
ui/ Top-Bar, Statusleiste, Kontextmenü, Resource Manager, Command-Line
|
||||
io/ Import/Export (DXF, .lin, .pat)
|
||||
io/ Import/Export (DXF, DWG, .lin, .pat), Swisstopo/LV95, OSM-Kontext
|
||||
i18n/ Wörterbücher de/en
|
||||
src-tauri/ Rust-Crates (render2d/render3d) — headless per wasm-pack zu WASM
|
||||
gebaut (npm run build:engine), Tauri-Host selbst ist ausrangiert
|
||||
```
|
||||
|
||||
## Konventionen
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# RESEARCH — Bauteile Treppe/Fenster/Tür: Rhino-Plugin als Vorbild
|
||||
|
||||
Stand: 2026-07-05. Vergleich der authoritativen Rhino-Plugin-Implementierung
|
||||
(`/Users/karim/PROJECTS/DOSSIER/rhino/`, C#/Python + RhinoCommon-B-Rep) mit
|
||||
DOSSIER-STANDALONEs vereinfachter TS-Umsetzung. Ziel: Bauteile aufs Rhino-Niveau
|
||||
heben. Belege = Rhino-Funktionsnamen.
|
||||
|
||||
**Grundsatz:** Rhino hat echten B-Rep-Kernel (3D). DOSSIER hat extrudierte Meshes +
|
||||
`kernel2d` (2D). Darum getrennt: **2D-Symbol-Logik** (leicht in TS/kernel2d
|
||||
übernehmbar) vs. **3D** (braucht Mesh-Pipeline bzw. später truck).
|
||||
|
||||
## Treppe (Rhino `treppe.py`, 1784 Z. — massiv reicher als `stair.ts`)
|
||||
- **Typen:** gerade / L (3- und 4-Punkt mit Podest) / Wendel (inkl. Spindel-Cone bei r_in<0.05). TS hat alle drei, aber simpler.
|
||||
- **3D-Querschnitt-Modi** `massiv`/`flach`/`plattenrand` (`_treppe_profile_2d`) — TS: nur Stufenboxen. **fehlt.**
|
||||
- **2D-Plansymbol** (`_make_treppe_2d_symbol`): Tritte, Lauflinie (4 Pfeil-Styles: klassisch/filled/breit/voll, visuell zentriert via `mid_off`), **Aussenlinie/Outline** (alle 3 Typen), Bruchlinie, Podest-Hexagon. TS: Tritte+Bruch+Lauflinie(1 Style) da; **Aussenlinie fehlt komplett**, Referenz links/mitte/rechts fehlt.
|
||||
- **Soll-Schrittmass** (2S+A, Lock), Show-Flags, Grips — fehlen in TS.
|
||||
|
||||
## Fenster (Rhino `elements.py` `_make_oeffnung_pieces`)
|
||||
- **3D:** Rahmen (BooleanDiff), Mittelpfosten je Flügel (1–4), Glas (einfach 12mm / Doppel 2×6+16), Sims aussen (4 Styles), Rahmen-Offset innen/mitte/aussen. TS: nur 2D-Rahmen+Glaslinien, Wand-Boolean-Loch (`1407c68`); kein Rahmen-3D.
|
||||
- **2D:** TS hat Rahmen + 1–2 Glaslinien (grob/mittel/fein). **Fehlt:** Brüstungslinie im Plan, Flügel-Mittelpfosten, Anschlag-Striche (nur Tür hat sie), Flügelanzahl.
|
||||
|
||||
## Tür (Rhino `elements.py`)
|
||||
- **Typen:** `normal` / `wandoeffnung` (reiner Durchbruch, kein Blatt). **Rahmen:** `zarge` (3-seitig, im Wandquerschnitt) / `block` (+5cm Überhang).
|
||||
- **2D-Schwung** (`_make_tuer_swing_curves`): Blatt+Arc, hinge_side/open_angle/aussenseite/swing_invert; Bogen-Anlage am Rahmen (standard/detail). **2D-Sturzlinien** (`_make_tuer_sturz_curves`, gestrichelt, Modi keine/innen/aussen/beide — SIA: Sturz über Schnittebene → gestrichelt). TS: Blatt+Arc+Anschlag (fein) da; **Sturzlinien fehlen komplett**, `wandoeffnung`-Typ + swing_invert fehlen.
|
||||
|
||||
---
|
||||
|
||||
## Gesamtreihenfolge „was zuerst" (Agenten-Empfehlung)
|
||||
|
||||
**Gruppe A — 2D-leicht, hoher Plan-Wert, kernel2d-kompatibel:**
|
||||
1. **Treppe Aussenlinie/Outline** (alle 3 Typen; gerade=4 Linien, L via Linienschnitt `_line_intersect_xy`, Wendel=2 Bögen+radiale). Ohne sie wirkt der Plan unfertig.
|
||||
2. **Fenster Brüstungslinie** im Plan (1 gepunktete Linie bei sillHeight>0). XS.
|
||||
3. **Tür Sturzlinien** (gestrichelt, SIA) — Schulfall der geplanten Schnitt/Ansichts-Logik.
|
||||
4. **Treppe Referenz links/mitte/rechts** + Lauflinie visuell zentriert.
|
||||
5. **Fenster Flügel-Mittelpfosten** (2D, `wingCount` ins Modell).
|
||||
6. **Tür `wandoeffnung`-Typ** (reine Öffnung ohne Blatt/Symbol).
|
||||
7. Treppe Show-Flags + `obere_dashed`; 8. Fenster Anschlag-Striche.
|
||||
|
||||
**Gruppe B — 2D mittel:** Tür-Schwung am Rahmen; Treppe Pfeil-Style `filled`; Fenster/Tür Styles/Presets; swing_invert.
|
||||
|
||||
**Gruppe C — 3D (nach Mesh-/B-Rep-Pipeline):** Treppe `flach`-Querschnitt; Rahmen als Mesh (Fenster/Tür); Türblatt+Glas; Sims; L-Podest-Hexagon; Wendel helikoidale Unterseite (truck).
|
||||
|
||||
**Bezug PENDENZEN:** Gruppe-A-Items 1–3 (Aussenlinie/Brüstung/Sturz) fallen in das Backlog-Item „Schnitt- vs. Ansichts-Darstellung nach Schnitthöhe" — Sturzlinie = Bauteil unter Schnittebene → gestrichelte Überkopf-Projektion. Gruppe C wartet auf den Textur-/Mesh-Pipeline-Nachfolger bzw. truck.
|
||||
@@ -0,0 +1,162 @@
|
||||
# RESEARCH — Von BIM-Tool zu „echtem CAD": Ansätze aus fortgeschrittenen Programmen
|
||||
|
||||
Stand: 2026-07-05. Direkt am Quellcode studiert (Repos nach `/tmp/cad-study` geklont,
|
||||
liegen NICHT im Projekt). Ziel: konkrete, verfolgbare Ansätze, um DOSSIER von einem
|
||||
spezialisierten BIM/Architektur-Werkzeug zu einem allgemeineren CAD zu erweitern.
|
||||
|
||||
## Studierte Programme
|
||||
|
||||
| Repo | Was | Lizenz | Stack | Für uns |
|
||||
|---|---|---|---|---|
|
||||
| [OpenCADStudio](https://github.com/HakanSeven12/OpenCADStudio) | Eigenständige 2D/3D-CAD-App, ~200k LOC Rust | GPL-3.0 | Iced (GUI) + wgpu + WASM-Web-Build | **App-Architektur-Blaupause** (Entity-Modell, Command-System, Snap, Grips). Fast unser Stack. |
|
||||
| [truck](https://github.com/ricosjp/truck) | Reiner Rust-CAD-**Kernel** (NURBS/B-Rep/Mesh/Boolean/STEP) | Apache-2.0 | 16 Crates, wgpu-Rendering + `truck-js` (WASM) | **3D-Kernel-Blaupause**. OpenCADStudio nutzt ihn als 3D-Backend. |
|
||||
| [acadrust](https://github.com/HakanSeven12/acadrust) | Pure-Rust **DWG+DXF** R13–R2018 read+write, 41 Entity-Typen | **MPL-2.0** | nom/byteorder/flate2/nalgebra/encoding_rs (alles pure Rust) | **DWG/DXF-Rückgrat**, MPL-2.0 nutzbar, sehr wahrscheinlich WASM-baubar. |
|
||||
|
||||
**Lizenz-Klarstellung:** `acadrust` ist **MPL-2.0** (file-level copyleft — für uns nutzbar,
|
||||
keine GPL-Ansteckung), NICHT GPL. `truck` ist Apache-2.0 (frei nutzbar). Nur OpenCADStudio
|
||||
selbst ist GPL-3.0 — wir lesen es als **Architektur-Referenz**, kopieren keinen Code.
|
||||
|
||||
---
|
||||
|
||||
## Die zentrale Lektion: was ein „echtes CAD" architektonisch ausmacht
|
||||
|
||||
DOSSIER ist heute **domänenzentriert** (Wand/Öffnung/Component/Plan-Pipeline, TS-Modell).
|
||||
Ein echtes CAD (Rhino/AutoCAD-artig) hat stattdessen einen **generischen Kern**. Aus
|
||||
OpenCADStudios `src/` destilliert:
|
||||
|
||||
### 1. Generisches Entity-Modell mit Traits (statt Wand-Spezialfall)
|
||||
`src/entities/` = 41 Entity-Typen (line, arc, circle, lwpolyline, spline, ellipse, hatch,
|
||||
text, mtext, dimension, leader, insert/block, table, viewport, mesh, solid3d, …). Jeder
|
||||
Typ implementiert ein **Trait-Set** statt einer Sonderbehandlung:
|
||||
- `TruckConvertible` → 3D-Kernel-Geometrie
|
||||
- `FallbackTess` → Display-Geometrie (Punkte/Snap-Vertices)
|
||||
- `Grippable` → editierbare Griffe + Hover-Menü (Add Vertex / Convert to Arc / Reverse / Lengthen…)
|
||||
- `PropertyEditable` → Eigenschaften-Panel
|
||||
- `Transformable` → Move/Rotate/Scale/Mirror
|
||||
- `MassProps` → Fläche/Umfang für Abfragen
|
||||
|
||||
→ **DOSSIER-Lehre:** ein generisches `Entity`-Modell mit Trait-Dispatch neben das BIM-Modell
|
||||
stellen. Wir haben Ansätze (`Drawing2D`, splitJoin-Editoren, kernel2d), aber keine einheitliche
|
||||
Entity-Taxonomie mit Traits.
|
||||
|
||||
### 2. Modeless Command-System — der eine `StepInput`-Step-Machine
|
||||
`src/command.rs`: EIN Enum `StepInput { Point / Text / EntityPick / StructurePick /
|
||||
SelectionComplete }`. **Jede** Eingabequelle (Kommandozeile, Viewport-Klick, Pick, Selektion,
|
||||
Dynamic Input, Plugin-API, Headless-Script) übersetzt in `StepInput` und läuft durch **eine**
|
||||
`feed_command`-Funktion → treibt die aktive `CadCommand`-Step-Machine, egal woher der Schritt kam.
|
||||
|
||||
→ **DOSSIER-Lehre:** Das ist der größte UX-Unterschied. Echtes CAD = „Befehl tippen → Punkte
|
||||
picken → Optionen". DOSSIER ist heute Tool-Button + Direktmanipulation. Ein `StepInput`-Funnel +
|
||||
Kommandozeile wäre der Hebel, um beliebige Werkzeuge einheitlich, scriptbar und headless-testbar
|
||||
zu machen.
|
||||
|
||||
### 3. Object-Snap-Engine (eigenständig, groß)
|
||||
`src/snap.rs` = 70 KB nur für OSNAP (endpoint/midpoint/center/intersection/tangent/
|
||||
perpendicular/…). Wir haben Teil-Snapping; ein echtes CAD hat eine dedizierte, vollständige
|
||||
Snap-Schicht.
|
||||
|
||||
### 4. Universelle Grip-Editierung
|
||||
Jede Entity liefert Griffe + kontextuelle Griff-Menüs mit teils numerischem Follow-up
|
||||
(„Lengthen" fragt Wert an der Kommandozeile ab). Einheitlich über das `Grippable`-Trait.
|
||||
|
||||
### 5. `CadDocument` als Dokumentmodell = das DWG/DXF-Objektmodell
|
||||
OpenCADStudio nutzt `acadrust::CadDocument` DIREKT als sein Datenmodell (Entities + Objects +
|
||||
Tables: Layer/Linetype/Style/Blocks + XData). D. h. das native CAD-Austauschformat IST das
|
||||
interne Modell → verlustfreier Round-Trip „for free".
|
||||
|
||||
---
|
||||
|
||||
## truck — 3D-Kernel-Blaupause (+ harte WASM-Realität)
|
||||
|
||||
Saubere Schichtung (Ship-of-Theseus, kleine ersetzbare Crates):
|
||||
|
||||
```
|
||||
truck-base Basistraits/Toleranz (cgmath)
|
||||
truck-geotrait ParametricCurve / ParametricSurface Traits
|
||||
truck-geometry Knot-Vektor, B-Spline, NURBS ← Kurven/Flächen
|
||||
truck-topology vertex/edge/wire/face/shell/solid ← B-Rep-Topologie
|
||||
truck-modeling Geometrie + Topologie integriert ← Solids bauen
|
||||
truck-polymesh Polygon-Datenstruktur + Meshing
|
||||
truck-meshalgo Tessellation der Shapes
|
||||
truck-shapeops Boolean-Ops auf Solids (v0.4 — früh)
|
||||
truck-stepio STEP read/write (v0.3)
|
||||
truck-platform wgpu-Grafik-Utility ┐ Rendering — brauchen wir NICHT
|
||||
truck-rendimpl Shape/Mesh-Visualisierung ┘ (wir haben eigenen wgpu-Renderer „Nordstern")
|
||||
truck-js WASM-Wrapper (v0.2)
|
||||
```
|
||||
|
||||
**WASM-Realität (zwei Befunde abgeglichen):**
|
||||
- OpenCADStudio pinnt `truck-meshalgo` 0.4 und schaltet den `solid3d`-Feature **im WASM-Build ab**,
|
||||
weil dessen ACIS-/vtkio-Pfad über `xz2 → lzma-sys` (C-Bibliothek) nicht nach wasm32 kommt →
|
||||
**im Web ist OpenCADStudio nur 2D.**
|
||||
- Neuere `truck-meshalgo` (0.6) gated `rayon`/`vtkio` selbst per `cfg(not(target_arch="wasm32"))`
|
||||
aus → **baut zu wasm32, aber single-threaded**. Der reine Geometrie-Stack
|
||||
(base → geotrait → geometry → topology → polymesh → meshalgo → modeling → shapeops) hat keine
|
||||
native-only-Deps und ist **sauber vom wgpu-Rendering trennbar** (truck-platform/-rendimpl weglassen).
|
||||
|
||||
**Reifegrad (Apache-2.0, aktiv, ~1 Hauptentwickler; ausdrücklich KEIN Produktionskern):**
|
||||
- NURBS/B-Spline + B-Rep-Topologie: **stabil** (v0.5/0.6).
|
||||
- Booleans (`truck-shapeops`): `and`/`or` vorhanden, aber **instabil bei coincident geometry**
|
||||
(Issue #114); **`difference` fehlt** (#85); **Fillets fehlen komplett**. Der Fork
|
||||
[`monstertruck`](https://github.com/virtualritz/monstertruck) hat difference + rolling-ball-Fillet.
|
||||
→ Für unsere Wandöffnungen (boolean holes) haben wir das ohnehin schon selbst gelöst (`spanCutouts`).
|
||||
- `truck-stepio`: STEP **schreiben** (aber NICHT für boolean-operierte Shapes) + **lesen** (v0.6 beta,
|
||||
neues `truck_stepio::in`).
|
||||
- `truck-js`: fertiger wasm-bindgen-Wrapper (kein npm-Paket → selbst per `wasm-pack` bauen), bündelt
|
||||
modeling/shapeops/meshalgo/stepio; `IntoWasm`-Muster.
|
||||
|
||||
---
|
||||
|
||||
## acadrust — DWG/DXF-Rückgrat
|
||||
|
||||
Pure Rust, **MPL-2.0**, `CadDocument`-Objektmodell (entities/objects/tables/xdata/classes),
|
||||
41 Entity-Typen, DWG „208/208 roundtrip-perfect", serde optional, failsafe-Parsing, ~40 Codepages.
|
||||
**Alle Deps pure Rust** (nom/byteorder/flate2-miniz/nalgebra/indexmap/ahash/encoding_rs) → sehr
|
||||
wahrscheinlich zu wasm32 baubar (Datei-I/O-API müsste auf Bytes/Reader statt `std::fs` umgestellt
|
||||
werden — vermutlich schon vorhanden). Heute nutzen wir JS `dxf-parser` (nur 2D) + `libredwg-web`.
|
||||
|
||||
---
|
||||
|
||||
## Web-Import-Landkarte (für den GEO-BLOCK / Import-Backlog)
|
||||
|
||||
Pro Format die aktive, web-/WASM-taugliche Lib (Stand Mitte 2026):
|
||||
|
||||
| Format | Empfohlene Lib | Lizenz | Anmerkung |
|
||||
|---|---|---|---|
|
||||
| DWG/DXF (browser) | `@mlightcad/cad-viewer` / `libredwg-web` (nutzen wir) **oder** `acadrust` (Rust) | MIT / GPL-3.0 / **MPL-2.0** | acadrust = Rust-Weg, MPL-2.0 |
|
||||
| STEP/IGES/BREP | `occt-wasm` (OCCT V8, ~4.5 MB) oder unser `opencascade.js` | LGPL-2.1 | occt-wasm = moderne TS-API |
|
||||
| **IFC** (BIM!) | `web-ifc` (ThatOpen) | MPL-2.0 | De-facto-Standard, read+write, sehr aktiv |
|
||||
| SHP/GIS | `shpjs` + `proj4` | MIT | direkt für GEO-BLOCK (SWISSIMAGE/Terrain) |
|
||||
| Punktwolken LAS/LAZ | `@loaders.gl/las` (+ `laz-perf`) | MIT/Apache | Rendering: `potree-core` oder deck.gl |
|
||||
| E57 | `e57-js` (Emscripten/libE57Format) | MIT | neu, funktional, sehr obskur |
|
||||
| glTF/OBJ/STL | three.js-Loader (haben wir via `three`) | MIT | |
|
||||
| 3MF | `lib3mf` (WASM) oder `THREE.3MFLoader` | BSD-2 / MIT | |
|
||||
| PDF-Vektor | `pdfjs-dist` `getOperatorList()` | Apache-2.0 | kein fertiges PDF→DXF; roher Pfad-Stream |
|
||||
|
||||
---
|
||||
|
||||
## Priorisierte Ansätze für DOSSIER (die eigentliche Antwort)
|
||||
|
||||
Reihenfolge = Nutzen × Machbarkeit, ohne die BIM-Stärke zu opfern.
|
||||
|
||||
1. **Generisches Entity-Modell + Trait-Dispatch** (Fundament). Neben das BIM-Modell eine
|
||||
generische Entity-Schicht (line/arc/circle/polyline/spline/text/dimension/hatch/block) mit
|
||||
Traits à la OpenCADStudio (Display/Grips/Props/Transform). Unser `kernel2d` liefert schon die
|
||||
Geometrie-Operationen dafür. **Größter struktureller Hebel.**
|
||||
2. **Modeless Command-System** (`StepInput`-Funnel + Kommandozeile). Ein Eingabe-Enum, eine
|
||||
`feed_command`-Schleife, `CadCommand`-Step-Machines. Macht Werkzeuge einheitlich, scriptbar,
|
||||
headless-testbar. **Größter UX-Hebel Richtung „echtes CAD".**
|
||||
3. **DWG/DXF-Round-Trip über `acadrust`** (Rust/WASM). MPL-2.0, pure Rust → passt in unseren
|
||||
neuen `src-tauri`-Kern. Ersetzt langfristig die JS-Parser, bringt echten Import/Export.
|
||||
Erst: WASM-Baubarkeit + Bytes-API verifizieren (Spike wie beim Textur-Spike).
|
||||
4. **Vollständige Object-Snap-Schicht** als eigenes Modul (endpoint/mid/center/intersection/
|
||||
tangent/perp/…), aufbauend auf `kernel2d`.
|
||||
5. **3D-B-Rep später, selektiv** (truck-geometry/-topology/-modeling als reine Crates, OHNE
|
||||
truck-Rendering — wir haben Nordstern). NUR wenn wir echte NURBS/Solids/Booleans brauchen; der
|
||||
Meshing-/Boolean-Pfad ist WASM-problematisch (native C-Deps) und Booleans sind noch früh.
|
||||
Bis dahin: truck als **Referenz** lesen, nicht einbinden.
|
||||
|
||||
**Kernaussage:** DOSSIERs Rust-nach-WASM-Kurs (den wir mit `kernel2d` gerade gehen) ist genau
|
||||
richtig — OpenCADStudio bestätigt ihn. Der Weg zu „echtem CAD" führt über (1) generisches
|
||||
Entity-Modell und (2) modeless Command-System; (3) `acadrust` bringt echten DWG/DXF-Austausch.
|
||||
3D-B-Rep (truck) ist ein späterer, selektiver Schritt mit klaren WASM-Vorbehalten.
|
||||
@@ -0,0 +1,137 @@
|
||||
# SPIKE — Bild-Texturen in `render3d` (`RenderStyle::Textured` real machen)
|
||||
|
||||
Stand: 2026-07-05. **Auftrag/Übergabe für einen Agenten. Kleinster ehrlicher
|
||||
Durchstich — kein Produktfeature, keine Integration.**
|
||||
|
||||
Ziel: Beweisen, dass der bestehende wgpu-3D-Renderer echte **Bild-Texturen** auf
|
||||
Wandflächen darstellen kann, sichtbar im `spike3d`-Fenster. Am Ende steht eine
|
||||
belastbare Aussage, wie viel Arbeit „richtig gutes texturiertes 3D" wirklich ist —
|
||||
statt Spekulation.
|
||||
|
||||
---
|
||||
|
||||
## 0. Ausgangslage (verifiziert am 2026-07-05)
|
||||
|
||||
- `render3d` ist **kein** Three.js-Wrapper, sondern ein eigenständiger wgpu-Renderer
|
||||
(~6400 LOC): echte GPU-Pipeline (wgpu 29), Tiefenpuffer, MSAA, WGSL-Shader.
|
||||
Läuft nativ (winit-Spike), headless (naga-validiert) und im Browser (WebGPU).
|
||||
- **`RenderStyle::Textured` existiert bereits als Stub** (`gpu.rs:34` Enum-Variante,
|
||||
`gpu.rs:47` Parse aus `"textured"`) — es gibt aber **kein echtes Texturing**:
|
||||
kein Sampler, keine Textur-Bind-Group, keine Bilddaten.
|
||||
- Die `cap_pipeline` mit Layout `[pos vec3, uv vec2]` + `CAP_WGSL` ist **nicht** für
|
||||
Bildtexturen, sondern für die **Schnittflächen-Kappen** (prozedurale Schraffur);
|
||||
die UVs steuern dort den Schraffur-Abstand. **Nicht damit verwechseln.**
|
||||
- **Günstig für uns:** Die Muster, die der Spike braucht, existieren schon —
|
||||
ein UV-tragendes Vertex-Layout und eine zweite/dritte Pipeline, die sich die
|
||||
`Globals`-Bind-Group teilt (`grid`, `cap`). Der texturierte Mesh-Pfad reiht sich
|
||||
1:1 in dieses Muster ein.
|
||||
|
||||
---
|
||||
|
||||
## 1. Echte Symbole — vor dem Coding lesen
|
||||
|
||||
| Was | Ort |
|
||||
|---|---|
|
||||
| Vertex heute interleaved `[pos.xyz, normal.xyz, color.rgb]`, `FLOATS_PER_VERTEX`, `Mesh`-Struct | `src-tauri/render3d/src/types.rs:322` ff. |
|
||||
| Quad-Emitter (Normale + Farbe je Vertex, Reihenfolge `(0,1,2)+(0,2,3)`) | `src-tauri/render3d/src/mesh.rs:886` |
|
||||
| Wand-Extrusion / Mesh-Bau | `src-tauri/render3d/src/mesh.rs` — `extrude_wall` (`:91`), `build_walls_mesh` (`:911`) |
|
||||
| Haupt-Pipeline + `Globals`-Bind-Group (group 0) | `src-tauri/render3d/src/gpu.rs:232`–`:320` |
|
||||
| Vorlage „zweite Pipeline teilt sich Globals" — Grid | `src-tauri/render3d/src/gpu.rs:329` |
|
||||
| Vorlage „Pipeline mit UV-Layout `[pos vec3, uv vec2]`" — Cap | `src-tauri/render3d/src/gpu.rs:408` |
|
||||
| `RenderStyle`-Enum + Stub `Textured` | `src-tauri/render3d/src/gpu.rs:34`, `:47` |
|
||||
| Pipeline-Bindung im Render-Pass (Muster für Stil-Umschaltung) | `src-tauri/render3d/src/gpu.rs:847` |
|
||||
| Shader als WGSL-Konstanten (`MESH_WGSL`, `CAP_WGSL`) | `src-tauri/render3d/src/shaders.rs` |
|
||||
| Beleuchtungsmodell (hemisphärisch + Directional + Fill) — Doku | `src-tauri/render3d/src/shaders.rs:1`–`40` |
|
||||
| naga-WGSL-Validierung headless (Test-Vorlage) | `src-tauri/render3d/src/lib.rs:924` (`cap_module`) |
|
||||
| Fenster-Spike mit Orbit-Kamera | `src-tauri/render3d/src/bin/spike3d.rs` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Umfang — exakt das, nicht mehr
|
||||
|
||||
### 2.1 UVs auf Wandflächen
|
||||
- In der Quad-Emitter-Funktion (`mesh.rs:886`) je Vertex eine **UV** berechnen:
|
||||
planare Projektion in **Metern** — `u` = Distanz entlang der Wandachse,
|
||||
`v` = Höhe (z). Textur-Raster damit weltmassstäblich (z. B. 1 Kachel = 1 m).
|
||||
- Den bestehenden `[pos, normal, color]`-Pfad **bitgleich unangetastet** lassen.
|
||||
Zwei zulässige Wege (Agent wählt begründet):
|
||||
1. **Separates additives UV-Array** in `Mesh` (Default leer/None), oder
|
||||
2. **Paralleler `build_walls_mesh_textured`** → interleaved
|
||||
`[pos.xyz, normal.xyz, uv.xy]`.
|
||||
- Regressionstests für den Alt-Pfad müssen grün bleiben (siehe §4).
|
||||
|
||||
### 2.2 Test-Textur prozedural (kein Asset, keine `image`-Crate)
|
||||
- Ein **256×256 RGBA-Schachbrett/Grid im Code** generieren (`Vec<u8>`).
|
||||
- `device.create_texture` + `queue.write_texture` + `Sampler`
|
||||
(`FilterMode::Linear`, `AddressMode::Repeat`). Mipmaps optional (nice-to-have für
|
||||
flache Blickwinkel; kein Muss für den Spike).
|
||||
- Selbstständig, damit der Spike ohne Dateipfade/Asset-Pipeline läuft.
|
||||
|
||||
### 2.3 Textur-Bind-Group (group 1)
|
||||
- Neue Bind-Group-Layout mit `texture_view` (`TextureSampleType::Float`) +
|
||||
`sampler`. **`Globals` bleibt group 0** und unverändert.
|
||||
|
||||
### 2.4 Textured-Pipeline + WGSL (`MESH_TEXTURED_WGSL`)
|
||||
- Vertex-Layout `[pos vec3, normal vec3, uv vec2]`, `TriangleList`.
|
||||
- **Dieselbe Beleuchtung wie `MESH_WGSL`** (hemisphärisches Ambient + Directional +
|
||||
Fill) — nur **Albedo = `textureSample(tex, samp, uv)`** statt Vertex-Farbe.
|
||||
- Depth-Format, MSAA (`SAMPLE_COUNT`) und Color-Target **identisch** zur
|
||||
Haupt-Pipeline (sonst inkompatibler Render-Pass).
|
||||
- Pipeline-Layout bindet group 0 (Globals) **und** group 1 (Textur).
|
||||
|
||||
### 2.5 Verdrahten
|
||||
- Bei `RenderStyle::Textured` im Render-Pass die neue Pipeline + beide Bind-Groups
|
||||
setzen (Muster: `cap_pipeline`-Bindung bei `gpu.rs:847`).
|
||||
|
||||
### 2.6 Spike sichtbar machen
|
||||
- `spike3d.rs` so erweitern, dass der Stil auf `Textured` schaltbar ist
|
||||
(Tastendruck, z. B. `T`, **oder** Startkonstante). Die Demo-Wände sollen
|
||||
texturiert im Orbit erscheinen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Randbedingungen (hart)
|
||||
|
||||
- **Nur** die `render3d`-Crate. `src/web.rs` und die Tauri-/`native3d`-Oberfläche
|
||||
**nicht** anfassen.
|
||||
- Feature-gegatet unter dem bestehenden `render`/`window`-Feature.
|
||||
**Default-Build und Default-Darstellung bleiben unverändert.**
|
||||
- **Keine neuen Dependencies** (insbesondere **kein `image`-Crate**) für den Spike.
|
||||
- Term-/Reihenfolge-sensible Geometrie (Parität) wird **nicht** berührt — es kommt
|
||||
nur additiv ein UV-Kanal + ein zweiter Render-Pfad dazu.
|
||||
- Kommentar-Stil und Sprache (Deutsch, ausführliche Begründungs-Kommentare) wie im
|
||||
umgebenden Code beibehalten.
|
||||
|
||||
---
|
||||
|
||||
## 4. Akzeptanz / Verifikation
|
||||
|
||||
1. `cargo test` (im Crate-Verzeichnis `src-tauri/render3d`) **grün**, inklusive:
|
||||
- bestehende Mesh-Regression (Alt-Pfad `[pos,normal,color]` unverändert),
|
||||
- **neuer naga-Validierungstest** für `MESH_TEXTURED_WGSL` (Vorlage:
|
||||
`lib.rs:924`).
|
||||
2. `cargo run --features window --bin spike3d` zeigt die Demo-Wände mit
|
||||
**erkennbarer, korrekt gemappter** Schachbrett-Textur:
|
||||
- Raster weltmassstäblich (in Metern), keine Verzerrung an Gehrungen/Ecken,
|
||||
- beleuchtet wie im Shaded-Modus (Volumen bleibt ablesbar).
|
||||
3. Umschalten Shaded ↔ Textured zur Laufzeit (oder per Startkonstante) funktioniert
|
||||
ohne Re-Meshing-Crash.
|
||||
|
||||
---
|
||||
|
||||
## 5. Abschlussbericht (vom Agenten am Ende zu liefern)
|
||||
|
||||
- Welche Dateien geändert/hinzugefügt wurden und warum.
|
||||
- Wie die UVs projiziert werden (Achswahl, Massstab, Verhalten an Gehrungen).
|
||||
- Welcher der beiden UV-Wege (§2.1) gewählt wurde und weshalb.
|
||||
- **Ehrliche Lückenliste für „richtig gutes" Texturing:** Asset-/Bild-Datei-Laden,
|
||||
Material→Textur-Zuordnung (Wandtyp/Layer → Material), Normal-/Roughness-Maps
|
||||
(PBR), anisotropes Filtern + Mipmaps, Web-Pfad (`web.rs`/WebGPU), UI zum
|
||||
Zuweisen. Grobschätzung Aufwand je Punkt.
|
||||
|
||||
---
|
||||
|
||||
## 6. Nicht im Scope
|
||||
|
||||
Asset-/Bild-Datei-Laden · Material-System · mehrere Texturen gleichzeitig ·
|
||||
PBR/Normal-Maps · Web-Pfad (`web.rs`) · jegliche UI · Anbindung unter die Webview.
|
||||
@@ -1,4 +1,4 @@
|
||||
# HAUPTINSTANZ-BRIEFING: Tauri + wgpu (Korrigiert)
|
||||
# ARCHITEKTUR-BRIEFING: Tauri + wgpu (Korrigiert)
|
||||
|
||||
**Stand:** 2026-07-01 — **KORREKTUR** (vorherige Dokumente waren unvollständig)
|
||||
|
||||
@@ -158,7 +158,7 @@ npm run tauri:build # → Windows .exe / macOS .app / Linux .deb
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte (für Hauptinstanz)
|
||||
## Nächste Schritte
|
||||
|
||||
1. **Lesen:** `docs/design/tauri-migration-plan.md` (technisch, konkret)
|
||||
2. **Spawn:** vier Agents (parallel, unabhängig)
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# DOSSIER-Feature-Audit (A1–A6, B1–B4, C1–C3, D1–D3, E)
|
||||
|
||||
Ausführliche Fassung des Audits, auf das HANDOVER.md Backlog-Punkt 9 nur noch mit
|
||||
Einzeilern verweist. Abgleich DOSSIER-Rhino-Referenzrepo (`/tmp/dossier-ref`) gegen
|
||||
den aktuellen Browser-Port. Belege (Datei:Zeile) beziehen sich auf `/tmp/dossier-ref/rhino/*.py`.
|
||||
|
||||
Status-Symbole: ❌ nicht übernommen · 🟡 teilweise übernommen.
|
||||
|
||||
## A — Grösserer Funktionsumfang
|
||||
|
||||
**A1 · ❌ Grafische Overrides — Regel-Engine** (`overrides.py:7-40`)
|
||||
ArchiCAD/Vectorworks-Stil: Regeln der Form `condition{type: layer_name|user_string|
|
||||
object_name, operator: equals|contains|starts_with|not_equals, value, key} →
|
||||
actions{color, lineweight, linetype}`, additiv angewendet, oberste Regel gewinnt,
|
||||
reversibel. Cross-Doc-Presets + Rule-Templates (`list_presets:147`,
|
||||
`list_rule_templates:218`). Der Port hat nur manuelle Per-Instanz-Farbe (By-Layer/
|
||||
By-Object/eigener Wert, kein Regelwerk). Wert: enorm für Architektur-Pläne
|
||||
(Bestand grau, Brandabschnitte, Bauphasen farblich markieren). Aufwand: M-L.
|
||||
|
||||
**A2 · ❌ Ausschnitte / View-Snapshots** (`ausschnitte.py:466-531`)
|
||||
Benannte, gespeicherte Ansichten, die Kamera-Zustand + Layer-Sichtbarkeit +
|
||||
Massstab + Detailgrad einfangen und wiederherstellen; organisiert in Ordnern +
|
||||
Presets (`_capture_camera:78`, `_capture_layers:157`, `_capture:466`). Wert:
|
||||
zentral für Wiederverwendbarkeit und Voraussetzung für A3. Aufwand: L.
|
||||
|
||||
**A3 · ❌ Print-Layout-Blätter** (`layouts.py:7-8`)
|
||||
Layout-Seiten mit mehreren Details, jedes Detail an einen Ausschnitt-Snapshot
|
||||
gebunden (`_BIND_KEY:28`), Papierformate A4/A3, Ordnerstruktur, PDF-Export pro
|
||||
Layout-Blatt. Der Port exportiert aktuell nur eine einzelne Zeichnung nach PDF.
|
||||
Aufwand: L, hängt an A2.
|
||||
|
||||
**A4 · ❌ Tragwerk-Elemente** (`elemente.py`)
|
||||
Stütze, Träger, Unterzug, I-Profil als eigene Elementtypen. Der Port kennt nur
|
||||
Wand/Decke/Öffnung/Treppe/Raum. Wert: vervollständigt den BIM-Elementsatz.
|
||||
Aufwand: M je Typ.
|
||||
|
||||
**A5 · 🟡 Öffnungen viel reicher** (`elemente.py:59-68`)
|
||||
Mehrere Flügel (`OEFF_FLUEGEL`), Sims innen+aussen mit eigenen Stilen
|
||||
(`SIMS_AUS`/`SIMS_IN`), Glas-Toggle (`OEFF_GLAS`), Rahmen-Lage aussen/mittig/innen
|
||||
(`RAHMEN_POS`), Rahmen-Profilbreite/-tiefe, Detailgrad einfach/standard/detail =
|
||||
SIA-400-Darstellung (`OEFF_DARSTELLUNG:68`). Der Port hat nur swing/hinge/
|
||||
frameDepth/frameThickness. Aufwand: M.
|
||||
|
||||
**A6 · 🟡 Element-Schedule / Bauteilliste** (`elemente_uebersicht.py:45,214`)
|
||||
Voller Element-Überblick + SIA-Flächenbilanz + CSV-Export
|
||||
(`_export_bilanz:214`). Der Port hat nur einen Raum-CSV-Export. Aufwand: M.
|
||||
|
||||
## B — Werkzeuge & Objekt-Info
|
||||
|
||||
**B1 · ❌ Text-Platzierungs-Werkzeug** (`text_create.py:972,847,105`)
|
||||
On-Canvas-Text-Objekte als eigenständiges Zeichenwerkzeug (nicht nur
|
||||
Raumstempel): Textstile, Fonts, Rich-Text fett/kursiv/unterstrichen,
|
||||
Ausrichtung, Symbol-Einfügung, „auf Selektion anwenden". Der Port hat den
|
||||
Rich-Text-Editor (`src/text/RichTextEditor.tsx`) bereits, aber kein
|
||||
Platzierungs-Werkzeug, um damit ein freistehendes Textobjekt zu zeichnen
|
||||
(Shortcut „1" ist bewusst noch unbelegt). Aufwand: M.
|
||||
|
||||
**B2 · 🟡 Object-Info numerisch erweitern** (`dimensions.py:342-350,240,267`)
|
||||
Position, Rotation um Z-Achse (`_rotate_around_axis:240`), Kreis-Radius,
|
||||
Linien-Länge (`_set_line_length:267`), Rechteck Breite/Höhe,
|
||||
Koordinatensystem World/CPlane, 9-Punkt-Referenz. Der Port kann nur per
|
||||
Anker-Griff skalieren. Aufwand: S-M.
|
||||
|
||||
**B3 · ❌ Kamera-Presets + Nordwinkel** (`kamera.py:28,36-45,87`)
|
||||
Nicht in HANDOVER.md gelistet, aber im Audit gefunden: Kardinal-Presets
|
||||
(N/O/S/W), Iso-Oktanten, Rotation um einen einstellbaren Nordwinkel
|
||||
(georeferenziert, relevant für Swisstopo-Kontext).
|
||||
|
||||
**B4 · ❌ Massstab-Toolbar-Funktionen** (nicht separat referenziert, Teil der
|
||||
Toolbar-Logik) — Zoom 1:1/auf Selektion, Linienstärken-Set, Grid/Ortho/
|
||||
Referenzlinien-Toggles direkt aus der Symbolleiste.
|
||||
|
||||
## C — Zusätzlich gefunden, nicht in HANDOVER.md
|
||||
|
||||
**C1 · LoD pro Ansicht** — Darstellung einfach/standard/detail je Ansicht
|
||||
umschaltbar (SIA-400-Detailgrad-Konvention), nicht nur global.
|
||||
|
||||
**C2 · Override-Presets + Rule-Templates als Bibliothek** — vertieft A1: die
|
||||
Regeln selbst sind wiederverwendbare, benannte Presets/Templates, nicht nur
|
||||
Ad-hoc-Zustand pro Dokument.
|
||||
|
||||
**C3 · Mass-Style-Presets** — Dezimalstellen/Rundung als benannte,
|
||||
wiederverwendbare Bemassungs-Stile.
|
||||
|
||||
## D — Weitere Backlog-Ergänzungen aus dem Audit
|
||||
|
||||
**D1** Per-Layout-PDF-Export (vertieft A3).
|
||||
**D2** Bauteil-CSV mit vollem Element-Set (vertieft A6).
|
||||
**D3** Komponenten-Thumbnails (visuelle Vorschau im Component-Manager).
|
||||
|
||||
## E — Bewusst NICHT zu portieren
|
||||
|
||||
Rhino-/Windows-gebundene Implementierungsdetails ohne Browser-Äquivalent:
|
||||
Window-Layout-XML-Persistierung, Auto-DPI via CoreGraphics, Custom-Grips-Code
|
||||
(Rhino-SDK-spezifisch), nativer `.3dm`-Geometrie-Import.
|
||||
|
||||
## Priorisierung (aus dem Original-Audit)
|
||||
|
||||
1. A1 (Override-Regel-Engine)
|
||||
2. A2 (View-Snapshots)
|
||||
3. A3 (Print-Layout-Blätter)
|
||||
4. A5 (reichere Öffnungen)
|
||||
5. B1 (Text-Platzierungs-Werkzeug)
|
||||
6. A4 (Tragwerk)
|
||||
7. B2 (Object-Info numerisch)
|
||||
8. A6 (Bauteil-Schedule)
|
||||
|
||||
Siehe auch `ROADMAP.md` §11 für eine noch breitere, unabhängig entstandene
|
||||
Backlog-Liste (4-Wege-Survey über Bauteile/Darstellung/Pläne/Kontext) mit
|
||||
teilweiser Überschneidung.
|
||||
@@ -0,0 +1,137 @@
|
||||
# Engine-Nordstern — Headless-Rendering (PNG-Export & Golden-Image-Tests)
|
||||
|
||||
> Betrifft `src-tauri/render2d` (Feature `headless`). Bezug: HANDOVER.md,
|
||||
> Abschnitt ENGINE-NORDSTERN, Punkt 3 ("deterministisches Headless-Rendering,
|
||||
> PNG-Export und Golden-Image-Tests ohne Fenster").
|
||||
|
||||
## Warum
|
||||
|
||||
`render2d` trennt die serde-only-Tessellierung (Feature-los, headless testbar)
|
||||
von der GPU-Schicht (Feature `render`, wgpu). Der Fenster-Pfad (`gpu::Renderer`,
|
||||
Feature `window`) braucht dafür bislang eine `wgpu::Surface` — also ein echtes
|
||||
Fenster mit Wayland-/X11-Session. Für deterministische PNG-Exporte und
|
||||
Golden-Image-Tests (CI, Regressions-Screenshots) ist das unnötig: wgpu kann
|
||||
genauso gut in eine `wgpu::Texture` rendern, ganz ohne Surface/Fenster.
|
||||
|
||||
`gpu::Renderer::render` nahm bereits vorher nur `Device`/`Queue`/`TextureView`
|
||||
entgegen — Surface- oder Offscreen-Textur macht für den Draw-Code keinen
|
||||
Unterschied. Der Offscreen-Pfad (`src/headless.rs`) dupliziert daher NICHTS,
|
||||
sondern baut nur Device/Queue ohne Surface sowie eine eigene Ziel-Textur +
|
||||
Buffer-Readback drumherum.
|
||||
|
||||
## Feature-Gating
|
||||
|
||||
Neues Cargo-Feature `headless = ["render", "dep:image"]`:
|
||||
- zieht `render` (wgpu/glyphon/bytemuck/pollster) plus die `image`-Crate
|
||||
(nur der PNG-Codec, `default-features = false, features = ["png"]`).
|
||||
- **berührt den wasm/web-Build nicht**: `cargo check --target wasm32-unknown-unknown
|
||||
--no-default-features --features web` zieht `image` nicht mit.
|
||||
- Binary `render_png` und Test `golden` sind zusätzlich per
|
||||
`required-features = ["headless"]` in `Cargo.toml` abgesichert.
|
||||
|
||||
## API
|
||||
|
||||
```rust
|
||||
pub struct HeadlessRenderer { /* Device, Queue, gpu::Renderer */ }
|
||||
|
||||
impl HeadlessRenderer {
|
||||
/// Instance/Adapter/Device OHNE Surface (Backend fest auf Vulkan gepinnt).
|
||||
/// `Err`, wenn kein Adapter verfügbar ist (z.B. CI-Runner ohne GPU) — die
|
||||
/// Aufrufer (CLI, Golden-Test) behandeln das, statt zu paniken.
|
||||
pub fn new() -> Result<Self, String>;
|
||||
|
||||
/// Rendert `scene` in ein `width`x`height`-Bild (Papier-Maßstab
|
||||
/// `paper_scale_n`, z.B. `100.0` für 1:100). Lädt die Szene bei jedem Aufruf
|
||||
/// neu hoch (kein Zwischenzustand nötig für CLI-/Test-Anwendungsfall).
|
||||
pub fn render_to_image(
|
||||
&mut self,
|
||||
scene: &Scene,
|
||||
width: u32,
|
||||
height: u32,
|
||||
view_box: ViewBox,
|
||||
paper_scale_n: f32,
|
||||
) -> RgbaImage; // { width, height, pixels: Vec<u8> (straff gepackt, RGBA8) }
|
||||
}
|
||||
|
||||
impl RgbaImage {
|
||||
pub fn encode_png(&self) -> Vec<u8>;
|
||||
/// Gegenstück für den Golden-Test: Referenz-PNG -> straff gepacktes RGBA8.
|
||||
pub fn decode_png(bytes: &[u8]) -> Result<Self, String>;
|
||||
}
|
||||
```
|
||||
|
||||
Backend bewusst auf **Vulkan** gepinnt (nicht das Standard-Backend-Set): Vulkan
|
||||
rendert offscreen ohne jede Fenster-/Display-Server-Abhängigkeit. GL bräuchte auf
|
||||
Linux i.d.R. einen EGL/GLX-Kontext, der ohne aktive Display-Session (X11/Wayland)
|
||||
Sonderfälle hat — auf dieser Maschine ist ohnehin ein echter Vulkan-ICD (RADV)
|
||||
vorhanden, daher kein Rückgriff auf `lavapipe`/`llvmpipe` nötig gewesen.
|
||||
|
||||
## Row-Alignment-Falle (wichtig für zukünftige Agenten)
|
||||
|
||||
`wgpu::Queue::copy_texture_to_buffer` / `CommandEncoder::copy_texture_to_buffer`
|
||||
verlangt, dass `bytes_per_row` im `ImageDataLayout` ein Vielfaches von
|
||||
`wgpu::COPY_BYTES_PER_ROW_ALIGNMENT` (256) ist. Bei RGBA8 (4 Byte/Pixel) trifft
|
||||
das **nur zufällig** zu — z.B. Breite 300 px → 1200 Byte/Zeile, kein Vielfaches
|
||||
von 256, der Copy schlägt sonst mit einem Validierungsfehler fehl (oder liefert
|
||||
verzerrte Zeilen, je nach Backend).
|
||||
|
||||
Lösung in `headless::HeadlessRenderer::read_pixels`:
|
||||
1. `padded_bytes_per_row = align_up(width * 4, 256)` — der Zielpuffer wird auf
|
||||
diese (größere oder gleiche) Zeilenbreite alloziert.
|
||||
2. Nach dem Mapping wird **jede Zeile einzeln** von `padded_bytes_per_row` auf
|
||||
die echte Breite (`width * 4`) zurückgeschnitten und in einen straff
|
||||
gepackten `Vec<u8>` kopiert — sonst hätte das Ausgabebild pro Zeile
|
||||
Garbage-Padding am rechten Rand.
|
||||
|
||||
## Referenzbild neu erzeugen
|
||||
|
||||
Bei einer **gewollten** Rendering-Änderung (z.B. neue Shader, neue Demo-Szene,
|
||||
geänderte Antialiasing-Parameter) weicht das Golden-Bild ab. Referenzbild
|
||||
danach bewusst neu erzeugen:
|
||||
|
||||
```sh
|
||||
cd src-tauri/render2d
|
||||
cargo run --features headless --bin render_png -- --width 990 --height 630 --out target/headless-demo.png
|
||||
cp target/headless-demo.png tests/golden/demo.png
|
||||
```
|
||||
|
||||
Vorher unbedingt das erzeugte PNG (`target/headless-demo.png`) visuell prüfen
|
||||
(z.B. mit einem Bildbetrachter oder Read-Tool eines Agenten) — nicht blind
|
||||
kopieren. Bei einem fehlgeschlagenen Testlauf schreibt der Golden-Test
|
||||
automatisch ein Diff-Bild nach `target/golden-diff.png` (abweichende Pixel
|
||||
signalrot markiert, Rest unverändert) — das hilft beim Einordnen, ob die
|
||||
Abweichung erwartet (Layout-/Farbänderung) oder ein Bug ist (z.B. verschobene
|
||||
Geometrie, fehlender Text).
|
||||
|
||||
## Golden-Test
|
||||
|
||||
`tests/golden.rs`, Test `demo_szene_entspricht_referenzbild`:
|
||||
- rendert die Demo-Szene (`demo::demo_scene`, dieselbe wie der Fenster-Spike)
|
||||
in 990×630 und vergleicht sie gegen `tests/golden/demo.png`.
|
||||
- Toleranz: ein Pixel gilt als "abweichend", wenn irgendein Kanal-Delta > 2 ist
|
||||
(deckt AA-Rundungsrauschen ab); der Test schlägt fehl, wenn mehr als 0,5%
|
||||
aller Pixel abweichen.
|
||||
- `#[ignore]`, weil eine Vulkan-fähige GPU auf CI-Runnern nicht garantiert ist
|
||||
(und ein Software-Rasterizer wie llvmpipe anderes AA liefern würde als das
|
||||
Referenzbild). Lokal explizit ausführen:
|
||||
`cargo test --features headless -- --include-ignored`.
|
||||
- Bei Abweichung über der Toleranz schreibt der Test zusätzlich zum Diff-Bild
|
||||
auch das Ist-Bild nach `target/golden-actual.png` — nach visueller Prüfung
|
||||
ist das die Vorlage für eine gewollte Referenz-Aktualisierung.
|
||||
- `HeadlessRenderer::new()` liefert bei fehlendem Adapter `Err` statt Panik —
|
||||
der Test überspringt sich dann sauber (`eprintln!` + `return`) statt rot zu
|
||||
schlagen, falls doch mal ohne `--ignored`/ohne GPU ausgeführt.
|
||||
|
||||
## Offener Punkt: Text-Determinismus zwischen GPU-Treibern
|
||||
|
||||
Der Textpass läuft über `glyphon`/`cosmic-text` (Systemfonts, `Inter`). Die
|
||||
exakte Glyphen-Rasterisierung (Subpixel-Antialiasing, Hinting) kann je nach
|
||||
installierter Font-Version, Fontconfig-Konfiguration und GPU-Treiber leicht
|
||||
variieren — auch bei identischer Geometrie. Auf **derselben** Maschine (gleicher
|
||||
Treiber, gleiche Font-Version) ist der Test wie erwartet bit-nah deterministisch
|
||||
(hier: 0 abweichende Pixel bei zwei aufeinanderfolgenden Läufen). Bei einem
|
||||
Wechsel der Maschine/des Treibers/der Fontconfig-Version ist nicht
|
||||
auszuschließen, dass die 0,5%-Toleranz für Textkanten knapp wird — sollte das
|
||||
in der Praxis auftreten: entweder Toleranz für den Text-Bereich lockern, oder
|
||||
den Text testweise aus der Golden-Szene ausklammern und separat (z.B. nur
|
||||
Zeilenbreite/Position, nicht Pixel) prüfen.
|
||||
@@ -0,0 +1,511 @@
|
||||
# Engine-Nordstern 1 — Strichbreiten-Audit (Papier-mm end-to-end)
|
||||
|
||||
> Bezug: HANDOVER.md, Abschnitt ENGINE-NORDSTERN Punkt 1 ("Papier-mm-exakte
|
||||
> Strichbreiten überall — Bildschirm bei jedem Massstab/Zoom = Druck"). Dieses
|
||||
> Dokument ist ein Prüfbericht, kein Umbau — es fasst zusammen, wie die
|
||||
> Strichbreite heute vom Modell bis zum Papier läuft, wo sie divergieren kann,
|
||||
> und was konkret zu tun wäre. Nichts hieraus wurde umgesetzt.
|
||||
|
||||
Betroffene Pfade: `src/plan/PlanView.tsx` (SVG-Default + Print-Vorschau),
|
||||
`src/plan/glPlan/*` (WebGL2, aktueller Default-GPU-Pfad), `src-tauri/render2d`
|
||||
(WASM/WebGPU, `?engine=wasm`), `src/export/sceneToPrintSvg.ts` +
|
||||
`src/export/exportPdf.ts` (Vektor-PDF). Referenz-Baseline für die
|
||||
2D-Engine-Parität ist laut Commit `ce6bd26` **`?gl=0`** (reiner SVG-Pfad) —
|
||||
NICHT der App-Default (der ist WebGL2, siehe Finding 1).
|
||||
|
||||
## 1. Modell der Wahrheit
|
||||
|
||||
So sollte eine Strichbreite in diesem Code korrekt fliessen:
|
||||
|
||||
1. **Quelle**: `generatePlan.ts` legt jede Strichbreite als `weightMm` /
|
||||
`strokeWidthMm` in **echten Papier-Millimetern** ab (Kommentarkopf,
|
||||
`generatePlan.ts:76-79`: "Alle Stricharten sind in mm Papier definiert").
|
||||
Diese Zahl ist unabhängig von Zoom, Massstab und Render-Pfad — eine 0.18 mm
|
||||
Wand-Umrisslinie bleibt 0.18 mm, ganz gleich wo sie später landet.
|
||||
2. **Bildschirm bei Massstab 1:N**: 1 Modell-Meter entspricht auf Papier
|
||||
`1000/N` mm. Der Bildschirm zeigt `PX_PER_M = 90` viewBox-Einheiten je
|
||||
Modell-Meter (`PlanView.tsx:23`). Eine `mm`-Breite belegt daher
|
||||
`mm · N/1000 · PX_PER_M` viewBox-Einheiten — das ist exakt
|
||||
`printStrokeVb()` (`PlanView.tsx:2598-2600`). Multipliziert mit der
|
||||
Geräte-px-je-viewBox-Einheit-Skala (`meet`, `PlanView.tsx:2074-2078`) ergibt
|
||||
das die tatsächliche Bildschirmbreite in Geräte-Pixeln. Reinzoomen (kleinere
|
||||
viewBox-Breite, grösseres `meet`) macht die Linie dicker — genau wie beim
|
||||
Herausvergrössern eines gedruckten Plans mit der Lupe. Das gilt für JEDE
|
||||
Zoomstufe gleichermassen: 250 % Zoom bei 1:50 zeigt exakt die 5-fache
|
||||
Pixelbreite von 100 % Zoom bei 1:50, und bei gegebenem Zoom ist 1:50 exakt
|
||||
doppelt so dick wie 1:100 (halber Nenner → doppelt so viele Weltmeter je
|
||||
Papiermm).
|
||||
3. **Druck/PDF**: dieselbe Formel, nur ohne Geräte-px-Zwischenschritt — direkt
|
||||
`mm` bleibt `mm` — die Seite ist direkt in echten
|
||||
Papiermillimetern aufgespannt (`sceneToPrintSvg.ts:99-125`). Zusätzlich wird
|
||||
auf ISO-nahe Stiftstufen gerundet (`PEN_STEPS`,
|
||||
`sceneToPrintSvg.ts:44`), weil ein reales Zeichengerät/Plotter nur endlich
|
||||
viele Stiftbreiten kennt.
|
||||
4. **Eine Wahrheit**: Seit `382771b` bauen sowohl der Viewport
|
||||
(`useWasmPlanRenderer.ts`/`nativeSync.ts`) als auch der PDF-Export
|
||||
(`exportPdf.ts:73`) dieselbe `planToRenderScene(plan)`-Szene — der PDF-Pfad
|
||||
ist nur ein anderes *Ziel* derselben Szene, kein zweiter Interpret.
|
||||
|
||||
Korrekt hiesse also: **jede** der vier Anzeige-/Exportarten (SVG-Default,
|
||||
SVG-Print-Vorschau, GPU-Viewport, PDF) muss aus **derselben** `weightMm`, für
|
||||
**denselben** `N`, exakt dieselbe Papier-mm-Breite ergeben — bis auf die
|
||||
bewusste PEN_STEPS-Rundung im Druckpfad, die dokumentiert und überall
|
||||
gleichermassen sichtbar sein sollte (ist sie nicht, siehe Finding 2).
|
||||
|
||||
Ausdrücklich **kein** Teil dieses Modells: der Haarlinien-Modus
|
||||
(`lineMode: "display"`, App-Default, `viewSlice.ts:38,74`). Er ist als
|
||||
bewusster Papier-mm-*Ausstieg* gedacht ("Display: all lines as constant
|
||||
hairlines (calm editing)", `en.ts:173`) — 1 Geräte-px, konstant, unabhängig
|
||||
von Zoom/Massstab. Er muss also NICHT der Papier-mm-Formel folgen, aber er
|
||||
muss in JEDEM Render-Pfad *gleichermassen* als Ausstieg wirken. Tut er nicht
|
||||
(Finding 1).
|
||||
|
||||
## 2. Pfad-für-Pfad-Trace
|
||||
|
||||
### 2a. SVG-Default (App-Start ohne `?gl=0`, `lineMode:"display"`)
|
||||
|
||||
Nur aktiv, wenn WebGL2 fehlschlägt (`wantGl` ist sonst `true`, s. Finding 1) —
|
||||
de facto der reine Fallback-Pfad.
|
||||
|
||||
- `PlanView.tsx:2613`: `print = !hairline && paperScale != null && paperScale > 0`
|
||||
— mit `hairline=true` (Default) ist `print` immer `false`.
|
||||
- `PlanView.tsx:2617-2618`:
|
||||
```ts
|
||||
const weight = (mm: number): number =>
|
||||
hairline ? HAIRLINE_PX : print ? printStrokeVb(mm, paperScale!) : mmToPx(mm);
|
||||
```
|
||||
`HAIRLINE_PX = 1` (`PlanView.tsx:2583`), Einheit: Geräte-unabhängiger
|
||||
CSS-Pixel, gezeichnet mit `vector-effect="non-scaling-stroke"`
|
||||
(`PlanView.tsx:2621`, `vfx`) → bleibt beim Pan/Zoom optisch exakt 1 px, egal
|
||||
wie stark reingezoomt wird. Papier-mm spielt hier explizit KEINE Rolle.
|
||||
|
||||
### 2b. SVG-Print-Vorschau (`lineMode:"print"`, weiterhin `?gl=0` oder
|
||||
WebGL2-Fallback)
|
||||
|
||||
- `print = true`, `weight(mm) = printStrokeVb(mm, paperScale!)`
|
||||
(`PlanView.tsx:2598-2600`):
|
||||
```ts
|
||||
function printStrokeVb(mm: number, n: number): number {
|
||||
return Math.max(1e-4, (mm * n) / 1000) * PX_PER_M;
|
||||
}
|
||||
```
|
||||
`vectorEffect` entfällt (`vfx = undefined`, `PlanView.tsx:2621`) — die Linie
|
||||
skaliert MIT der Geometrie beim Zoomen, wie physisches Papier unter der
|
||||
Lupe. `paperScale` selbst ist der **stabile, gemessene** 1:N-Nenner
|
||||
(`PlanView.tsx:458-460`), nicht der live aus jedem Radzoom abgeleitete Wert
|
||||
— sonst würde Reinzoomen die Linien nicht dicker, sondern konstant halten
|
||||
(das wäre wieder Haarlinien-Verhalten). Keine PEN_STEPS-Rundung — die rohe
|
||||
`mm`-Zahl wird 1:1 in viewBox-Einheiten übersetzt.
|
||||
|
||||
### 2c. GPU-Viewport (WebGL2 `useGlPlanRenderer.ts` — App-DEFAULT; WASM
|
||||
`useWasmPlanRenderer.ts` bei `?engine=wasm`)
|
||||
|
||||
- Beide Hooks reichen `paperScaleN` unverändert an die Engine durch:
|
||||
`useWasmPlanRenderer.ts:131-149` (`render(viewBox, paperScaleN=100,
|
||||
textScaleN=100)` → `r.set_paper_scale(paperScaleN)`); WebGL2 analog
|
||||
(`useGlPlanRenderer.ts:102-116`, kein `textScaleN`-Parameter überhaupt, s.
|
||||
Finding 3).
|
||||
- `PlanView.tsx` ruft in JEDEM Render-Aufruf
|
||||
(`PlanView.tsx:494`,`502`,`1080`) `renderGl(view, paperScaleForGl(view),
|
||||
textScaleForGl())`. `paperScaleForGl` (`PlanView.tsx:475-476`):
|
||||
```ts
|
||||
const paperScaleForGl = (v: ViewBox): number =>
|
||||
paperScaleRef.current ?? scaleFromView(v, svgRef.current) ?? 100;
|
||||
```
|
||||
**Kein `hairline`-Zweig.** Egal ob `lineMode` "display" oder "print" ist,
|
||||
hier kommt immer ein echter 1:N-Papier-Nenner heraus.
|
||||
- Rust-Seite (`gpu.rs:606-616`):
|
||||
```rust
|
||||
let mm_px = mm_to_device_px(view_box, vw, vh, self.paper_scale_n);
|
||||
```
|
||||
`mm_to_device_px` (`ortho.rs:96-99`) reproduziert exakt `printStrokeVb`:
|
||||
`(paper_scale_n/1000.0) * PX_PER_M * meet` — `PX_PER_M = 90.0`
|
||||
(`tessellate.rs:22`, identisch zu TS). Die reine Mathematik ist also
|
||||
deckungsgleich zum SVG-Print-Pfad (2b) — aber sie läuft **immer**, auch wenn
|
||||
der Nutzer "Display: Haarlinien" gewählt hat. Siehe Finding 1.
|
||||
- Stiftbreite pro Batch im Shader (`shaders.rs:91`, `LINE_WGSL`):
|
||||
`width_px = max(0.6, stroke_px * stroke_scale) * miter` — harte Untergrenze
|
||||
0.6 Geräte-px, die weder die SVG- noch die PDF-Seite kennt (Finding 6).
|
||||
Identische Formel für Bögen (`shaders.rs:209`, `ARC_WGSL`) und im
|
||||
WebGL2-Pfad (`glPlanRender.ts:243-249`, Kommentar "klemmt bei ~0.6 px").
|
||||
- Schraffur-Musterlinien laufen NICHT über `mm_px`, sondern über
|
||||
`width_screen`/`px_per_screen` (`gpu.rs:661-665`): Geräte-px = Breite ×
|
||||
`meet`-Skala statt × mm→px — bewusst zoom-skalierend wie das SVG-`<pattern>`,
|
||||
nicht papierkonstant (s. `toRenderScene.ts:371-375`, Finding 4).
|
||||
|
||||
### 2d. Vektor-PDF (`exportPdf.ts` → `sceneToPrintSvg.ts`)
|
||||
|
||||
- `exportPdf.ts:73`: `const scene = planToRenderScene(plan);` — dieselbe
|
||||
Szene wie 2c, unabhängig vom aktuell gewählten `lineMode` der Ansicht.
|
||||
- `sceneToPrintSvg.ts:243-244`:
|
||||
```ts
|
||||
const effectiveMm = widthScreen ? (widthMm / PX_PER_M) * mmPerM : widthMm;
|
||||
const strokeMm = quantizePen(effectiveMm);
|
||||
```
|
||||
`PEN_STEPS = [0.13, 0.18, 0.25, 0.35, 0.5, 0.7, 1.0]`, `MIN_PEN_MM = 0.13`
|
||||
(`sceneToPrintSvg.ts:44,47`). JEDE Linie/Umriss/Bogen/Schraffur wird auf die
|
||||
nächsthöhere Stufe gerundet, bevor sie ins SVG/PDF geht
|
||||
(`sceneToPrintSvg.ts:251,274,301`). Das ist der EINZIGE der vier Pfade, der
|
||||
überhaupt quantisiert.
|
||||
- `widthScreen`-Konvertierung: `widthMm` (hier eigentlich viewBox-Einheiten,
|
||||
siehe 2c) wird über `/PX_PER_M * mmPerM` zurück in Weltmeter und dann in
|
||||
Papier-mm beim GEWÄHLTEN `opts.scaleDenominator` übersetzt — nicht beim
|
||||
Massstab, den die Live-Ansicht gerade zeigt (Finding 4).
|
||||
- Text: `sizeMm` kommt unverändert aus `RText.sizeMm`
|
||||
(`sceneToPrintSvg.ts:325`, `t.sizeMm`, keine Nachskalierung) — aber die
|
||||
VERTIKALE Zeilenposition dieser Texte wurde in `toRenderScene.ts` mit einem
|
||||
festen `STAMP_REF_N = 100` in Weltmeter umgerechnet (`toRenderScene.ts:163-165,
|
||||
481-483`), unabhängig vom tatsächlich gewählten Export-`N` (Finding 3).
|
||||
|
||||
## 3. Findings (nach Schwere geordnet)
|
||||
|
||||
### 1. Haarlinien-Modus (App-Default) wird vom GPU-Renderer komplett ignoriert — betrifft den Standard-Zustand der App
|
||||
|
||||
`lineMode` startet auf `"display"` (`viewSlice.ts:74`), und WebGL2 ist der
|
||||
Default-Renderer (`wantGl` ist `true`, ausser `?gl=0`,
|
||||
`PlanView.tsx:433-437`) — **d. h. im frisch geladenen, unkonfigurierten App-
|
||||
Zustand rendert bereits der GPU-Pfad, und die "Display: Haarlinien"-Option
|
||||
tut nichts.** `paperScaleForGl` (`PlanView.tsx:475-476`) liest nur
|
||||
`paperScaleRef.current ?? scaleFromView(...) ?? 100` — der `hairline`-Boolean
|
||||
aus den Props (`PlanView.tsx:2571`) wird an dieser Stelle nie geprüft, obwohl
|
||||
das benachbarte `textScaleForGl` (`PlanView.tsx:485-488`) exakt diesen Zweig
|
||||
korrekt hat:
|
||||
```ts
|
||||
const textScaleForGl = (): number => {
|
||||
const print = !hairline && paperScale != null && paperScale > 0;
|
||||
return print ? paperScale! : 100;
|
||||
};
|
||||
```
|
||||
Ergebnis: Text (Raumstempel) fällt im Haarlinien-Modus korrekt auf die
|
||||
Referenzskala 100 zurück, aber jede Wand-/Tür-/2D-Zeichenlinie wird trotzdem
|
||||
mit dem echten (gemessenen oder geschätzten) `paperScale` gezeichnet — sie
|
||||
wird beim Reinzoomen dicker statt konstant zu bleiben, exakt das Gegenteil
|
||||
dessen, was der Menüpunkt verspricht ("calm editing", `en.ts:173`). Der Bug
|
||||
betrifft sowohl `useGlPlanRenderer.ts` (Default) als auch
|
||||
`useWasmPlanRenderer.ts` (`?engine=wasm`) — keiner der beiden `render()`-
|
||||
Aufrufe kennt einen `hairline`-Parameter überhaupt.
|
||||
|
||||
### 2. PEN_STEPS-Quantisierung existiert NUR im PDF-Export — beide Live-Vorschauen (SVG-Print und GPU) zeigen unquantisierte Rohwerte
|
||||
|
||||
Konkret, mit den tatsächlichen Konstanten aus `generatePlan.ts`:
|
||||
|
||||
- `LAYER_LINE_MM = 0.13` (`generatePlan.ts:82`), `LAYER_DETAIL_FACTOR.fein =
|
||||
0.7` (`generatePlan.ts:90-93`) → Schichtfuge im Detailgrad "fein":
|
||||
`0.13 · 0.7 = 0.091 mm` (`generatePlan.ts:1022`,
|
||||
`strokeWidthMm: LAYER_LINE_MM * LAYER_DETAIL_FACTOR[detail]`). Die
|
||||
Bildschirm-Print-Vorschau zeigt genau diese 0.091 mm (`printStrokeVb(0.091,
|
||||
N)`); der PDF-Export klemmt via `quantizePen` auf `MIN_PEN_MM = 0.13` mm —
|
||||
**+43 % dicker im Druck als in der Vorschau.**
|
||||
- Türschwenkbogen: `doorLwMm * 0.6` (`generatePlan.ts:902`) mit dem üblichen
|
||||
Fallback `doorLwMm = LAYER_LINE_MM = 0.13` (`generatePlan.ts:354`) ergibt
|
||||
`0.078 mm`. Screen zeigt 0.078 mm (nahezu unsichtbar dünn bei kleinen
|
||||
Massstäben), PDF klemmt auf 0.13 mm — **+67 %.**
|
||||
- Wand-Umriss im Detailgrad "grob": `outlineMm = wallLwMm ·
|
||||
OUTLINE_DETAIL_FACTOR.grob (1.6)` (`generatePlan.ts:84-88, 648, 796`); beim
|
||||
häufigen Fallback `WALL_FALLBACK_MM = 0.18` (`generatePlan.ts:97`) ergibt das
|
||||
`0.288 mm`. `quantizePen(0.288)` rundet auf die nächste Stufe `0.35`
|
||||
(`PEN_STEPS`, da `0.25 < 0.288 ≤ 0.35`) — **+21.5 % im Druck.**
|
||||
|
||||
Das systematische Muster: der Detailgrad "fein" existiert explizit, um
|
||||
Nebenlinien DÜNNER zu machen (`generatePlan.ts:52-58`,
|
||||
`LAYER_DETAIL_FACTOR.fein = 0.7`) — aber sobald der berechnete Wert unter
|
||||
`MIN_PEN_MM` fällt, hebt die PDF-Quantisierung ihn wieder auf die Untergrenze
|
||||
an, ohne dass die Bildschirm-Vorschau (weder SVG-Print noch GPU) davon
|
||||
irgendetwas zeigt. Wer die Druckvorschau am Bildschirm beurteilt, sieht NICHT,
|
||||
was tatsächlich gedruckt wird.
|
||||
|
||||
### 3. Stempel-Zeilenabstand ist nur bei Massstab 1:100 exakt — SVG- und RenderScene-Pfad nutzen zwei verschiedene Formeln für dieselbe Grösse
|
||||
|
||||
Bereits als bekannte Alt-Lücke in HANDOVER.md (Commit `382771b`, Punkt 6)
|
||||
vermerkt; hier präzise verortet:
|
||||
|
||||
- SVG (`PlanView.tsx:2738-2739`):
|
||||
```ts
|
||||
const nRef = print ? paperScale! : 100;
|
||||
const unitPerPt = (1 / 72) * 0.0254 * nRef * PX_PER_M;
|
||||
```
|
||||
`nRef` folgt im Druckmodus dem TATSÄCHLICH aktiven `paperScale` — der
|
||||
Zeilenabstand (`lineGap = baseFs * 1.3`, `PlanView.tsx:2741`) ist für jedes
|
||||
`N` korrekt in viewBox-Einheiten, weil er direkt aus `unitPerPt` (das `N`
|
||||
enthält) abgeleitet wird.
|
||||
- RenderScene (`toRenderScene.ts:163-165`):
|
||||
```ts
|
||||
const STAMP_REF_N = 100;
|
||||
const MM_TO_M = STAMP_REF_N / 1000;
|
||||
```
|
||||
und (`toRenderScene.ts:481-483`):
|
||||
```ts
|
||||
const baseH = baseMm * MM_TO_M; // Basisgröße in Modell-Metern
|
||||
const lineGap = baseH * 1.3;
|
||||
```
|
||||
Hier ist der Umrechnungsfaktor `MM_TO_M` FEST auf `N = 100` verdrahtet,
|
||||
unabhängig davon, mit welchem `N` der WASM-Viewport oder der PDF-Export
|
||||
tatsächlich rendert (`paper_scale_n` in `gpu.rs`, `opts.scaleDenominator` in
|
||||
`sceneToPrintSvg.ts`). Bei `N = 100` sind beide Formeln identisch (das war
|
||||
offenbar der Verifikationsfall in `382771b`); bei jedem anderen `N` — z. B.
|
||||
1:50 — driftet der vertikale Zeilenabstand der Stempel-Mehrzeiler
|
||||
proportional zum Verhältnis `N_wahr/100` auseinander, während die einzelne
|
||||
Zeilenhöhe (`sizeMm`) selbst korrekt bleibt. Betroffen: WASM-Viewport (2c)
|
||||
UND PDF-Export (2d) gleichermassen (beide beziehen `RText` aus derselben
|
||||
`toRenderScene`-Funktion); die WebGL2-Ansicht ist NICHT betroffen, weil sie
|
||||
Text grundsätzlich nicht selbst zeichnet, sondern die SVG-Overlay-Ebene
|
||||
weiterverwendet (`PlanView.tsx:1592`,
|
||||
`useGpuRenderer ? wantWasm || p.kind !== "text" : ...` — bei WebGL2
|
||||
(`wantWasm=false`) werden Text-Primitive NIE aus dem SVG-Rendering
|
||||
herausgefiltert).
|
||||
|
||||
### 4. Schraffur-Strichbreite im PDF hängt vom GEWÄHLTEN Export-Massstab ab, nicht vom Massstab der Live-Vorschau
|
||||
|
||||
`hatchPx` wird in `toRenderScene.ts:374-375` genau einmal, massstabsunabhängig
|
||||
berechnet:
|
||||
```ts
|
||||
const hatchMm = p.hatch.lineWeight > 0 ? p.hatch.lineWeight : 0.13;
|
||||
const hatchPx = Math.max(0.6, hatchMm * (1 / 0.13));
|
||||
```
|
||||
— eine reine viewBox-Grösse (analog zum SVG-`<pattern>`-Strich, der
|
||||
absichtlich mit dem Zoom mitskaliert, damit die Schraffur bei jedem Zoom
|
||||
gleich dicht aussieht: Kommentar `toRenderScene.ts:371-373`). Erst beim
|
||||
Export wird daraus in `sceneToPrintSvg.ts:243`
|
||||
`effectiveMm = (widthMm / PX_PER_M) * mmPerM` — und `mmPerM = 1000 /
|
||||
opts.scaleDenominator` (`sceneToPrintSvg.ts:101`) verwendet den vom
|
||||
Export-Dialog gewählten Massstab, NICHT den `paperScale`, den der Nutzer
|
||||
gerade in der "Print"-Live-Vorschau sieht (`exportPdf.ts` ruft
|
||||
`sceneToPrintSvg` mit `opts.scaleDenominator` aus den Export-Optionen,
|
||||
`exportPdf.ts:61-79`, völlig unabhängig vom `PlanView`-State).
|
||||
|
||||
Beispiel: `lineWeight = 0.13` mm (Standard-Fuge, `getHatch`-Default) →
|
||||
`hatchPx = max(0.6, 0.13 · (1/0.13)) = 1.0` viewBox-Einheiten.
|
||||
- Export bei 1:50 (`mmPerM = 20`): `effectiveMm = (1.0/90)·20 = 0.222 mm` →
|
||||
`quantizePen` → **0.25 mm**.
|
||||
- Export bei 1:200 (`mmPerM = 5`): `effectiveMm = (1.0/90)·5 = 0.056 mm` →
|
||||
auf `MIN_PEN_MM` geklemmt → **0.13 mm**.
|
||||
|
||||
Fast die doppelte Strichstärke allein durch die Wahl des Export-Massstabs,
|
||||
bei UNVERÄNDERTER Quell-`lineWeight` — und ohne dass die Bildschirm-
|
||||
"Print"-Vorschau (die ja mit dem live gemessenen `paperScale` arbeitet,
|
||||
nicht mit `opts.scaleDenominator`) das anzeigen könnte, solange beide Werte
|
||||
nicht zufällig übereinstimmen.
|
||||
|
||||
### 5. Dieselbe Formel existiert vierfach, nur durch Kommentare (nicht durch Typen/Code) synchron gehalten
|
||||
|
||||
`PX_PER_M = 90` ist unabhängig deklariert in: `PlanView.tsx:23` (SVG),
|
||||
`glPlan/glPlanRender.ts:12` (WebGL2), `tessellate.rs:22` (Rust, geteilt
|
||||
zwischen WASM-Viewport und Headless/Golden-Test) und `sceneToPrintSvg.ts:64`
|
||||
(PDF-Serializer, mit explizitem Kommentar "MUSS mit dem dortigen
|
||||
uebereinstimmen, sonst driftet die Schraffur-Dichte des PDFs vom Viewport
|
||||
ab", `sceneToPrintSvg.ts:61-62`). Die Hatch-Dichte-Formel
|
||||
`Math.max(0.6, weightMm · (1/0.13))` ist separat dupliziert in
|
||||
`PlanView.tsx:2344-2345` (`hatchStrokePx`, SVG-`<pattern>`) und
|
||||
`toRenderScene.ts:375` (`hatchPx`, GPU + PDF) — beide mit demselben
|
||||
"magischen" Faktor `1/0.13`, ohne gemeinsame Konstante. Aktuell sind alle vier
|
||||
Kopien konsistent; das Risiko ist rein prospektiv (nächste Tuning-Änderung an
|
||||
einer Stelle vergisst die anderen drei) — aber genau das ist der
|
||||
Mechanismus, über den Findings wie 1-4 überhaupt erst entstehen können, ohne
|
||||
dass ein Typfehler oder Test anschlägt.
|
||||
|
||||
### 6. Drei unabhängige, nicht aufeinander abgestimmte Mindestbreiten-Politiken
|
||||
|
||||
- SVG-Print: `Math.max(1e-4, ...)` (`PlanView.tsx:2599`) — praktisch keine
|
||||
Untergrenze, verlässt sich auf das Antialiasing des Browsers.
|
||||
- GPU (WebGL2 UND WASM, Linien UND Bögen): harter Floor von **0.6
|
||||
Geräte-Pixel** (`shaders.rs:91,209`; `glPlanRender.ts:243-249`,
|
||||
Kommentar "klemmt bei ~0.6 px").
|
||||
- PDF: **`MIN_PEN_MM = 0.13` mm** (`sceneToPrintSvg.ts:38` in
|
||||
`planToPrintSvg.ts`, äquivalent `sceneToPrintSvg.ts:47`) — eine
|
||||
Papier-mm-Grösse, kein Pixelwert, konzeptionell etwas anderes als die
|
||||
GPU-Pixel-Untergrenze.
|
||||
|
||||
Auswirkung: bei sehr kleinem Massstab (weit rausgezoomt oder grosses `N`,
|
||||
z. B. Übersichtsplan 1:500) hält der GPU-Pfad sehr dünne Linien künstlich bei
|
||||
0.6 px sichtbar, während dieselbe Linie im SVG-Pfad fast verschwindet und im
|
||||
PDF auf eine ganz andere (mm-basierte, massstabsabhängige) Grösse geklemmt
|
||||
wird. Kein Pfad kennt die Politik der anderen beiden. Niedrigere Priorität
|
||||
als 1-4, weil hier keine der drei Politiken die *exportierte* Papier-mm-Wahrheit
|
||||
verändert (die bleibt PDF-exklusiv über `MIN_PEN_MM`) — es geht nur um
|
||||
Bildschirm-Konsistenz zwischen SVG/GPU bei Extremzoom.
|
||||
|
||||
### 7. Toter Zweitpfad `planToPrintSvg.ts` dupliziert PEN_STEPS/mm-Logik komplett, unbenutzt aber vorhanden
|
||||
|
||||
`src/export/planToPrintSvg.ts` trägt seit `382771b` einen Kopfkommentar
|
||||
"ERSETZT durch sceneToPrintSvg.ts … wird vom PDF-Export NICHT MEHR
|
||||
verwendet" (`planToPrintSvg.ts:1-6`) und ist tatsächlich nirgends mehr
|
||||
importiert (verifiziert per Grep über `src/`). Er enthält jedoch weiterhin
|
||||
eine eigene, unabhängige Kopie von `PEN_STEPS`/`MIN_PEN_MM`/`quantizePen`
|
||||
(`planToPrintSvg.ts:35,38,40-45`) und einer kompletten Plan→SVG-Serialisierung
|
||||
inkl. eigener `PX_PER_M`-Handhabung (`planToPrintSvg.ts:326`). Kein aktiver
|
||||
Bug, aber eine Falle: Copy-Paste-Wiederverwendung dieses Altpfads (z. B. für
|
||||
einen zukünftigen DXF/PNG-Export) würde eine dritte, potenziell abweichende
|
||||
Quantisierungs-Tabelle in den Baum ziehen.
|
||||
|
||||
## 4. Vorgeschlagene Fixes
|
||||
|
||||
### zu Finding 1 (Haarlinien-Modus im GPU-Pfad)
|
||||
|
||||
Kleinste, korrekte Lösung: den `hairline`-Zustand als eigenen Parameter bis
|
||||
in den Shader durchreichen, analog zu `paper_scale_n`/`text_scale_n`.
|
||||
|
||||
- **Rust (`src-tauri/render2d/src/gpu.rs`)**: neues Feld
|
||||
`pub hairline: bool` auf `Renderer` (Default `false`, neben `paper_scale_n`
|
||||
bei `gpu.rs:422`). In `Renderer::render` (`gpu.rs:606-616`): wenn
|
||||
`self.hairline`, `mm_px` NICHT aus `mm_to_device_px(...)` berechnen, sondern
|
||||
auf einen konstanten Geräte-px-Wert setzen (z. B. `1.0`), UND dafür sorgen,
|
||||
dass der Shader `width_mm` in diesem Fall ignoriert (sonst bleibt die
|
||||
RELATIVE Differenz zwischen z. B. 0.13 mm und 0.35 mm bestehen, nur global
|
||||
skaliert — nicht das gewünschte "alle Linien exakt 1 px"). Sauberster Weg:
|
||||
ein neues `hairline: u32`-Feld in die pro-Frame-Uniform (`Globals`,
|
||||
`shaders.rs:20-23` und `ArcGlobals`), im Fragment-/Vertex-Shader
|
||||
(`shaders.rs:91`, `LINE_WGSL`; `shaders.rs:209`, `ARC_WGSL`) per
|
||||
`select(...)` auf einen festen `1.0`-px-Wert umschalten statt
|
||||
`max(0.6, stroke_px * stroke_scale)`.
|
||||
- **`src-tauri/render2d/src/web.rs`**: neue Methode `set_hairline(&mut self,
|
||||
on: bool)` neben `set_paper_scale`/`set_text_scale`
|
||||
(`web.rs:146-155`-Nachbarschaft).
|
||||
- **`src/plan/useWasmPlanRenderer.ts`**: `render()`
|
||||
(`useWasmPlanRenderer.ts:131-155`) um einen `hairline: boolean`-Parameter
|
||||
erweitern, `r.set_hairline(hairline)` vor `r.render()` aufrufen.
|
||||
- **`src/plan/useGlPlanRenderer.ts`**: analog — `render()`
|
||||
(Signatur aktuell `(viewBox, paperScaleN=100)`) um `hairline` erweitern;
|
||||
im WebGL2-Shader-Uniform-Pfad (`glPlanRender.ts:159-165,213-215`)
|
||||
`mmToDevicePx` bei `hairline===true` durch einen konstanten Wert ersetzen
|
||||
und im Fragment-Shader denselben `select`-Trick wie oben anwenden
|
||||
(`glPlanShaders.ts`, dort wo "~0.6 px"-Klemmung passiert, siehe Finding 6).
|
||||
- **`src/plan/PlanView.tsx`**: `paperScaleForGl` (`PlanView.tsx:475-476`)
|
||||
bleibt wie sie ist (wird weiter für den Massstab-Nenner gebraucht, sobald
|
||||
der Nutzer zurück auf "Print" schaltet); stattdessen an allen drei
|
||||
`renderGl(...)`-Aufrufstellen (`PlanView.tsx:494,502,1080`) zusätzlich
|
||||
`hairline` durchreichen: `renderGl(view, paperScaleForGl(view),
|
||||
textScaleForGl(), hairline)`.
|
||||
|
||||
### zu Finding 2 (PEN_STEPS nur im PDF)
|
||||
|
||||
Zwei mögliche Richtungen, je nach gewünschtem Produktverhalten:
|
||||
|
||||
- **Option A (Vorschau matcht Druck)**: `quantizePen`
|
||||
(`sceneToPrintSvg.ts:50-54`) in ein gemeinsames Modul auslagern (z. B.
|
||||
`src/plan/penSteps.ts`, exportiert `PEN_STEPS`, `MIN_PEN_MM`,
|
||||
`quantizePen`), von `sceneToPrintSvg.ts` UND von `PlanView.tsx`
|
||||
(`printStrokeVb`, `PlanView.tsx:2598-2600`) importieren und dort ebenfalls
|
||||
anwenden: `printStrokeVb(mm, n) = Math.max(1e-4, (quantizePen(mm) * n) /
|
||||
1000) * PX_PER_M`. Für den GPU-Pfad müsste die Quantisierung dann VOR dem
|
||||
Scene-Bau passieren (in `toRenderScene.ts`, auf `widthMm` jeder
|
||||
`RLine`/`ROutline`/`RPolyline`/`RArc`, ausgenommen `widthScreen`-Einträge),
|
||||
damit WASM/WebGL2-Viewport dieselben Stufen zeigen wie SVG und PDF.
|
||||
- **Option B (Vorschau bleibt Rohgrösse, aber sichtbar gemacht)**: falls die
|
||||
feinkörnige Vorschau bewusst erhalten bleiben soll, zumindest den
|
||||
Stiftstufen-Sprung an der Stelle sichtbar machen, an der die Detailgrad-
|
||||
Faktoren definiert werden (`generatePlan.ts:84-93`) — z. B. ein
|
||||
Entwickler-/Lint-Test, der bei jedem `OUTLINE_DETAIL_FACTOR`/
|
||||
`LAYER_DETAIL_FACTOR`-Wert prüft, ob das Produkt mit den üblichen
|
||||
Fallback-`weightMm`-Werten (`WALL_FALLBACK_MM`, `LAYER_LINE_MM`) exakt auf
|
||||
einer `PEN_STEPS`-Stufe landet, und sonst warnt.
|
||||
|
||||
Empfehlung: Option A — sie erfüllt den Nordstern wörtlich ("Bildschirm bei
|
||||
jedem Massstab = Druck").
|
||||
|
||||
### zu Finding 3 (STAMP_REF_N)
|
||||
|
||||
In `toRenderScene.ts` `STAMP_REF_N = 100` (`toRenderScene.ts:163`) durch den
|
||||
tatsächlichen Ziel-Massstab ersetzen. `planToRenderScene(plan)` kennt aktuell
|
||||
keinen `N`-Parameter (`exportPdf.ts:73` ruft es ohne Massstabsangabe auf) —
|
||||
die Funktion müsste ein optionales `paperScaleN`-Argument bekommen (Default
|
||||
100, um den Viewport-Aufruf ohne Massstabskontext — `nativeSync.ts` — nicht
|
||||
zu brechen, dort ist 100 ohnehin die richtige Referenz für den WASM-Viewport
|
||||
im Anzeigemodus, s. `gpu.rs:422`), und `exportPdf.ts:73` müsste
|
||||
`planToRenderScene(plan, opts.scaleDenominator)` aufrufen. `MM_TO_M`
|
||||
(`toRenderScene.ts:165`) dann aus diesem Parameter statt aus der Konstante
|
||||
ableiten.
|
||||
|
||||
### zu Finding 4 (Hatch-mm im PDF folgt dem Export-N, nicht dem Vorschau-N)
|
||||
|
||||
Kein Bug im engeren Sinn (die Formel ist in sich konsistent — die Schraffur
|
||||
soll ja pro *gewähltem Blatt-Massstab* eine sinnvolle Dichte haben), aber die
|
||||
Bildschirm-„Print"-Vorschau sollte denselben `N` verwenden, den der
|
||||
Export-Dialog tatsächlich benutzen wird, sonst lügt die Vorschau. Fix:
|
||||
`paperScale` in `PlanView.tsx` beim Öffnen des Export-Dialogs (bzw. der
|
||||
Export-Dialog selbst) mit `opts.scaleDenominator` vorbelegen/synchronisieren,
|
||||
statt zwei unabhängige State-Quellen zu pflegen — Ort: dort, wo der
|
||||
Export-Dialog `scaleDenominator` initialisiert (App.tsx, PDF-Export-UI) einen
|
||||
Default aus `paperScale`/`liveScale` (`App.tsx`, `onScale`-Callback,
|
||||
`PlanView.tsx:794`) übernehmen.
|
||||
|
||||
### zu Finding 5 (vierfache Formel-Duplikation)
|
||||
|
||||
Eine gemeinsame TS-Konstantendatei `src/plan/renderConstants.ts` mit
|
||||
`PX_PER_M = 90`, `HATCH_DENSITY_FACTOR = 1/0.13`, `HATCH_MIN_PX = 0.6`
|
||||
anlegen; `PlanView.tsx`, `glPlan/glPlanRender.ts`, `toRenderScene.ts`,
|
||||
`sceneToPrintSvg.ts` importieren daraus statt eigener Literale. Für die
|
||||
Rust-Seite (`tessellate.rs:22`, `shaders.rs:142`) ist echtes Teilen über die
|
||||
Sprachgrenze hinweg nicht trivial — dort bleibt nur ein Kommentar-Link auf die
|
||||
TS-Konstante plus ein Parity-Test (siehe Abschnitt 5), der bei Abweichung
|
||||
fehlschlägt statt bei stillem Drift.
|
||||
|
||||
### zu Finding 6 (drei Mindestbreiten-Politiken)
|
||||
|
||||
Niedrige Priorität, aber falls angegangen: den 0.6-px-Floor aus den Shadern
|
||||
(`shaders.rs:91,209`, `glPlanShaders.ts`) als benannte Konstante
|
||||
exportieren/dokumentieren und explizit von `1e-4` (SVG) und `MIN_PEN_MM=0.13`
|
||||
(PDF) abgrenzen — mindestens per Kommentar klarstellen, dass die drei
|
||||
UNTERSCHIEDLICHE Zwecke haben (GPU: Pixel-Sichtbarkeits-Floor;
|
||||
PDF: Papier-mm-Stiftgrenze) und NICHT synchronisiert werden müssen, damit
|
||||
niemand versehentlich versucht, sie anzugleichen und dabei die jeweils
|
||||
andere Semantik bricht.
|
||||
|
||||
### zu Finding 7 (toter Pfad)
|
||||
|
||||
`src/export/planToPrintSvg.ts` entfernen (nach kurzer Rücksprache, ob der
|
||||
Kopfkommentar-Vermerk "bleibt nur als Referenz stehen" noch gewollt ist) oder,
|
||||
falls er als Referenz bleiben soll, seine `PEN_STEPS`/`quantizePen`-Kopie
|
||||
durch einen Import aus der in Finding 2 vorgeschlagenen gemeinsamen
|
||||
`penSteps.ts` ersetzen.
|
||||
|
||||
## 5. Verifikations-Rezept
|
||||
|
||||
Was schon existiert:
|
||||
|
||||
- `scripts/probe-engine-parity.mjs` vergleicht `?gl=0` gegen `?engine=wasm`
|
||||
rein visuell (Screenshot-Diff von Auge, `probe-parity-svg.png` vs.
|
||||
`probe-parity-wasm.png`) — prüft NICHT quantitativ, ob Strichbreiten in
|
||||
Pixeln übereinstimmen, und deckt weder den Haarlinien-Modus noch den
|
||||
WebGL2-Default-Pfad noch den PDF-Export ab.
|
||||
- `src-tauri/render2d/tests/golden.rs` vergleicht die Demo-Szene
|
||||
Pixel-für-Pixel gegen `tests/golden/demo.png` (Toleranz: Kanal-Delta > 2 auf
|
||||
< 0.5 % der Pixel, `docs/design/engine-headless.md`) — ein reiner
|
||||
GPU-Regressionstest, ohne SVG- oder PDF-Vergleich.
|
||||
- Die einzige bisher dokumentierte mm-genaue Messung ist manuell:
|
||||
HANDOVER.md, Commit `382771b` — `pdftoppm`-Messung eines exportierten PDFs
|
||||
(53.51×43.69 mm gegen erwartete 53.45×43.45 mm bei 1:100), einmalig, nicht
|
||||
als wiederholbares Skript hinterlegt.
|
||||
|
||||
Um den Nordstern ("Bildschirm bei jedem Zoom/Massstab = Druck") tatsächlich
|
||||
beweisbar zu machen, fehlt ein quantitativer End-to-End-Test, der:
|
||||
|
||||
1. Für eine feste Test-Szene (idealerweise die bestehende `demo::demo_scene`
|
||||
aus `src-tauri/render2d/src/demo.rs`, die bereits sowohl einen `width_mm`-
|
||||
als auch einen `width_screen`-Strich enthält, `demo.rs:34,49-50`) bei
|
||||
mehreren `N` (1:10, 1:50, 1:100) UND mehreren Zoomstufen:
|
||||
- den PDF-Export erzeugt und via `pdftoppm -r <dpi>` in ein PNG rendert,
|
||||
dort die Strichbreite in Pixeln misst und in mm zurückrechnet (bekannte
|
||||
DPI ⇒ bekannte mm/px);
|
||||
- denselben Frame headless über `HeadlessRenderer::render_to_image`
|
||||
(`src-tauri/render2d/src/headless.rs`) mit identischem `paper_scale_n`
|
||||
rendert und dieselbe Pixel-Breite misst;
|
||||
- beide mm-Werte gegen den erwarteten `mm · N/1000`-Sollwert UND
|
||||
gegeneinander vergleicht, mit einer Toleranz, die die PEN_STEPS-Rundung
|
||||
(Finding 2, sobald behoben: siehe Option A) einbezieht.
|
||||
2. Den Haarlinien-Modus separat abdeckt: ein Screenshot-Vergleich bei zwei
|
||||
verschiedenen Zoomstufen im `lineMode:"display"` — die gemessene
|
||||
Pixelbreite MUSS bei beiden Zoomstufen identisch sein (Beweis, dass
|
||||
Finding 1 behoben ist), sowohl für WebGL2 (`?gl` ohne `=0`, App-Default)
|
||||
als auch für WASM (`?engine=wasm`).
|
||||
3. `probe-engine-parity.mjs` um eine tatsächliche Pixel-Differenz-Metrik
|
||||
erweitert (statt nur Kindanzahl/Warnungen zu loggen wie aktuell
|
||||
`probe-engine-parity.mjs:36-44`) — z. B. eine bekannte Referenzlinie im
|
||||
Testmodell an fester Bildschirmposition, deren Strichbreite in beiden
|
||||
Screenshots per Pixel-Sampling gemessen und verglichen wird.
|
||||
|
||||
Bis dieser Test existiert, bleibt jede Aussage über "Bildschirm = Druck" eine
|
||||
Behauptung, die nur durch manuelles `pdftoppm`-Nachmessen einzelner
|
||||
Stichproben gedeckt ist — die in diesem Audit gefundenen Divergenzen (v. a.
|
||||
Finding 1 und 2) hätten mit den heutigen Probes nicht auffallen können, weil
|
||||
keiner von ihnen den Default-Zustand der App (`lineMode:"display"`, WebGL2)
|
||||
gegen den Druckpfad misst.
|
||||
@@ -0,0 +1,186 @@
|
||||
# Schnitt/Ansicht-Pipeline: analytische Prismen-Projektion (`render3d::section`)
|
||||
|
||||
> Gegenstueck zum OCCT-WASM-HLR-Spike (`docs/welle-c-hlr-spike/FEASIBILITY.md`):
|
||||
> statt eines generischen CAD-Kernels nutzt dieser Ansatz eine Invariante des
|
||||
> Modells, um Schnitt/Ansicht rein analytisch (kein Hidden-Line-Removal-Solver)
|
||||
> zu berechnen. Implementiert in `src-tauri/render3d/src/section.rs`.
|
||||
|
||||
## Ansatz
|
||||
|
||||
Jedes Bauteil in diesem Modell ist ein **Prisma**: ein 2D-Grundriss-Fussabdruck-
|
||||
Polygon (Wand-Band bzw. Decken-Umriss, siehe `mesh.rs`), konstant extrudiert
|
||||
ueber ein Hoehenintervall `[z0, z1]`. Diese Einschraenkung — konstanter
|
||||
Querschnitt ueber die gesamte Hoehe — macht die Schnittgeometrie trivial im
|
||||
Vergleich zu generischem HLR:
|
||||
|
||||
- **Schnitt einer vertikalen Ebene mit einem Prisma** = 2D-Geraden/Polygon-
|
||||
Clipping IM GRUNDRISS (die Schnittebene projiziert im Grundriss auf eine
|
||||
Linie) → ein oder mehrere u-Intervalle, in denen die Linie das Fussabdruck-
|
||||
Polygon durchquert. Jedes Intervall × `[z0, z1]` ist das Cut-Rechteck. Da die
|
||||
Hoehe unabhaengig von der Grundriss-Position ist, ist das Ergebnis **immer
|
||||
ein Rechteck**, nie ein Trapez — auch bei diagonalen Wandachsen.
|
||||
- **Projektion/Ansicht** (Kanten hinter der Ebene, in Blickrichtung) reduziert
|
||||
sich auf ein **2D-Sichtbarkeitsproblem im Grundriss** kombiniert mit einem
|
||||
Hoehen-Ueberlappungstest: da alle Seitenflaechen der Prismen vertikal sind,
|
||||
genuegt ein Sichtstrahl-Test im Grundriss (verdeckt ein naeheres Prisma-
|
||||
Fussabdruckpolygon die Sichtlinie?) plus Ueberlappung der Hoehenintervalle.
|
||||
|
||||
Das ersetzt einen 66-MB-WASM-CAD-Kernel durch closed-form Arithmetik in reinem
|
||||
Rust, ohne zusaetzliches Laufzeitgewicht ueber `render3d` hinaus.
|
||||
|
||||
## `SectionOutput`-Format
|
||||
|
||||
Modul: `render3d::section`. Alle Groessen in **Metern**.
|
||||
|
||||
```rust
|
||||
pub struct SectionPlane { pub point: [f32; 3], pub normal: [f32; 3] }
|
||||
|
||||
pub struct SectionOutput {
|
||||
pub cut_polygons: Vec<CutPolygon>, // JSON: "cutPolygons"
|
||||
pub visible_edges: Vec<SectionEdge>, // JSON: "visibleEdges"
|
||||
pub hidden_edges: Vec<SectionEdge>, // JSON: "hiddenEdges"
|
||||
}
|
||||
|
||||
pub struct CutPolygon { pub component: ComponentRef, pub color: Rgb, pub pts: Vec<[f32; 2]> }
|
||||
pub struct SectionEdge { pub component: ComponentRef, pub a: [f32; 2], pub b: [f32; 2] }
|
||||
pub struct ComponentRef { pub kind: ComponentKind /* Wall | Slab */, pub index: usize }
|
||||
```
|
||||
|
||||
**Koordinatensystem der Ausgabe (u, v):**
|
||||
|
||||
- Ursprung: `SectionPlane::point`, projiziert.
|
||||
- `u` (horizontal): Strecke entlang der Schnittebene, senkrecht zur
|
||||
Blickrichtung, berechnet als `normalize(cross(normal, world_up))` — dieselbe
|
||||
rechtshaendige Konvention wie `math::look_at`s `right`-Vektor. Bei den vier
|
||||
Standard-Konstruktoren (`looking_plus_x/minus_x/plus_y/minus_y`) ist `u`
|
||||
direkt die jeweils andere Grundriss-Achse.
|
||||
- `v` (vertikal, "Hoehe"): `v = world.y`, ABSOLUT. Diese Codebasis ist
|
||||
durchgaengig Y-up (`types.rs`/`mesh.rs`/`math.rs`: „world.y = Hoehe"); `v`
|
||||
folgt bewusst dieser etablierten Konvention statt einer wortwoertlichen
|
||||
„world.z"-Lesart, um modulübergreifend konsistent zu bleiben.
|
||||
- Nur **vertikale** Schnittebenen (Normale ohne Hoehen-Komponente) sind
|
||||
unterstuetzt — Grundriss-/Horizontalschnitte bleiben Sache der bestehenden
|
||||
2D-Plan-Pipeline.
|
||||
|
||||
`cut_polygons` sind bei diesem Modell immer Rechtecke (siehe oben), aber als
|
||||
generischer Punktering abgelegt — kompatibel zu `render2d::types::FillPolygon`
|
||||
(`pts: Vec<Point>`), dem vorgesehenen Zielformat fuer die spaetere 1:1-
|
||||
Uebersetzung in die Plan-/Schnitt-Ansicht.
|
||||
|
||||
## Oeffnungen (Tueren/Fenster)
|
||||
|
||||
`WallInput` hat seit diesem Nachtrag ein Feld `openings: Vec<Opening>`
|
||||
(`types.rs`), unabhaengig von der reinen Achse/Dicke/Hoehe: je Oeffnung ein
|
||||
Intervall entlang der Wandachse (`from`/`to`, Meter ab `start`) plus Bruestungs-
|
||||
und Kopfhoehe (`sill`/`height`, relativ zu `base_elevation`). Eine Tuer ist der
|
||||
Sonderfall `sill == 0.0`.
|
||||
|
||||
**Cut-Polygone.** Trifft die Schnittlinie eine Wand exakt im Bereich einer
|
||||
Oeffnung, splittet `wall_cut_rectangles` das sonst einzelne Vollrechteck
|
||||
`[z0,z1]` in bis zu ZWEI Teil-Rechtecke: Bruestung `[z0, z0+sill]` und Sturz
|
||||
`[z0+sill+height, z1]`. Gewaehlt wurde diese Mehrfach-Rechteck-Darstellung
|
||||
BEWUSST gegenueber einem Loch-Polygon (Ring mit Aussparung): mehrere einfache,
|
||||
konvexe Ringe sind direkt kompatibel zum Zielformat `render2d::types::
|
||||
FillPolygon` (nur einfache Ringe, kein Loch-Format), waehrend ein
|
||||
Loch-Polygon eine zusaetzliche Datenstruktur (Ring-mit-Loch) noetig gemacht
|
||||
haette, die die Zielstruktur (noch) nicht kennt. Ausserhalb einer Oeffnung
|
||||
bleibt es beim einzelnen Vollrechteck (Regressionsfall).
|
||||
|
||||
Die Zuordnung "welche Wandachsen-Position entspricht dieser Schnittposition"
|
||||
ist fuer den STANDARDFALL (Schnittebene senkrecht zur Wandachse — der uebliche
|
||||
architektonische Wandquerschnitt) EXAKT konstant ueber das Cut-Intervall. Fuer
|
||||
schraege Wand/Ebene-Kombinationen wird sie als AFFINE Funktion (`axis_map`)
|
||||
exakt mitgefuehrt (keine Naeherung noetig, da die Abbildung linear ist).
|
||||
|
||||
**Projektion/Ansicht.** Eine Wand mit Oeffnungen bekommt zusaetzlich zur
|
||||
Boxen-Drahtsilhouette die vier Rahmenkanten jeder Oeffnung (zwei Leibungen,
|
||||
Sturz, Bruestung), berechnet auf der Wandachsen-Mittellinie (dieselbe
|
||||
Vereinfachung wie die Bounding-Box-Verdeckung generell — keine eigene
|
||||
Dicken-Aufloesung der Leibungsflaeche). Fuer die Verdeckung wird jede
|
||||
Wand-Bounding-Box um ihre Oeffnungen als "Loch" reduziert
|
||||
(`PrismBounds::opening_voids`): ein anderes (oder dasselbe) Bauteil hinter der
|
||||
Wand wird in genau dem (u, Hoehe)-Rechteck der Oeffnung NICHT von dieser Wand
|
||||
verdeckt — "Durchblick". Die eigenen Rahmenkanten einer Oeffnung werden dabei
|
||||
NICHT gegen das eigene Bauteil auf Verdeckung geprueft (ein Loch kann sich
|
||||
nicht selbst verdecken); gegen alle anderen Bauteile gilt die normale
|
||||
Verdeckungslogik unveraendert (inkl. der bereits dokumentierten
|
||||
Selbstverdeckung der Rueckseite eines ANDEREN Bauteils durch dessen eigene
|
||||
Vorderseite).
|
||||
|
||||
GENAUIGKEIT (bewusste Vereinfachung): die u-Zuordnung fuer den Durchblick ist
|
||||
nur DANN aussagekraeftig, wenn die Wandachse hinreichend parallel zur u-Achse
|
||||
der Schnittebene steht — das ist GENAU der Fall, in dem man die Wand als
|
||||
Elevation/Ansicht von vorne sieht (und ein Fenster ueberhaupt als Durchblick
|
||||
sichtbar waere). Steht die Wand naeher an "senkrecht zur u-Achse" (Wand auf
|
||||
Kante gesehen bzw. der reine Cut-Fall), wird die Durchblick-Berechnung
|
||||
uebersprungen und die Wand bleibt fuer die Verdeckung VOLL UNDURCHSICHTIG
|
||||
(konservativ hidden) — Schwelle `AXIS_ALIGN_EPS = 1e-3` in `section.rs`. Ein
|
||||
Kante-auf-Kante gesehenes Fenster liefert ohnehin keine sinnvolle
|
||||
Durchblick-Flaeche in der Projektion.
|
||||
|
||||
Getestet in `section::tests` (u. a. `schnitt_durch_fenster_liefert_bruestung_
|
||||
und_sturz`, `schnitt_neben_dem_fenster_liefert_volles_rechteck`,
|
||||
`ansicht_zeigt_fensterrahmen_sichtbar_und_durchblick_bei_dahinterliegender_
|
||||
kante`) und im Beweis-SVG (`examples/section_svg.rs`): Schenkel A der L-Wand
|
||||
hat dort ein Fenster, eine kurze zusaetzliche Wand steht dahinter genau im
|
||||
Fensterband und erscheint im SVG als durchgezogene (sichtbare) Linie zwischen
|
||||
Bruestungs- und Sturzhoehe, gestrichelt (verdeckt) darueber/darunter.
|
||||
|
||||
## Bekannte Luecken
|
||||
|
||||
- **Keine Wandknoten-Verschneidung (T-/X-Stoesse).** Wie in `mesh.rs`
|
||||
(M1-Stand) werden Waende als eigenstaendige, stumpf abgeschlossene Quader
|
||||
behandelt, die sich an Ecken UEBERLAPPEN statt sich zu vereinen (kein
|
||||
Miter-Join). Der Schnitt-Extraktor erbt das: an einem L-/T-Knoten kann eine
|
||||
Wand als „in die andere eingebettet" verdeckt erscheinen (im Testmodul
|
||||
`section::tests` bewusst als reales, erwartetes Verhalten dokumentiert und
|
||||
geprueft — kein Bug dieses Moduls, sondern ein Artefakt der fehlenden
|
||||
Verschneidungslogik weiter oben in der Pipeline).
|
||||
- **Oeffnungen sind NICHT in der Vollkoerper-Extrusion (`mesh.rs`)
|
||||
nachgezogen.** `wall_prism`/`wall_cut_rectangles`/die Verdeckung in
|
||||
`section.rs` werten `WallInput::openings` vollstaendig aus (siehe Abschnitt
|
||||
"Oeffnungen" oben), aber `mesh::extrude_wall` extrudiert weiterhin die volle
|
||||
Wandflaeche ohne Aussparung (`mesh.rs`-Moduldoc: „Oeffnungen kommen in
|
||||
spaeteren Milestones"). Cut-/Ansichts-Pipeline und 3D-Solid-Mesh sind bis zum
|
||||
Nachziehen von `mesh.rs` also bewusst inkonsistent — eine bekannte, separate
|
||||
Luecke (nicht Gegenstand dieses Nachtrags).
|
||||
- **Oeffnungs-Durchblick nutzt dieselbe achsparallele Bounding-Box-Naeherung**
|
||||
wie die allgemeine Verdeckung (kein exaktes Polygon-Clipping der Lochflaeche
|
||||
gegen dahinterliegende Kanten) und wird bei Wand-Orientierungen nahe
|
||||
"senkrecht zur u-Achse" konservativ auf "kein Durchblick" zurueckgestuft
|
||||
(siehe Abschnitt "Oeffnungen" oben, `AXIS_ALIGN_EPS`).
|
||||
- **Keine echte Component-/Material-Id.** `WallInput`/`SlabInput` haben aktuell
|
||||
keine eigene Id; `ComponentRef` referenziert daher nur `(Art, Index im
|
||||
Eingabe-Array)` + die rohe Albedo-Farbe als Material-Platzhalter. Sobald ein
|
||||
echtes Ressourcen-/Material-System existiert, sollte `ComponentRef` auf eine
|
||||
stabile Id umgestellt werden (Indizes sind nicht stabil ueber Modell-Edits).
|
||||
- **Verdeckungstest nutzt Bounding-Boxen, nicht die exakte Fussabdruckform.**
|
||||
Fuer Wand-Baender und (i. d. R. konvexe) Deckenumrisse ist das exakt; bei
|
||||
stark konkaven oder diagonalen Grundrissen kann es zu Ueberverdeckung
|
||||
fuehren (ein Prisma blockiert dann auch Bereiche seiner eigenen „Nischen").
|
||||
- **Kein generisches HLR.** Der Ansatz funktioniert NUR, weil alle Bauteile
|
||||
Prismen mit konstantem Querschnitt sind. Fuer echte gekrümmte oder nicht-
|
||||
prismatische Geometrie (Bogenwaende, Freiformdaecher, …) waere er nicht
|
||||
anwendbar — dafuer bliebe der OCCT-Weg (oder eine eigene, generischere
|
||||
HLR-Implementierung) die Referenz.
|
||||
- **Keine robuste Sonderfallbehandlung fuer Vertices exakt auf der
|
||||
Schnittlinie** (Toleranz-basiert, kein Tie-Breaking/Pertubation) — die
|
||||
Testszenarien vermeiden diesen Fall bewusst.
|
||||
|
||||
## Vergleich zum OCCT-WASM-Spike
|
||||
|
||||
| | OCCT-WASM (`docs/welle-c-hlr-spike`) | Dieser Ansatz (`render3d::section`) |
|
||||
|---|---|---|
|
||||
| Verfahren | Generisches HLR (`HLRAppli_ReflectLines`) | Analytische Prismen-Projektion |
|
||||
| Anwendbarkeit | Beliebige BREP-Geometrie | Nur Prismen (konstanter Querschnitt über Höhe) |
|
||||
| Zusaetzliches Gewicht | ~62,8 MB WASM (~19,6 MB gzip), separater Lazy-Chunk | Keins — reines Rust in `render3d`, kein weiteres WASM-Asset |
|
||||
| Modul-Init | ~450 ms (Browser) / ~680 ms (Node) | Kein Initialisierungsschritt (kein Fremd-Modul zu laden) |
|
||||
| Rechenzeit (L-Wand + Platte) | ~21 ms (reiner HLR-Lauf, gemessen im Browser) | Nicht separat gemessen (kein Millisekunden-Timer im Test); die Operationen sind reine Vektor-/Intervall-Arithmetik über wenige Kanten (O(Anzahl-Prismen × Kanten-pro-Prisma) mit kleinen Konstanten) und liegen der Groessenordnung nach klar unter 1 ms fuer Szenen dieser Groesse — eine belastbare Messung steht noch aus |
|
||||
| Sichtbarkeit (verdeckte Kanten) | Exakt (echter HLR-Solver) | Naeherung über Bounding-Boxen im Grundriss + Hoehenintervall-Ueberlappung; exakt fuer achsparallele/konvexe Fussabdruecke |
|
||||
| Reifegrad | Isolierter Spike, nicht verdrahtet | Isolierter Spike (dieses Modul), nicht in `render2d`/die Plan-Ansicht verdrahtet |
|
||||
|
||||
**Fazit:** Fuer den ueberwiegenden Regelfall dieses Projekts (Waende, Decken —
|
||||
alles Prismen) ist die analytische Loesung der pragmatischere Weg: kein
|
||||
zusaetzliches WASM-Gewicht, keine Fremd-Bibliothek, headless testbar wie der
|
||||
Rest von `render3d`. Der OCCT-Weg bleibt die Referenz, falls/sobald echte
|
||||
generische Volumenkoerper (Booleans, gekrümmte Flaechen) ins Modell kommen.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Ribbon-UI + modulare Bars — Plan
|
||||
|
||||
Nutzer-Vision (2026-07-05, mit OCS/AutoCAD-Ribbon als Referenz). Ziel: klare,
|
||||
einheitliche Werkzeug-/Eigenschaften-Darstellung; Werkzeug-Sidebar entfällt.
|
||||
|
||||
## Zielbild
|
||||
|
||||
- **Ribbon-Oberleiste mit Tabs: 2D · 3D · BIM · Ansichten.** Jeder Tab zeigt
|
||||
gruppierte Werkzeug-/Aktions-Icons (wie OCS Draw/Model/Insert/Annotate/View).
|
||||
- **2D**: Zeichnen (Select/Linie/Polylinie/Rechteck/Kreis/Bogen/Text) · Ändern
|
||||
(Move/Copy/Mirror/Offset/Trim/Join/Boolean).
|
||||
- **3D**: Volumen/Boolean (vorhandene 3D-Aktionen), Kamera-Presets.
|
||||
- **BIM**: Wand/Fenster/Tür/Treppe/Decke/Raum (die Bauteile).
|
||||
- **Ansichten**: Geschoss/Schnitt/Ansicht-Wechsel, Zoom/Einpassen, Render-Modi.
|
||||
- **Werkzeug-Sidebar entfällt** → rechtes Panel (Attribute) bekommt volle Höhe.
|
||||
- **Objektinfo unter die Attribute** mergen (Wandstil-Definition etc. zusammen).
|
||||
- **XYZ-Referenzpunkt-Box oben rechts bleibt** (vom Nutzer ausdrücklich gewünscht).
|
||||
- **Eigenschaften-Grid im OCS-Stil** — ✅ bereits erledigt (`00733d8`): Sektion-
|
||||
Balken, Zeilentrenner, füllende linksbündige Wertfelder (`.attr-*`).
|
||||
|
||||
## Architektur — DATENGETRIEBEN (ermöglicht modulare Custom-Bar)
|
||||
|
||||
Kern: EINE Registry beschreibt alle Ribbon-Elemente; Ribbon-Tabs UND eine
|
||||
benutzerdefinierte Custom-Bar sind bloß zwei Ansichten über dieselbe Registry.
|
||||
|
||||
```
|
||||
RibbonItem =
|
||||
| { kind: "tool"; id: ToolId } // aktiviert ein Werkzeug (onSelectTool)
|
||||
| { kind: "command"; name: string } // startet einen Engine-Befehl (engine.start)
|
||||
| { kind: "action"; id: string; run: () => void } // freie App-Aktion (Zoom, Render-Modus …)
|
||||
|
||||
RibbonGroup = { titleKey: string; items: RibbonItem[] }
|
||||
RibbonTab = { id: "2d"|"3d"|"bim"|"views"; labelKey: string; groups: RibbonGroup[] }
|
||||
RIBBON: RibbonTab[]
|
||||
```
|
||||
|
||||
- Icons: `ToolIcon` (vorhanden) für `tool`-Items; für `command`/`action` eine
|
||||
kleine Icon-Map (SVG) analog `ToolsPanel`.
|
||||
- Aktivierung: `tool` → `onSelectTool(id)` · `command` → `engine.start(name)` ·
|
||||
`action` → `run()`. Aktiv-Highlight über `activeTool` bzw. laufenden Befehl.
|
||||
- **Custom-Bar (modular)**: der Nutzer wählt beliebige `RibbonItem`s in eine
|
||||
persistierte Liste (`viewSlice`/Projekt); eine `CustomBar`-Komponente rendert
|
||||
genau diese. Gleiche Item-Typen, gleiche Aktivierung — kein Sonderweg.
|
||||
|
||||
## Phasen
|
||||
|
||||
1. ✅ **Gerüst + 2D/BIM-Tab** (`9d6e86d`). `RibbonBar` unter der TopBar (additiv).
|
||||
Datengetriebene Registry (`src/ui/ribbon/ribbonItems.ts`), 2D-Tab (Zeichnen +
|
||||
Ändern) und BIM-Tab (Bauteile) gefüllt, Tab-State lokal, Modify-Icons ergänzt.
|
||||
2. ✅ **TopBar → „Ansichten"-Tab gemergt** (`85011cb`, Nutzer-Entscheid „voll
|
||||
mergen"). Die Ansichts-/Zoom-/Darstellungs-Cluster der TopBar (View-Grid +
|
||||
Kamera, Ebenen-/Zeichnungs-Kombis, Detailgrad, Massstab/Zoom, Darstellungsart)
|
||||
sind als `ViewRibbonTab` (in `TopBar.tsx`) in den „Ansichten"-Tab gewandert;
|
||||
App reicht sie als `viewsContent`-Node an `RibbonBar` (wie das Layout-Menü).
|
||||
Die **Tab-Reiter sitzen in der TopBar-Zeile** (`RibbonTabs`, in TopBar
|
||||
gerendert; Tab-State in App), der Ribbon-Inhalt (`RibbonBar`) folgt darunter →
|
||||
nur ZWEI Bänder. Die TopBar ist eine **schmale Leiste** (Höhe 40px): Marke/
|
||||
Ressourcen · Tabs · Datei/Export/Einstellungen · Fensterknöpfe. Die Text-/
|
||||
Font-Formatierung (`TextGroup`) liegt im eigenen **„Text"-Tab** (Reihenfolge
|
||||
2D·3D·BIM·Text·Ansichten; `RibbonBar.tabContent` trägt „text" + „views").
|
||||
- **Offen (visuell iterieren):** ob Zoom/Massstab zusätzlich als Dauer-Anzeige
|
||||
(Statusleiste) sichtbar sein soll; Default-Tab (aktuell 2D); 3D-Tab noch
|
||||
leer (Kamera-Presets/3D-Aktionen füllen).
|
||||
3. **Sidebar raus** (ToolsPanel aus Default-Layout) + rechtes Panel volle Höhe;
|
||||
Objektinfo unter Attribute mergen.
|
||||
4. **Modulare Custom-Bar**: Item-Picker + persistierte Custom-Bar-Ansicht.
|
||||
|
||||
## Hinweise / Randbedingungen
|
||||
|
||||
- Additiv bauen: bestehende Kommandozeile + Nummern-Shortcuts + `ToolsPanel`
|
||||
bleiben funktionsfähig, bis Phase 3 die Sidebar bewusst entfernt.
|
||||
- Werkzeug-Kopplung Tool↔Befehl existiert bereits (`TOOL_COMMAND`) — Ribbon nutzt
|
||||
denselben `onSelectTool`/`engine.start`-Pfad (EIN Pfad, keine Duplikate).
|
||||
- Empfehlung: Phase 1+ in fokussierter Session mit visueller Iteration im Tauri.
|
||||
@@ -146,6 +146,6 @@ Neue Keys in de.ts UND en.ts: `text.style`, `text.font`, `text.size`,
|
||||
## 7. Gate (Pflicht)
|
||||
|
||||
`rm -f tsconfig.tsbuildinfo && npx tsc -b` grün, `npm run build` grün, keine
|
||||
AI-Spuren (grep auf Claude/Anthropic/AI/Generated/Co-Authored), Boot-Probe
|
||||
Trace-Scan (grep auf Co-Authored/Generated), Boot-Probe
|
||||
(`node scripts/probe.mjs`) ohne Konsolenfehler, Screenshot der Oberleiste.
|
||||
KEIN Commit.
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
# truck-Integration — Profil-Extrusion (B-Rep → 3D-Mesh)
|
||||
|
||||
> Übergabe-Dokument für die Implementierung. Lies zuerst CONVENTIONS.md.
|
||||
|
||||
## Ziel
|
||||
|
||||
Nutzer zeichnen im Grundriss ein geschlossenes Polygon (z. B. L-Profil einer Stütze)
|
||||
und können es als 3D-Körper auf eine Höhe extrudieren. Das Ergebnis erscheint sofort
|
||||
im 3D-Viewport neben Wänden und Decken.
|
||||
|
||||
**MVP-Scope (dieser Auftrag):**
|
||||
- Neue Rust/WASM-Crate `trucksolid` mit zwei Funktionen: `extrude_polygon` + `extrude_circle`
|
||||
- Neue TS-Wrapper-Datei `src/engine/truckSolid.ts` (WASM laden + typisierte API)
|
||||
- KEIN neues Werkzeug, KEIN UI — nur die Geometrie-Schicht. UI kommt in einem
|
||||
separaten Folgeauftrag.
|
||||
|
||||
---
|
||||
|
||||
## Architektur-Kontext
|
||||
|
||||
```
|
||||
src-tauri/
|
||||
render3d/ ← bestehend: wgpu-Renderer (wasm-pack → pkg3d/)
|
||||
kernel2d/ ← bestehend: 2D-Geometrie-Kern
|
||||
trucksolid/ ← NEU (dieser Auftrag)
|
||||
Cargo.toml
|
||||
src/
|
||||
lib.rs
|
||||
|
||||
src/engine/
|
||||
pkg3d/ ← render3d WASM-Output
|
||||
pkgTruck/ ← NEU: trucksolid WASM-Output (wasm-pack Ziel)
|
||||
truckSolid.ts ← NEU: TS-Wrapper
|
||||
|
||||
src/plan/
|
||||
toWalls3d.ts ← bestehend: baut RenderScene; emitMeshes() muss erweitert werden
|
||||
```
|
||||
|
||||
Das Koordinatensystem in DOSSIER ist **Modell-Meter: X = rechts, Y = oben (Grundriss),
|
||||
Z = Höhe**. render3d erwartet `positions` im gleichen System — `mesh.rs` macht intern
|
||||
`(x, y, z) → (x, z, y)` für wgpu (Y-up world). Truck arbeitet in 3D; wir bauen
|
||||
das Polygon in der XY-Ebene (z=0) und extrudieren nach +Z.
|
||||
|
||||
---
|
||||
|
||||
## 1 — Rust-Crate `src-tauri/trucksolid/`
|
||||
|
||||
### 1.1 Cargo.toml
|
||||
|
||||
Exakt dasselbe Muster wie `src-tauri/kernel2d/Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[workspace] # entkoppelt vom cad-tauri-Workspace (Pflicht)
|
||||
|
||||
[package]
|
||||
name = "trucksolid"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Profil-Extrusion via truck (B-Rep → tesselliertes Mesh für render3d)"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
web = [
|
||||
"dep:wasm-bindgen",
|
||||
"dep:serde_json",
|
||||
"dep:console_error_panic_hook",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
# truck: nur die stabilen Crates (KEIN truck-modeling — Booleans instabil)
|
||||
truck-geometry = "0.3"
|
||||
truck-topology = "0.3"
|
||||
truck-rendimesh = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
```
|
||||
|
||||
**Wichtig:** `truck-modeling` (die Crate mit Boolean-Operatoren) wird NICHT eingebunden.
|
||||
Nur `truck-geometry`, `truck-topology` und `truck-rendimesh` — die sind stabil.
|
||||
|
||||
### 1.2 src/lib.rs — Kern-Logik
|
||||
|
||||
#### Eingabe/Ausgabe-Typen
|
||||
|
||||
```rust
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Input: flaches Array [x0,y0, x1,y1, ...] in Modell-Metern (Grundriss-XY).
|
||||
/// Muss ≥ 3 Punkte enthalten; Wiederholung des ersten Punkts am Ende optional.
|
||||
/// Reihenfolge CCW oder CW — truck normalisiert selbst.
|
||||
#[derive(Deserialize)]
|
||||
pub struct ExtrudePolyInput {
|
||||
pub points: Vec<f64>, // flat: [x0,y0, x1,y1, ...]
|
||||
pub height: f64, // Extrusionshöhe in Metern (> 0)
|
||||
}
|
||||
|
||||
/// Output: trianguliertes Mesh, kompatibel mit render3d::types::MeshInput.
|
||||
/// positions: flat [x0,y0,z0, x1,y1,z1, ...] in Modell-Metern
|
||||
/// indices: Dreiecks-Indizes (je 3 = 1 Dreieck), 0-basiert
|
||||
#[derive(Serialize)]
|
||||
pub struct MeshOutput {
|
||||
pub positions: Vec<f32>,
|
||||
pub indices: Vec<u32>,
|
||||
}
|
||||
```
|
||||
|
||||
#### Polygon-Extrusion (truck-API)
|
||||
|
||||
```rust
|
||||
use truck_geometry::prelude::*;
|
||||
use truck_topology::*;
|
||||
|
||||
pub fn extrude_polygon_core(pts: &[(f64, f64)], height: f64) -> Result<MeshOutput, String> {
|
||||
if pts.len() < 3 { return Err("min 3 Punkte".into()); }
|
||||
if height <= 0.0 { return Err("height muss > 0 sein".into()); }
|
||||
|
||||
// 1. Punkte in der XY-Ebene (z=0) als truck-Vertices
|
||||
let verts: Vec<Vertex<Point3>> = pts.iter()
|
||||
.map(|(x, y)| Vertex::new(Point3::new(*x, *y, 0.0)))
|
||||
.collect();
|
||||
|
||||
// 2. Kanten: je zwei aufeinanderfolgende Vertices verbinden, Ring schliessen
|
||||
let edges: Vec<Edge<_, Line<Point3>>> = verts.windows(2)
|
||||
.chain(std::iter::once([verts.last().unwrap(), &verts[0]].as_slice()))
|
||||
.map(|w| {
|
||||
let p0 = *w[0].point();
|
||||
let p1 = *w[1].point();
|
||||
Edge::new(&w[0], &w[1], Line(p0, p1))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 3. Wire (geschlossener Kantenzug)
|
||||
let wire = Wire::from_iter(edges);
|
||||
|
||||
// 4. Planare Face aus dem Wire
|
||||
// truck-topology::Face::new braucht die äussere Boundary + optional Holes
|
||||
let face = Face::new(vec![wire]);
|
||||
|
||||
// 5. Lineare Extrusion: tsweep entlang +Z um `height`
|
||||
let solid = face.tsweep(&Vector3::new(0.0, 0.0, height));
|
||||
|
||||
// 6. Tessellieren (chord-tolerance in Metern — 0.005 = 5 mm)
|
||||
use truck_rendimesh::MeshedShape;
|
||||
let mesh = solid.triangulation(0.005).to_polygon();
|
||||
|
||||
// 7. positions + indices extrahieren
|
||||
let positions: Vec<f32> = mesh.positions().iter()
|
||||
.flat_map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
let indices: Vec<u32> = mesh.tri_faces().iter()
|
||||
.flat_map(|tri| tri.iter().map(|idx| idx.pos as u32))
|
||||
.collect();
|
||||
|
||||
Ok(MeshOutput { positions, indices })
|
||||
}
|
||||
```
|
||||
|
||||
> **Achtung:** Die exakte truck-API (Methoden-Namen, Trait-Imports, tsweep-Signatur)
|
||||
> kann je nach veröffentlichter Version leicht abweichen. Prüfe die Docs von
|
||||
> `truck-topology 0.3` und `truck-rendimesh 0.3` auf docs.rs. Die Struktur oben
|
||||
> ist das Ziel-Pattern — passe Methoden-Signaturen an, wenn der Compiler es verlangt.
|
||||
> Ändere NICHT die Eingabe/Ausgabe-JSON-Struktur.
|
||||
|
||||
#### Kreis-Extrusion (Zylinder)
|
||||
|
||||
```rust
|
||||
pub fn extrude_circle_core(cx: f64, cy: f64, r: f64, height: f64) -> Result<MeshOutput, String> {
|
||||
// Kreis in XY-Ebene tessellieren (N Segmente je nach r),
|
||||
// dann wie extrude_polygon_core aufrufen.
|
||||
// Alternativ: truck-geometry::Ellipse/Circle direkt nutzen, falls vorhanden.
|
||||
let n = (2.0 * std::f64::consts::PI * r / 0.02).ceil().max(16.0) as usize;
|
||||
let pts: Vec<(f64, f64)> = (0..n)
|
||||
.map(|i| {
|
||||
let a = 2.0 * std::f64::consts::PI * i as f64 / n as f64;
|
||||
(cx + r * a.cos(), cy + r * a.sin())
|
||||
})
|
||||
.collect();
|
||||
extrude_polygon_core(&pts, height)
|
||||
}
|
||||
```
|
||||
|
||||
#### WASM-Bindings (Feature "web")
|
||||
|
||||
```rust
|
||||
#[cfg(feature = "web")]
|
||||
mod web {
|
||||
use super::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
/// Extrudiert ein Polygon-Profil.
|
||||
/// `input_json`: `{ "points": [x0,y0,…], "height": 2.5 }`
|
||||
/// Rückgabe: `{ "positions": […], "indices": […] }` oder throws JsError.
|
||||
#[wasm_bindgen]
|
||||
pub fn extrude_polygon(input_json: &str) -> Result<String, JsError> {
|
||||
let input: ExtrudePolyInput = serde_json::from_str(input_json)
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
let pts: Vec<(f64, f64)> = input.points.chunks(2)
|
||||
.map(|c| (c[0], c[1]))
|
||||
.collect();
|
||||
let mesh = extrude_polygon_core(&pts, input.height)
|
||||
.map_err(|e| JsError::new(&e))?;
|
||||
serde_json::to_string(&mesh).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Extrudiert einen Kreis-Querschnitt (Zylinder).
|
||||
/// `input_json`: `{ "cx": 0, "cy": 0, "r": 0.15, "height": 3.0 }`
|
||||
#[wasm_bindgen]
|
||||
pub fn extrude_circle(input_json: &str) -> Result<String, JsError> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct In { cx: f64, cy: f64, r: f64, height: f64 }
|
||||
let i: In = serde_json::from_str(input_json)
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
let mesh = extrude_circle_core(i.cx, i.cy, i.r, i.height)
|
||||
.map_err(|e| JsError::new(&e))?;
|
||||
serde_json::to_string(&mesh).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 Tests (headless, ohne Feature "web")
|
||||
|
||||
Mindestens diese drei Tests müssen mit `cargo test` grün sein:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quad_extrusion_vertex_count() {
|
||||
// 1m × 1m Quadrat, 2m hoch → 8 Eckpunkte minimum (6 Flächen × 2 Dreiecke)
|
||||
let pts = vec![(0.0,0.0),(1.0,0.0),(1.0,1.0),(0.0,1.0)];
|
||||
let m = extrude_polygon_core(&pts, 2.0).unwrap();
|
||||
assert!(m.positions.len() >= 8 * 3); // ≥ 8 Vertices × 3 floats
|
||||
assert_eq!(m.indices.len() % 3, 0); // vollständige Dreiecke
|
||||
assert!(m.indices.len() >= 12 * 3); // ≥ 12 Dreiecke (Quader)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn l_profile_extrusion() {
|
||||
// L-Profil: 6 Punkte
|
||||
let pts = vec![
|
||||
(0.0,0.0),(0.3,0.0),(0.3,0.1),(0.1,0.1),(0.1,0.3),(0.0,0.3),
|
||||
];
|
||||
let m = extrude_polygon_core(&pts, 3.0).unwrap();
|
||||
assert_eq!(m.indices.len() % 3, 0);
|
||||
assert!(m.positions.len() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cylinder_extrusion() {
|
||||
let m = extrude_circle_core(0.0, 0.0, 0.15, 3.0).unwrap();
|
||||
assert_eq!(m.indices.len() % 3, 0);
|
||||
assert!(m.positions.len() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_too_few_points() {
|
||||
assert!(extrude_polygon_core(&[(0.0,0.0),(1.0,0.0)], 1.0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_height() {
|
||||
let pts = vec![(0.0,0.0),(1.0,0.0),(0.5,1.0)];
|
||||
assert!(extrude_polygon_core(&pts, 0.0).is_err());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2 — Build-Script (package.json)
|
||||
|
||||
Füge in `package.json` unter `"scripts"` hinzu:
|
||||
|
||||
```json
|
||||
"build:truck": "wasm-pack build src-tauri/trucksolid --release --target web --out-dir ../../src/engine/pkgTruck --out-name trucksolid --no-default-features --features web"
|
||||
```
|
||||
|
||||
Und in `src-tauri/Cargo.toml` unter `exclude`:
|
||||
|
||||
```toml
|
||||
exclude = ["render2d", "render3d", "geometry", "kernel2d", "dwgimport", "trucksolid"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3 — TS-Wrapper `src/engine/truckSolid.ts`
|
||||
|
||||
Gleiche Lade-Pattern wie `src/plan/useWasmPlanRenderer.ts` (render2d) und
|
||||
`src/viewport/useWasm3dRenderer.ts` (render3d):
|
||||
|
||||
```typescript
|
||||
// Lazy-Singleton: WASM einmalig laden, dann gecacht.
|
||||
let modulePromise: Promise<typeof import("../../engine/pkgTruck/trucksolid")> | null = null;
|
||||
|
||||
async function getModule() {
|
||||
if (!modulePromise) {
|
||||
modulePromise = import("../../engine/pkgTruck/trucksolid").then(async (m) => {
|
||||
await m.default(); // WASM-Binary initialisieren
|
||||
return m;
|
||||
});
|
||||
}
|
||||
return modulePromise;
|
||||
}
|
||||
|
||||
export interface ExtrudedMesh {
|
||||
positions: number[];
|
||||
indices: number[];
|
||||
}
|
||||
|
||||
/** Extrudiert ein geschlossenes Polygon-Profil (Modell-Meter XY) um `height` m. */
|
||||
export async function extrudePolygon(
|
||||
points: number[], // flat [x0,y0, x1,y1, ...]
|
||||
height: number,
|
||||
): Promise<ExtrudedMesh> {
|
||||
const m = await getModule();
|
||||
const json = m.extrude_polygon(JSON.stringify({ points, height }));
|
||||
return JSON.parse(json) as ExtrudedMesh;
|
||||
}
|
||||
|
||||
/** Extrudiert einen Kreis-Querschnitt (Zylinder). */
|
||||
export async function extrudeCircle(
|
||||
cx: number, cy: number, r: number, height: number,
|
||||
): Promise<ExtrudedMesh> {
|
||||
const m = await getModule();
|
||||
const json = m.extrude_circle(JSON.stringify({ cx, cy, r, height }));
|
||||
return JSON.parse(json) as ExtrudedMesh;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4 — Integration in toWalls3d.ts (Vorbereitung)
|
||||
|
||||
In `src/plan/toWalls3d.ts` ist `RMeshKind` bereits definiert:
|
||||
|
||||
```typescript
|
||||
export type RMeshKind = "terrain" | "imported";
|
||||
```
|
||||
|
||||
Erweitere auf `"extrusion"` (damit der Renderer später eine eigene Farbe/Darstellung
|
||||
wählen kann, auch wenn heute noch kein Unterschied besteht):
|
||||
|
||||
```typescript
|
||||
export type RMeshKind = "terrain" | "imported" | "extrusion";
|
||||
```
|
||||
|
||||
Die `emitMeshes()`-Funktion liest bereits `project.drawings2d` und ähnliche Arrays.
|
||||
Für extrudierte Körper wird es später ein `project.extrudedSolids`-Array geben
|
||||
(oder ähnlich — das ist Teil des UI-Folgeauftrags). Die `emitMeshes()`-Erweiterung
|
||||
kommt dann.
|
||||
|
||||
---
|
||||
|
||||
## 5 — Verifikations-Checkliste
|
||||
|
||||
Bevor du als fertig meldest, müssen alle Punkte grün sein:
|
||||
|
||||
- [ ] `cd src-tauri/trucksolid && cargo test` → alle 5 Tests grün, keine Warnings
|
||||
- [ ] `npm run build:truck` → `src/engine/pkgTruck/trucksolid.js` + `.wasm` erzeugt
|
||||
- [ ] `npx tsc --noEmit` (aus Root) → 0 Fehler
|
||||
- [ ] `npx vitest run` → alle bestehenden Tests weiterhin grün (keine Regression)
|
||||
- [ ] `src-tauri/Cargo.toml` hat `"trucksolid"` im `exclude`-Array
|
||||
- [ ] `package.json` hat `"build:truck"` im `"scripts"`-Block
|
||||
|
||||
---
|
||||
|
||||
## 6 — Was NICHT in diesem Auftrag
|
||||
|
||||
- Kein neues UI/Werkzeug (kommt später)
|
||||
- Kein `truck-modeling` (Boolean-Operationen — instabil upstream)
|
||||
- Kein STEP-Export (kommt in Folgeauftrag wenn Basisschicht steht)
|
||||
- Kein Eintrag in `project.extrudedSolids` oder Store (UI-Auftrag)
|
||||
- Keine Änderungen an render3d, generatePlan, App.tsx
|
||||
|
||||
---
|
||||
|
||||
## 7 — Koordinatensystem-Reminder
|
||||
|
||||
| DOSSIER-Modell | truck (im Code) | render3d (wgpu) |
|
||||
|---|---|---|
|
||||
| X = rechts | X = rechts | X = rechts |
|
||||
| Y = oben (Grundriss) | Y = oben (Grundriss) | Z = oben (Y-up swapped) |
|
||||
| Z = Höhe | Z = Höhe | Y = Höhe |
|
||||
|
||||
`mesh.rs` in render3d macht bereits `(model.x, model.y, model.z) → (x, z, y)` beim
|
||||
Hochladen. Du musst also in truck DOSSIER-Koordinaten verwenden (XY-Ebene = Grundriss,
|
||||
Z = Höhe) — das ist konsistent mit der bestehenden `MeshInput.positions`-Konvention.
|
||||
@@ -1,13 +1,12 @@
|
||||
# Briefing — Nativer wgpu-2D-Renderer (parallele Instanz)
|
||||
|
||||
> Für die Instanz/den Agenten, der den nativen GPU-Renderer baut. Isoliert vom
|
||||
> Für den Entwickler, der den nativen GPU-Renderer baut. Isoliert vom
|
||||
> Web-Renderer; koordiniert über dieses Dokument.
|
||||
|
||||
## COMMIT-REGEL (verbindlich)
|
||||
Dieses Repo darf **keinerlei Hinweise auf KI-Werkzeuge** enthalten — nicht im Code,
|
||||
in Kommentaren oder in der Git-Historie. Keine `Co-Authored-By`-Trailer, keine
|
||||
„Generated with"-Zeilen, keine Modell-/Agenten-Namen. Kommentare deutsch,
|
||||
Identifier englisch. **Kein Commit ohne Ansage der Hauptinstanz.**
|
||||
Dieses Repo darf **keinerlei Fremd-Tool-Hinweise** enthalten — nicht im Code,
|
||||
in Kommentaren oder in der Git-Historie. Kommentare deutsch, Identifier englisch.
|
||||
**Kein Commit ohne Absprache.**
|
||||
|
||||
## Warum
|
||||
Der 2D-Plan wird aktuell in einem WebGL-Canvas gerendert (Web-Stack). In Chromium
|
||||
@@ -72,9 +71,9 @@ per Matrix-Uniform macht. Fenster-Integration in Tauri ist der zweite, separate
|
||||
- **Eigener Branch** (z. B. `feature/wgpu-renderer`), NICHT auf `master`/
|
||||
`feature/parametric-walls` committen. Isolierter Worktree.
|
||||
- **Nur** `src-tauri/` + neue Rust-Module + `docs/`. **NICHT** `src/App.tsx`,
|
||||
`src/plan/*`, `types.ts` anfassen (die Hauptinstanz arbeitet dort an Features).
|
||||
`src/plan/*`, `types.ts` anfassen (dort laufen parallel Features).
|
||||
- Der Web-WebGL-Renderer bleibt bestehen (Browser-Lauffähigkeit + Referenz).
|
||||
- Ergebnisse/Diffs zurückliefern; die Hauptinstanz committet und pflegt das Memory.
|
||||
- Ergebnisse/Diffs zurückliefern; Projektleitung committet.
|
||||
|
||||
## Gates
|
||||
`cargo check`/`cargo test`/`cargo build` grün. Trace-Scan sauber (COMMIT-REGEL).
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
> Port-Plan) und M1 (entkoppelter Standalone-Spike). Noch NICHT die Migration.
|
||||
|
||||
## Commit-/Spuren-Regel
|
||||
Wie im ganzen Repo: keine Hinweise auf KI-Werkzeuge — nicht im Code, in Kommentaren
|
||||
oder in der Historie. Kommentare deutsch, Identifier englisch. Kein Commit ohne
|
||||
Ansage der Hauptinstanz.
|
||||
Wie im ganzen Repo: keine Fremd-Tool-Hinweise im Code, in Kommentaren oder der Historie. Kommentare deutsch, Identifier englisch. Kein Commit ohne Absprache.
|
||||
|
||||
## Warum
|
||||
Der 3D-View laeuft heute als three.js/WebGL im Tauri-Webview (WebKitGTK). Wie beim
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="576" height="420" viewBox="0 0 576.0 420.0">
|
||||
<rect x="0" y="0" width="576.0" height="420.0" fill="#ffffff"/>
|
||||
<polygon points="48.00,372.00 528.00,372.00 528.00,348.00 48.00,348.00" fill="#c9d2d6" stroke="#2b2b2b" stroke-width="1.2"/>
|
||||
<line x1="108.00" y1="348.00" x2="108.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="348.00" x2="468.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="108.00" y1="348.00" x2="468.00" y2="348.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="108.00" y1="48.00" x2="468.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="228.00" y1="252.00" x2="228.00" y2="132.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="348.00" y1="252.00" x2="348.00" y2="132.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="228.00" y1="252.00" x2="348.00" y2="252.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="228.00" y1="132.00" x2="348.00" y2="132.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="480.00" y1="348.00" x2="480.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="348.00" x2="480.00" y2="348.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="48.00" x2="480.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="252.00" y1="252.00" x2="252.00" y2="132.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="324.00" y1="252.00" x2="324.00" y2="132.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="348.00" x2="468.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="108.00" y1="348.00" x2="108.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="468.00" y1="348.00" x2="108.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="468.00" y1="48.00" x2="108.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="480.00" y1="348.00" x2="480.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="348.00" x2="456.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="348.00" x2="456.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="480.00" y1="348.00" x2="456.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="480.00" y1="48.00" x2="456.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="348.00" x2="468.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="48.00" x2="468.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="252.00" y1="348.00" x2="252.00" y2="252.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="252.00" y1="132.00" x2="252.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="324.00" y1="348.00" x2="324.00" y2="252.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="324.00" y1="132.00" x2="324.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="324.00" y1="348.00" x2="324.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="252.00" y1="348.00" x2="252.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="252.00" y1="348.00" x2="324.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="252.00" y1="48.00" x2="324.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="324.00" y1="348.00" x2="252.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="324.00" y1="48.00" x2="252.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="48.00" y1="372.00" x2="48.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="528.00" y1="372.00" x2="528.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="48.00" y1="372.00" x2="528.00" y2="372.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="48.00" y1="348.00" x2="528.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
Generated
+227
-1
@@ -27,11 +27,13 @@
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/three": "^0.169.0",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"electron": "^43.0.0",
|
||||
"playwright": "^1.61.1",
|
||||
"puppeteer": "^25.2.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
"vitest": "^4.1.9"
|
||||
"vitest": "^4.1.9",
|
||||
"wasm-pack": "^0.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -325,6 +327,50 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron-internal/extract-zip": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.4.tgz",
|
||||
"integrity": "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/get": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz",
|
||||
"integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"env-paths": "^3.0.0",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"progress": "^2.0.3",
|
||||
"semver": "^7.6.3",
|
||||
"sumchecker": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"undici": "^7.24.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/get/node_modules/semver": {
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
@@ -750,6 +796,19 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^7.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
@@ -1871,6 +1930,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
|
||||
@@ -2206,6 +2275,16 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chromium-bidi": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz",
|
||||
@@ -2355,6 +2434,25 @@
|
||||
"loglevel": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "43.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-43.0.0.tgz",
|
||||
"integrity": "sha512-PV60GsWU6qufhuOhw3n+Yix3WPDcqDtBqE8orbEQGQGHEkgp9o/JCPgb7L4vIL0r1HnfPdqSRtboOTqbDkcFDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron-internal/extract-zip": "^1.0.1",
|
||||
"@electron/get": "^5.0.0",
|
||||
"@types/node": "^24.9.0"
|
||||
},
|
||||
"bin": {
|
||||
"electron": "cli.js",
|
||||
"install-electron": "install.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.380",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz",
|
||||
@@ -2369,6 +2467,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
|
||||
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.2.0.tgz",
|
||||
@@ -2534,6 +2645,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
@@ -2986,6 +3104,29 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
|
||||
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
@@ -3201,6 +3342,16 @@
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer": {
|
||||
"version": "25.2.1",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.2.1.tgz",
|
||||
@@ -3543,6 +3694,19 @@
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sumchecker": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-pathdata": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
@@ -3577,6 +3741,33 @@
|
||||
"url": "https://github.com/fontello/svg2ttf?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.19",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz",
|
||||
"integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"minizlib": "^3.1.0",
|
||||
"yallist": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/yallist": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
@@ -3666,6 +3857,24 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
|
||||
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
@@ -3968,6 +4177,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/wasm-pack": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.15.0.tgz",
|
||||
"integrity": "sha512-DdqtGWc3+iFx+7lL7QU5LBWs7qMnwQSWxF0htSfE15sNa3roVwHjAkTm2JXgueU2GGfSwNqbq2EzyC2b/biKDA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"tar": "^7.5.3"
|
||||
},
|
||||
"bin": {
|
||||
"wasm-pack": "run.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/webdriver-bidi-protocol": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.2.tgz",
|
||||
|
||||
+10
-2
@@ -13,7 +13,13 @@
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build",
|
||||
"dump:native": "node scripts/dump-native-scene.mjs",
|
||||
"shell": "scripts/chromium-shell.sh"
|
||||
"shell": "scripts/chromium-shell.sh",
|
||||
"electron": "scripts/electron-shell.sh",
|
||||
"build:engine": "wasm-pack build src-tauri/render2d --release --target web --out-dir ../../src/engine/pkg --out-name render2d --no-default-features --features web",
|
||||
"build:engine3d": "wasm-pack build src-tauri/render3d --release --target web --out-dir ../../src/engine/pkg3d --out-name render3d --no-default-features --features web",
|
||||
"build:geometry": "wasm-pack build src-tauri/geometry --release --target web --out-dir ../../src/engine/pkgGeometry --out-name geometry --no-default-features --features web",
|
||||
"build:kernel2d": "wasm-pack build src-tauri/kernel2d --release --target web --out-dir ../../src/engine/pkgKernel2d --out-name kernel2d --no-default-features --features web",
|
||||
"build:dwgimport": "wasm-pack build src-tauri/dwgimport --release --target web --out-dir ../../src/engine/pkgDwgImport --out-name dwgimport --no-default-features --features web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mlightcad/libredwg-web": "^0.7.7",
|
||||
@@ -35,10 +41,12 @@
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/three": "^0.169.0",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"electron": "^43.0.0",
|
||||
"playwright": "^1.61.1",
|
||||
"puppeteer": "^25.2.1",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
"vitest": "^4.1.9"
|
||||
"vitest": "^4.1.9",
|
||||
"wasm-pack": "^0.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Archivo Project Authors (https://github.com/Omnibus-Type/Archivo)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Jost Project Authors (https://github.com/indestructible-type)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2019 The Karla Project Authors (https://github.com/googlefonts/karla)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2018 The Manrope Project Authors (https://github.com/sharanda/manrope)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2015 The Public Sans Project Authors (https://github.com/uswds/public-sans)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2015 The Rubik Project Authors (https://github.com/googlefonts/rubik)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2010-2020 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2014 The Source Serif 4 Project Authors (https://github.com/adobe-fonts/source-serif)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -77,11 +77,19 @@ trap cleanup EXIT
|
||||
|
||||
mkdir -p "${PROFILE_DIR}"
|
||||
|
||||
echo "chromium-shell: öffne ${URL} in ${BROWSER_BIN} (App-Modus) …"
|
||||
# WebGPU: Auf Linux liegt WebGPU in Chromium noch hinter Flags. Für den
|
||||
# render2d-WASM-Viewport (`?engine=wasm`, useWasmPlanRenderer) muss `navigator.gpu`
|
||||
# vorhanden sein — daher `--enable-unsafe-webgpu` (aktiviert die API auch ohne
|
||||
# volle Konformität) plus `--enable-features=Vulkan` (WebGPU nutzt unter Linux das
|
||||
# Vulkan-Backend). Ohne `?engine=wasm` sind die Flags folgenlos (der Standard-
|
||||
# WebGL2-Pfad läuft unverändert weiter).
|
||||
echo "chromium-shell: öffne ${URL} in ${BROWSER_BIN} (App-Modus, WebGPU aktiv) …"
|
||||
"${BROWSER_BIN}" \
|
||||
--app="${URL}" \
|
||||
--user-data-dir="${PROFILE_DIR}" \
|
||||
--window-size=1600,1000 \
|
||||
--enable-unsafe-webgpu \
|
||||
--enable-features=Vulkan \
|
||||
>/tmp/cad-chromium-shell-browser.log 2>&1
|
||||
|
||||
# Läuft weiter, bis der Browserprozess (das App-Fenster) beendet wird; dann
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// electron-main.js — Electron-Prototyp-Shell für die CAD-App.
|
||||
//
|
||||
// Ersetzt WebKitGTK (Tauri-Linux-Webview) durch Chromium, damit WebGPU
|
||||
// (render2d-WASM-Engine, ?engine=wasm) zuverlässig läuft. Kein Rust-Backend
|
||||
// nötig: compute_joins hat einen TS-Fallback (src/compute/index.ts).
|
||||
|
||||
const { app, BrowserWindow, Menu, ipcMain } = require("electron");
|
||||
const path = require("node:path");
|
||||
|
||||
// Randloses App-Fenster wie chromium-shell.sh (--app=…): keine native
|
||||
// Menüleiste (File/Edit/View/Window), kein Fensterrahmen. Minimieren/
|
||||
// Maximieren/Schliessen laufen darum über eine eigene Fenstersteuerung im
|
||||
// Renderer (WindowControls.tsx) + IPC hier unten — ohne frame gibt es sonst
|
||||
// gar keine Möglichkeit, das Fenster zu steuern.
|
||||
Menu.setApplicationMenu(null);
|
||||
|
||||
// Gleiche Flags wie scripts/chromium-shell.sh — WebGPU liegt unter Linux
|
||||
// hinter --enable-unsafe-webgpu, und WebGPU braucht das Vulkan-Backend.
|
||||
app.commandLine.appendSwitch("enable-unsafe-webgpu");
|
||||
app.commandLine.appendSwitch("enable-features", "Vulkan");
|
||||
|
||||
const DEV_URL = "http://localhost:5187";
|
||||
const isDev = !app.isPackaged;
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 1000,
|
||||
frame: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, "electron-preload.cjs"),
|
||||
},
|
||||
});
|
||||
|
||||
const notifyMaximized = () => {
|
||||
win.webContents.send("window:maximized-changed", win.isMaximized());
|
||||
};
|
||||
win.on("maximize", notifyMaximized);
|
||||
win.on("unmaximize", notifyMaximized);
|
||||
|
||||
if (isDev) {
|
||||
win.loadURL(DEV_URL);
|
||||
} else {
|
||||
win.loadFile(require("node:path").join(__dirname, "..", "dist", "index.html"));
|
||||
}
|
||||
}
|
||||
|
||||
// Fenstersteuerung vom Renderer (WindowControls.tsx über electron-preload.cjs).
|
||||
// Wirkt auf das Fenster, aus dem das Event kam (mehrfachfenster-sicher).
|
||||
ipcMain.on("window:minimize", (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
||||
});
|
||||
ipcMain.on("window:toggle-maximize", (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (!win) return;
|
||||
if (win.isMaximized()) win.unmaximize();
|
||||
else win.maximize();
|
||||
});
|
||||
ipcMain.on("window:close", (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.close();
|
||||
});
|
||||
ipcMain.handle("window:is-maximized", (event) => {
|
||||
return BrowserWindow.fromWebContents(event.sender)?.isMaximized() ?? false;
|
||||
});
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// electron-preload.cjs — Bruecke zwischen dem isolierten Renderer (contextIsolation)
|
||||
// und dem Electron-Hauptprozess. Braucht es NUR fuer die custom Fenstersteuerung
|
||||
// (Minimieren/Maximieren/Schliessen) im randlosen Fenster (frame:false, siehe
|
||||
// electron-main.cjs) — sonst gaebe es keine Moeglichkeit, das Fenster ueberhaupt
|
||||
// zu steuern.
|
||||
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("dossierWindow", {
|
||||
minimize: () => ipcRenderer.send("window:minimize"),
|
||||
toggleMaximize: () => ipcRenderer.send("window:toggle-maximize"),
|
||||
close: () => ipcRenderer.send("window:close"),
|
||||
isMaximized: () => ipcRenderer.invoke("window:is-maximized"),
|
||||
onMaximizedChange: (callback) => {
|
||||
const listener = (_event, isMaximized) => callback(isMaximized);
|
||||
ipcRenderer.on("window:maximized-changed", listener);
|
||||
return () => ipcRenderer.removeListener("window:maximized-changed", listener);
|
||||
},
|
||||
});
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# electron-shell.sh — startet die CAD-Oberfläche im Electron-Prototyp-Fenster.
|
||||
#
|
||||
# Wie chromium-shell.sh: stellt sicher, dass der Vite-Dev-Server läuft, startet
|
||||
# ihn sonst selbst und beendet ihn wieder, sobald das Electron-Fenster schliesst.
|
||||
|
||||
set -u
|
||||
|
||||
PORT=5187
|
||||
URL="http://localhost:${PORT}"
|
||||
|
||||
server_is_up() {
|
||||
curl --silent --fail --max-time 1 --output /dev/null "${URL}"
|
||||
}
|
||||
|
||||
DEV_SERVER_PID=""
|
||||
|
||||
if server_is_up; then
|
||||
echo "electron-shell: Dev-Server läuft bereits auf ${URL}."
|
||||
else
|
||||
echo "electron-shell: starte Vite-Dev-Server (Port ${PORT}) …"
|
||||
npm run dev >/tmp/cad-electron-shell-dev.log 2>&1 &
|
||||
DEV_SERVER_PID=$!
|
||||
|
||||
ATTEMPTS=0
|
||||
MAX_ATTEMPTS=60
|
||||
until server_is_up; do
|
||||
ATTEMPTS=$((ATTEMPTS + 1))
|
||||
if [ "${ATTEMPTS}" -ge "${MAX_ATTEMPTS}" ]; then
|
||||
echo "electron-shell: Dev-Server antwortet nach ${MAX_ATTEMPTS}s nicht, Abbruch." >&2
|
||||
kill "${DEV_SERVER_PID}" 2>/dev/null
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "electron-shell: Dev-Server bereit."
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${DEV_SERVER_PID}" ]; then
|
||||
echo "electron-shell: beende selbst gestarteten Dev-Server (PID ${DEV_SERVER_PID}) …"
|
||||
kill "${DEV_SERVER_PID}" 2>/dev/null
|
||||
wait "${DEV_SERVER_PID}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "electron-shell: öffne Electron-Fenster …"
|
||||
npx electron "$(dirname "$0")/electron-main.cjs"
|
||||
@@ -0,0 +1,60 @@
|
||||
// Vergleicht den SVG-Referenzpfad gegen den nativen render2d-WASM/WebGPU-Pfad
|
||||
// (?engine=wasm): lädt die App einmal ohne Engine-Param (SVG) und einmal mit
|
||||
// ?engine=wasm, gleicher Viewport/Zoom, schießt je einen Screenshot. Prüft
|
||||
// zusätzlich, ob WebGPU verfügbar ist und der native Renderer tatsächlich
|
||||
// initialisiert hat (statt still auf SVG zurückzufallen).
|
||||
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
const BASE = process.env.PROBE_URL || "http://localhost:5187/";
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
args: [
|
||||
"--no-sandbox",
|
||||
"--enable-unsafe-webgpu",
|
||||
"--enable-features=Vulkan",
|
||||
"--use-angle=vulkan",
|
||||
"--ignore-gpu-blocklist",
|
||||
],
|
||||
});
|
||||
|
||||
async function shoot(url, path, tag) {
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1400, height: 900, deviceScaleFactor: 1 });
|
||||
const logs = [];
|
||||
page.on("console", (m) => logs.push(`[${m.type()}] ${m.text()}`));
|
||||
page.on("pageerror", (e) => logs.push(`[PAGEERROR] ${e.message}`));
|
||||
try {
|
||||
await page.goto(url, { waitUntil: "networkidle0", timeout: 30000 });
|
||||
} catch (e) {
|
||||
logs.push(`[GOTO] ${e.message}`);
|
||||
}
|
||||
// Warten, bis Plan + evtl. WASM/WebGPU-Renderer initialisiert und gezeichnet hat.
|
||||
await new Promise((r) => setTimeout(r, 2500));
|
||||
|
||||
const info = await page.evaluate(() => {
|
||||
const svg = document.querySelector(".plan-svg");
|
||||
const canvases = document.querySelectorAll("canvas");
|
||||
return {
|
||||
hasGpu: typeof navigator !== "undefined" && "gpu" in navigator,
|
||||
hasSvg: !!svg,
|
||||
svgChildren: svg ? svg.querySelectorAll("path,polygon,polyline,line,rect,text").length : 0,
|
||||
canvasCount: canvases.length,
|
||||
};
|
||||
});
|
||||
|
||||
await page.screenshot({ path });
|
||||
console.log(`\n=== ${tag} (${url}) ===`);
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
const warns = logs.filter((l) => /warn|error|PAGEERROR/i.test(l));
|
||||
console.log("Warnungen/Fehler:", warns.length ? "\n" + warns.join("\n") : "(keine)");
|
||||
await page.close();
|
||||
return info;
|
||||
}
|
||||
|
||||
await shoot(BASE + "?gl=0", "scripts/probe-parity-svg.png", "SVG-Referenzpfad");
|
||||
await shoot(BASE + "?engine=wasm", "scripts/probe-parity-wasm.png", "WASM/WebGPU-Engine");
|
||||
|
||||
await browser.close();
|
||||
console.log("\nScreenshots: scripts/probe-parity-svg.png vs scripts/probe-parity-wasm.png");
|
||||
@@ -0,0 +1,129 @@
|
||||
// Probe: belegt den nativen render3d-Renderer als WASM/WebGPU-Viewport in der
|
||||
// 3D-Ansicht (`?engine=wasm`, siehe src/viewport/Wasm3DViewport.tsx).
|
||||
//
|
||||
// Ablauf: App mit ?engine=wasm laden, in die Perspektive wechseln (Ansichts-
|
||||
// Icon-Grid der Oberleiste), auf die Engine-Initialisierung warten, Screenshot
|
||||
// scripts/probe-engine3d.png schiessen und dann PRÜFEN, dass das Engine-Canvas
|
||||
// (canvas[data-engine="render3d"]) vorhanden ist UND Geometrie zeigt.
|
||||
//
|
||||
// PIXEL-SAMPLING über den Screenshot (nicht drawImage): ein WebGPU-Canvas
|
||||
// liefert nach dem Present über drawImage/getImageData nur transparente Pixel
|
||||
// (der Frame ist bereits verbraucht). Der CDP-Screenshot enthält dagegen die
|
||||
// komposittierte Ausgabe — er wird als Data-URL zurück in die Seite geladen und
|
||||
// im Rechteck des Engine-Canvas gegen die Löschfarbe (#f5f5f5) ausgezählt.
|
||||
//
|
||||
// WebGPU liegt in Chromium/Linux hinter Flags (siehe scripts/chromium-shell.sh):
|
||||
// --enable-unsafe-webgpu --enable-features=Vulkan. HEADLESS initialisiert die
|
||||
// API zwar (Adapter/Device ok, Init-Logs erscheinen), PRÄSENTIERT aber keine
|
||||
// Frames — das gilt genauso für den etablierten render2d-Viewport (gegen-
|
||||
// geprüft). Verlässlich verifiziert daher nur headful:
|
||||
// HEADFUL=1 [CHROMIUM_BIN=/usr/bin/chromium] node scripts/probe-engine3d.mjs
|
||||
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
const BASE = process.env.PROBE_URL || "http://localhost:5187/";
|
||||
const URL = BASE.replace(/\/?$/, "/") + "?engine=wasm";
|
||||
const HEADFUL = !!process.env.HEADFUL;
|
||||
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: HEADFUL ? false : "new",
|
||||
// Headful: das System-Chromium nutzen (Puppeteers gebündeltes Chrome kommt
|
||||
// mit der Wayland/niri-Session hier nicht hoch).
|
||||
executablePath: HEADFUL ? process.env.CHROMIUM_BIN || "/usr/bin/chromium" : undefined,
|
||||
args: ["--no-sandbox", "--enable-unsafe-webgpu", "--enable-features=Vulkan"],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1400, height: 900, deviceScaleFactor: 1 });
|
||||
|
||||
const logs = [];
|
||||
page.on("console", (m) => logs.push(`[${m.type()}] ${m.text()}`));
|
||||
page.on("pageerror", (e) => logs.push(`[PAGEERROR] ${e.message}`));
|
||||
|
||||
await page.goto(URL, { waitUntil: "networkidle0", timeout: 25000 });
|
||||
const hasGpu = await page.evaluate(() => "gpu" in navigator);
|
||||
|
||||
// In die Perspektive wechseln: die 3D-Blicke im Ansichts-Icon-Grid tragen ihr
|
||||
// Label als aria-label (TopBar.tsx).
|
||||
const clicked = await page.evaluate(() => {
|
||||
const btn = document.querySelector('button.view-icon[aria-label="Perspektive"]');
|
||||
if (!btn) return false;
|
||||
btn.click();
|
||||
return true;
|
||||
});
|
||||
|
||||
// Engine-Init (WASM-Load + Adapter/Device) + erster Frame brauchen einen Moment.
|
||||
await wait(3500);
|
||||
|
||||
// Position/Größe des Engine-Canvas im Viewport (CSS-Pixel, dsf=1).
|
||||
const rect = await page.evaluate(() => {
|
||||
const c = document.querySelector('canvas[data-engine="render3d"]');
|
||||
if (!c) return null;
|
||||
const r = c.getBoundingClientRect();
|
||||
return { x: r.x, y: r.y, w: r.width, h: r.height };
|
||||
});
|
||||
|
||||
await page.screenshot({ path: "scripts/probe-engine3d.png" });
|
||||
|
||||
let verdict = { present: !!rect };
|
||||
if (rect && rect.w > 4 && rect.h > 4) {
|
||||
// Screenshot zurück in die Seite laden und im Canvas-Rechteck auszählen.
|
||||
const b64 = await page.screenshot({ encoding: "base64" });
|
||||
verdict = await page.evaluate(
|
||||
async (b64Png, r) => {
|
||||
const img = new Image();
|
||||
img.src = "data:image/png;base64," + b64Png;
|
||||
await img.decode();
|
||||
const s = document.createElement("canvas");
|
||||
s.width = img.width;
|
||||
s.height = img.height;
|
||||
const ctx = s.getContext("2d");
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const d = ctx.getImageData(
|
||||
Math.round(r.x),
|
||||
Math.round(r.y),
|
||||
Math.round(r.w),
|
||||
Math.round(r.h),
|
||||
).data;
|
||||
let nonBackground = 0;
|
||||
const total = d.length / 4;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
if (
|
||||
Math.abs(d[i] - 245) > 12 ||
|
||||
Math.abs(d[i + 1] - 245) > 12 ||
|
||||
Math.abs(d[i + 2] - 245) > 12
|
||||
) {
|
||||
nonBackground++;
|
||||
}
|
||||
}
|
||||
return { present: true, rect: r, total, nonBackground };
|
||||
},
|
||||
b64,
|
||||
rect,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`URL: ${URL}`);
|
||||
console.log("navigator.gpu:", hasGpu, "| Perspektive-Button:", clicked);
|
||||
console.log(JSON.stringify(verdict, null, 2));
|
||||
const relevant = logs.filter((l) => /render3d|render2d|webgpu|wasm|error|warn/i.test(l));
|
||||
console.log("Logs:", relevant.length ? "\n" + relevant.join("\n") : "(keine)");
|
||||
|
||||
await browser.close();
|
||||
|
||||
// Erfolg: Canvas vorhanden und deutlich mehr als nur die Löschfarbe sichtbar
|
||||
// (>3 % der Pixel abseits von #f5f5f5 — das Gebäude deckt real ~40 % ab; das
|
||||
// Ansichts-Badge allein läge unter 1 %).
|
||||
const ok = verdict.present && (verdict.nonBackground ?? 0) > (verdict.total ?? 1) * 0.03;
|
||||
if (ok) {
|
||||
console.log(
|
||||
`\nOK: Engine-Canvas zeigt Geometrie (${verdict.nonBackground}/${verdict.total} Pixel abseits der Löschfarbe). Screenshot: scripts/probe-engine3d.png`,
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
`\nFEHLSCHLAG: Engine-Canvas ${verdict.present ? "ohne sichtbare Geometrie" : "fehlt"} (navigator.gpu=${hasGpu}). ` +
|
||||
"Headless präsentiert keine WebGPU-Frames — mit HEADFUL=1 erneut ausführen.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Probe (Materialien-Tab — PBR-Kugel-Vorschau + Filter): öffnet den Ressourcen-
|
||||
// Manager, wechselt in den neuen „Materialien"-Tab, prüft dass Kacheln
|
||||
// erscheinen, mindestens eine Kugel-Vorschau (Bild) gerendert wurde, und dass
|
||||
// Such-/Kategorie-Filter die Trefferzahl verändern.
|
||||
//
|
||||
// Aufruf: PROBE_URL=http://localhost:5187/ node scripts/probe-material-tiles.mjs
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
const URL = process.env.PROBE_URL || "http://localhost:5187/";
|
||||
const OUT = "scripts";
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
args: ["--no-sandbox", "--use-gl=swiftshader", "--enable-unsafe-swiftshader"],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1360, height: 900, deviceScaleFactor: 2 });
|
||||
|
||||
const logs = [];
|
||||
page.on("console", (m) => logs.push(`[${m.type()}] ${m.text()}`));
|
||||
page.on("pageerror", (e) => logs.push(`[PAGEERROR] ${e.message}`));
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const shot = (n) => page.screenshot({ path: `${OUT}/${n}` });
|
||||
|
||||
async function clickByText(selector, text) {
|
||||
const handle = await page.evaluateHandle(
|
||||
(sel, t) =>
|
||||
[...document.querySelectorAll(sel)].find((el) => el.textContent.includes(t)),
|
||||
selector,
|
||||
text,
|
||||
);
|
||||
const el = handle.asElement();
|
||||
if (!el) throw new Error(`nicht gefunden: ${selector} ~ "${text}"`);
|
||||
await el.click();
|
||||
return el;
|
||||
}
|
||||
|
||||
await page.goto(URL, { waitUntil: "domcontentloaded", timeout: 20000 });
|
||||
await page.waitForSelector(".topbar", { timeout: 20000 });
|
||||
await sleep(600);
|
||||
|
||||
// 1) Ressourcen öffnen → in den Materialien-Tab wechseln.
|
||||
await page.waitForSelector('button[aria-label="Ressourcen"]', { timeout: 8000 });
|
||||
await page.click('button[aria-label="Ressourcen"]');
|
||||
await page.waitForSelector(".res-drawer", { timeout: 8000 });
|
||||
await clickByText(".res-tab-btn", "Materialien");
|
||||
await page.waitForSelector(".mat-lib-grid .mat-tile", { timeout: 8000 });
|
||||
|
||||
const tileCount = await page.$$eval(".mat-lib-grid .mat-tile", (els) => els.length);
|
||||
console.log("Material-Kacheln (ungefiltert):", tileCount);
|
||||
|
||||
// 2) Warten, bis mindestens eine Kugel-Vorschau gerendert ist (Bild im Tile).
|
||||
await page.waitForFunction(
|
||||
() => document.querySelectorAll(".mat-sphere img").length > 0,
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
// Sichtbare Kacheln lazy nachladen lassen (Warteschlange rendert seriell).
|
||||
await sleep(2500);
|
||||
const renderedCount = await page.$$eval(".mat-sphere img", (els) => els.length);
|
||||
console.log("Gerenderte Kugel-Vorschauen (sichtbarer Bereich):", renderedCount);
|
||||
|
||||
// Erstes Vorschaubild darf kein leeres/transparentes 1x1-Pixelbild sein.
|
||||
const firstSrc = await page.$eval(".mat-sphere img", (img) => img.src);
|
||||
console.log("Erste Vorschau-DataURL-Länge:", firstSrc.length);
|
||||
|
||||
await shot("probe-material-tiles-1-grid.png");
|
||||
|
||||
// 3) Kategorie-Filter: erste konkrete Kategorie-Chip anklicken (nicht "Alle").
|
||||
const chipLabels = await page.$$eval(".mat-chip", (els) => els.map((e) => e.textContent));
|
||||
console.log("Kategorie-Chips:", chipLabels);
|
||||
const firstCategoryChip = chipLabels[1]; // Index 0 = "Alle".
|
||||
await clickByText(".mat-chip", firstCategoryChip);
|
||||
await sleep(300);
|
||||
const filteredByCategory = await page.$$eval(".mat-lib-grid .mat-tile", (els) => els.length);
|
||||
console.log(`Kacheln nach Kategorie-Filter ("${firstCategoryChip}"):`, filteredByCategory);
|
||||
await shot("probe-material-tiles-2-category.png");
|
||||
|
||||
// Filter zurücksetzen ("Alle").
|
||||
await clickByText(".mat-chip", "Alle");
|
||||
await sleep(200);
|
||||
|
||||
// 4) Textsuche: nach "Holz" suchen (mehrere Bibliotheks-Einträge betroffen).
|
||||
await page.type(".mat-search", "Holz");
|
||||
await sleep(400);
|
||||
const filteredByText = await page.$$eval(".mat-lib-grid .mat-tile", (els) => els.length);
|
||||
console.log('Kacheln nach Textsuche ("Holz"):', filteredByText);
|
||||
await shot("probe-material-tiles-3-search.png");
|
||||
|
||||
// 5) Kachel auswählen → aktiv-Zustand + Statuszeile prüfen.
|
||||
await page.click(".mat-lib-grid .mat-tile");
|
||||
await sleep(200);
|
||||
const hasActive = await page.$(".mat-lib-grid .mat-tile.active");
|
||||
const selectedLine = await page.$eval(".mat-lib-selected", (el) => el.textContent).catch(() => null);
|
||||
console.log("Aktive Kachel vorhanden:", !!hasActive, "| Auswahl-Zeile:", selectedLine);
|
||||
await shot("probe-material-tiles-4-selected.png");
|
||||
|
||||
console.log("--- console ---");
|
||||
for (const l of logs.slice(-30)) console.log(l);
|
||||
|
||||
if (tileCount === 0) throw new Error("Keine Material-Kacheln gefunden");
|
||||
if (renderedCount === 0) throw new Error("Keine Kugel-Vorschau gerendert");
|
||||
if (firstSrc.length < 200) throw new Error("Vorschau-DataURL wirkt leer");
|
||||
if (filteredByCategory >= tileCount) throw new Error("Kategorie-Filter hat nichts gefiltert");
|
||||
if (filteredByText >= tileCount) throw new Error("Textsuche hat nichts gefiltert");
|
||||
if (!hasActive) throw new Error("Kachel-Auswahl hat keinen aktiven Zustand gesetzt");
|
||||
|
||||
await browser.close();
|
||||
console.log("done");
|
||||
+51
-10
@@ -12,6 +12,7 @@ import { join } from "node:path";
|
||||
|
||||
const URL = process.env.PROBE_URL || "http://localhost:5187/";
|
||||
const OUT = process.env.SCRATCH || "/tmp/pdf-probe";
|
||||
const PEN_STEPS_LOG = "0.13, 0.18, 0.25, 0.35, 0.5, 0.7, 1.0";
|
||||
if (!existsSync(OUT)) mkdirSync(OUT, { recursive: true });
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
@@ -69,10 +70,12 @@ const info = await page.evaluate(() => {
|
||||
});
|
||||
console.log("Store:", JSON.stringify(info));
|
||||
|
||||
// PDF-Button in der Oberleiste klicken (Text „PDF"). Öffnet den Export-Dialog.
|
||||
// PDF-Button in der Oberleiste klicken (Icon-Button, aria-label „PDF" — die
|
||||
// TopBar zeigt seit dem Icon-Redesign nur noch ein Material-Symbol, kein
|
||||
// Klartext mehr). Öffnet den Export-Dialog.
|
||||
const clickedBtn = await page.evaluate(() => {
|
||||
const btns = Array.from(document.querySelectorAll(".tb-btn"));
|
||||
const b = btns.find((x) => x.textContent.trim() === "PDF");
|
||||
const btns = Array.from(document.querySelectorAll(".tb-iconbtn"));
|
||||
const b = btns.find((x) => x.getAttribute("aria-label")?.trim() === "PDF");
|
||||
if (b) {
|
||||
b.click();
|
||||
return true;
|
||||
@@ -118,13 +121,15 @@ const pdfBytes = Buffer.from(captured.b64, "base64");
|
||||
const pdfPath = join(OUT, captured.name.endsWith(".pdf") ? captured.name : captured.name + ".pdf");
|
||||
writeFileSync(pdfPath, pdfBytes);
|
||||
|
||||
// Visuelle Prüfung: das DRUCK-SVG (exakt die Vektor-Quelle des PDF) im Browser
|
||||
// erzeugen und nach PNG rastern. Zeigt scharfe Linien, Schraffuren, weisses Blatt.
|
||||
// Visuelle Prüfung: das DRUCK-SVG (exakt die Vektor-Quelle des PDF — jetzt aus
|
||||
// der RENDER-SZENE, derselben Quelle wie der Viewport) im Browser erzeugen und
|
||||
// nach PNG rastern. Zeigt scharfe Linien, Schraffuren, weisses Blatt.
|
||||
try {
|
||||
const svgPng = await page.evaluate(async () => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const gp = await import("/src/plan/generatePlan.ts");
|
||||
const sp = await import("/src/model/sampleProject.ts");
|
||||
const ptp = await import("/src/export/planToPrintSvg.ts");
|
||||
const trs = await import("/src/plan/toRenderScene.ts");
|
||||
const stp = await import("/src/export/sceneToPrintSvg.ts");
|
||||
const proj = sp.sampleProject;
|
||||
const codes = new Set();
|
||||
const walk = (cs) =>
|
||||
@@ -136,12 +141,29 @@ try {
|
||||
});
|
||||
walk(proj.layers);
|
||||
const plan = gp.generatePlan(proj, "eg", codes, undefined, "mittel", false, false);
|
||||
const print = ptp.planToPrintSvg(plan, {
|
||||
const scene = trs.planToRenderScene(plan);
|
||||
const print = stp.sceneToPrintSvg(scene, {
|
||||
scaleDenominator: 100,
|
||||
pageWidthMm: 297,
|
||||
pageHeightMm: 210,
|
||||
marginMm: 10,
|
||||
});
|
||||
|
||||
// Schraffur-Nachweis: widthScreen-Polylinien der Szene sind die Schraffur-
|
||||
// Musterlinien (siehe toRenderScene.ts); im Print-SVG muessen sie als
|
||||
// <polyline> mit ISO-Stiftstufen-Strichbreite auftauchen.
|
||||
const hatchRunCount = scene.polylines.filter((p) => p.widthScreen).length;
|
||||
const PEN_STEPS = [0.13, 0.18, 0.25, 0.35, 0.5, 0.7, 1.0];
|
||||
const strokeWidths = Array.from(
|
||||
print.svg.querySelectorAll("polyline, polygon, line, path"),
|
||||
)
|
||||
.map((el) => el.getAttribute("stroke-width"))
|
||||
.filter((w) => w !== null)
|
||||
.map(Number);
|
||||
const offPenStep = strokeWidths.filter(
|
||||
(w) => !PEN_STEPS.some((s) => Math.abs(s - w) < 1e-6),
|
||||
);
|
||||
|
||||
const xml = new XMLSerializer().serializeToString(print.svg);
|
||||
const blob = new Blob([xml], { type: "image/svg+xml" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -159,11 +181,30 @@ try {
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
return canvas.toDataURL("image/png");
|
||||
return {
|
||||
png: canvas.toDataURL("image/png"),
|
||||
hatchRunCount,
|
||||
strokedElementCount: strokeWidths.length,
|
||||
offPenStepCount: offPenStep.length,
|
||||
offPenStepSample: offPenStep.slice(0, 5),
|
||||
};
|
||||
});
|
||||
const b64 = svgPng.split(",")[1];
|
||||
const b64 = result.png.split(",")[1];
|
||||
writeFileSync(join(OUT, "print-svg.png"), Buffer.from(b64, "base64"));
|
||||
console.log("Druck-SVG → PNG:", join(OUT, "print-svg.png"));
|
||||
|
||||
console.log("\n=== SCHRAFFUR / STIFTSTUFEN (Print-SVG) ===");
|
||||
console.log(`Schraffur-Musterlinien (widthScreen-Polylinien) in der Szene: ${result.hatchRunCount}`);
|
||||
console.log(`Gestrichelte/-gezeichnete Elemente mit stroke-width im Print-SVG: ${result.strokedElementCount}`);
|
||||
console.log(
|
||||
`Strichbreiten ausserhalb der ISO-Stiftstufen (${PEN_STEPS_LOG}): ${result.offPenStepCount}` +
|
||||
(result.offPenStepCount ? ` (Beispiele: ${result.offPenStepSample.join(", ")})` : ""),
|
||||
);
|
||||
console.log(
|
||||
`→ Schraffur vorhanden UND alle Strichbreiten auf Stiftstufen? ${
|
||||
result.hatchRunCount > 0 && result.offPenStepCount === 0 ? "JA" : "NEIN"
|
||||
}`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("(SVG-Render übersprungen:", e.message + ")");
|
||||
}
|
||||
|
||||
Generated
+408
-368
File diff suppressed because it is too large
Load Diff
+12
-8
@@ -1,10 +1,14 @@
|
||||
[workspace]
|
||||
members = [".", "geometry"]
|
||||
# render2d/render3d sind eigenstaendige Crates (jeweils eigener [workspace]), damit
|
||||
# sie headless ohne Tauri-Toolchain baubar/testbar bleiben. Sie liegen zwar im
|
||||
# src-tauri-Baum, gehoeren aber NICHT zum cad-tauri-Workspace — hier ausschliessen,
|
||||
# damit `cad-tauri` sie dennoch per Pfad als (optionale) Abhaengigkeit nutzen kann.
|
||||
exclude = ["render2d", "render3d"]
|
||||
members = ["."]
|
||||
# render2d/render3d/geometry sind eigenstaendige Crates, damit sie headless ohne
|
||||
# Tauri-Toolchain UND per wasm-pack (Feature "web") zu WASM baubar bleiben. Sie
|
||||
# liegen zwar im src-tauri-Baum, gehoeren aber NICHT zum cad-tauri-Workspace —
|
||||
# hier ausschliessen, damit `cad-tauri` sie dennoch per Pfad als Abhaengigkeit
|
||||
# nutzen kann. geometry: neu ausgeschlossen (war Member), seit es zu WASM gebaut
|
||||
# wird und das TS-Frontend die Joins direkt per WASM aufruft (statt TS-Duplikat).
|
||||
# kernel2d: analog — eigenstaendiger 2D-Geometrie-Kern (Port von kernel2d.ts),
|
||||
# per wasm-pack (Feature "web") zu WASM gebaut, ausserhalb des cad-tauri-Workspace.
|
||||
exclude = ["render2d", "render3d", "geometry", "kernel2d", "dwgimport"]
|
||||
|
||||
[package]
|
||||
name = "cad-tauri"
|
||||
@@ -47,5 +51,5 @@ render2d = { path = "render2d", features = ["window"], optional = true }
|
||||
render3d = { path = "render3d", features = ["window"], optional = true }
|
||||
# Versionen an render2d gekoppelt (identische raw-window-handle 0.6-Kette).
|
||||
winit = { version = "0.30", optional = true }
|
||||
wgpu = { version = "22", optional = true }
|
||||
pollster = { version = "0.3", optional = true }
|
||||
wgpu = { version = "29", optional = true }
|
||||
pollster = { version = "0.4", optional = true }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user