Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3402e6ac96 | |||
| b4c4a2cc6b | |||
| 382771b2e7 | |||
| a8e491e534 | |||
| 76029dbf68 | |||
| 4b02e40238 | |||
| ce6bd26ff7 | |||
| 54211b1443 | |||
| c8a4188618 | |||
| 7ca5197b6b | |||
| a010584d9b | |||
| e6d061cc91 | |||
| 0161b0231d | |||
| 34317e53f4 | |||
| 27e41077b1 | |||
| a37b1c0cc9 | |||
| 4eb635cc89 | |||
| 4734c04ec0 | |||
| 2f971a54c0 | |||
| 8cfe6c2521 | |||
| 665cbce1f9 | |||
| 6eead6d493 | |||
| 7b9f72e79f | |||
| cd0f976b4f | |||
| 919bc67bfa | |||
| ec5cbf6fa0 | |||
| f1b802299f | |||
| 0337422ae6 | |||
| d39244989b | |||
| 01424c1e22 | |||
| 1811e82005 | |||
| 0a1aaedef8 | |||
| 483ad3f278 | |||
| d27622d581 | |||
| f315c80748 | |||
| bda256d3a4 | |||
| c93b168f5c | |||
| a971cd610b | |||
| 48bbcaed1e | |||
| 6d4b4e66c1 | |||
| 27bb5a42ec | |||
| 8fd8987b70 | |||
| 7b3b597abc | |||
| 4a08161e4b | |||
| 39236c65c7 | |||
| b9dc1838c7 |
+1
-1
@@ -57,7 +57,7 @@ Rendern angewandt, nie in die Geometrie eingebacken.
|
|||||||
|
|
||||||
## Arbeitsweise (für Beiträge)
|
## Arbeitsweise (für Beiträge)
|
||||||
|
|
||||||
- Substanzielle, mehrstufige Arbeit schrittweise in isolierten Schritten angehen.
|
- Substanzielle, mehrstufige Arbeit an **Subagenten** delegieren, wo möglich.
|
||||||
- Änderungen verifizieren: `npx tsc -b`, `npm run build`, und Screenshot via
|
- Ä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
|
`node scripts/probe.mjs` (schreibt `scripts/probe.png`) bzw. `probe-ff*.mjs` für
|
||||||
Firefox-Fälle. Screenshot ansehen und Geometrie visuell prüfen.
|
Firefox-Fälle. Screenshot ansehen und Geometrie visuell prüfen.
|
||||||
|
|||||||
+942
-65
File diff suppressed because it is too large
Load Diff
-142
@@ -1,142 +0,0 @@
|
|||||||
# 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
@@ -1,124 +0,0 @@
|
|||||||
# 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,43 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# 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 @@
|
|||||||
# ARCHITEKTUR-BRIEFING: Tauri + wgpu (Korrigiert)
|
# HAUPTINSTANZ-BRIEFING: Tauri + wgpu (Korrigiert)
|
||||||
|
|
||||||
**Stand:** 2026-07-01 — **KORREKTUR** (vorherige Dokumente waren unvollständig)
|
**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
|
## Nächste Schritte (für Hauptinstanz)
|
||||||
|
|
||||||
1. **Lesen:** `docs/design/tauri-migration-plan.md` (technisch, konkret)
|
1. **Lesen:** `docs/design/tauri-migration-plan.md` (technisch, konkret)
|
||||||
2. **Spawn:** vier Agents (parallel, unabhängig)
|
2. **Spawn:** vier Agents (parallel, unabhängig)
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,511 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# 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)
|
## 7. Gate (Pflicht)
|
||||||
|
|
||||||
`rm -f tsconfig.tsbuildinfo && npx tsc -b` grün, `npm run build` grün, keine
|
`rm -f tsconfig.tsbuildinfo && npx tsc -b` grün, `npm run build` grün, keine
|
||||||
Trace-Scan (grep auf Co-Authored/Generated), Boot-Probe
|
AI-Spuren (grep auf Claude/Anthropic/AI/Generated/Co-Authored), Boot-Probe
|
||||||
(`node scripts/probe.mjs`) ohne Konsolenfehler, Screenshot der Oberleiste.
|
(`node scripts/probe.mjs`) ohne Konsolenfehler, Screenshot der Oberleiste.
|
||||||
KEIN Commit.
|
KEIN Commit.
|
||||||
|
|||||||
@@ -1,403 +0,0 @@
|
|||||||
# 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,12 +1,13 @@
|
|||||||
# Briefing — Nativer wgpu-2D-Renderer (parallele Instanz)
|
# Briefing — Nativer wgpu-2D-Renderer (parallele Instanz)
|
||||||
|
|
||||||
> Für den Entwickler, der den nativen GPU-Renderer baut. Isoliert vom
|
> Für die Instanz/den Agenten, der den nativen GPU-Renderer baut. Isoliert vom
|
||||||
> Web-Renderer; koordiniert über dieses Dokument.
|
> Web-Renderer; koordiniert über dieses Dokument.
|
||||||
|
|
||||||
## COMMIT-REGEL (verbindlich)
|
## COMMIT-REGEL (verbindlich)
|
||||||
Dieses Repo darf **keinerlei Fremd-Tool-Hinweise** enthalten — nicht im Code,
|
Dieses Repo darf **keinerlei Hinweise auf KI-Werkzeuge** enthalten — nicht im Code,
|
||||||
in Kommentaren oder in der Git-Historie. Kommentare deutsch, Identifier englisch.
|
in Kommentaren oder in der Git-Historie. Keine `Co-Authored-By`-Trailer, keine
|
||||||
**Kein Commit ohne Absprache.**
|
„Generated with"-Zeilen, keine Modell-/Agenten-Namen. Kommentare deutsch,
|
||||||
|
Identifier englisch. **Kein Commit ohne Ansage der Hauptinstanz.**
|
||||||
|
|
||||||
## Warum
|
## Warum
|
||||||
Der 2D-Plan wird aktuell in einem WebGL-Canvas gerendert (Web-Stack). In Chromium
|
Der 2D-Plan wird aktuell in einem WebGL-Canvas gerendert (Web-Stack). In Chromium
|
||||||
@@ -71,9 +72,9 @@ per Matrix-Uniform macht. Fenster-Integration in Tauri ist der zweite, separate
|
|||||||
- **Eigener Branch** (z. B. `feature/wgpu-renderer`), NICHT auf `master`/
|
- **Eigener Branch** (z. B. `feature/wgpu-renderer`), NICHT auf `master`/
|
||||||
`feature/parametric-walls` committen. Isolierter Worktree.
|
`feature/parametric-walls` committen. Isolierter Worktree.
|
||||||
- **Nur** `src-tauri/` + neue Rust-Module + `docs/`. **NICHT** `src/App.tsx`,
|
- **Nur** `src-tauri/` + neue Rust-Module + `docs/`. **NICHT** `src/App.tsx`,
|
||||||
`src/plan/*`, `types.ts` anfassen (dort laufen parallel Features).
|
`src/plan/*`, `types.ts` anfassen (die Hauptinstanz arbeitet dort an Features).
|
||||||
- Der Web-WebGL-Renderer bleibt bestehen (Browser-Lauffähigkeit + Referenz).
|
- Der Web-WebGL-Renderer bleibt bestehen (Browser-Lauffähigkeit + Referenz).
|
||||||
- Ergebnisse/Diffs zurückliefern; Projektleitung committet.
|
- Ergebnisse/Diffs zurückliefern; die Hauptinstanz committet und pflegt das Memory.
|
||||||
|
|
||||||
## Gates
|
## Gates
|
||||||
`cargo check`/`cargo test`/`cargo build` grün. Trace-Scan sauber (COMMIT-REGEL).
|
`cargo check`/`cargo test`/`cargo build` grün. Trace-Scan sauber (COMMIT-REGEL).
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
> Port-Plan) und M1 (entkoppelter Standalone-Spike). Noch NICHT die Migration.
|
> Port-Plan) und M1 (entkoppelter Standalone-Spike). Noch NICHT die Migration.
|
||||||
|
|
||||||
## Commit-/Spuren-Regel
|
## Commit-/Spuren-Regel
|
||||||
Wie im ganzen Repo: keine Fremd-Tool-Hinweise im Code, in Kommentaren oder der Historie. Kommentare deutsch, Identifier englisch. Kein Commit ohne Absprache.
|
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.
|
||||||
|
|
||||||
## Warum
|
## Warum
|
||||||
Der 3D-View laeuft heute als three.js/WebGL im Tauri-Webview (WebKitGTK). Wie beim
|
Der 3D-View laeuft heute als three.js/WebGL im Tauri-Webview (WebKitGTK). Wie beim
|
||||||
|
|||||||
+1
-4
@@ -16,10 +16,7 @@
|
|||||||
"shell": "scripts/chromium-shell.sh",
|
"shell": "scripts/chromium-shell.sh",
|
||||||
"electron": "scripts/electron-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: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: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": {
|
"dependencies": {
|
||||||
"@mlightcad/libredwg-web": "^0.7.7",
|
"@mlightcad/libredwg-web": "^0.7.7",
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -1,93 +0,0 @@
|
|||||||
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.
@@ -4,14 +4,10 @@
|
|||||||
// (render2d-WASM-Engine, ?engine=wasm) zuverlässig läuft. Kein Rust-Backend
|
// (render2d-WASM-Engine, ?engine=wasm) zuverlässig läuft. Kein Rust-Backend
|
||||||
// nötig: compute_joins hat einen TS-Fallback (src/compute/index.ts).
|
// nötig: compute_joins hat einen TS-Fallback (src/compute/index.ts).
|
||||||
|
|
||||||
const { app, BrowserWindow, Menu, ipcMain } = require("electron");
|
const { app, BrowserWindow, Menu } = require("electron");
|
||||||
const path = require("node:path");
|
|
||||||
|
|
||||||
// Randloses App-Fenster wie chromium-shell.sh (--app=…): keine native
|
// Randloses App-Fenster wie chromium-shell.sh (--app=…): keine native
|
||||||
// Menüleiste (File/Edit/View/Window), kein Fensterrahmen. Minimieren/
|
// Menüleiste (File/Edit/View/Window), kein Fensterrahmen.
|
||||||
// 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);
|
Menu.setApplicationMenu(null);
|
||||||
|
|
||||||
// Gleiche Flags wie scripts/chromium-shell.sh — WebGPU liegt unter Linux
|
// Gleiche Flags wie scripts/chromium-shell.sh — WebGPU liegt unter Linux
|
||||||
@@ -31,16 +27,9 @@ function createWindow() {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
nodeIntegration: false,
|
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) {
|
if (isDev) {
|
||||||
win.loadURL(DEV_URL);
|
win.loadURL(DEV_URL);
|
||||||
} else {
|
} else {
|
||||||
@@ -48,24 +37,6 @@ function createWindow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.whenReady().then(createWindow);
|
||||||
|
|
||||||
app.on("window-all-closed", () => {
|
app.on("window-all-closed", () => {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Generated
-2
@@ -1488,10 +1488,8 @@ dependencies = [
|
|||||||
name = "geometry"
|
name = "geometry"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+6
-10
@@ -1,14 +1,10 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["."]
|
members = [".", "geometry"]
|
||||||
# render2d/render3d/geometry sind eigenstaendige Crates, damit sie headless ohne
|
# render2d/render3d sind eigenstaendige Crates (jeweils eigener [workspace]), damit
|
||||||
# Tauri-Toolchain UND per wasm-pack (Feature "web") zu WASM baubar bleiben. Sie
|
# sie headless ohne Tauri-Toolchain baubar/testbar bleiben. Sie liegen zwar im
|
||||||
# liegen zwar im src-tauri-Baum, gehoeren aber NICHT zum cad-tauri-Workspace —
|
# src-tauri-Baum, gehoeren aber NICHT zum cad-tauri-Workspace — hier ausschliessen,
|
||||||
# hier ausschliessen, damit `cad-tauri` sie dennoch per Pfad als Abhaengigkeit
|
# damit `cad-tauri` sie dennoch per Pfad als (optionale) Abhaengigkeit nutzen kann.
|
||||||
# nutzen kann. geometry: neu ausgeschlossen (war Member), seit es zu WASM gebaut
|
exclude = ["render2d", "render3d"]
|
||||||
# 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]
|
[package]
|
||||||
name = "cad-tauri"
|
name = "cad-tauri"
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
|
||||||
"identifier": "default",
|
|
||||||
"description": "Basis-Permissions + Fenstersteuerung fuer die randlose (decorations:false) Titelleiste: Minimieren/Maximieren/Schliessen + Ziehen der eigenen Titelleiste (data-tauri-drag-region).",
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
"core:window:allow-minimize",
|
|
||||||
"core:window:allow-maximize",
|
|
||||||
"core:window:allow-unmaximize",
|
|
||||||
"core:window:allow-toggle-maximize",
|
|
||||||
"core:window:allow-is-maximized",
|
|
||||||
"core:window:allow-close",
|
|
||||||
"core:window:allow-start-dragging"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Generated
-578
@@ -1,578 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "acadrust"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "153498e7c9b058326ff0fae11bc9fd870cf2ea0aa9111875d03625fa9018c218"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
"anyhow",
|
|
||||||
"bitflags",
|
|
||||||
"byteorder",
|
|
||||||
"encoding_rs",
|
|
||||||
"flate2",
|
|
||||||
"indexmap",
|
|
||||||
"itoa",
|
|
||||||
"nalgebra",
|
|
||||||
"nom",
|
|
||||||
"once_cell",
|
|
||||||
"ryu",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "adler2"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.8.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"getrandom",
|
|
||||||
"once_cell",
|
|
||||||
"version_check",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.103"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "approx"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.20.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytemuck"
|
|
||||||
version = "1.25.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_error_panic_hook"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc32fast"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dwgimport"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"acadrust",
|
|
||||||
"console_error_panic_hook",
|
|
||||||
"getrandom",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_rs"
|
|
||||||
version = "0.8.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "flate2"
|
|
||||||
version = "1.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|
||||||
dependencies = [
|
|
||||||
"crc32fast",
|
|
||||||
"miniz_oxide",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
|
||||||
"libc",
|
|
||||||
"r-efi",
|
|
||||||
"wasip2",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.17.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "js-sys"
|
|
||||||
version = "0.3.103"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.186"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "matrixmultiply"
|
|
||||||
version = "0.3.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"rawpointer",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minimal-lexical"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "miniz_oxide"
|
|
||||||
version = "0.8.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
|
||||||
dependencies = [
|
|
||||||
"adler2",
|
|
||||||
"simd-adler32",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nalgebra"
|
|
||||||
version = "0.32.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4"
|
|
||||||
dependencies = [
|
|
||||||
"approx",
|
|
||||||
"matrixmultiply",
|
|
||||||
"nalgebra-macros",
|
|
||||||
"num-complex",
|
|
||||||
"num-rational",
|
|
||||||
"num-traits",
|
|
||||||
"simba",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nalgebra-macros"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "7.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"minimal-lexical",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-complex"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-integer"
|
|
||||||
version = "0.1.46"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-rational"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
|
||||||
dependencies = [
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.106"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.46"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "r-efi"
|
|
||||||
version = "5.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rawpointer"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "safe_arch"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.150"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "simba"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae"
|
|
||||||
dependencies = [
|
|
||||||
"approx",
|
|
||||||
"num-complex",
|
|
||||||
"num-traits",
|
|
||||||
"paste",
|
|
||||||
"wide",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "simd-adler32"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.118"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typenum"
|
|
||||||
version = "1.20.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasip2"
|
|
||||||
version = "1.0.4+wasi-0.2.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wide"
|
|
||||||
version = "0.7.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"safe_arch",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen"
|
|
||||||
version = "0.57.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.8.52"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.8.52"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Eigener leerer Workspace-Block: entkoppelt dwgimport vollstaendig vom
|
|
||||||
# cad-tauri-Workspace (src-tauri/Cargo.toml). Muster: kernel2d/render2d.
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[package]
|
|
||||||
name = "dwgimport"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "DXF-Import-Probe: beweist, dass acadrust (MPL-2.0) aus einem Byte-Buffer parst und nach wasm32-unknown-unknown kompiliert."
|
|
||||||
|
|
||||||
# cdylib: fuer wasm-pack (Feature \"web\"). rlib: fuer cargo test + Pfad-Dep.
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# Standard: headless, kein WASM-Overhead — per `cargo test` pruefbar.
|
|
||||||
default = []
|
|
||||||
# Browser-Bindings: wasm-bindgen-Fassade; nur fuer wasm32-unknown-unknown.
|
|
||||||
web = ["dep:wasm-bindgen", "dep:serde_json", "dep:console_error_panic_hook"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# DXF/DWG-Parser (MPL-2.0), von crates.io.
|
|
||||||
acadrust = "0.4"
|
|
||||||
|
|
||||||
# getrandom wird transitiv via ahash→acadrust gezogen. Fuer wasm32 muss das
|
|
||||||
# Feature "wasm_js" explizit aktiviert werden (sonst Compile-Fehler).
|
|
||||||
# Direkte Dep mit Feature-Aktivierung unifiziert das Flag fuer alle Abhaengigen.
|
|
||||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
|
||||||
|
|
||||||
# Serialisierung: immer vorhanden (fuer den headless parse_dxf_summary-Kern).
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = { version = "1", optional = true }
|
|
||||||
|
|
||||||
# WASM-Bindings — nur im Feature "web" aktiv.
|
|
||||||
wasm-bindgen = { version = "0.2", optional = true }
|
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
// dwgimport — SPIKE: beweist, dass acadrust (MPL-2.0) aus einem Byte-Buffer
|
|
||||||
// ein DXF parst und nach wasm32-unknown-unknown kompiliert.
|
|
||||||
//
|
|
||||||
// Aufbau:
|
|
||||||
// - `parse_dxf_summary`: headless, feature-frei testbar.
|
|
||||||
// - `#[cfg(feature="web")]` wasm-bindgen-Fassade darueber.
|
|
||||||
//
|
|
||||||
// API-Stuetzpunkt acadrust:
|
|
||||||
// `DxfReader::from_reader<R: Read + Seek + 'static>(reader: R)`
|
|
||||||
// → std::io::Cursor<&[u8]> implementiert Read + Seek → WASM-tauglich.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use acadrust::DxfReader;
|
|
||||||
|
|
||||||
// serde_json nur bei Feature "web" als optionale Dep, aber serde ist immer da.
|
|
||||||
// Fuer den headless-Kern bauen wir das JSON mit einem einfachen Format-String,
|
|
||||||
// damit keine Compile-Time-Dep auf serde_json im Default-Feature noetig ist.
|
|
||||||
|
|
||||||
/// Parst ein DXF aus einem Byte-Slice und gibt ein JSON-Summary zurueck.
|
|
||||||
///
|
|
||||||
/// Rueckgabe-JSON-Schema:
|
|
||||||
/// ```json
|
|
||||||
/// {
|
|
||||||
/// "version": "AC1015",
|
|
||||||
/// "entityCount": 5,
|
|
||||||
/// "entityTypeCounts": { "LINE": 3, "CIRCLE": 2 },
|
|
||||||
/// "layerNames": ["0", "Wand"]
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Fehler: String mit Fehlerbeschreibung.
|
|
||||||
pub fn parse_dxf_summary(bytes: &[u8]) -> Result<String, String> {
|
|
||||||
// Cursor<Vec<u8>> implementiert Read + Seek + 'static — kein std::fs noetig.
|
|
||||||
// (from_reader verlangt R: 'static; Cursor<&[u8]> wuerde die Lifetime verletzen.)
|
|
||||||
let cursor = Cursor::new(bytes.to_vec());
|
|
||||||
let reader = DxfReader::from_reader(cursor)
|
|
||||||
.map_err(|e| format!("DxfReader::from_reader fehlgeschlagen: {e}"))?;
|
|
||||||
|
|
||||||
let doc = reader
|
|
||||||
.read()
|
|
||||||
.map_err(|e| format!("DxfReader::read fehlgeschlagen: {e}"))?;
|
|
||||||
|
|
||||||
// Versions-String aus dem Enum (Debug-Format, z. B. "AC1015").
|
|
||||||
let version = format!("{:?}", doc.version);
|
|
||||||
|
|
||||||
// Entitaeten zaehlen und nach Typ aufschluesseln.
|
|
||||||
let mut type_counts: HashMap<&str, usize> = HashMap::new();
|
|
||||||
for entity in doc.entities() {
|
|
||||||
let typ = entity.as_entity().entity_type();
|
|
||||||
*type_counts.entry(typ).or_insert(0) += 1;
|
|
||||||
}
|
|
||||||
let entity_count = type_counts.values().sum::<usize>();
|
|
||||||
|
|
||||||
// Layer-Namen aus der Layer-Tabelle.
|
|
||||||
let mut layer_names: Vec<String> = doc
|
|
||||||
.layers
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.name.clone())
|
|
||||||
.collect();
|
|
||||||
layer_names.sort();
|
|
||||||
|
|
||||||
// JSON-Serialisierung ohne optionale serde_json-Dep im Default-Feature.
|
|
||||||
// Fuer den headless-Test reicht ein sauberer Format-String.
|
|
||||||
let type_counts_json = {
|
|
||||||
let mut pairs: Vec<String> = type_counts
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| format!(" \"{k}\": {v}"))
|
|
||||||
.collect();
|
|
||||||
pairs.sort(); // deterministisch fuer Tests
|
|
||||||
format!("{{\n{}\n }}", pairs.join(",\n"))
|
|
||||||
};
|
|
||||||
|
|
||||||
let layer_names_json = {
|
|
||||||
let quoted: Vec<String> = layer_names
|
|
||||||
.iter()
|
|
||||||
.map(|n| format!("\"{}\"", n.replace('"', "\\\"")))
|
|
||||||
.collect();
|
|
||||||
format!("[{}]", quoted.join(", "))
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(format!(
|
|
||||||
"{{\n \"version\": \"{version}\",\n \"entityCount\": {entity_count},\n \"entityTypeCounts\": {type_counts_json},\n \"layerNames\": {layer_names_json}\n}}"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// WASM-Fassade (nur bei Feature "web" compiliert)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
mod wasm {
|
|
||||||
use super::parse_dxf_summary;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
/// Initialisiert den Panic-Hook fuer bessere Fehlermeldungen im Browser.
|
|
||||||
#[wasm_bindgen(start)]
|
|
||||||
pub fn init_panic_hook() {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WASM-Fassade: nimmt einen Byte-Buffer (Uint8Array aus JS) und gibt ein
|
|
||||||
/// JSON-String zurueck, das das DXF-Summary beschreibt.
|
|
||||||
///
|
|
||||||
/// JS-Aufruf:
|
|
||||||
/// ```js
|
|
||||||
/// import init, { parse_dxf_summary_json } from './pkgDwgImport/dwgimport.js';
|
|
||||||
/// await init();
|
|
||||||
/// const json = parse_dxf_summary_json(new Uint8Array(buffer));
|
|
||||||
/// ```
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn parse_dxf_summary_json(bytes: &[u8]) -> Result<String, JsValue> {
|
|
||||||
parse_dxf_summary(bytes)
|
|
||||||
.map_err(|e| JsValue::from_str(&e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Tests
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Minimales ASCII-DXF mit 3 LINE- und 2 CIRCLE-Entitaeten auf Layer "WAND"
|
|
||||||
/// und Layer "0". Wird direkt als Byte-Slice eingebettet — kein Datei-IO.
|
|
||||||
const MINI_DXF: &str = r#" 0
|
|
||||||
SECTION
|
|
||||||
2
|
|
||||||
HEADER
|
|
||||||
9
|
|
||||||
$ACADVER
|
|
||||||
1
|
|
||||||
AC1015
|
|
||||||
0
|
|
||||||
ENDSEC
|
|
||||||
0
|
|
||||||
SECTION
|
|
||||||
2
|
|
||||||
TABLES
|
|
||||||
0
|
|
||||||
TABLE
|
|
||||||
2
|
|
||||||
LAYER
|
|
||||||
70
|
|
||||||
2
|
|
||||||
0
|
|
||||||
LAYER
|
|
||||||
2
|
|
||||||
0
|
|
||||||
70
|
|
||||||
0
|
|
||||||
62
|
|
||||||
7
|
|
||||||
6
|
|
||||||
Continuous
|
|
||||||
0
|
|
||||||
LAYER
|
|
||||||
2
|
|
||||||
WAND
|
|
||||||
70
|
|
||||||
0
|
|
||||||
62
|
|
||||||
3
|
|
||||||
6
|
|
||||||
Continuous
|
|
||||||
0
|
|
||||||
ENDTAB
|
|
||||||
0
|
|
||||||
ENDSEC
|
|
||||||
0
|
|
||||||
SECTION
|
|
||||||
2
|
|
||||||
ENTITIES
|
|
||||||
0
|
|
||||||
LINE
|
|
||||||
8
|
|
||||||
WAND
|
|
||||||
10
|
|
||||||
0.0
|
|
||||||
20
|
|
||||||
0.0
|
|
||||||
30
|
|
||||||
0.0
|
|
||||||
11
|
|
||||||
100.0
|
|
||||||
21
|
|
||||||
0.0
|
|
||||||
31
|
|
||||||
0.0
|
|
||||||
0
|
|
||||||
LINE
|
|
||||||
8
|
|
||||||
WAND
|
|
||||||
10
|
|
||||||
100.0
|
|
||||||
20
|
|
||||||
0.0
|
|
||||||
30
|
|
||||||
0.0
|
|
||||||
11
|
|
||||||
100.0
|
|
||||||
21
|
|
||||||
50.0
|
|
||||||
31
|
|
||||||
0.0
|
|
||||||
0
|
|
||||||
LINE
|
|
||||||
8
|
|
||||||
0
|
|
||||||
10
|
|
||||||
100.0
|
|
||||||
20
|
|
||||||
50.0
|
|
||||||
30
|
|
||||||
0.0
|
|
||||||
11
|
|
||||||
0.0
|
|
||||||
21
|
|
||||||
0.0
|
|
||||||
31
|
|
||||||
0.0
|
|
||||||
0
|
|
||||||
CIRCLE
|
|
||||||
8
|
|
||||||
WAND
|
|
||||||
10
|
|
||||||
50.0
|
|
||||||
20
|
|
||||||
25.0
|
|
||||||
30
|
|
||||||
0.0
|
|
||||||
40
|
|
||||||
10.0
|
|
||||||
0
|
|
||||||
CIRCLE
|
|
||||||
8
|
|
||||||
0
|
|
||||||
10
|
|
||||||
20.0
|
|
||||||
20
|
|
||||||
10.0
|
|
||||||
30
|
|
||||||
0.0
|
|
||||||
40
|
|
||||||
5.0
|
|
||||||
0
|
|
||||||
ENDSEC
|
|
||||||
0
|
|
||||||
EOF
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_mini_dxf() {
|
|
||||||
let result = parse_dxf_summary(MINI_DXF.as_bytes());
|
|
||||||
assert!(result.is_ok(), "parse_dxf_summary schlug fehl: {:?}", result);
|
|
||||||
|
|
||||||
let json = result.unwrap();
|
|
||||||
println!("DXF-Summary:\n{json}");
|
|
||||||
|
|
||||||
// Version muss AC1015 sein.
|
|
||||||
assert!(json.contains("AC1015"), "Version AC1015 nicht im JSON: {json}");
|
|
||||||
|
|
||||||
// Genau 5 Entitaeten: 3 LINE + 2 CIRCLE.
|
|
||||||
assert!(
|
|
||||||
json.contains("\"entityCount\": 5"),
|
|
||||||
"entityCount soll 5 sein: {json}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
json.contains("\"LINE\": 3"),
|
|
||||||
"LINE-Zaehler soll 3 sein: {json}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
json.contains("\"CIRCLE\": 2"),
|
|
||||||
"CIRCLE-Zaehler soll 2 sein: {json}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Layer "WAND" und "0" muessen erscheinen.
|
|
||||||
assert!(json.contains("\"WAND\""), "Layer WAND fehlt: {json}");
|
|
||||||
assert!(json.contains("\"0\""), "Layer 0 fehlt: {json}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,28 +2,9 @@
|
|||||||
name = "geometry"
|
name = "geometry"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Serde-only Geometrie-Kern (Wand-Verschneidungen/Joins) — headless und per wasm-pack (Feature \"web\") zu WASM baubar; Single Source of Truth fuer TS + nativen Host."
|
|
||||||
|
|
||||||
# cdylib: von wasm-pack (Feature "web") fuer das .wasm-Modul benoetigt. rlib:
|
|
||||||
# damit die Crate weiterhin als Pfad-Abhaengigkeit (cad-tauri) und im Test-Build
|
|
||||||
# nutzbar bleibt. Der cdylib-Artefakt-Build auf nativen Zielen ist harmlos (leere
|
|
||||||
# Export-Oberflaeche ohne Feature "web"). Muster: render2d/render3d.
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# Standard: reine serde-Geometrie, headless per `cargo test` pruefbar.
|
|
||||||
default = []
|
|
||||||
# Browser-Bindings: dieselbe Geometrie hinter einer wasm-bindgen-Fassade
|
|
||||||
# (`compute_joins_json`), aus TS via wasm-pack aufgerufen. Baut nur fuer
|
|
||||||
# target wasm32-unknown-unknown sinnvoll. Muster: render3d/Cargo.toml.
|
|
||||||
web = ["dep:wasm-bindgen", "dep:serde_json", "dep:console_error_panic_hook"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
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 }
|
|
||||||
|
|
||||||
# Nur fuers Paritaets-Beispiel (examples/parity.rs) — JSON von stdin lesen.
|
# Nur fuers Paritaets-Beispiel (examples/parity.rs) — JSON von stdin lesen.
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
+35
-735
@@ -9,20 +9,6 @@
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Browser-Fassade (Feature "web"): JSON rein/raus, damit das TS-Frontend die
|
|
||||||
/// EINE Rust-Join-Implementierung per wasm-pack aufrufen kann (Single Source of
|
|
||||||
/// Truth statt TS-Duplikat). `input_json` = serialisiertes `JoinInput`, Rueckgabe
|
|
||||||
/// = serialisiertes `Vec<WallCuts>`.
|
|
||||||
#[cfg(feature = "web")]
|
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
|
||||||
pub fn compute_joins_json(input_json: &str) -> Result<String, wasm_bindgen::JsValue> {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
let input: JoinInput = serde_json::from_str(input_json)
|
|
||||||
.map_err(|e| wasm_bindgen::JsValue::from_str(&e.to_string()))?;
|
|
||||||
let out = compute_joins(input);
|
|
||||||
serde_json::to_string(&out).map_err(|e| wasm_bindgen::JsValue::from_str(&e.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Clone, Copy)]
|
||||||
pub struct Vec2 {
|
pub struct Vec2 {
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
@@ -36,21 +22,6 @@ pub struct Line {
|
|||||||
pub dir: Vec2,
|
pub dir: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Eine Schicht einer Wand, bereits aus dem WallType aufgeloest (Komponenten-Id
|
|
||||||
/// + Prioritaet + Dicke) -- fuer die materialbewusste T-Stoss-Erweiterung
|
|
||||||
/// (siehe `resolve_join_priority`/`apply_layer_cuts`). ADDITIV: fehlt der Key
|
|
||||||
/// `layers` in der JSON-Eingabe, liefert `#[serde(default)]` eine leere Liste,
|
|
||||||
/// sodass die bestehende Paritaets-Eingabe (ohne Schichten) unveraendert
|
|
||||||
/// funktioniert.
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct LayerInput {
|
|
||||||
#[serde(rename = "componentId")]
|
|
||||||
pub component_id: String,
|
|
||||||
#[serde(rename = "joinPriority")]
|
|
||||||
pub join_priority: f64,
|
|
||||||
pub thickness: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct WallInput {
|
pub struct WallInput {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -59,8 +30,6 @@ pub struct WallInput {
|
|||||||
pub thickness: f64,
|
pub thickness: f64,
|
||||||
#[serde(rename = "referenceOffset")]
|
#[serde(rename = "referenceOffset")]
|
||||||
pub reference_offset: f64,
|
pub reference_offset: f64,
|
||||||
#[serde(default)]
|
|
||||||
pub layers: Vec<LayerInput>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -68,33 +37,6 @@ pub struct JoinInput {
|
|||||||
pub walls: Vec<WallInput>,
|
pub walls: Vec<WallInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pro-Schicht-Override der Nahflaechen-Cuts (materialbewusster T-Stoss).
|
|
||||||
/// Index = Schicht-Index in `WallInput.layers` der Abzweig-Wand. Spiegelt
|
|
||||||
/// `LayerCuts` aus `model/joins.ts` 1:1.
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct LayerCuts {
|
|
||||||
pub start: Vec<Option<Line>>,
|
|
||||||
pub end: Vec<Option<Line>>,
|
|
||||||
#[serde(rename = "startSide")]
|
|
||||||
pub start_side: Vec<Option<Line>>,
|
|
||||||
#[serde(rename = "endSide")]
|
|
||||||
pub end_side: Vec<Option<Line>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Aussparung der DURCHGANGSWAND am materialbewussten T-Stoss -- Rust-Spiegel
|
|
||||||
/// von `SpanCutout` aus `model/joins.ts`. `from`/`to` = Achsen-Intervall (Meter
|
|
||||||
/// ab `wall.start`), `offA`/`offB` = Quer-Offsetbereich (Nah-Putz-Zone) in der
|
|
||||||
/// Offset-Konvention der Schicht-Baender; `offA` ist stets die Nahflaeche.
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy)]
|
|
||||||
pub struct SpanCutout {
|
|
||||||
pub from: f64,
|
|
||||||
pub to: f64,
|
|
||||||
#[serde(rename = "offA")]
|
|
||||||
pub off_a: f64,
|
|
||||||
#[serde(rename = "offB")]
|
|
||||||
pub off_b: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schnittlinien einer Wand an ihren beiden Achsenden.
|
/// Schnittlinien einer Wand an ihren beiden Achsenden.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct WallCuts {
|
pub struct WallCuts {
|
||||||
@@ -104,212 +46,6 @@ pub struct WallCuts {
|
|||||||
pub start_cut: Option<Line>,
|
pub start_cut: Option<Line>,
|
||||||
#[serde(rename = "endCut")]
|
#[serde(rename = "endCut")]
|
||||||
pub end_cut: Option<Line>,
|
pub end_cut: Option<Line>,
|
||||||
/// ADDITIV: nur gesetzt, wenn an einem T-Stoss mindestens eine Schicht
|
|
||||||
/// materialbewusst verschmilzt (siehe `apply_layer_cuts`). Fehlt sie in der
|
|
||||||
/// Ausgabe (`None` -> von serde bei Serialisierung weggelassen), gilt
|
|
||||||
/// weiterhin `start_cut`/`end_cut` fuer alle Schichten.
|
|
||||||
#[serde(rename = "layerCuts", skip_serializing_if = "Option::is_none")]
|
|
||||||
pub layer_cuts: Option<LayerCuts>,
|
|
||||||
/// ADDITIV (Durchgangswand-Seite): Nah-Putz-Aussparungen, durch die ein
|
|
||||||
/// Abzweig-Kern sticht. Leer -> von serde bei Serialisierung weggelassen
|
|
||||||
/// (`skip_serializing_if`), Wand wird unveraendert gezeichnet.
|
|
||||||
#[serde(rename = "spanCutouts", skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub span_cutouts: Vec<SpanCutout>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verschneidungs-Entscheidung zweier Bauteile am Stoss -- Rust-Spiegel von
|
|
||||||
/// `JoinPriorityResolution`/`resolveJoinPriority` aus `model/joins.ts`.
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum JoinPriorityResolution {
|
|
||||||
Merge,
|
|
||||||
TrimA,
|
|
||||||
TrimB,
|
|
||||||
Coexist,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_join_priority(
|
|
||||||
a_id: &str,
|
|
||||||
a_prio: f64,
|
|
||||||
b_id: &str,
|
|
||||||
b_prio: f64,
|
|
||||||
) -> JoinPriorityResolution {
|
|
||||||
if a_prio == b_prio {
|
|
||||||
if a_id == b_id {
|
|
||||||
JoinPriorityResolution::Merge
|
|
||||||
} else {
|
|
||||||
JoinPriorityResolution::Coexist
|
|
||||||
}
|
|
||||||
} else if a_prio > b_prio {
|
|
||||||
JoinPriorityResolution::TrimB
|
|
||||||
} else {
|
|
||||||
JoinPriorityResolution::TrimA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Materialbewusste Pro-Schicht-Erweiterung eines T-Stoss-Cuts -- Rust-Spiegel
|
|
||||||
/// von `applyLayerCuts` aus `model/joins.ts`. Vergleicht jede Schicht der
|
|
||||||
/// Abzweig-Wand mit dem Rueckgrat (hoechste `joinPriority`) der Durchgangswand;
|
|
||||||
/// verschmilzt sie (gleiches Rueckgrat-Material, oder die Abzweig-Schicht ist
|
|
||||||
/// sogar prioritaerer), bekommt sie einen Cut an der NAHFLAECHE DES RUECKGRAT-
|
|
||||||
/// KERNS der Durchgangswand -- der verschmelzende Kern sticht nur durch den
|
|
||||||
/// Nah-Putz und stoppt am Durchgangs-Kern, statt bis zur Achse durchzulaufen.
|
|
||||||
/// Getrimmte Schichten neben einer verschmelzenden Nachbarschicht bekommen
|
|
||||||
/// zusaetzlich die seitliche L-Linie an eben dieser Rueckgrat-Nahflaeche.
|
|
||||||
/// `sign`/`off_t` = Nahseite bzw. Referenzversatz der Durchgangswand.
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn apply_layer_cuts(
|
|
||||||
result: &mut [WallCuts],
|
|
||||||
branch_wall_idx: usize,
|
|
||||||
branch_end: WallEndKind,
|
|
||||||
branch_layers: &[LayerInput],
|
|
||||||
through_layers: &[LayerInput],
|
|
||||||
face_cut: Line,
|
|
||||||
axis_point: Vec2,
|
|
||||||
through_dir: Vec2,
|
|
||||||
sign: f64,
|
|
||||||
off_t: f64,
|
|
||||||
// Durchgangswand-Aussparung (Phase 1c): Index/Start der Durchgangswand,
|
|
||||||
// Achsrichtung + Referenzversatz des Abzweigs -- fuer die Projektion der
|
|
||||||
// Kernbreite auf die Durchgangsachse.
|
|
||||||
through_wall_idx: usize,
|
|
||||||
through_start: Vec2,
|
|
||||||
branch_dir: Vec2,
|
|
||||||
branch_ref_off: f64,
|
|
||||||
) {
|
|
||||||
if branch_layers.is_empty() || through_layers.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rueckgrat der Durchgangswand: die Schicht mit der hoechsten joinPriority
|
|
||||||
// (Index mitgefuehrt fuer die Nah-Putz-Dicke davor).
|
|
||||||
let mut backbone = &through_layers[0];
|
|
||||||
let mut backbone_idx = 0usize;
|
|
||||||
for (i, l) in through_layers.iter().enumerate() {
|
|
||||||
if l.join_priority > backbone.join_priority {
|
|
||||||
backbone = l;
|
|
||||||
backbone_idx = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = branch_layers.len();
|
|
||||||
let mut merged = vec![false; n];
|
|
||||||
for (i, layer) in branch_layers.iter().enumerate() {
|
|
||||||
let res = resolve_join_priority(
|
|
||||||
&layer.component_id,
|
|
||||||
layer.join_priority,
|
|
||||||
&backbone.component_id,
|
|
||||||
backbone.join_priority,
|
|
||||||
);
|
|
||||||
merged[i] = matches!(res, JoinPriorityResolution::Merge | JoinPriorityResolution::TrimB);
|
|
||||||
}
|
|
||||||
if !merged.iter().any(|&m| m) {
|
|
||||||
return; // keine Materialuebereinstimmung -> Default (kein layer_cuts)
|
|
||||||
}
|
|
||||||
|
|
||||||
let cuts = &mut result[branch_wall_idx];
|
|
||||||
if cuts.layer_cuts.is_none() {
|
|
||||||
cuts.layer_cuts = Some(LayerCuts {
|
|
||||||
start: vec![None; n],
|
|
||||||
end: vec![None; n],
|
|
||||||
start_side: vec![None; n],
|
|
||||||
end_side: vec![None; n],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let lc = cuts.layer_cuts.as_mut().unwrap();
|
|
||||||
if lc.start.len() != n {
|
|
||||||
return; // Schicht-Anzahl passt nicht zusammen (sollte nicht vorkommen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nahflaechen-Cut des verschmelzenden Kerns: von der Durchgangswand-Nah-
|
|
||||||
// flaeche (off_t + sign*t_t/2) um die Nah-Putz-Dicke nach innen versetzt =
|
|
||||||
// die dem Abzweig zugewandte Flaeche des Rueckgrat-Kerns. Nah-Putz = Summe
|
|
||||||
// der Durchgangswand-Schichten zwischen Nahseite (sign) und Rueckgrat: auf
|
|
||||||
// der +nT-Nahseite (sign>0) die Schichten hinter dem Rueckgrat
|
|
||||||
// (Index > backbone_idx), auf der -nT-Nahseite die davor.
|
|
||||||
let t_t: f64 = through_layers.iter().map(|l| l.thickness).sum();
|
|
||||||
let mut near_plaster = 0.0;
|
|
||||||
for (i, l) in through_layers.iter().enumerate() {
|
|
||||||
let near = if sign > 0.0 { i > backbone_idx } else { i < backbone_idx };
|
|
||||||
if near {
|
|
||||||
near_plaster += l.thickness;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let n_t = left_normal(through_dir);
|
|
||||||
let merge_point = add(axis_point, scale(n_t, off_t + sign * (t_t / 2.0 - near_plaster)));
|
|
||||||
let merge_cut = Line { point: merge_point, dir: through_dir };
|
|
||||||
|
|
||||||
// Getrimmte Nachbarschichten schliessen mit ihrer L-Seitenlinie an dieser
|
|
||||||
// Rueckgrat-Nahflaeche ab (nicht an der Wandachse). In einem Block, damit die
|
|
||||||
// mutable Ausleihe von `result[branch_wall_idx]` vor dem Zugriff auf die
|
|
||||||
// Durchgangswand unten endet.
|
|
||||||
// Nah-Putz-Komponenten der Durchgangswand (dieselbe sign-Seite wie
|
|
||||||
// near_plaster oben): ist die getrimmte Abzweig-Schicht materialgleich mit
|
|
||||||
// einer davon, fuellt der Nah-Putz den Abzweig-Putz an dieser Stelle
|
|
||||||
// durchgehend (spanCutout schneidet nur die KERN-Breite aus, nicht die
|
|
||||||
// schmalen Putzflanken) -> Putz-L OHNE Trennnaht, also KEINE L-Seitenlinie.
|
|
||||||
let near_plaster_comps: Vec<&str> = through_layers
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(i, _)| if sign > 0.0 { *i > backbone_idx } else { *i < backbone_idx })
|
|
||||||
.map(|(_, l)| l.component_id.as_str())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
{
|
|
||||||
let side_line = merge_cut;
|
|
||||||
let (face_arr, side_arr) = match branch_end {
|
|
||||||
WallEndKind::Start => (&mut lc.start, &mut lc.start_side),
|
|
||||||
WallEndKind::End => (&mut lc.end, &mut lc.end_side),
|
|
||||||
};
|
|
||||||
for i in 0..n {
|
|
||||||
face_arr[i] = if merged[i] { Some(merge_cut) } else { Some(face_cut) };
|
|
||||||
let adj_merged =
|
|
||||||
!merged[i] && ((i > 0 && merged[i - 1]) || (i + 1 < n && merged[i + 1]));
|
|
||||||
let merges_with_near_plaster =
|
|
||||||
near_plaster_comps.contains(&branch_layers[i].component_id.as_str());
|
|
||||||
side_arr[i] = if adj_merged && !merges_with_near_plaster {
|
|
||||||
Some(side_line)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Durchgangswand-Aussparung (Phase 1c): den Nah-Putz der Durchgangswand ueber
|
|
||||||
// die Breite des durchstechenden Abzweig-Kerns ECHT ausschneiden. Nur noetig,
|
|
||||||
// wenn davor Nah-Putz liegt (near_plaster > 0).
|
|
||||||
if near_plaster > 1e-9 {
|
|
||||||
let total_branch: f64 = branch_layers.iter().map(|l| l.thickness).sum();
|
|
||||||
let mut b_off = branch_ref_off - total_branch / 2.0;
|
|
||||||
let mut core_lo = f64::INFINITY;
|
|
||||||
let mut core_hi = f64::NEG_INFINITY;
|
|
||||||
for (i, l) in branch_layers.iter().enumerate() {
|
|
||||||
if merged[i] {
|
|
||||||
if b_off < core_lo {
|
|
||||||
core_lo = b_off;
|
|
||||||
}
|
|
||||||
if b_off + l.thickness > core_hi {
|
|
||||||
core_hi = b_off + l.thickness;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b_off += l.thickness;
|
|
||||||
}
|
|
||||||
if core_hi > core_lo {
|
|
||||||
// Projektion der Kern-Offsethuelle auf die Durchgangsachse.
|
|
||||||
let n_branch = left_normal(branch_dir);
|
|
||||||
let proj = dot(n_branch, through_dir);
|
|
||||||
let axis_at = dot(sub(axis_point, through_start), through_dir);
|
|
||||||
let e1 = axis_at + core_lo * proj;
|
|
||||||
let e2 = axis_at + core_hi * proj;
|
|
||||||
let off_a = off_t + sign * (t_t / 2.0);
|
|
||||||
let off_b = off_t + sign * (t_t / 2.0 - near_plaster);
|
|
||||||
result[through_wall_idx].span_cutouts.push(SpanCutout {
|
|
||||||
from: e1.min(e2),
|
|
||||||
to: e1.max(e2),
|
|
||||||
off_a,
|
|
||||||
off_b,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Vektor-Helfer -----------------------------------------------------------
|
// --- Vektor-Helfer -----------------------------------------------------------
|
||||||
@@ -346,11 +82,6 @@ fn cross(p: Vec2, q: Vec2) -> f64 {
|
|||||||
p.x * q.y - p.y * q.x
|
p.x * q.y - p.y * q.x
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Skalarprodukt zweier 2D-Vektoren.
|
|
||||||
fn dot(p: Vec2, q: Vec2) -> f64 {
|
|
||||||
p.x * q.x + p.y * q.y
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schnittpunkt der Geraden (a + t*da) mit (b + s*db).
|
/// Schnittpunkt der Geraden (a + t*da) mit (b + s*db).
|
||||||
/// Liefert None bei (nahezu) parallelen Richtungen.
|
/// Liefert None bei (nahezu) parallelen Richtungen.
|
||||||
fn line_intersect(a: Vec2, da: Vec2, b: Vec2, db: Vec2) -> Option<Vec2> {
|
fn line_intersect(a: Vec2, da: Vec2, b: Vec2, db: Vec2) -> Option<Vec2> {
|
||||||
@@ -388,10 +119,8 @@ fn dir_of(w: &WallInput) -> Vec2 {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet fuer jede Wand die Gehrungs-Schnittlinien.
|
* Berechnet fuer jede Wand die Gehrungs-Schnittlinien.
|
||||||
* Behandelt werden: L-Ecken (genau zwei Wandenden treffen sich), T-Knoten
|
* Nur L-Ecken (genau zwei Wandenden treffen sich) werden behandelt; freie
|
||||||
* (drei Wandenden treffen sich, zwei davon kollinear) und Mittelspannen-
|
* Enden und T-/X-Stoesse bleiben rechtwinklig.
|
||||||
* T-Stoesse (ein freies Wandende trifft auf die Seite einer anderen Wand).
|
|
||||||
* X-Stoesse (vier und mehr Enden) bleiben vorerst rechtwinklig.
|
|
||||||
*/
|
*/
|
||||||
pub fn compute_joins(input: JoinInput) -> Vec<WallCuts> {
|
pub fn compute_joins(input: JoinInput) -> Vec<WallCuts> {
|
||||||
let walls = input.walls;
|
let walls = input.walls;
|
||||||
@@ -403,8 +132,6 @@ pub fn compute_joins(input: JoinInput) -> Vec<WallCuts> {
|
|||||||
wall_id: w.id.clone(),
|
wall_id: w.id.clone(),
|
||||||
start_cut: None,
|
start_cut: None,
|
||||||
end_cut: None,
|
end_cut: None,
|
||||||
layer_cuts: None,
|
|
||||||
span_cutouts: Vec::new(),
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -435,51 +162,33 @@ pub fn compute_joins(input: JoinInput) -> Vec<WallCuts> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (_key, ends) in &junctions {
|
for (_key, ends) in &junctions {
|
||||||
// Freies Ende -> kein Schnitt hier (wird unten separat auf Mittelspannen-T
|
// Freies Ende -> kein Schnitt.
|
||||||
// geprueft, Fall 2).
|
|
||||||
if ends.len() == 1 {
|
if ends.len() == 1 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// T-/X-Stoesse (>2 Enden): vorerst rechtwinklig lassen.
|
||||||
if ends.len() == 2 {
|
if ends.len() != 2 {
|
||||||
let a_idx = match index.get(ends[0].wall_id.as_str()) {
|
|
||||||
Some(i) => *i,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
let b_idx = match index.get(ends[1].wall_id.as_str()) {
|
|
||||||
Some(i) => *i,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
let a = &walls[a_idx];
|
|
||||||
let b = &walls[b_idx];
|
|
||||||
|
|
||||||
let cut = match miter_line(a, ends[0].end, b) {
|
|
||||||
Some(c) => c,
|
|
||||||
None => continue, // kollinear -> kein Schnitt
|
|
||||||
};
|
|
||||||
|
|
||||||
set_cut(&mut result[a_idx], ends[0].end, cut);
|
|
||||||
set_cut(&mut result[b_idx], ends[1].end, cut);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ends.len() == 3 {
|
let a_idx = match index.get(ends[0].wall_id.as_str()) {
|
||||||
apply_t_junction(&walls, &index, ends, &mut result);
|
Some(i) => *i,
|
||||||
continue;
|
None => continue,
|
||||||
}
|
};
|
||||||
|
let b_idx = match index.get(ends[1].wall_id.as_str()) {
|
||||||
|
Some(i) => *i,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let a = &walls[a_idx];
|
||||||
|
let b = &walls[b_idx];
|
||||||
|
|
||||||
// X-Stoesse u. ae. (>3 Enden): vorerst rechtwinklig lassen (Folge-Arbeit).
|
let cut = match miter_line(a, ends[0].end, b) {
|
||||||
}
|
Some(c) => c,
|
||||||
|
None => continue, // kollinear -> kein Schnitt
|
||||||
|
};
|
||||||
|
|
||||||
// Fall 2: Mittelspannen-T-Stoss. Ein freies Wandende (kein anderes Wandende
|
set_cut(&mut result[a_idx], ends[0].end, cut);
|
||||||
// teilt sich seinen Knoten) kann trotzdem auf die Seite einer anderen Wand
|
set_cut(&mut result[b_idx], ends[1].end, cut);
|
||||||
// treffen (nicht an deren Endpunkten). In dem Fall bekommt das freie Ende
|
|
||||||
// einen Schnitt entlang der zugewandten Flaeche dieser Wand.
|
|
||||||
for (_key, ends) in &junctions {
|
|
||||||
if ends.len() != 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
apply_mid_span_tee(&walls, &index, &ends[0], &mut result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
@@ -493,212 +202,11 @@ fn set_cut(cuts: &mut WallCuts, end: WallEndKind, cut: Line) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fall 1 -- T-Knoten: drei Wandenden treffen sich im selben (gerundeten)
|
|
||||||
* Knoten. Typisch sind zwei davon kollinear (die zwei Haelften der
|
|
||||||
* Durchgangswand, die geometrisch als eine Achse durchlaeuft) und das dritte
|
|
||||||
* zweigt ab. Die kollinearen zwei bleiben ungeschnitten; der Abzweig bekommt
|
|
||||||
* einen Schnitt entlang der ihm zugewandten Flaeche der Durchgangswand.
|
|
||||||
* Findet sich kein eindeutig kollineares Paar (z. B. echter Y-/X-Knoten),
|
|
||||||
* bleibt der Knoten unangetastet (rechtwinklig).
|
|
||||||
*/
|
|
||||||
fn apply_t_junction(
|
|
||||||
walls: &[WallInput],
|
|
||||||
index: &std::collections::HashMap<&str, usize>,
|
|
||||||
ends: &[WallEnd],
|
|
||||||
result: &mut [WallCuts],
|
|
||||||
) {
|
|
||||||
let idx_of = |id: &str| index.get(id).copied();
|
|
||||||
|
|
||||||
let dirs: Vec<Vec2> = match ends
|
|
||||||
.iter()
|
|
||||||
.map(|e| idx_of(e.wall_id.as_str()).map(|i| dir_of(&walls[i])))
|
|
||||||
.collect::<Option<Vec<Vec2>>>()
|
|
||||||
{
|
|
||||||
Some(d) => d,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Suche das (naeherungsweise) kollineare Paar: |cos(Winkel der Achsen)| ~ 1.
|
|
||||||
let mut through_pair: Option<(usize, usize)> = None;
|
|
||||||
'outer: for i in 0..3 {
|
|
||||||
for k in (i + 1)..3 {
|
|
||||||
if 1.0 - dot(dirs[i], dirs[k]).abs() < 1e-6 {
|
|
||||||
through_pair = Some((i, k));
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let (ti, tk) = match through_pair {
|
|
||||||
Some(p) => p,
|
|
||||||
None => return, // kein eindeutiger Durchgang erkennbar
|
|
||||||
};
|
|
||||||
|
|
||||||
let branch_idx = match (0..3).find(|&i| i != ti && i != tk) {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let branch_end = &ends[branch_idx];
|
|
||||||
let through_end = &ends[ti];
|
|
||||||
let branch_wall_idx = match idx_of(branch_end.wall_id.as_str()) {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let through_wall_idx = match idx_of(through_end.wall_id.as_str()) {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let branch_wall = &walls[branch_wall_idx];
|
|
||||||
let through_wall = &walls[through_wall_idx];
|
|
||||||
|
|
||||||
let j = match through_end.end {
|
|
||||||
WallEndKind::Start => through_wall.start,
|
|
||||||
WallEndKind::End => through_wall.end,
|
|
||||||
};
|
|
||||||
let t_t = through_wall.thickness;
|
|
||||||
let u_t = dir_of(through_wall);
|
|
||||||
let n_t = left_normal(u_t);
|
|
||||||
let off_t = through_wall.reference_offset;
|
|
||||||
|
|
||||||
// Auslaufrichtung des Abzweigs vom Knoten weg, in seinen eigenen Koerper
|
|
||||||
// (gleiche Konvention wie d_a/d_b in miter_line).
|
|
||||||
let d_branch = match branch_end.end {
|
|
||||||
WallEndKind::Start => dir_of(branch_wall),
|
|
||||||
WallEndKind::End => scale(dir_of(branch_wall), -1.0),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Zugewandte Flaeche: die Seite der Durchgangsachse, zu der der Abzweig
|
|
||||||
// zeigt (positive n_t-Seite, wenn der Abzweig dorthin auslaeuft).
|
|
||||||
let sign = if dot(n_t, d_branch) >= 0.0 { 1.0 } else { -1.0 };
|
|
||||||
let face_point = add(j, scale(n_t, off_t + sign * (t_t / 2.0)));
|
|
||||||
let face_cut = Line { point: face_point, dir: u_t };
|
|
||||||
|
|
||||||
set_cut(&mut result[branch_wall_idx], branch_end.end, face_cut);
|
|
||||||
apply_layer_cuts(
|
|
||||||
result,
|
|
||||||
branch_wall_idx,
|
|
||||||
branch_end.end,
|
|
||||||
&branch_wall.layers,
|
|
||||||
&through_wall.layers,
|
|
||||||
face_cut,
|
|
||||||
j,
|
|
||||||
u_t,
|
|
||||||
sign,
|
|
||||||
off_t,
|
|
||||||
through_wall_idx,
|
|
||||||
through_wall.start,
|
|
||||||
dir_of(branch_wall),
|
|
||||||
branch_wall.reference_offset,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fall 2 -- Mittelspannen-T-Stoss: prueft, ob das freie Wandende `free_end`
|
|
||||||
* auf die innere Spanne einer anderen Wand trifft (projizierter Parameter im
|
|
||||||
* Inneren, nicht an deren Endpunkten) und dabei senkrecht nah genug an deren
|
|
||||||
* Achse liegt (<= halbe Dicke + Toleranz). Bei Treffer bekommt das freie Ende
|
|
||||||
* einen Schnitt entlang der zugewandten Flaeche der getroffenen Wand; bei
|
|
||||||
* mehreren Treffern gewinnt die naechstliegende Wand.
|
|
||||||
*/
|
|
||||||
fn apply_mid_span_tee(
|
|
||||||
walls: &[WallInput],
|
|
||||||
index: &std::collections::HashMap<&str, usize>,
|
|
||||||
free_end: &WallEnd,
|
|
||||||
result: &mut [WallCuts],
|
|
||||||
) {
|
|
||||||
let free_wall_idx = match index.get(free_end.wall_id.as_str()) {
|
|
||||||
Some(i) => *i,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let free_wall = &walls[free_wall_idx];
|
|
||||||
let p = match free_end.end {
|
|
||||||
WallEndKind::Start => free_wall.start,
|
|
||||||
WallEndKind::End => free_wall.end,
|
|
||||||
};
|
|
||||||
|
|
||||||
let end_gap = 1e-4; // Mindestabstand zu den Wandenden fuer "innere Spanne"
|
|
||||||
let tol = 1e-3; // Toleranz fuer den senkrechten Abstand zur Wandflaeche
|
|
||||||
|
|
||||||
// (Index in `walls`, perp-Abstand vorzeichenbehaftet, absoluter Abstand).
|
|
||||||
let mut best: Option<(usize, f64, f64)> = None;
|
|
||||||
for (i, w) in walls.iter().enumerate() {
|
|
||||||
if w.id == free_wall.id {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let u = dir_of(w);
|
|
||||||
let w_len = len(sub(w.end, w.start));
|
|
||||||
if w_len < 1e-9 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rel = sub(p, w.start);
|
|
||||||
let dist_along = dot(rel, u);
|
|
||||||
if dist_along <= end_gap || dist_along >= w_len - end_gap {
|
|
||||||
continue; // an Endpunkt, kein Mittelspann
|
|
||||||
}
|
|
||||||
|
|
||||||
let perp = dot(rel, left_normal(u));
|
|
||||||
let tw = w.thickness;
|
|
||||||
let dist = perp.abs();
|
|
||||||
if dist > tw / 2.0 + tol {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if best.map_or(true, |(_, _, best_dist)| dist < best_dist) {
|
|
||||||
best = Some((i, perp, dist));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let (w_idx, perp, _dist) = match best {
|
|
||||||
Some(b) => b,
|
|
||||||
None => return, // kein Treffer -> rechtwinklig
|
|
||||||
};
|
|
||||||
|
|
||||||
let w = &walls[w_idx];
|
|
||||||
let tw = w.thickness;
|
|
||||||
let u_w = dir_of(w);
|
|
||||||
let n_w = left_normal(u_w);
|
|
||||||
let off_w = w.reference_offset;
|
|
||||||
let sign = if perp >= 0.0 { 1.0 } else { -1.0 };
|
|
||||||
let face_point = add(w.start, scale(n_w, off_w + sign * (tw / 2.0)));
|
|
||||||
let face_cut = Line { point: face_point, dir: u_w };
|
|
||||||
|
|
||||||
set_cut(&mut result[free_wall_idx], free_end.end, face_cut);
|
|
||||||
// Achsenpunkt auf der getroffenen Wand, auf Hoehe der Projektion des
|
|
||||||
// freien Endes (nicht zwingend w.start) -- Anker fuer die
|
|
||||||
// materialbewusste Pro-Schicht-Erweiterung (analog zum T-Knoten-Fall).
|
|
||||||
let dist_along = dot(sub(p, w.start), u_w);
|
|
||||||
let axis_point = add(w.start, scale(u_w, dist_along));
|
|
||||||
apply_layer_cuts(
|
|
||||||
result,
|
|
||||||
free_wall_idx,
|
|
||||||
free_end.end,
|
|
||||||
&free_wall.layers,
|
|
||||||
&w.layers,
|
|
||||||
face_cut,
|
|
||||||
axis_point,
|
|
||||||
u_w,
|
|
||||||
sign,
|
|
||||||
off_w,
|
|
||||||
w_idx,
|
|
||||||
w.start,
|
|
||||||
dir_of(free_wall),
|
|
||||||
free_wall.reference_offset,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gemeinsame Gehrungslinie zweier Waende A, B, die sich im Knoten J treffen.
|
* Gemeinsame Gehrungslinie zweier Waende A, B, die sich im Knoten J treffen.
|
||||||
* Robust gegen beliebige Wicklung, ungleiche Dicken und JEDEN Oeffnungswinkel
|
* Robust gegen beliebige Wicklung und ungleiche Dicken: A's Aussenflaeche wird
|
||||||
* (inkl. spitzer): Die Wandflaechen werden orientierungsbasiert (vorzeichen-
|
* mit der NAECHSTGELEGENEN Flaeche von B verschnitten, A's Innenflaeche mit der
|
||||||
* richtig) gepaart -- A's Aussenflaeche verschneidet B's Aussenflaeche zum
|
* jeweils anderen. Die Gerade durch beide Eckpunkte ist die Gehrung.
|
||||||
* aeusseren Apex, A's Innenflaeche B's Innenflaeche zum inneren Apex. "Aussen"
|
|
||||||
* ist jeweils die vom Koerper der anderen Wand ABGEWANDTE Flaeche. Die Gerade
|
|
||||||
* durch beide Apexe ist die Gehrung; sie trennt beide Wandbaender ueberlappungs-
|
|
||||||
* frei.
|
|
||||||
*
|
|
||||||
* Die fruehere Distanz-Heuristik ("naechstgelegene B-Flaeche") kippt bei spitzen
|
|
||||||
* Winkeln -- dort wird die falsche Flaeche zur naeheren, sodass Aussen mit Innen
|
|
||||||
* gepaart wird und die Poche-Baender sich kreuzweise ueberlappen.
|
|
||||||
*
|
*
|
||||||
* Dicke und Referenzversatz kommen direkt aus WallInput (bereits flach).
|
* Dicke und Referenzversatz kommen direkt aus WallInput (bereits flach).
|
||||||
*/
|
*/
|
||||||
@@ -727,29 +235,11 @@ fn miter_line(a: &WallInput, a_end: WallEndKind, b: &WallInput) -> Option<Line>
|
|||||||
let p_lb = add(j, scale(n_b, off_b + t_b / 2.0));
|
let p_lb = add(j, scale(n_b, off_b + t_b / 2.0));
|
||||||
let p_rb = add(j, scale(n_b, off_b - t_b / 2.0));
|
let p_rb = add(j, scale(n_b, off_b - t_b / 2.0));
|
||||||
|
|
||||||
// Auslauf-Richtungen der Achsen vom Knoten weg, in den jeweiligen Wandkoerper.
|
// Fuer A's linke Flaeche die naehere B-Flaeche waehlen; A's rechte die andere.
|
||||||
let d_a = match a_end {
|
let lb_closer = len(sub(p_la, p_lb)) <= len(sub(p_la, p_rb));
|
||||||
WallEndKind::Start => u_a,
|
let b_for_left = if lb_closer { p_lb } else { p_rb };
|
||||||
WallEndKind::End => scale(u_a, -1.0),
|
let b_for_right = if lb_closer { p_rb } else { p_lb };
|
||||||
};
|
|
||||||
let b_at_start = len(sub(b.start, j)) <= len(sub(b.end, j));
|
|
||||||
let d_b = if b_at_start { u_b } else { scale(u_b, -1.0) };
|
|
||||||
|
|
||||||
// Orientierungsbasierte, vorzeichenrichtige Flaechen-Paarung. "Aussen" ist die
|
|
||||||
// vom Koerper der anderen Wand ABGEWANDTE Flaeche: A's +nA-Flaeche (p_la) liegt
|
|
||||||
// aussen, wenn nA*dB < 0; B's +nB-Flaeche (p_lb) aussen, wenn nB*dA < 0. Gepaart
|
|
||||||
// wird Aussen-mit-Aussen und Innen-mit-Innen -- nie ueber Kreuz. (nA*dB = 0 tritt
|
|
||||||
// nur bei kollinearen Achsen auf; dann sind die Flaechen parallel und
|
|
||||||
// line_intersect liefert unten ohnehin None.)
|
|
||||||
let b_outer = if dot(n_b, d_a) < 0.0 { p_lb } else { p_rb };
|
|
||||||
let b_inner = if dot(n_b, d_a) < 0.0 { p_rb } else { p_lb };
|
|
||||||
let a_left_is_outer = dot(n_a, d_b) < 0.0;
|
|
||||||
let b_for_left = if a_left_is_outer { b_outer } else { b_inner };
|
|
||||||
let b_for_right = if a_left_is_outer { b_inner } else { b_outer };
|
|
||||||
|
|
||||||
// c1 an A's linke Flaeche (p_la), c2 an A's rechte (p_ra) gebunden -- Reihenfolge
|
|
||||||
// wie zuvor, damit rechte/stumpfe Ecken bit-identisch bleiben; nur die
|
|
||||||
// B-Partnerwahl ist jetzt orientierungs- statt distanzbasiert.
|
|
||||||
let c1 = line_intersect(p_la, u_a, b_for_left, u_b)?;
|
let c1 = line_intersect(p_la, u_a, b_for_left, u_b)?;
|
||||||
let c2 = line_intersect(p_ra, u_a, b_for_right, u_b)?;
|
let c2 = line_intersect(p_ra, u_a, b_for_right, u_b)?;
|
||||||
|
|
||||||
@@ -773,7 +263,6 @@ mod tests {
|
|||||||
end: Vec2 { x: ex, y: ey },
|
end: Vec2 { x: ex, y: ey },
|
||||||
thickness,
|
thickness,
|
||||||
reference_offset: 0.0,
|
reference_offset: 0.0,
|
||||||
layers: Vec::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,34 +306,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn acute_corner_pairs_outer_with_outer() {
|
|
||||||
// Symmetrische V-Ecke, Knoten (0,0), Oeffnungswinkel 60 Grad (spitz),
|
|
||||||
// nach oben. Beide Wandkoerper laufen unter +/-30 Grad zur +y-Achse aus.
|
|
||||||
// WA endet im Knoten, WB startet dort. Erwartet: vertikale Gehrung durch
|
|
||||||
// Aussen-Apex (0,-0.4) und Innen-Apex (0,0.4). Die alte Distanz-Heuristik
|
|
||||||
// lieferte hier eine um 90 Grad verdrehte, horizontale Gehrung.
|
|
||||||
let s3 = 3.0_f64.sqrt() / 2.0;
|
|
||||||
let input = JoinInput {
|
|
||||||
walls: vec![
|
|
||||||
w("WA", -1.0, 2.0 * s3, 0.0, 0.0, 0.4),
|
|
||||||
w("WB", 0.0, 0.0, 1.0, 2.0 * s3, 0.4),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
let out = compute_joins(input);
|
|
||||||
let ca = find(&out, "WA");
|
|
||||||
let cb = find(&out, "WB");
|
|
||||||
let cut = ca.end_cut.or(cb.start_cut).expect("Gehrung gesetzt");
|
|
||||||
|
|
||||||
// Abstand Punkt->Gerade: |cross(dir, q - point)| / len(dir).
|
|
||||||
let dist = |q: Vec2| cross(cut.dir, sub(q, cut.point)).abs() / len(cut.dir);
|
|
||||||
assert!(dist(Vec2 { x: 0.0, y: -0.4 }) < 1e-6, "Aussen-Apex auf Gehrung");
|
|
||||||
assert!(dist(Vec2 { x: 0.0, y: 0.4 }) < 1e-6, "Innen-Apex auf Gehrung");
|
|
||||||
// Gehrung vertikal: dir.x ~ 0 (die falsche, horizontale Gehrung haette
|
|
||||||
// stattdessen dir.y ~ 0 gehabt).
|
|
||||||
assert!(cut.dir.x.abs() / len(cut.dir) < 1e-6, "vertikale Gehrung erwartet");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn free_end_no_cut() {
|
fn free_end_no_cut() {
|
||||||
let input = JoinInput {
|
let input = JoinInput {
|
||||||
@@ -858,12 +319,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn t_junction_branch_gets_face_cut() {
|
fn t_junction_no_cut() {
|
||||||
// Drei Enden treffen sich in (5,0): A/C sind kollinear (Durchgangswand),
|
// Drei Enden treffen sich in (5,0): rechtwinklig lassen.
|
||||||
// B zweigt ab. A/C bleiben ungeschnitten; B bekommt einen Schnitt entlang
|
|
||||||
// der ihm zugewandten Flaeche der Durchgangswand (hier: y = +0.1, da B
|
|
||||||
// nach +y auslaeuft und die Durchgangswand entlang +x verlaeuft, also
|
|
||||||
// liegt deren linke Flaeche bei +0.1).
|
|
||||||
let input = JoinInput {
|
let input = JoinInput {
|
||||||
walls: vec![
|
walls: vec![
|
||||||
w("A", 0.0, 0.0, 5.0, 0.0, 0.2),
|
w("A", 0.0, 0.0, 5.0, 0.0, 0.2),
|
||||||
@@ -873,21 +330,11 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let out = compute_joins(input);
|
let out = compute_joins(input);
|
||||||
assert_eq!(out.len(), 3);
|
assert_eq!(out.len(), 3);
|
||||||
|
for id in ["A", "B", "C"] {
|
||||||
let ca = find(&out, "A");
|
let c = find(&out, id);
|
||||||
let cc = find(&out, "C");
|
assert!(c.start_cut.is_none(), "{id} startCut none");
|
||||||
assert!(ca.start_cut.is_none(), "A startCut ist freies Ende");
|
assert!(c.end_cut.is_none(), "{id} endCut none");
|
||||||
assert!(ca.end_cut.is_none(), "A durchgehend -> kein Schnitt");
|
}
|
||||||
assert!(cc.start_cut.is_none(), "C durchgehend -> kein Schnitt");
|
|
||||||
assert!(cc.end_cut.is_none(), "C endCut ist freies Ende");
|
|
||||||
|
|
||||||
let cb = find(&out, "B");
|
|
||||||
let cut = cb.start_cut.expect("B startCut gesetzt");
|
|
||||||
assert!(cb.end_cut.is_none(), "B endCut ist freies Ende");
|
|
||||||
assert!((cut.point.x - 5.0).abs() < 1e-9);
|
|
||||||
assert!((cut.point.y - 0.1).abs() < 1e-9, "Flaeche bei y=+0.1 erwartet");
|
|
||||||
// Schnittlinie verlaeuft entlang der Durchgangsachse (parallel zu +x).
|
|
||||||
assert!(cut.dir.y.abs() / len(cut.dir) < 1e-9);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -907,151 +354,4 @@ mod tests {
|
|||||||
assert!(c.end_cut.is_none(), "{id} endCut none");
|
assert!(c.end_cut.is_none(), "{id} endCut none");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mid_span_tee_free_end_hits_wall_side() {
|
|
||||||
// Freie Wand B endet bei (5,0) exakt auf der Achse der durchgehenden
|
|
||||||
// Wand A (0,0)-(10,0), mittig auf deren Spanne (nicht an deren Enden).
|
|
||||||
// B's anderes Ende (5,2) bleibt frei (zu weit von A entfernt). Der
|
|
||||||
// Treffer liegt exakt auf A's Achse (perp=0) -> die zugewandte Flaeche
|
|
||||||
// ist A's linke Flaeche bei y=+tw/2=+0.1 (sign=+1 per Konvention bei
|
|
||||||
// perp>=0). Die Schnittlinie ist als Punkt+Richtung auf A's Anker
|
|
||||||
// (A.start) verankert, verlaeuft aber unabhaengig davon entlang y=0.1.
|
|
||||||
let input = JoinInput {
|
|
||||||
walls: vec![
|
|
||||||
w("A", 0.0, 0.0, 10.0, 0.0, 0.2),
|
|
||||||
w("B", 5.0, 2.0, 5.0, 0.0, 0.2),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
let out = compute_joins(input);
|
|
||||||
assert_eq!(out.len(), 2);
|
|
||||||
|
|
||||||
let ca = find(&out, "A");
|
|
||||||
assert!(ca.start_cut.is_none(), "A durchgehend -> kein Schnitt");
|
|
||||||
assert!(ca.end_cut.is_none(), "A durchgehend -> kein Schnitt");
|
|
||||||
|
|
||||||
let cb = find(&out, "B");
|
|
||||||
assert!(cb.start_cut.is_none(), "B startCut ist freies Ende bei (5,2)");
|
|
||||||
let cut = cb.end_cut.expect("B endCut (Mittelspann-Treffer) gesetzt");
|
|
||||||
|
|
||||||
// Verankert an A.start = (0,0), verschoben um A's halbe Dicke entlang
|
|
||||||
// ihrer linken Normale (0,1) -> Punkt (0, 0.1), Richtung parallel zu A.
|
|
||||||
assert!((cut.point.x - 0.0).abs() < 1e-9);
|
|
||||||
assert!((cut.point.y - 0.1).abs() < 1e-9);
|
|
||||||
assert!(cut.dir.y.abs() / len(cut.dir) < 1e-9, "Schnittlinie parallel zu A");
|
|
||||||
|
|
||||||
// Robuster: die Gerade verlaeuft exakt bei y=0.1, unabhaengig vom x.
|
|
||||||
let dist = |q: Vec2| cross(cut.dir, sub(q, cut.point)).abs() / len(cut.dir);
|
|
||||||
assert!(dist(Vec2 { x: 5.0, y: 0.1 }) < 1e-9, "Treffpunkt (5,0.1) auf Gehrung");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// W9-artiger Wandtyp: Innenputz (0.015) / Backstein-Kern (0.12) / Innenputz
|
|
||||||
/// (0.015), joinPriority 10 bzw. 50 -- Spiegel von `layeredWallProject` aus
|
|
||||||
/// `model/joins.test.ts`.
|
|
||||||
fn w_iw(id: &str, sx: f64, sy: f64, ex: f64, ey: f64) -> WallInput {
|
|
||||||
WallInput {
|
|
||||||
id: id.to_string(),
|
|
||||||
start: Vec2 { x: sx, y: sy },
|
|
||||||
end: Vec2 { x: ex, y: ey },
|
|
||||||
thickness: 0.15,
|
|
||||||
reference_offset: 0.0,
|
|
||||||
layers: vec![
|
|
||||||
LayerInput { component_id: "render-int".into(), join_priority: 10.0, thickness: 0.015 },
|
|
||||||
LayerInput { component_id: "brick".into(), join_priority: 50.0, thickness: 0.12 },
|
|
||||||
LayerInput { component_id: "render-int".into(), join_priority: 10.0, thickness: 0.015 },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn t_junction_layer_cuts_merge_and_trim() {
|
|
||||||
// Wie t_junction_branch_gets_face_cut, aber alle drei Waende vom
|
|
||||||
// W9-artigen Wandtyp: der Backstein-Kern (Index 1) sticht durch den
|
|
||||||
// Nah-Putz und stoppt an der Kern-Nahflaeche (y=0.06), die beiden
|
|
||||||
// Innenputz-Schichten (Index 0/2) werden getrimmt. Da der Nah-Putz der
|
|
||||||
// Durchgangswand materialgleich ist, entsteht KEINE L-Trennnaht.
|
|
||||||
let input = JoinInput {
|
|
||||||
walls: vec![
|
|
||||||
w_iw("A", 0.0, 0.0, 5.0, 0.0),
|
|
||||||
w_iw("B", 5.0, 0.0, 5.0, 3.0),
|
|
||||||
w_iw("C", 5.0, 0.0, 10.0, 0.0),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
let out = compute_joins(input);
|
|
||||||
let cb = find(&out, "B");
|
|
||||||
|
|
||||||
// Aggregat-Cut bleibt unveraendert (bit-identisch zum bisherigen Verhalten).
|
|
||||||
let agg = cb.start_cut.expect("B startCut gesetzt");
|
|
||||||
assert!((agg.point.x - 5.0).abs() < 1e-9);
|
|
||||||
assert!((agg.point.y - 0.075).abs() < 1e-9);
|
|
||||||
|
|
||||||
let lc = cb.layer_cuts.as_ref().expect("layerCuts gesetzt (Backstein verschmilzt)");
|
|
||||||
assert_eq!(lc.start.len(), 3);
|
|
||||||
|
|
||||||
let dist = |cut: Line, q: Vec2| cross(cut.dir, sub(q, cut.point)).abs() / len(cut.dir);
|
|
||||||
|
|
||||||
// Schicht 1 (Backstein-Kern): Cut an der Rueckgrat-Nahflaeche y=0.06
|
|
||||||
// (Halbdicke 0.075 minus Nah-Putz 0.015), NICHT null/Achse; keine
|
|
||||||
// Seitenlinie (verschmilzt).
|
|
||||||
let l1 = lc.start[1].expect("Schicht 1 (Kern) hat Merge-Cut");
|
|
||||||
assert!(dist(l1, Vec2 { x: 5.0, y: 0.06 }) < 1e-9);
|
|
||||||
assert!(dist(l1, Vec2 { x: 0.0, y: 0.06 }) < 1e-9);
|
|
||||||
assert!(dist(l1, Vec2 { x: 5.0, y: 0.0 }) > 1e-6); // nicht bis zur Achse
|
|
||||||
assert!(lc.start_side[1].is_none());
|
|
||||||
|
|
||||||
// Schichten 0/2 (Innenputz): derselbe Nahflaechen-Cut wie das Aggregat...
|
|
||||||
let l0 = lc.start[0].expect("Schicht 0 getrimmt");
|
|
||||||
let l2 = lc.start[2].expect("Schicht 2 getrimmt");
|
|
||||||
assert!((l0.point.y - 0.075).abs() < 1e-9);
|
|
||||||
assert!((l2.point.y - 0.075).abs() < 1e-9);
|
|
||||||
// ... aber KEINE seitliche L-Linie: der Innenputz der Durchgangswand ist
|
|
||||||
// materialgleich und fuellt den getrimmten Abzweig-Putz durchgehend
|
|
||||||
// (Putz-L ohne Trennnaht). Nur bei materialFREMDEM Nah-Putz stuende hier
|
|
||||||
// eine L-Linie.
|
|
||||||
assert!(lc.start_side[0].is_none());
|
|
||||||
assert!(lc.start_side[2].is_none());
|
|
||||||
|
|
||||||
// Die kollineare Durchgangswand bleibt bei den layerCuts unangetastet.
|
|
||||||
let ca = find(&out, "A");
|
|
||||||
let cc = find(&out, "C");
|
|
||||||
assert!(ca.layer_cuts.is_none());
|
|
||||||
assert!(cc.layer_cuts.is_none());
|
|
||||||
|
|
||||||
// ... bekommt aber die Durchgangswand-Aussparung (Phase 1c): die als
|
|
||||||
// Durchgang gewaehlte Haelfte A wird ueber die Backstein-Kernbreite (0.12)
|
|
||||||
// im Nah-Putz-Bereich (y in [0.06,0.075]) ausgeschnitten. Das Intervall
|
|
||||||
// ist die Projektion der Kernbreite auf die Achse, zentriert am Knoten x=5.
|
|
||||||
assert_eq!(ca.span_cutouts.len(), 1, "A (Durchgang) hat genau eine Aussparung");
|
|
||||||
let sc = ca.span_cutouts[0];
|
|
||||||
assert!((sc.from - 4.94).abs() < 1e-9, "from = 5 - 0.06");
|
|
||||||
assert!((sc.to - 5.06).abs() < 1e-9, "to = 5 + 0.06");
|
|
||||||
assert!(((sc.to - sc.from) - 0.12).abs() < 1e-9, "Intervallbreite = Kernbreite");
|
|
||||||
assert!((sc.off_a - 0.075).abs() < 1e-9, "offA = Nahflaeche");
|
|
||||||
assert!((sc.off_b - 0.06).abs() < 1e-9, "offB = Rueckgrat-Nahflaeche");
|
|
||||||
// Der Abzweig B traegt keine Aussparung (die gehoert der Durchgangswand).
|
|
||||||
assert!(cb.span_cutouts.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mid_span_tee_through_wall_gets_span_cutout() {
|
|
||||||
// Mittelspannen-T-Stoss (wie der reale W9-Fall): Durchgangswand W als EINE
|
|
||||||
// Wand (0,0)->(10,0), Abzweig B trifft mit seinem freien Ende (4,0) mittig
|
|
||||||
// auf ihre Seite. W bekommt EINE Aussparung um x=4, Breite = Kernbreite.
|
|
||||||
let input = JoinInput {
|
|
||||||
walls: vec![
|
|
||||||
w_iw("W", 0.0, 0.0, 10.0, 0.0),
|
|
||||||
w_iw("B", 4.0, 3.0, 4.0, 0.0),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
let out = compute_joins(input);
|
|
||||||
let cw = find(&out, "W");
|
|
||||||
assert_eq!(cw.span_cutouts.len(), 1, "W hat genau eine Aussparung");
|
|
||||||
let sc = cw.span_cutouts[0];
|
|
||||||
assert!((sc.from - 3.94).abs() < 1e-9);
|
|
||||||
assert!((sc.to - 4.06).abs() < 1e-9);
|
|
||||||
assert!((sc.off_a - 0.075).abs() < 1e-9);
|
|
||||||
assert!((sc.off_b - 0.06).abs() < 1e-9);
|
|
||||||
// Der Abzweig selbst hat keine Aussparung.
|
|
||||||
assert!(find(&out, "B").span_cutouts.is_empty());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-195
@@ -1,195 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bumpalo"
|
|
||||||
version = "3.20.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_error_panic_hook"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kernel2d"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"console_error_panic_hook",
|
|
||||||
"robust",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell"
|
|
||||||
version = "1.21.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.106"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.46"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "robust"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
||||||
dependencies = [
|
|
||||||
"serde_core",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_core"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.228"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.150"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.118"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"rustversion",
|
|
||||||
"wasm-bindgen-macro",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
|
||||||
dependencies = [
|
|
||||||
"quote",
|
|
||||||
"wasm-bindgen-macro-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-macro-support"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wasm-bindgen-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-bindgen-shared"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Eigener leerer Workspace-Block: entkoppelt kernel2d vollstaendig vom
|
|
||||||
# cad-tauri-Workspace (src-tauri/Cargo.toml), damit `cargo test`/`wasm-pack`
|
|
||||||
# aus diesem Verzeichnis heraus nicht faelschlich dessen Workspace erben.
|
|
||||||
# Muster: render2d/render3d. (Parent-`exclude` allein greift beim Bauen aus
|
|
||||||
# dem Unterverzeichnis nicht zuverlaessig.)
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[package]
|
|
||||||
name = "kernel2d"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "2D-Geometrie-Kern (Port von src/geometry/kernel2d.ts) — handgeschriebene f64-Mathematik, headless per `cargo test` UND per wasm-pack (Feature \"web\") zu WASM baubar. Hinter identischer TS-Fassade; TS-Legacy bleibt Differential-Referenz."
|
|
||||||
|
|
||||||
# cdylib: von wasm-pack (Feature "web") fuer das .wasm-Modul. rlib: als
|
|
||||||
# Pfad-Abhaengigkeit und fuer den Test-/Example-Build (parity). Muster:
|
|
||||||
# render2d/render3d/geometry.
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# Standard: reiner f64-Rechenkern, headless per `cargo test` pruefbar.
|
|
||||||
default = []
|
|
||||||
# Browser-Bindings: dieselbe Geometrie hinter wasm-bindgen-Batch-Fassaden,
|
|
||||||
# aus TS via wasm-pack aufgerufen. Nur fuer wasm32-unknown-unknown sinnvoll.
|
|
||||||
web = ["dep:wasm-bindgen", "dep:serde_json", "dep:console_error_panic_hook"]
|
|
||||||
# ADDITIV, NICHT im web-Default: exakte Orientierungs-Praedikate (robust::orient2d)
|
|
||||||
# nur intern fuer Korrektheits-Golden-Cases (detectRooms/point-in-polygon). Der
|
|
||||||
# Zufalls-Diff-Test gegen TS-naiv laeuft OHNE dieses Feature (siehe PORT_PLAN §3).
|
|
||||||
robust-predicates = ["dep:robust"]
|
|
||||||
|
|
||||||
[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 }
|
|
||||||
robust = { version = "1", optional = true }
|
|
||||||
|
|
||||||
# Nur fuers Paritaets-Beispiel (examples/parity.rs) — JSON von stdin lesen.
|
|
||||||
[dev-dependencies]
|
|
||||||
serde_json = "1"
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,7 @@ struct Globals {
|
|||||||
|
|
||||||
struct VsIn {
|
struct VsIn {
|
||||||
@location(0) position : vec2<f32>, // Bildschirm-Raum-Endpunkt
|
@location(0) position : vec2<f32>, // Bildschirm-Raum-Endpunkt
|
||||||
@location(1) bisector : vec2<f32>, // Bildschirm-Raum-Versatzrichtung (Einheit; Normale bei Quads, Radialrichtung bei Rundkappen/-ecken)
|
@location(1) bisector : vec2<f32>, // Bildschirm-Raum-Miter-Bisektor (Einheit)
|
||||||
@location(2) side : f32, // +1.0 oder -1.0
|
@location(2) side : f32, // +1.0 oder -1.0
|
||||||
@location(3) miter : f32, // 1/cos(theta/2)-Laengenfaktor (Gehrung)
|
@location(3) miter : f32, // 1/cos(theta/2)-Laengenfaktor (Gehrung)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -252,25 +252,22 @@ fn cross(a: Point, b: Point, c: Point) -> f32 {
|
|||||||
(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
|
(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Einheits-Richtung von `from` nach `to` (Bildschirm-Raum); None bei Nulllaenge.
|
/// Maximaler Miter-Laengenfaktor; darueber wird geklemmt (kein Spike an spitzen Ecken).
|
||||||
|
const MITER_LIMIT: f32 = 8.0;
|
||||||
|
|
||||||
|
/// Einheits-Links-Normale von `from` nach `to` (Bildschirm-Raum); None bei Nulllaenge.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unit_dir(from: Point, to: Point) -> Option<Point> {
|
fn left_normal(from: Point, to: Point) -> Option<Point> {
|
||||||
let dx = to[0] - from[0];
|
let dx = to[0] - from[0];
|
||||||
let dy = to[1] - from[1];
|
let dy = to[1] - from[1];
|
||||||
let len = (dx * dx + dy * dy).sqrt();
|
let len = (dx * dx + dy * dy).sqrt();
|
||||||
if len < 1e-9 {
|
if len < 1e-6 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some([dx / len, dy / len])
|
Some([-dy / len, dx / len])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 90-Grad-Linksdrehung einer Einheits-Richtung -> Einheits-Links-Normale.
|
|
||||||
#[inline]
|
|
||||||
fn left_normal_of(d: Point) -> Point {
|
|
||||||
[-d[1], d[0]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Liegt p im (a,b,c)-Dreieck? (CCW-orientiert).
|
/// Liegt p im (a,b,c)-Dreieck? (CCW-orientiert).
|
||||||
fn point_in_tri(a: Point, b: Point, c: Point, p: Point) -> bool {
|
fn point_in_tri(a: Point, b: Point, c: Point, p: Point) -> bool {
|
||||||
let d1 = cross(a, b, p);
|
let d1 = cross(a, b, p);
|
||||||
@@ -393,10 +390,7 @@ pub struct GpuGeometry {
|
|||||||
pub fill_idx: Vec<u32>,
|
pub fill_idx: Vec<u32>,
|
||||||
pub fill_batches: Vec<FillBatch>,
|
pub fill_batches: Vec<FillBatch>,
|
||||||
/// Linien-Vertices, interleaved [x,y, bx,by, side, miter] in Bildschirm-Raum
|
/// Linien-Vertices, interleaved [x,y, bx,by, side, miter] in Bildschirm-Raum
|
||||||
/// (bx,by = Einheits-Versatzrichtung — Segment-Normale bei Quad-Raendern,
|
/// (bx,by = Miter-Bisektor, miter = 1/cos(theta/2)-Laengenfaktor).
|
||||||
/// Radialrichtung bei Rundkappen-/Rundecken-Faechern; side = 0 im Faecher-
|
|
||||||
/// Zentrum, sonst +-1; miter ist stets 1 — die Strichbreite wird vollstaendig
|
|
||||||
/// im Vertex-Shader angewandt, s. `shaders.rs`).
|
|
||||||
pub line_verts: Vec<f32>,
|
pub line_verts: Vec<f32>,
|
||||||
pub line_idx: Vec<u32>,
|
pub line_idx: Vec<u32>,
|
||||||
pub line_batches: Vec<LineBatch>,
|
pub line_batches: Vec<LineBatch>,
|
||||||
@@ -467,23 +461,11 @@ impl GpuGeometry {
|
|||||||
.push((BatchKind::Line, (self.line_batches.len() - 1) as u32));
|
.push((BatchKind::Line, (self.line_batches.len() - 1) as u32));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Zeichnet einen zusammenhaengenden Linienzug (Modell-Meter) mit ECHTEN
|
/// Zeichnet einen zusammenhaengenden Linienzug (Modell-Meter) als EINEN
|
||||||
/// RUNDEN Kappen/Ecken (wie SVG `stroke-linecap/linejoin: round` und der
|
/// gehrten Streifen: an jedem Stuetzpunkt wird der Versatz entlang des Miter-
|
||||||
/// Vektor-PDF-Pfad) statt eckigem Butt-Cap/Gehrung:
|
/// Bisektors verlaengert (1/cos(theta/2)), sodass benachbarte Segmente buendig
|
||||||
/// - jedes Segment ist ein eigenstaendiges Quad mit BUTT-Enden (eigene
|
/// verschmelzen -> gehrte Ecke statt Butt-Cap-Stufe. `closed` schliesst den Ring
|
||||||
/// Segment-Normale, keine Miter-Verlaengerung);
|
/// (letzter<->erster Punkt). Vertex-Layout: [x,y, bx,by, side, miter] (6 floats).
|
||||||
/// - an jedem inneren Stuetzpunkt fuellt ein Dreiecksfaecher (Radius = halbe
|
|
||||||
/// Strichbreite) die Aussenseite der Ecke rund auf (Innenseite ueberlappt
|
|
||||||
/// unsichtbar, wie bei jedem Disjoint-Segment-Linienbreiten-Ansatz);
|
|
||||||
/// - an offenen Enden (nicht `closed`) sitzt ein Halbkreis-Faecher als runde
|
|
||||||
/// Kappe.
|
|
||||||
/// Die tatsaechliche Pixel-Breite bleibt bildschirmkonstant: alle Faecher-/
|
|
||||||
/// Quad-Vertices tragen nur eine EINHEITS-Richtung (`bx,by`) + `side`-Skalar;
|
|
||||||
/// der Vertex-Shader multipliziert im Clip-Raum mit der aktuellen
|
|
||||||
/// Strichbreite (`stroke_px*stroke_scale`) — die Tessellierung selbst kennt
|
|
||||||
/// keine Pixelmasse. `closed` schliesst den Ring (letzter<->erster Punkt).
|
|
||||||
/// Vertex-Layout unveraendert: [x,y, bx,by, side, miter] (miter bleibt hier
|
|
||||||
/// immer 1, s. Shader-Vertrag in `shaders.rs`).
|
|
||||||
///
|
///
|
||||||
/// 1:1-Port von `strokePolyline` in `src/plan/glPlan/glPlanCompile.ts`.
|
/// 1:1-Port von `strokePolyline` in `src/plan/glPlan/glPlanCompile.ts`.
|
||||||
fn stroke_polyline(
|
fn stroke_polyline(
|
||||||
@@ -519,135 +501,69 @@ impl GpuGeometry {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_idx_len = self.line_idx.len();
|
let base = (self.line_verts.len() / 6) as u32;
|
||||||
|
for i in 0..k {
|
||||||
// Vertex fuer Quad-Rand ODER Faecher (Zentrum bei side=0, Rand bei side=1)
|
let has_in = closed || i > 0;
|
||||||
// anhaengen; gibt den neuen Index zurueck.
|
let has_out = closed || i < k - 1;
|
||||||
#[inline]
|
let n_in = if has_in {
|
||||||
fn push_vert(verts: &mut Vec<f32>, p: Point, n: Point, side: f32) -> u32 {
|
left_normal(s[(i + k - 1) % k], s[i])
|
||||||
verts.extend_from_slice(&[p[0], p[1], n[0], n[1], side, 1.0]);
|
|
||||||
(verts.len() / 6 - 1) as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
let segs = if closed { k } else { k - 1 };
|
|
||||||
let mut seg_dir: Vec<Option<Point>> = Vec::with_capacity(segs);
|
|
||||||
for i in 0..segs {
|
|
||||||
seg_dir.push(unit_dir(s[i], s[(i + 1) % k]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Segment-Quads: buttendig, jedes mit seiner EIGENEN Normale (kein
|
|
||||||
// gemeinsamer Bisektor mehr — Ecken werden separat durch Faecher geschlossen).
|
|
||||||
for i in 0..segs {
|
|
||||||
let Some(d) = seg_dir[i] else {
|
|
||||||
continue; // entartetes (Laenge-0) Segment
|
|
||||||
};
|
|
||||||
let n = left_normal_of(d);
|
|
||||||
let a = s[i];
|
|
||||||
let b = s[(i + 1) % k];
|
|
||||||
let i0 = push_vert(&mut self.line_verts, a, n, 1.0);
|
|
||||||
let i1 = push_vert(&mut self.line_verts, a, n, -1.0);
|
|
||||||
let i2 = push_vert(&mut self.line_verts, b, n, 1.0);
|
|
||||||
let i3 = push_vert(&mut self.line_verts, b, n, -1.0);
|
|
||||||
self.line_idx
|
|
||||||
.extend_from_slice(&[i0, i1, i2, i1, i3, i2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kreisbogen-Faecher (Zentrum = Vertex, Radius = halbe Strichbreite, per
|
|
||||||
// Shader skaliert): `side=0` am Zentrum (kein Versatz), `side=1` an den
|
|
||||||
// Randpunkten (voller Versatz in Richtung (cos(t),sin(t))). Segmentzahl
|
|
||||||
// skaliert mit dem ueberstrichenen Winkel (18 Grad je Schritt).
|
|
||||||
#[inline]
|
|
||||||
fn add_fan_arc(verts: &mut Vec<f32>, idx: &mut Vec<u32>, p: Point, a_from: f32, a_to: f32) {
|
|
||||||
let delta = a_to - a_from;
|
|
||||||
let steps = ((delta.abs() / (std::f32::consts::PI / 10.0)).ceil() as u32).max(1);
|
|
||||||
let i_center = push_vert(verts, p, [1.0, 0.0], 0.0);
|
|
||||||
let mut prev = push_vert(verts, p, [a_from.cos(), a_from.sin()], 1.0);
|
|
||||||
for step in 1..=steps {
|
|
||||||
let t = a_from + delta * (step as f32) / (steps as f32);
|
|
||||||
let cur = push_vert(verts, p, [t.cos(), t.sin()], 1.0);
|
|
||||||
idx.extend_from_slice(&[i_center, prev, cur]);
|
|
||||||
prev = cur;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runder Join an einem inneren Stuetzpunkt: Faecher NUR auf der konvexen
|
|
||||||
// (aeusseren) Seite der Ecke — die konkave Seite ueberlappt bereits durch
|
|
||||||
// die beiden Segment-Quads (kein Loch, keine zusaetzliche Geometrie noetig).
|
|
||||||
#[inline]
|
|
||||||
fn add_round_join(verts: &mut Vec<f32>, idx: &mut Vec<u32>, p: Point, d1: Point, d2: Point) {
|
|
||||||
let turn = d1[0] * d2[1] - d1[1] * d2[0];
|
|
||||||
let dot = d1[0] * d2[0] + d1[1] * d2[1];
|
|
||||||
if turn.abs() < 1e-6 && dot > 0.0 {
|
|
||||||
return; // praktisch gerade
|
|
||||||
}
|
|
||||||
let n1 = left_normal_of(d1);
|
|
||||||
let n2 = left_normal_of(d2);
|
|
||||||
if dot < -0.9999 {
|
|
||||||
// ~180-Grad-Umkehr: Aussenseite mehrdeutig -> voller Kreis (robust,
|
|
||||||
// entspricht zwei gestapelten Rund-Kappen an derselben Stelle).
|
|
||||||
let a0 = n1[1].atan2(n1[0]);
|
|
||||||
add_fan_arc(verts, idx, p, a0, a0 + 2.0 * std::f32::consts::PI);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let outer = if turn > 0.0 { -1.0 } else { 1.0 };
|
|
||||||
let u1 = [outer * n1[0], outer * n1[1]];
|
|
||||||
let u2 = [outer * n2[0], outer * n2[1]];
|
|
||||||
let a1 = u1[1].atan2(u1[0]);
|
|
||||||
let a2 = u2[1].atan2(u2[0]);
|
|
||||||
let mut delta = a2 - a1;
|
|
||||||
while delta <= -std::f32::consts::PI {
|
|
||||||
delta += 2.0 * std::f32::consts::PI;
|
|
||||||
}
|
|
||||||
while delta > std::f32::consts::PI {
|
|
||||||
delta -= 2.0 * std::f32::consts::PI;
|
|
||||||
}
|
|
||||||
if delta.abs() < 1e-4 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
add_fan_arc(verts, idx, p, a1, a1 + delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runde Kappe an einem offenen Ende: Halbkreis, der auf der Aussenseite
|
|
||||||
// (weg von der Linie) ausbaucht.
|
|
||||||
#[inline]
|
|
||||||
fn add_round_cap(verts: &mut Vec<f32>, idx: &mut Vec<u32>, p: Point, n: Point, sweep_sign: f32) {
|
|
||||||
let a0 = n[1].atan2(n[0]);
|
|
||||||
add_fan_arc(verts, idx, p, a0, a0 + sweep_sign * std::f32::consts::PI);
|
|
||||||
}
|
|
||||||
|
|
||||||
for v in 0..k {
|
|
||||||
let has_in = closed || v > 0;
|
|
||||||
let has_out = closed || v < k - 1;
|
|
||||||
let d_in = if has_in {
|
|
||||||
seg_dir[(v + segs - 1) % segs]
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let d_out = if has_out { seg_dir[v % segs] } else { None };
|
let n_out = if has_out {
|
||||||
match (d_in, d_out) {
|
left_normal(s[i], s[(i + 1) % k])
|
||||||
(Some(di), Some(do_)) => {
|
} else {
|
||||||
add_round_join(&mut self.line_verts, &mut self.line_idx, s[v], di, do_)
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (bx, by, miter): (f32, f32, f32);
|
||||||
|
match (n_in, n_out) {
|
||||||
|
(Some(a), Some(b)) => {
|
||||||
|
let sx = a[0] + b[0];
|
||||||
|
let sy = a[1] + b[1];
|
||||||
|
let slen = (sx * sx + sy * sy).sqrt();
|
||||||
|
if slen < 1e-3 {
|
||||||
|
// ~180-Grad-Umkehr -> kein sinnvoller Bisektor, gerade weiter.
|
||||||
|
bx = b[0];
|
||||||
|
by = b[1];
|
||||||
|
miter = 1.0;
|
||||||
|
} else {
|
||||||
|
bx = sx / slen;
|
||||||
|
by = sy / slen;
|
||||||
|
let denom = bx * b[0] + by * b[1]; // cos(theta/2)
|
||||||
|
miter = if denom > 1e-3 {
|
||||||
|
(1.0 / denom).min(MITER_LIMIT)
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(None, Some(do_)) => {
|
(Some(n), None) | (None, Some(n)) => {
|
||||||
// Start-Kappe: baucht rueckwaerts (weg vom ersten Segment) aus.
|
bx = n[0];
|
||||||
let n = left_normal_of(do_);
|
by = n[1];
|
||||||
add_round_cap(&mut self.line_verts, &mut self.line_idx, s[v], n, 1.0)
|
miter = 1.0;
|
||||||
}
|
}
|
||||||
(Some(di), None) => {
|
(None, None) => {
|
||||||
// End-Kappe: baucht vorwaerts (weg vom letzten Segment) aus.
|
// Bei k>=2 unerreichbar; sicherheitshalber gerade lassen.
|
||||||
let n = left_normal_of(di);
|
bx = 0.0;
|
||||||
add_round_cap(&mut self.line_verts, &mut self.line_idx, s[v], n, -1.0)
|
by = 0.0;
|
||||||
|
miter = 1.0;
|
||||||
}
|
}
|
||||||
(None, None) => {}
|
|
||||||
}
|
}
|
||||||
|
self.line_verts
|
||||||
|
.extend_from_slice(&[s[i][0], s[i][1], bx, by, 1.0, miter]);
|
||||||
|
self.line_verts
|
||||||
|
.extend_from_slice(&[s[i][0], s[i][1], bx, by, -1.0, miter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.add_line_batch(
|
let segs = if closed { k } else { k - 1 };
|
||||||
(self.line_idx.len() - start_idx_len) as u32,
|
for i in 0..segs {
|
||||||
color,
|
let a = base + 2 * i as u32;
|
||||||
stroke_mm,
|
let b = base + 2 * (((i + 1) % k) as u32);
|
||||||
width_screen,
|
self.line_idx
|
||||||
);
|
.extend_from_slice(&[a, a + 1, b, a + 1, b + 1, b]);
|
||||||
|
}
|
||||||
|
self.add_line_batch((segs * 6) as u32, color, stroke_mm, width_screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user