Compare commits
2 Commits
f95674aa09
...
a6c2c04736
| Author | SHA1 | Date | |
|---|---|---|---|
| a6c2c04736 | |||
| 956a85d93f |
-19
@@ -12,25 +12,6 @@ scripts/*.png
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# ── Broad material textures (fetched via scripts/fetch-materials.mjs) ─────────
|
|
||||||
# Binary JPGs from the DOSSIER-LIBRARY mirror are NOT committed here — run:
|
|
||||||
# node scripts/fetch-materials.mjs --library <DOSSIER-LIBRARY-path>
|
|
||||||
# to populate them locally. The 12 starter folders are explicitly excluded from
|
|
||||||
# this ignore rule so they stay tracked in git.
|
|
||||||
public/assets/materials/*/
|
|
||||||
!public/assets/materials/Bricks104/
|
|
||||||
!public/assets/materials/Concrete048/
|
|
||||||
!public/assets/materials/Grass005/
|
|
||||||
!public/assets/materials/Gravel043/
|
|
||||||
!public/assets/materials/Ground103/
|
|
||||||
!public/assets/materials/Marble012/
|
|
||||||
!public/assets/materials/Metal063/
|
|
||||||
!public/assets/materials/PavingStones150/
|
|
||||||
!public/assets/materials/Plaster001/
|
|
||||||
!public/assets/materials/Tiles141/
|
|
||||||
!public/assets/materials/Wood095/
|
|
||||||
!public/assets/materials/WoodFloor051/
|
|
||||||
|
|
||||||
# Generierte native-Viewport-Szenen (aus sampleProject via scripts/dump-native-scene.mjs)
|
# Generierte native-Viewport-Szenen (aus sampleProject via scripts/dump-native-scene.mjs)
|
||||||
src-tauri/assets/native2d_scene.json
|
src-tauri/assets/native2d_scene.json
|
||||||
src-tauri/assets/native3d_walls.json
|
src-tauri/assets/native3d_walls.json
|
||||||
|
|||||||
+297
-359
@@ -1,452 +1,390 @@
|
|||||||
# Architektur — Standalone Browser-BIM (cad)
|
# Architektur — Dossier (Desktop-CAAD)
|
||||||
|
|
||||||
> Stand: 2026-06-29
|
> Stand: 2026-07-21 (grundlegend überarbeitet — siehe [STATUS.md](STATUS.md) für
|
||||||
> Vision, Phasen und Backlog: [ROADMAP.md](ROADMAP.md). Konventionen: [CONVENTIONS.md](CONVENTIONS.md).
|
> die volle Bestandsaufnahme inkl. Mist-Liste, die diese Überarbeitung begründet).
|
||||||
> Detail-Designs: [docs/design/elements.md](docs/design/elements.md) ·
|
> Vision/Phasen (historisch, Tag-1-Stand): [ROADMAP.md](ROADMAP.md). Konventionen:
|
||||||
> [docs/design/plans-output.md](docs/design/plans-output.md) ·
|
> [CONVENTIONS.md](CONVENTIONS.md). Detail-Designs (teils ebenfalls veraltet,
|
||||||
> [docs/design/resources-graphics.md](docs/design/resources-graphics.md).
|
> siehe Hinweis in [docs/README.md](docs/README.md)): [docs/design/](docs/design/).
|
||||||
|
|
||||||
Dieses Dokument beschreibt, **wie** die Standalone-Browser-App gebaut wird und
|
Dieses Dokument beschreibt, **wie** Dossier tatsächlich gebaut ist — nicht wie
|
||||||
wie sie die Konzepte des DOSSIER-Rhino-Plugins in Browser-Äquivalente übersetzt.
|
es am ersten Tag geplant war. Dossier ist die eigenständige Neuimplementierung
|
||||||
DOSSIER ist ein Rhino-8-Plugin (Python + React-WebView); `cad` ist die *eigen
|
des Rhino-Plugins [DOSSIER](https://git.openbureau.ch/karim/dossier): dieselbe
|
||||||
ständige* Browser-Variante: kein Rhino-Document, kein `doc.Strings`, kein
|
Denkweise (Geschosse, Kategorie-Ebenen, mehrschichtige Bauteile,
|
||||||
IronPython — stattdessen ein **eigenes typisiertes Datenmodell in TypeScript**,
|
Prioritäts-Verschneidung), aber als **native Desktop-App** mit einem **eigenen
|
||||||
gerendert über **Three.js** (3D) und **SVG** (Plan), gespeichert als **JSON-Datei**.
|
typisierten Datenmodell in TypeScript** und **zwei eigenen Rust/WASM-Rendering-
|
||||||
|
Engines** („Nordstern") statt Rhino-Dokument/IronPython.
|
||||||
|
|
||||||
Alle Bezeichner im Code sind **englisch** (Vectorworks-Terminologie); Prosa und
|
**Desktop-Rahmen (plattformabhängig, wegen WebGPU):** auf **macOS Tauri**
|
||||||
UI-Texte sind deutsch. Einheiten intern in **Metern**.
|
(WKWebView unterstützt WebGPU), auf **Linux Electron/Chromium** (Tauris
|
||||||
|
Linux-Webview WebKitGTK unterstützt WebGPU nicht zuverlässig — die
|
||||||
|
render2d/render3d-Engines brauchen es). Beide teilen dieselbe React-App und
|
||||||
|
randlose Titelleiste; Laufzeit-Erkennung über `window.__TAURI__` bzw.
|
||||||
|
`window.dossierWindow` (Electron-`contextBridge`). Details: STATUS.md §2.7.
|
||||||
|
|
||||||
|
Alle Bezeichner im Code sind **englisch**; Prosa und UI-Texte sind deutsch.
|
||||||
|
Einheiten intern in **Metern**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 0. Leitprinzip — ein Modell, viele Darstellungen
|
## 0. Leitprinzip — ein Modell, viele Darstellungen
|
||||||
|
|
||||||
Das semantische Gebäudemodell ist die **einzige Wahrheit**. Jede Sicht (3D,
|
Das semantische Gebäudemodell (`Project`) ist die **einzige Wahrheit**. Jede
|
||||||
Grundriss, Schnitt, Ansicht) ist eine **reine Ableitung** (`derive`) daraus.
|
Sicht (3D, Grundriss, Schnitt, Ansicht) ist eine **reine Ableitung** daraus.
|
||||||
Darstellung (Detailgrad, Stile, Schraffuren, Overrides) wird **beim Rendern**
|
Darstellung (Detailgrad, Stile, Schraffuren, Overrides) wird **beim Rendern**
|
||||||
angewandt, nie in die Geometrie eingebacken.
|
angewandt, nie in die Geometrie eingebacken. Dieses Prinzip hat sich über drei
|
||||||
|
Wochen und ~125.000 Zeilen Code bewährt und wird strikt gehalten — es ist der
|
||||||
|
einzige Teil der ursprünglichen Architektur-Vision, der **unverändert** Bestand
|
||||||
|
hat. Alle konkreten Technologie-Entscheidungen darunter (Rendering-Engine,
|
||||||
|
State-Store, Schnitt-Mechanismus) sind anders gelaufen als am Tag 1 geplant;
|
||||||
|
Details dazu in [STATUS.md](STATUS.md) §5.
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────────────┐
|
┌──────────────────────────────────────────┐
|
||||||
│ Project (semantisches Modell, JSON) │ ← einzige Wahrheit
|
│ Project (semantisches Modell, JSON) │ ← einzige Wahrheit
|
||||||
│ resources · types · designLevels · │
|
│ resources · types · drawingLevels · │
|
||||||
│ layers · elements │
|
│ layers · walls/doors/openings/stairs/… │
|
||||||
└──────────────┬───────────────────────────┘
|
└──────────────┬───────────────────────────┘
|
||||||
│ pure derive()
|
│ pure derive()
|
||||||
┌───────────────────────┼────────────────────────────┐
|
┌───────────────────────┼────────────────────────────┬───────────────┐
|
||||||
▼ ▼ ▼
|
▼ ▼ ▼ ▼
|
||||||
Scene3D (Three.js) PlanModel → SVG SectionModel → SVG
|
3D-Viewport 2D-Plan (generatePlan) 3D-Live-Schnitt Export
|
||||||
buildScene() generatePlan() generateSection() (HLR)
|
Viewport3D (three.js) PlanView (SVG) · glPlan (GL2) render3d/section IFC/DXF/
|
||||||
│ │ │
|
ODER Wasm3DViewport ODER render2d (Rust/WGSL) (Rust, analytisch)PDF/STL
|
||||||
└────────── beide lesen dieselben joins/components/styles ──────────┘
|
(Rust/wgpu, Default)
|
||||||
|
│ │ │ │
|
||||||
|
└────────── alle lesen dieselben joins/components/styles ─────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
Das steht im Spike bereits: `generatePlan.ts` und `Viewport3D.tsx` extrudieren
|
|
||||||
**dasselbe** gehrte Band-Polygon (`clippedBand`). Diese Symmetrie ist der Kern
|
|
||||||
und wird beim Ausbau strikt gehalten.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Repo-Struktur (Ziel)
|
## 1. Repo-Struktur (IST-Zustand)
|
||||||
|
|
||||||
Wächst aus dem heutigen `src/` (model/plan/viewport/ui). Module sind **klein und
|
|
||||||
fachlich geschnitten** — wir vermeiden bewusst den `elemente.py`-Monolithen
|
|
||||||
(7244 LOC) aus DOSSIER (dort als Schwachstelle #4.1 dokumentiert).
|
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
model/
|
model/ Project-Schema (types.ts, ~2500 LOC), joins.ts (Wand-Gehrung/
|
||||||
types.ts // Project, DesignLevel, LayerCategory, Element-Union (existiert)
|
-Prio-Stösse), parametricWalls.ts, roomStamp.ts, terrain.ts,
|
||||||
geometry.ts // 2D/3D-Mathe ohne Kernel (existiert)
|
geoRebase.ts, sampleProject.ts
|
||||||
joins.ts // Wand-Verschneidung (Gehrung; später Prio-T/X) (existiert)
|
geometry/ 2D-Kernel (kernel2d.ts: offset/trim/fillet/split — LIVE, siehe
|
||||||
sampleProject.ts // Demo-Haus (existiert)
|
§3), ceiling/opening/roomArea/roomBoundary/stair/roof/column.ts,
|
||||||
project.ts // Factory, Defaults, Migrationen
|
polygonHoles.ts
|
||||||
selectors.ts // abgeleitete Reads (visibleCodes, elementsOnLevel…)
|
commands/ Rhino-artiges Kommandosystem: types/registry/engine/parseInput.ts
|
||||||
ids.ts // newId(prefix) — UUID-Erzeugung
|
+ cmds/ (ein Modul je Kommando: wall, opening, stair, roof,
|
||||||
elements/ // pro Bauteil ein Modul (Daten + Generierung)
|
column, room, line/rect/circle/arc, move/mirror/copy/offset/
|
||||||
wall.ts opening.ts slab.ts stair.ts roof.ts structure.ts space.ts
|
trim/join, extrude, import, terrain, measure, georef, …)
|
||||||
resources/
|
tools/ Interaktive Zeichenwerkzeuge, snapping.ts, transform.ts (Grips)
|
||||||
componentManager.ts hatchManager.ts lineManager.ts symbolLibrary.ts
|
compute/ Compute-Boundary: leitet Ops (aktuell nur computeJoins) unter
|
||||||
overrides.ts // regelbasierte Engine (Condition → Action)
|
Tauri via `invoke` an Rust weiter, sonst TS-Fallback
|
||||||
store/
|
plan/ generatePlan.ts (2D-Ableitung), PlanView.tsx (SVG + Pan/Zoom/
|
||||||
store.ts // Zustand-Store: { project, ui }, Aktionen, Undo/Redo
|
Grips), glPlan/ (eigener WebGL2-Renderer), toSection.ts/
|
||||||
persistence.ts // Datei save/load (JSON), Autosave (IndexedDB)
|
toElevation.ts (Schnitt/Ansicht-Ableitung), toWalls3d.ts,
|
||||||
history.ts // Undo/Redo-Ring
|
toRenderScene.ts (Szene für Rust-render2d), wallMeshCut.ts
|
||||||
plan/
|
viewport/ Viewport3D.tsx (three.js, „Free") UND Wasm3DViewport.tsx
|
||||||
generatePlan.ts // Grundriss aus Footprint + Symbolik (existiert)
|
(Rust/wgpu „Nordstern", Default/editierbar), raycast3d.ts
|
||||||
generateSection.ts // Schnitt/Ansicht aus 3D-Projektion (HLR-Worker)
|
section/ TOTER Code (OCCT-WASM-HLR-Spike, keine Aufrufer mehr) — Schnitt
|
||||||
PlanView.tsx // SVG-Renderer + Pan/Zoom/Grips (existiert)
|
läuft über render3d/section.rs, siehe §4.3
|
||||||
primitives.ts // Primitive-Union, SVG-Serializer, DXF/PDF-Export
|
export/ exportIfc.ts (IFC4), exportDxf.ts/dxfWriter.ts, exportPdf.ts,
|
||||||
viewport/
|
layoutPdf.ts (Mehrseiten-PDF pro Ordner), exportMesh.ts (STL/OBJ),
|
||||||
Viewport3D.tsx // Three.js-Szene aus dem Modell (existiert)
|
exportSchedule.ts (CSV-Bauteilliste), sceneToPrintSvg.ts
|
||||||
scene.ts // buildScene(project) → THREE.Group (Layer-Spiegel)
|
materials/ ambientcg.ts (Live-Suche ambientCG-API), library.ts (13
|
||||||
clip.ts // Plan-/Schnitt-Clipping über THREE.Plane
|
gebündelte Starter-Materialien), runtime.ts (PBR-Material aus
|
||||||
camera.ts // Kamera-Presets, Norden-Rotation
|
ComponentMaterial via three.js TextureLoader)
|
||||||
sheets/
|
io/ DXF/DWG-Import, Swisstopo (swissBUILDINGS3D/-ALTI3D/SWISSIMAGE,
|
||||||
layout.ts // Sheet/Viewport-Datenmodell
|
LV95↔WGS84), OSM-Overpass, .lin/.pat-Parser, projectFile.ts
|
||||||
SheetEditor.tsx // Plansatz-Editor
|
(.obp Speichern/Laden, Tauri-Lock)
|
||||||
exportPdf.ts // Vektor-PDF (svg → pdf-lib / jsPDF)
|
overrides/ Regelbasierte Override-Engine (Bedingung → Aktion)
|
||||||
workers/
|
state/ Eigener Store auf `useSyncExternalStore` (KEIN Zustand/Redux):
|
||||||
geometry.worker.ts // Booleans (Öffnungen) + HLR via Comlink
|
store.ts + Slices (project/history/selection/view/layout/site/
|
||||||
ui/
|
notify), appStore.ts komponiert sie
|
||||||
App.tsx Navigator panels… // React-Oberfläche (heute in App.tsx, wird gesplittet)
|
panels/ Dock-/Floating-Panel-System (Dock/FloatingPanel/TabStrip/
|
||||||
|
registry/layout) + Panels (Tools, Attributes, ObjectInfo, Layers,
|
||||||
|
DrawingLevels, Site, RoomBalance, Elements, ViewSnapshots, Layouts)
|
||||||
|
ui/ App.tsx (Shell, **7.130 LOC — noch nicht auf dünne Shell
|
||||||
|
reduziert**, siehe STATUS.md §4.3), TopBar, ResourceManager.tsx
|
||||||
|
(Material-/Hatch-/Line-/Typ-Editoren, natives Fenster),
|
||||||
|
ContextMenu, CommandLine.tsx, LayoutSheet/-Menu, ribbon/
|
||||||
|
native/ NUR Tauri: native Fenster (Resources/Settings/DrawingLevels/
|
||||||
|
LayerSettings/ContextImport), macOS-Menüleiste, Fenster-Chrome —
|
||||||
|
jede Funktion no-opt via `isTauriRuntime()` im Browser
|
||||||
|
editors/ booleanOps.ts (2D-Boolean via polygon-clipping), splitJoin.ts
|
||||||
|
text/ Rich-Text (richText.ts, RichTextEditor.tsx, renderHtml.ts)
|
||||||
|
theme/ Hell/Dunkel, Akzentfarben
|
||||||
|
i18n/ de.ts/en.ts Wörterbücher, eigener t()-Mechanismus
|
||||||
|
engine/ Reine WASM-Lade-Glue: engine3d.ts (pkg3d), truckSolid.ts
|
||||||
|
(pkgTruck), plan/useWasmPlanRenderer.ts (pkg) — pkgGeometry und
|
||||||
|
pkgDwgImport sind gebaut, aber unbenutzt (siehe STATUS.md §4.1)
|
||||||
|
|
||||||
|
src-tauri/
|
||||||
|
src/ Tauri-Host (Fenster, native Dialoge, fs4-Exklusiv-Lock)
|
||||||
|
render2d/ 2D-Plan-GPU-Renderer (wgpu/WGSL, + glyphon-Text), auch → WASM
|
||||||
|
render3d/ „Nordstern" 3D-Engine (wgpu/WGSL): Wandextrusion + Schicht-
|
||||||
|
bänder + Gehrung, LIVE 2D=3D-Schnitt (section*.rs, analytisch,
|
||||||
|
kein HLR), Kanten-Extraktion, Materialtextur-Arrays,
|
||||||
|
Aerial-Drape, Render-Styles (Shaded/White/Textured/Wireframe/
|
||||||
|
Hidden/ShadedEdges) — auch → WASM
|
||||||
|
kernel2d/ Rust-Port von geometry/kernel2d.ts — NUR Paritätstest, nicht
|
||||||
|
produktiv (WASM war < 100 Wänden langsamer als TS)
|
||||||
|
geometry/ Wand-Join-Mathe — Rust-Port, UNBENUTZT (kein Aufrufer)
|
||||||
|
trucksolid/ CSG/Extrusion (`truck`-Crate + `csgrs`-Booleans) → WASM,
|
||||||
|
genutzt vom Extrude-Kommando; Boolean NICHT an Wände/
|
||||||
|
Öffnungen angeschlossen
|
||||||
|
dwgimport/ DXF-Parser-Spike (`acadrust`) → WASM, UNBENUTZT (DWG-Import
|
||||||
|
läuft über npm `@mlightcad/libredwg-web`)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Jedes Crate unter `src-tauri/` ausser dem Host ist ein **eigenständiges
|
||||||
|
Cargo-Package** (kein gemeinsamer Workspace), das sowohl headless
|
||||||
|
(`cargo test`) als auch per `wasm-pack --features web` baut und dann via
|
||||||
|
`src/engine/pkg*/` von der TS-Seite geladen wird (`npm run build:engine{,3d,
|
||||||
|
Geometry,Kernel2d,DwgImport}`/`build:truck`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Datenmodell
|
## 2. Datenmodell
|
||||||
|
|
||||||
### 2.1 Zwei unabhängige Achsen (DOSSIER-Modell, im Spike umgesetzt)
|
### 2.1 Zwei unabhängige Achsen (unverändert gegenüber der Vision)
|
||||||
|
|
||||||
DOSSIER trennt zwei orthogonale Konzepte, persistiert als zwei getrennte
|
1. **Zeichnungsebenen** (`DrawingLevel`) — Geschoss (`kind:"floor"`,
|
||||||
JSON-Bäume (`dossier_zeichnungsebenen`, `dossier_ebenen`). Wir übernehmen das
|
`floorHeight`/`cutHeight`/`baseElevation`), Schnitt/Ansicht
|
||||||
1:1 in `types.ts` (existiert bereits als `drawingLevels` + `layers`):
|
(`kind:"section"|"elevation"`), oder freie Zeichnung (`kind:"drawing"`).
|
||||||
|
2. **Ebenen** (`LayerCategory`) — das Grafik-Kategorie-Schema (Baum,
|
||||||
|
`{code, name, color, lw, visible, locked, hatch?, children}`), in jedem
|
||||||
|
Geschoss gültig. Codes 1:1 aus DOSSIER (`00 Raster · 01 Vermessung ·
|
||||||
|
20 Wände (└21 Türen/Fenster, 25 Stützen) · 30 Decken · 31 Dächer ·
|
||||||
|
40 Treppen · 50 Text · 60 Räume · 80 Plangrafik …`).
|
||||||
|
|
||||||
1. **Zeichnungsebenen** (`DesignLevel` / `DrawingLevel`) — die obersten
|
### 2.2 Elemente — typisierte Arrays statt `Element[]`-Union
|
||||||
Dokument-Abschnitte. Zwei produktive Arten:
|
|
||||||
- **Geschoss** (`kind:"floor"`): `floorHeight`, `cutHeight`,
|
|
||||||
`baseElevation` (akkumuliert via `recomputeFloorElevations`), `visible`/`locked`.
|
|
||||||
- **Schnitt/Ansicht** (`kind:"section"|"elevation"`): `linePoints`,
|
|
||||||
`directionSign`, Höhenbereich, Tiefe.
|
|
||||||
- **Zeichnung** (`kind:"drawing"`): freie 2D-Ebene ohne Geschossbezug.
|
|
||||||
2. **Ebenen** (`LayerCategory`) — das **Grafik-Kategorie-Schema**, das in *jedem*
|
|
||||||
Geschoss gilt. Baum mit `{code, name, color, lw, visible, locked, hatch?, children}`.
|
|
||||||
Codes 1:1 wie DOSSIER (`DEFAULT_LAYER_SCHEMA` aus `launcher/src/App.jsx`):
|
|
||||||
`00 Raster · 01 Vermessung · 20 Wände (└21 Türen/Fenster, 22 Möbel, 25 Stützen) ·
|
|
||||||
30 Decken · 31 Dächer · 35 Träger · 50 Text · 60 Plangrafik …`
|
|
||||||
|
|
||||||
Ein **Element** kennt sein **Geschoss** (`floorId`) *und* seine **Ebene**
|
Anders als ursprünglich geplant hält `Project` (`src/model/types.ts:2095`)
|
||||||
(`categoryCode`). Sichtbarkeit ergibt sich aus dem Schnitt (Geschoss `geschoss × code`
|
**pro Bauteiltyp ein eigenes (meist optionales) Array**:
|
||||||
Matrix), genau wie DOSSIERs `apply_visibility(z_mode, e_mode)` in `layer_builder.py`.
|
|
||||||
|
|
||||||
> **Begriffsnotiz:** Heute heißt der Typ im Code `DrawingLevel`. ROADMAP §5 nennt
|
|
||||||
> die Modell-Eingabe (Geschosse) Vectorworks-konform **Design Layer** und die
|
|
||||||
> abgeleitete Ausgabe **Drawing Layer / Sheet**. Wir behalten `DrawingLevel` als
|
|
||||||
> Union (kind=floor ≙ Design Layer, kind=section/elevation/drawing ≙ Drawing
|
|
||||||
> Layer) und führen `Sheet` erst separat ein (§2.5, plans-output.md). Kein Rename
|
|
||||||
> ohne expliziten Auftrag.
|
|
||||||
|
|
||||||
### 2.2 Ressourcen-Bibliotheken (Vectorworks-Stil)
|
|
||||||
|
|
||||||
Neuer Block `Project.resources` (heute provisorisch als flache `materials[]`).
|
|
||||||
Alles verweist **per id** — zentral änderbar. Details: resources-graphics.md.
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
interface Resources {
|
interface Project {
|
||||||
lineStyles: LineStyle[]; // Line Manager: { id, name, weight(mm), color, dash:number[] }
|
walls: Wall[];
|
||||||
hatches: Hatch[]; // Hatch Manager: { id, name, pattern, scale, angle, lineStyleId }
|
ceilings?: Ceiling[];
|
||||||
components: Component[]; // Component Manager (= DOSSIER-Material erweitert):
|
roofs?: Roof[];
|
||||||
// { id, name, hatchId, color3d, texture3d?, joinPriority }
|
doors: Door[]; // ÄLTER — siehe openings; bewusste Doppelspur
|
||||||
|
openings?: Opening[]; // NEUER, allgemeiner: kind:"window"|"door"
|
||||||
|
stairs?: Stair[];
|
||||||
|
rooms?: Room[];
|
||||||
|
columns?: Column[];
|
||||||
|
extrudedSolids?: ExtrudedSolid[];
|
||||||
|
drawings2d: Drawing2D[];
|
||||||
|
context?: ContextObject[]; // Terrain/Importe — NICHT semantisch
|
||||||
|
parametricWalls?: ParametricWall[];
|
||||||
|
overrideRules?: OverrideRule[];
|
||||||
|
viewSnapshots?: ViewSnapshot[];
|
||||||
|
viewSnapshotFolders?: ViewSnapshotFolder[];
|
||||||
|
layouts?: Layout[];
|
||||||
|
masterLayouts?: MasterLayout[];
|
||||||
|
layoutFolders?: LayoutFolder[];
|
||||||
|
// + Bibliotheken: lineStyles, hatches, components, wallTypes, roofTypes?,
|
||||||
|
// doorTypes?, windowTypes?, stairTypes?, ceilingTypes?
|
||||||
|
// + drawingLevels, layers, geoAnchor?, referenceElevationMasl?
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`joinPriority` ist DOSSIERs `_MATERIAL_PRIO` (Beton 800 … Putz 100) als
|
Ein `Element`-Typalias existiert noch (`kind:"door"|"window"` etc.), wird aber
|
||||||
**Daten** statt Hardcode — steuert die Prioritäts-T-Verschneidung (Risiko #1).
|
nirgends im Code referenziert — Selektion/Element-Baum arbeiten direkt auf den
|
||||||
|
typisierten Arrays. `doors`/`openings` sind eine **bekannte, noch nicht
|
||||||
|
konsolidierte Doppelspur** (PENDENZEN.md).
|
||||||
|
|
||||||
### 2.3 Aufbauten (mehrschichtige Typen)
|
### 2.3 Ressourcen & Aufbauten (unverändert gegenüber der Vision)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
interface Resources { lineStyles: LineStyle[]; hatches: Hatch[]; components: Component[]; }
|
||||||
interface WallType { id; name; layers: { componentId; thickness }[]; }
|
interface WallType { id; name; layers: { componentId; thickness }[]; }
|
||||||
interface SlabType { id; name; layers: { componentId; thickness }[]; }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Schichtdicke liegt am Layer, **Priorität am Component** (so muss man Prio nur
|
`joinPriority` sitzt am `Component` (Daten statt Hardcode) und steuert die
|
||||||
einmal pflegen). 3D und Plan lesen dieselben `layers[]`.
|
Prioritäts-T-/X-Verschneidung — **fertig implementiert**, inklusive Rust-Port
|
||||||
|
für den 3D-Live-Schnitt (`section_boolean.rs` = Port von
|
||||||
|
`toSection.ts::subtractDominantBands`).
|
||||||
|
|
||||||
### 2.4 Elemente
|
### 2.4 Layouts / Ausschnitte
|
||||||
|
|
||||||
Diskriminierte Union `Element` (heute `Wall | Door`), erweitert um
|
`ViewSnapshot` (Kamera/Massstab/Detailgrad/Sichtbarkeiten/Override-Preset) in
|
||||||
`Window | Slab | Stair | Roof | Column | Beam | Space | Draw2d`. Jedes Element:
|
`ViewSnapshotFolder`-Bäumen; `Layout` (Papierformat, mehrere
|
||||||
`{ id, type, floorId, categoryCode, ... }`. Gehostete Elemente (Tür/Fenster)
|
`LayoutViewport`s, freie `LayoutAnnotation`s, optionale `MasterLayout`-Vererbung)
|
||||||
tragen `hostWallId` statt `floorId` (Geschoss ergibt sich aus der Wand). Volle
|
in `LayoutFolder`-Bäumen. Beide gehören ins Dokument (nicht LocalStorage).
|
||||||
Felder: elements.md.
|
|
||||||
|
|
||||||
### 2.5 Pläne / Output (eigene Achse)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Sheet { // Plansatz-Blatt (≙ DOSSIER Layout/PageView)
|
|
||||||
id; name; paper: "A0".."A4"|"Letter"; landscape: boolean;
|
|
||||||
viewports: SheetViewport[]; // platzierte Ableitungen + Titelblock
|
|
||||||
folder?: string;
|
|
||||||
}
|
|
||||||
interface SheetViewport { // ≙ DOSSIER Detail + gebundener Ausschnitt
|
|
||||||
id; rect: Rect; sourceViewId: string; // referenziert DrawingLevel oder ViewSnapshot
|
|
||||||
scale: number; // 1:N
|
|
||||||
}
|
|
||||||
interface ViewSnapshot { // ≙ DOSSIER Ausschnitt (View-Snapshot)
|
|
||||||
id; name; folder?;
|
|
||||||
camera: CameraState; scale: number; detailLevel: DetailLevel;
|
|
||||||
visibility: VisibilityState; // pro Geschoss/Ebene
|
|
||||||
overrides?: { presetId?; enabled };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. State, Persistenz, Undo/Redo
|
## 3. State, Persistenz, Undo/Redo
|
||||||
|
|
||||||
### 3.1 Store — Zustand
|
### 3.1 Store — eigener `useSyncExternalStore`, nicht Zustand
|
||||||
|
|
||||||
Heute: `useState<Project>` in `App.tsx`, immutabel via `setProject`. Das skaliert
|
Entgegen der ursprünglichen Empfehlung (Zustand-Library) wurde ein
|
||||||
nicht für Werkzeuge + Undo. Migration auf **Zustand** (ROADMAP §4):
|
**abhängigkeitsfreier Store** gebaut (`src/state/store.ts`, gleiches Muster wie
|
||||||
|
`src/i18n`): `createStore()` komponiert Slice-Fabriken über eine gemeinsame
|
||||||
|
`RootState`. `appStore.ts` fügt zusammen: `projectSlice` (Projekt + Undo/Redo,
|
||||||
|
`setProject` als einziger Mutations-Einstiegspunkt), `historySlice`,
|
||||||
|
`selectionSlice`, `viewSlice`, `layoutSlice`, `siteSlice`, `notifySlice`
|
||||||
|
(Toast/Confirm-Ersatz für `window.alert`, in Tauris WKWebView deaktiviert).
|
||||||
|
Komponenten lesen über `useStore(selector)`.
|
||||||
|
|
||||||
```ts
|
**Nicht umgesetzt** (geplant in `docs/design/state-architecture.md`): die
|
||||||
interface AppState {
|
Extraktion von View-Routing nach `src/views/` und Kontextmenü-Aufbau nach
|
||||||
project: Project; // das Dokument
|
`src/menus/` — beide Ordner existieren nicht, diese Logik liegt weiterhin
|
||||||
ui: { // flüchtig, NICHT persistiert/undobar
|
inline in `App.tsx` (7.130 Zeilen). Siehe STATUS.md §4.3.
|
||||||
activeLevelId; activeViewType; selection: string[];
|
|
||||||
activeTool: ToolId; hover; cameraByView; planTransform;
|
|
||||||
};
|
|
||||||
// Aktionen mutieren via Immer-Producer; jede Modell-Mutation pusht History.
|
|
||||||
apply(mutator: (p: Project) => void): void;
|
|
||||||
undo(): void; redo(): void;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Trennung Modell ↔ UI** ist hart: nur `project` ist undobar und wird gespeichert.
|
### 3.2 Persistenz
|
||||||
`ui` (Auswahl, Kamera, aktives Werkzeug) lebt separat. Das ersetzt DOSSIERs
|
|
||||||
`sc.sticky`-Bus — wo DOSSIER cross-modul über Sticky-Keys kommuniziert, lesen bei
|
|
||||||
uns einfach alle Komponenten denselben Store (reaktiv). **Kein Sticky, kein
|
|
||||||
Bridge-Polling, keine `is not None`-Lücken** (DOSSIER-Schwachstelle #4.4).
|
|
||||||
|
|
||||||
### 3.2 Persistenz — JSON-Datei statt `doc.Strings`
|
- **Datei:** eigenes `.obp`-Format (`src/io/projectFile.ts`), unter Tauri über
|
||||||
|
native Speichern/Öffnen-Dialoge (`plugin-fs`/`plugin-dialog`), im Browser
|
||||||
DOSSIER speichert *alles* in `doc.Strings` (Key-Value am Rhino-Document) +
|
Blob-Fallback. Ältere `.json`-Projekte bleiben ladbar.
|
||||||
globale Presets als JSON-Dateien im User-Home. Browser-Äquivalent:
|
OS-Level-Exklusiv-Lock (`fs4`-Crate, `src-tauri/src/lock.rs`) verhindert
|
||||||
|
Doppelöffnen desselben Projekts.
|
||||||
| DOSSIER | Browser (cad) |
|
- **Compute-Boundary:** `src/compute/index.ts` leitet einzelne Operationen
|
||||||
|---|---|
|
(aktuell nur `computeJoins`) unter Tauri via `invoke` an eine native Rust-
|
||||||
| `doc.Strings.SetString(key, json)` | Feld im `Project`-Objekt (ein JSON-Baum) |
|
Implementierung weiter; im Browser bzw. für nicht migrierte Ops (Room-
|
||||||
| `.3dm`-Datei mit eingebetteten Strings | **`.cad.json`-Datei** via File System Access API |
|
Detection, DWG-Parsing) läuft die TS-Implementierung.
|
||||||
| Globale Presets (`~/Library/.../*.json`) | **LocalStorage** (`cad.presets.*`) + Export/Import |
|
|
||||||
| Launcher schreibt Settings-Datei | **IndexedDB** Autosave (Phase 5) |
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// persistence.ts
|
|
||||||
const FORMAT_VERSION = 1;
|
|
||||||
function serialize(p: Project): string // JSON.stringify({ version, project })
|
|
||||||
function deserialize(text: string): Project // + migrate(version) bis aktuell
|
|
||||||
async function saveToFile(p: Project): Promise<void> // showSaveFilePicker → .cad.json
|
|
||||||
async function loadFromFile(): Promise<Project> // showOpenFilePicker; Fallback <input type=file>
|
|
||||||
async function autosave(p: Project) // IndexedDB, debounced 2 s
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Save/Load:** [File System Access API](https://developer.mozilla.org/docs/Web/API/File_System_Access_API)
|
|
||||||
(`showSaveFilePicker`/`showOpenFilePicker`); Fallback Download-Blob + `<input type=file>`
|
|
||||||
für Firefox/Safari.
|
|
||||||
- **Autosave/Recovery:** IndexedDB (via `idb`), entkoppelt vom expliziten Speichern.
|
|
||||||
- **Cross-Projekt-Presets:** LocalStorage mit Namespacing (`cad.presets.overrides`,
|
|
||||||
`cad.presets.wallTypes`, …) + JSON-Export/Import — ersetzt DOSSIERs
|
|
||||||
`~/Library/.../override_presets.json` (overrides.py).
|
|
||||||
- **Migrationen:** `migrate(data, fromVersion)`-Kette, additiv. DOSSIER macht das
|
|
||||||
per Sticky-Prefix (`traite_`→`pause_`→`dossier_`); wir machen es versioniert im
|
|
||||||
Datei-Schema.
|
|
||||||
|
|
||||||
### 3.3 Undo/Redo
|
### 3.3 Undo/Redo
|
||||||
|
|
||||||
DOSSIER überlässt Undo Rhino (und hat dadurch Cache-Stale-Bugs, Schwachstelle
|
Eigener History-Ring im `projectSlice` (kein DOSSIER-Sticky-Bus, kein
|
||||||
#4.3). Wir besitzen das Dokument selbst → **eigener History-Ring**:
|
Cache-Stale-Problem, da alle Sichten pure Ableitungen sind).
|
||||||
|
|
||||||
```ts
|
|
||||||
// history.ts — Snapshots des project-Teilbaums (strukturelles Sharing via Immer-Patches)
|
|
||||||
interface History { undo: Patch[][]; redo: Patch[][]; }
|
|
||||||
```
|
|
||||||
|
|
||||||
Jede `apply()`-Mutation erzeugt Immer-Patches → Push auf `undo`. Da abgeleitete
|
|
||||||
Sichten **pure** sind, ist nach `undo()` kein Cache zu invalidieren — der
|
|
||||||
ganze Cache-Stale-Komplex aus DOSSIER (`_JOINTS_CACHE_KEY`, Material-Cache)
|
|
||||||
entfällt strukturell.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Rendering
|
## 4. Rendering
|
||||||
|
|
||||||
### 4.1 Three.js-Szene spiegelt den Ebenen-Baum
|
### 4.1 Zwei 3D-Viewports
|
||||||
|
|
||||||
DOSSIER baut in Rhino eine **Layer-Hierarchie** `Zeichnungsebene → CODE_Name-Sublayer`
|
- **`Viewport3D.tsx`** — three.js, die „Free"-Stufe.
|
||||||
(`layer_builder.build_layers`) und hängt jedes Objekt an den passenden Sublayer.
|
- **`Wasm3DViewport.tsx`** — Rust/wgpu, „Nordstern", editierbar, **Default**.
|
||||||
Browser-Spiegel: ein **`THREE.Group`-Baum** mit identischer Struktur, gebaut aus
|
Ein Settings-Schalter wählt die Engine; WebGL2/three.js nur noch als
|
||||||
dem Modell (nicht persistiert — reine Ableitung):
|
explizite Wahl oder Fallback.
|
||||||
|
|
||||||
|
`render3d` (10.246 LOC) baut Wandextrusion inkl. Schichtbändern und
|
||||||
|
Prioritäts-Gehrung (`compute_wall_miters`), Materialtextur-Arrays (pro
|
||||||
|
Wandschicht) + separate Aerial-Drape-Textur, Render-Styles (Shaded/White/
|
||||||
|
Textured/Wireframe/Hidden/ShadedEdges via Kanten-Extraktion mit
|
||||||
|
Crease-Erkennung). **Dächer/Treppen/Stützen haben KEINE eigene Rust-Geometrie**
|
||||||
|
— sie werden vollständig in TypeScript erzeugt (`geometry/roof.ts`,
|
||||||
|
`emitRoofs`/`emitColumns`) und Rust nur als vorberechnetes Dreiecks-Mesh
|
||||||
|
(`MeshInput`/`append_context_mesh`, derselbe generische Importpfad wie für
|
||||||
|
swissBUILDINGS3D/DXF) übergeben.
|
||||||
|
|
||||||
|
### 4.2 Grundriss = drei koexistierende Renderer
|
||||||
|
|
||||||
|
1. **`PlanView.tsx`** (SVG) — bleibt immer im DOM (Hit-Testing/Grips),
|
||||||
|
unabhängig vom aktiven Zeichenpfad.
|
||||||
|
2. **`plan/glPlan/`** — eigener TypeScript-WebGL2-Renderer.
|
||||||
|
3. **`useWasmPlanRenderer.ts`** → Rust-`render2d` (WGSL, wgpu nativ,
|
||||||
|
WebGPU/WebGL2-Fallback im Web, echtes Text-Rendering via `glyphon`).
|
||||||
|
|
||||||
|
Beide GPU-Pfade fallen bei Init-Fehler still auf SVG zurück.
|
||||||
|
|
||||||
|
### 4.3 Schnitt/Ansicht — analytische Rust-Pipeline, KEIN HLR
|
||||||
|
|
||||||
|
Der ursprünglich geplante OCCT-WASM-HLR-Pfad (`src/section/hlr.ts`/`occt.ts`)
|
||||||
|
ist **toter Code** (keine Aufrufer mehr). Der tatsächlich funktionierende
|
||||||
|
Live-Schnitt nutzt aus, dass jedes Bauteil ein Prisma mit konstantem
|
||||||
|
Querschnitt ist — eine Schnittebene liefert dadurch **immer** ein
|
||||||
|
achsparalleles Rechteck, nie ein Trapez, wodurch HLR unnötig wird:
|
||||||
|
|
||||||
```
|
```
|
||||||
scene
|
App.tsx (section3dCutId/section3dPlane)
|
||||||
└─ levelGroup[floorId] (y-Offset = baseElevation) ← Geschoss
|
→ Wasm3DViewport.tsx (section3d-Prop → setSectionPlane)
|
||||||
└─ categoryGroup[categoryCode] (userData.code) ← Ebene
|
→ src-tauri/render3d/src/{section.rs, section_boolean.rs, section_fill.rs}
|
||||||
└─ element meshes (userData.elementId)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
`section_boolean.rs` ist ein 1:1-Port von `toSection.ts::subtractDominantBands`
|
||||||
// scene.ts
|
— 2D-Plan-Schnitt und 3D-Live-Schnitt nutzen dieselbe Prioritäts-Logik und
|
||||||
function buildScene(project: Project, vis: VisibilityState): THREE.Group
|
stimmen dadurch exakt überein. Kein Worker, kein Comlink (beides war geplant,
|
||||||
function syncScene(root: THREE.Group, project, vis) // diff statt full rebuild (Perf)
|
existiert nirgends im Projekt) — läuft synchron GPU-seitig.
|
||||||
```
|
|
||||||
|
|
||||||
- **Sichtbarkeit:** `categoryGroup.visible = vis.codes.has(code)` und
|
### 4.4 Öffnungen als Löcher (kein Mesh-Boolean)
|
||||||
`levelGroup.visible = vis.floors.has(floorId)` — entspricht DOSSIERs
|
|
||||||
`apply_visibility`, aber als simples `.visible`-Toggle statt Layer-Table-Modify.
|
|
||||||
- **Farbe/Material:** Component → `MeshStandardMaterial` (PBR später), per
|
|
||||||
`componentId` gecacht (wie heute `layerMaterial`-Cache, aber projektweit).
|
|
||||||
- **„Grau/gesperrt"-Modi** (DOSSIER `grey`/`grey_locked`): graues Override-Material
|
|
||||||
auf der Gruppe statt Layer-Color-Tausch.
|
|
||||||
|
|
||||||
### 4.2 Grundriss = Clipping-Ebene + symbolische Generierung
|
Fenster/Türen schneiden echte achsparallele Rechteck-Löcher aus dem Wandkörper
|
||||||
|
(`plan/toWalls3d.ts` `subtractSpans`, gespiegelt in `render3d::mesh.rs`).
|
||||||
|
Ein generisches Mesh-CSG-Boolean existiert bereits (`trucksolid::boolean_mesh`,
|
||||||
|
`csgrs`, für das Extrude-Kommando genutzt), ist aber nicht an die
|
||||||
|
Wand/Öffnungs-Pipeline angeschlossen.
|
||||||
|
|
||||||
DOSSIER zeigt den Grundriss eines Geschosses, indem es eine **Clipping-Plane** auf
|
### 4.5 Materialien
|
||||||
`okff + schnitthöhe` legt, Normale −Z (`layer_builder.update_clipping_plane`).
|
|
||||||
Zwei Browser-Pfade — **beide nötig** (ROADMAP §3):
|
|
||||||
|
|
||||||
1. **3D-Viewport im Plan-Modus:** `THREE.Plane(normal=(0,0,-1), constant=cutZ)` über
|
`materials/library.ts` (13 gebündelte PBR-Starter, ambientCG CC0) +
|
||||||
`renderer.clippingPlanes` + `material.clippingPlanes`. `clip.ts` setzt die Ebene
|
`materials/ambientcg.ts` (Live-Suche der kompletten ambientCG-Bibliothek,
|
||||||
pro aktivem Geschoss (`cutZ = baseElevation + cutHeight`), Kamera orthografisch
|
1K/2K/4K-Auflösungswahl, On-Demand-Download via `jszip`, Proxy wegen CORS) →
|
||||||
von oben. So sieht man den **echten geschnittenen Volumenkörper**.
|
`materials/runtime.ts` baut daraus gecachte `THREE.MeshStandardMaterial`s mit
|
||||||
2. **Symbolischer SVG-Grundriss** (der Hauptweg, schnell + sauber): `generatePlan.ts`
|
physisch korrekter Kachelgrösse (UV in Weltmetern).
|
||||||
erzeugt Vektor-Primitive **direkt aus Parametern** — Wand-Schnittbänder,
|
|
||||||
Öffnungs-Aussparungen, Tür-Schwenkbögen — *ohne* Mesh zu schneiden. Steht im
|
|
||||||
Spike. Ausbau: Schraffuren, Lauflinien, Bemaßung, LoD (plans-output.md).
|
|
||||||
|
|
||||||
### 4.3 Schnitt/Ansicht = Kamera + Schnittebenen + HLR
|
|
||||||
|
|
||||||
DOSSIER setzt 1–2 Clipping-Planes (Cut + Back), Parallel-Projektion senkrecht zur
|
|
||||||
Linie, zoomt auf die BBox (`schnitte.activate_schnitt`). Browser:
|
|
||||||
|
|
||||||
- **3D-Vorschau:** zwei `THREE.Plane` (Cut auf der Linie, Back in `directionSign`-
|
|
||||||
Richtung um `depthBack` versetzt) + orthografische Kamera senkrecht zur Linie.
|
|
||||||
- **Vektor-Ergebnis (Risiko #4):** Hidden-Line-Removal durch das zusammengebaute
|
|
||||||
Gebäude → saubere Linien als SVG. Via **OpenCascade.js** (`HLRBRep`) im
|
|
||||||
**Web Worker** (Comlink), Ergebnis gecacht pro (Schnitt, sichtbare Ebenen,
|
|
||||||
Modell-Hash). Geschnittene Bauteile bekommen Component-Schraffur (Section-Style,
|
|
||||||
resources-graphics.md). Details: plans-output.md.
|
|
||||||
|
|
||||||
### 4.4 SVG-Renderer + Geometrie-Booleans
|
|
||||||
|
|
||||||
- **Plan-Output:** SVG (`PlanView.tsx`). `Primitive`-Union (polygon/line/arc/text/
|
|
||||||
hatch) → SVG-Elemente; derselbe Serializer speist DXF- und PDF-Export.
|
|
||||||
- **Öffnungs-Booleans (Risiko #2):** Tür/Fenster schneidet Loch in Wand. Für 3D
|
|
||||||
reicht heute das Aussparen per Segment-Extrusion (im Spike). Für exakte B-Rep-
|
|
||||||
Verschneidung (und IFC) **OpenCascade.js** *oder* **Manifold** im Worker —
|
|
||||||
Entscheidung in Phase 0 (ROADMAP §8): OCC = exakt/B-Rep, Manifold = schnell/Mesh.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. React-Panel-Struktur
|
## 5. React-Panel-Struktur
|
||||||
|
|
||||||
DOSSIER ist eine Sammlung **getrennter WebView-Panels** (EBENEN, ELEMENTE,
|
Dock-/Floating-Panel-System (`src/panels/`: Dock, FloatingPanel, TabStrip,
|
||||||
GESTALTUNG, OBERLEISTE, MASSSTAB, AUSSCHNITTE, DIMENSIONEN, LAYOUTS, OVERRIDES),
|
Registry) mit eingebauten Panels: Tools, Attributes, ObjectInfo,
|
||||||
die über die Python-Bridge + `sc.sticky` kommunizieren. Im Browser ist alles
|
DrawingLevels, Layers, Site (Kontext/Terrain-Import), RoomBalance
|
||||||
**eine SPA** mit einem geteilten Store — die Panels werden zu Docking-Bereichen.
|
(SIA-416-CSV), Elements (Bauteilbaum), ViewSnapshots, Layouts.
|
||||||
|
|
||||||
```
|
Der **Resource Manager läuft bewusst NICHT als Dock-Panel**, sondern als
|
||||||
┌─────────────────────────────────────────────────────────────────────┐
|
eigenständiges (unter Tauri natives) Fenster — ebenso Settings,
|
||||||
│ TopBar Werkzeuge · Ansichtstyp · Massstab · Snaps · Save/Load │ ≙ OBERLEISTE+MASSSTAB
|
DrawingLevels-Detaileditor, LayerSettings und ContextImport
|
||||||
├──────────────┬──────────────────────────────────────┬───────────────┤
|
(`src/native/*Window.ts` + `*WindowApp.tsx`), jeweils mit
|
||||||
│ Navigator │ Viewport (Three.js oder SVG-Plan) │ Inspector │
|
`isTauriRuntime()`-Gate und ohne separate Browser-Variante (im Browser bleibt
|
||||||
│ ┌──────────┐ │ ┌────────────────────────────────┐ │ ┌───────────┐ │
|
die entsprechende In-App-Overlay-Variante aktiv, wo vorhanden).
|
||||||
│ │Zeichnungs│ │ │ 3D / Grundriss / Schnitt / │ │ │ aktives │ │ ≙ ELEMENTE/
|
|
||||||
│ │-ebenen │ │ │ Ansicht — abgeleitet aus dem │ │ │ Element + │ │ GESTALTUNG-
|
|
||||||
│ ├──────────┤ │ │ Modell │ │ │ Stil │ │ Properties
|
|
||||||
│ │ Ebenen │ │ └────────────────────────────────┘ │ └───────────┘ │
|
|
||||||
│ │ (Baum) │ │ │ Manager-Tabs: │
|
|
||||||
│ │ │ │ │ Components · │ ≙ Resource-
|
|
||||||
│ └──────────┘ │ │ Hatch · Line │ Manager
|
|
||||||
├──────────────┴──────────────────────────────────────┴───────────────┤
|
|
||||||
│ BottomBar Koordinaten · Snaps · Statusmeldungen │
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
Modal/Drawer: Ausschnitte · Sheets/Layouts · Overrides · Kamera-Presets · SIA-Bilanz
|
|
||||||
```
|
|
||||||
|
|
||||||
**Komponenten-Map** (heute alles in `App.tsx`; wird gesplittet):
|
**Werkzeug-System:** `Tool`-Interface (`src/tools/types.ts`) für Zeichenwerkzeuge
|
||||||
|
mit Snap-Engine; daneben das umfangreichere **Kommandosystem** (`src/commands/`)
|
||||||
| Bereich | DOSSIER-Panel | cad-Komponente |
|
für Rhino-artige getippte Eingabe (`5,3`/`r5,3`/`5<45`, Tab-Feld-Zyklus in
|
||||||
|---|---|---|
|
`CommandLine.tsx`) — beide koexistieren, decken unterschiedliche
|
||||||
| Zeichnungsebenen-Liste | EBENEN (oben) | `Navigator/DrawingLevels.tsx` |
|
Interaktionsstile ab.
|
||||||
| Ebenen-Baum | EBENEN (Baum) | `Navigator/LayerTree.tsx` (Spike: `CategoryRow`) |
|
|
||||||
| Top-Bar (View/Snaps/Massstab) | OBERLEISTE, MASSSTAB | `TopBar.tsx` |
|
|
||||||
| Element-Eigenschaften | ELEMENTE, ELEMENT-PROPERTIES | `Inspector/ElementProps.tsx` |
|
|
||||||
| Stil/Attribute der Auswahl | GESTALTUNG | `Inspector/StylePanel.tsx` |
|
|
||||||
| Component/Hatch/Line | (Project-Settings, mass_style) | `managers/*Manager.tsx` |
|
|
||||||
| View-Snapshots | AUSSCHNITTE | `panels/Snapshots.tsx` |
|
|
||||||
| Plansätze | LAYOUTS | `sheets/SheetEditor.tsx` |
|
|
||||||
| Regelbasierte Overrides | OVERRIDES | `panels/Overrides.tsx` |
|
|
||||||
| Bemaßung | DIMENSIONEN | `panels/Dimensions.tsx` |
|
|
||||||
| Element-Übersicht (BIM-Tree) | ELEMENTE-ÜBERSICHT | `panels/ElementTree.tsx` |
|
|
||||||
| Kamera-Presets | KAMERA | `panels/CameraPresets.tsx` |
|
|
||||||
|
|
||||||
**Werkzeug-System** (ersetzt DOSSIERs Rhino-Command-Aliases `cmd/wand.py` etc.):
|
|
||||||
ein `Tool`-Interface mit `onPointerDown/Move/Up`, das Vorschau-Primitive
|
|
||||||
zurückgibt und beim Bestätigen `store.apply()` ruft. Pointer-Events auf Canvas/SVG
|
|
||||||
mit Snap-Engine (Endpunkt/Mitte/Schnitt/Ortho/Raster). Tabelle der Tools:
|
|
||||||
elements.md §8.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Rhino → Browser — Mapping-Tabelle
|
## 6. Rhino → Dossier — Mapping-Tabelle (Kernkonzepte)
|
||||||
|
|
||||||
| DOSSIER (Rhino-Plugin) | cad (Standalone-Browser) | Anmerkung |
|
| DOSSIER (Rhino-Plugin) | Dossier (Tauri) | Anmerkung |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Rhino `RhinoDoc`** | `Project` (TS-Objekt im Store) | einzige Wahrheit |
|
| Rhino `RhinoDoc` | `Project` (TS-Objekt im eigenen Store) | einzige Wahrheit |
|
||||||
| **`doc.Strings[key]=json`** | Feld im `Project`-JSON | persistiert in `.cad.json` |
|
| `doc.Strings[key]=json` | Feld im `Project`-JSON | persistiert in `.obp` |
|
||||||
| **`.3dm`-Datei** | **`.cad.json`** via File System Access API | + IndexedDB-Autosave |
|
| `.3dm`-Datei | **`.obp`**-Datei (Tauri `plugin-fs`) | + Blob-Fallback im Browser |
|
||||||
| **Globale Presets (~/Library/*.json)** | **LocalStorage** + Export/Import | cross-Projekt |
|
| `sc.sticky` (cross-modul Bus) | eigener `useSyncExternalStore`-Store | kein Polling |
|
||||||
| **`sc.sticky` (cross-modul Bus)** | **Zustand-Store** (reaktiv) | kein Polling, kein None-Bug |
|
| Rhino Layer-Tabelle | `LayerCategory[]`-Baum, Sichtbarkeit als `.visible`-Flag | pro Renderer umgesetzt |
|
||||||
| **`panel_base.BaseBridge` / WebView-IPC** | direkte React-Props/Store | keine `document.title`-Hacks |
|
| Clipping-Plane (`AddClippingPlane`) | `render3d::section.rs` (analytisch, Rechteck-Subtraktion) | KEIN HLR |
|
||||||
| **`document.title="RHINOMSG::"` Polling** | entfällt | SPA, kein WebView |
|
| `HLRBRep` | — (ungenutzt; OCCT-Spike ist toter Code) | ersetzt durch obiges |
|
||||||
| **Eto.Forms Satelliten-Fenster** | React-Modal/Drawer | z.B. Ausschnitt-Settings |
|
| `Rhino.Geometry.Brep`-Booleans | `trucksolid::boolean_mesh` (csgrs) | existiert, nicht an Wände angeschlossen |
|
||||||
| **Rhino Layer-Tabelle (Sublayer-Baum)** | `LayerCategory[]`-Baum + `THREE.Group`-Spiegel | `scene.ts` |
|
| Rhino-Grips + DisplayConduit | Pointer-Events + Grip-Overlay (2D vollständig, 3D nur Basis, kein Snap) | siehe STATUS.md §4.7 |
|
||||||
| **`layer_builder.build_layers`** | `buildScene` (Ableitung) | Gruppen statt Layer |
|
| Swisstopo via .NET HttpClient | `fetch()` (CORS-offen) | radiusgenau zugeschnitten (nicht ganze STAC-Kachel) |
|
||||||
| **`layer.PlotWeight`** | `LineStyle.weight` (mm) → SVG `stroke-width` | massstabsabh. (§plans) |
|
| IronPython-Laufzeit-Risiken | entfällt | TS/Rust |
|
||||||
| **`SectionStyle` (Layer)** | Component-Schraffur beim Schnitt-Rendern | resources-graphics.md |
|
|
||||||
| **Clipping-Plane (`AddClippingPlane`)** | `THREE.Plane` + `renderer.clippingPlanes` | `clip.ts` |
|
|
||||||
| **`vp.ChangeToParallelProjection`** | `THREE.OrthographicCamera` | `camera.ts` |
|
|
||||||
| **`vp.GetFrustum()` → Massstab** | Frustum-Breite (ortho) → 1:N | gleiche Mathe (§plans) |
|
|
||||||
| **CoreGraphics DPI-Auto-Detect** | `window.devicePixelRatio` + CSS-px | Browser kennt DPI nativ |
|
|
||||||
| **`Rhino.Geometry.Brep`-Booleans** | OpenCascade.js / Manifold (Worker) | Öffnungen, HLR |
|
|
||||||
| **`HLRBRep` (über Rhino-Display)** | OpenCascade.js `HLRBRep` (Worker) | Schnitt/Ansicht-Linien |
|
|
||||||
| **Rhino-Grips + DisplayConduit** | Pointer-Events auf SVG/Canvas + Overlay | grip-editing (elements.md) |
|
|
||||||
| **`Rhino.Input.Custom.GetPoint`** | `Tool`-Pointer-Handler + Snap | Werkzeug-System |
|
|
||||||
| **`MouseCallback` (Doppelklick Schnitt)** | `onDoubleClick` auf SVG-Symbol | plans-output.md |
|
|
||||||
| **Rhino Undo/Redo** | eigener History-Ring (Immer-Patches) | kein Cache-Stale |
|
|
||||||
| **`HatchPattern`-Tabelle** | `Hatch`-Ressourcen + SVG `<pattern>` | resources-graphics.md |
|
|
||||||
| **`Linetype`-Tabelle** | `LineStyle.dash[]` → SVG `stroke-dasharray` | resources-graphics.md |
|
|
||||||
| **`TextEntity` / Rich-Text** | SVG `<text>` / HTML-Annotation | plans-output.md |
|
|
||||||
| **`RhinoPageView` (Layout)** | `Sheet` + `SheetEditor` | plans-output.md |
|
|
||||||
| **`FilePdf` Multi-Page-Export** | svg → `pdf-lib`/`jsPDF` (Vektor) | plans-output.md |
|
|
||||||
| **Swisstopo/OSM via .NET HttpClient** | `fetch()` (CORS-fähige STAC/Overpass-APIs) | Phase 4 |
|
|
||||||
| **LV95↔WGS84 (Python-Formeln)** | dieselben Formeln in TS portiert | Phase 4 |
|
|
||||||
| **IronPython 2.7/3 Runtime-Risiken** | entfällt (TS/Browser) | — |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Was wir von DOSSIER übernehmen — und was nicht
|
## 7. Was übernommen wurde — und was bewusst anders lief
|
||||||
|
|
||||||
**Übernehmen (bewährte Konzepte):**
|
**Übernommen (Prinzipien, bewährt):**
|
||||||
- Zwei-Achsen-Dokumentmodell (Zeichnungsebenen × Ebenen).
|
- Zwei-Achsen-Dokumentmodell, Layer-Codes 1:1, Component/Hatch/Line-Manager
|
||||||
- Layer-Codes/Farben/lw 1:1 (`DEFAULT_LAYER_SCHEMA`).
|
mit `joinPriority`, LoD (grob/mittel/fein), regelbasierte Overrides,
|
||||||
- Component/Hatch/Line-Manager mit id-Verweisen + `joinPriority`.
|
Ausschnitte, SIA-416-Räume, Norden-Rotation.
|
||||||
- Ansichtstypen = Kamera + optionaler Schnitt (vereinheitlicht).
|
- Pure-Ableitungs-Architektur (kein Cache-Stale, kein Sticky-Bus).
|
||||||
- LoD (`darstellung`: einfach/standard/detail) mit Dokument-Override.
|
|
||||||
- Regelbasierte Overrides (Condition → Action, additive Priorität, reversibel).
|
|
||||||
- Ausschnitte (View-Snapshots), Layer-Kombinationen, Massstab-pro-Viewport.
|
|
||||||
- SIA-416-Räume, Norden-Rotation, Stil-Kataloge.
|
|
||||||
|
|
||||||
**Bewusst anders (Browser-nativ, Schwachstellen vermeiden):**
|
**Anders gelaufen als geplant (siehe STATUS.md §5 für die volle Tabelle):**
|
||||||
- **Kein Sticky-Bus** → ein reaktiver Store (vermeidet DOSSIER #4.4/#4.5/#4.6).
|
- Eigene Rust/WASM-Rendering-Engines statt Three.js/OpenCascade.js/web-ifc.
|
||||||
- **Kein Monolith** → Bauteil-Module statt 7244-LOC-`elemente.py` (#4.1).
|
- Typisierte Arrays pro Bauteiltyp statt `Element[]`-Union.
|
||||||
- **Pure Ableitungen** statt mutierter Doc-Objekte → kein Cache-Stale (#4.3),
|
- Eigener Store statt Zustand-Library.
|
||||||
kein Undo/Redo-Loch.
|
- Analytische Rust-Schnitt-Pipeline statt HLR/Worker/Comlink.
|
||||||
- **Versioniertes Datei-Schema** statt UserString-Sticky-Migration.
|
- App.tsx wurde **nicht** wie geplant auf eine dünne Shell reduziert
|
||||||
- **Persistenz im Project-JSON** statt verteilt über `doc.Strings`.
|
(`src/views/`/`src/menus/` wurden nie angelegt) — bekannter, unbereinigter
|
||||||
|
Punkt, siehe STATUS.md §4.3.
|
||||||
|
|
||||||
**Anti-Over-Engineering (aus DOSSIER-CONVENTIONS.md übernommen):** keine Abstraktion
|
**Anti-Over-Engineering (weiterhin gültig):** keine Abstraktion ohne konkretes
|
||||||
ohne konkretes Problem; kein `try/catch: {}` als Bug-Versteck; erst grep/lesen,
|
Problem; erst lesen, dann editieren; Geometrie nie „nebenbei" refactoren.
|
||||||
dann editieren; Wand-Geometrie nie „nebenbei" refactoren.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Reihenfolge bei Code-Arbeit
|
## 8. Reihenfolge bei Code-Arbeit
|
||||||
|
|
||||||
1. **Dieses Dokument + das relevante Detail-Design** (elements/plans-output/
|
1. **STATUS.md** (aktueller Ist-Zustand) + dieses Dokument lesen.
|
||||||
resources-graphics) lesen.
|
2. **Das betroffene Modul** lesen (nicht raten) — bei Zweifel: welcher der
|
||||||
2. **Dann das betroffene Modul** lesen (nicht raten).
|
mehreren Renderer/Pfade ist gerade aktiv (§4.1/§4.2)?
|
||||||
3. **Erst danach editieren**; `Project` immer immutabel via `store.apply()` ändern.
|
3. **Erst danach editieren**; `Project` immer immutabel via den Store ändern.
|
||||||
4. **Verifizieren:** `npx tsc -b`, `npm run build`, Screenshot via
|
4. **Verifizieren:** `npx tsc -b`, `npm test` (Vitest), `cargo test` in den
|
||||||
`node scripts/probe.mjs` — Geometrie visuell prüfen.
|
betroffenen Crates, bei Rust-Änderungen `npm run build:engine{,3d}` (WASM
|
||||||
5. **Dieses Dokument aktuell halten**, wenn sich Patterns/Mapping ändern.
|
neu bauen, nur `.rs` committen — `src/engine/pkg*/` ist gitignored). Bei
|
||||||
|
render3d/Wasm3DViewport-Änderungen testet der Nutzer selbst in der
|
||||||
|
Tauri-Dev-App (nicht per Puppeteer/Browser verifizierbar).
|
||||||
|
5. **Dieses Dokument aktuell halten**, wenn sich Patterns ändern — insbesondere
|
||||||
|
nicht wieder in „Ziel-Struktur"-Beschreibungen abdriften, die Monate lang
|
||||||
|
niemand nachführt.
|
||||||
|
|||||||
+25
-11
@@ -1,6 +1,8 @@
|
|||||||
# Projekt-Konventionen — Browser-BIM (cad)
|
# Projekt-Konventionen — Dossier
|
||||||
|
|
||||||
Siehe [ROADMAP.md](ROADMAP.md) für Vision, Architektur und Phasen.
|
Siehe [ARCHITECTURE.md](ARCHITECTURE.md) für die aktuelle Architektur,
|
||||||
|
[STATUS.md](STATUS.md) für den vollständigen Ist-Zustand und
|
||||||
|
[ROADMAP.md](ROADMAP.md) für die ursprüngliche (historische) Produktvision.
|
||||||
|
|
||||||
## Code-Konventionen (verbindlich)
|
## Code-Konventionen (verbindlich)
|
||||||
|
|
||||||
@@ -15,16 +17,26 @@ Siehe [ROADMAP.md](ROADMAP.md) für Vision, Architektur und Phasen.
|
|||||||
CCW-Wicklung zeigt `+n` nach innen. Schichten werden außen (−T/2) → innen (+T/2)
|
CCW-Wicklung zeigt `+n` nach innen. Schichten werden außen (−T/2) → innen (+T/2)
|
||||||
gestapelt.
|
gestapelt.
|
||||||
|
|
||||||
## Code-Struktur (kein God-Component)
|
## Code-Struktur (kein God-Component — bisher nur teilweise erreicht)
|
||||||
|
|
||||||
- **`App.tsx` bleibt ein dünner Shell** (Store-Provider, Oberleiste, Docks+View-Router,
|
- **Ziel:** `App.tsx` bleibt ein dünner Shell (Store-Provider, Oberleiste,
|
||||||
Statusleiste, Floating-Panels, Ressourcen-Overlay) — keine Geschäftslogik darin.
|
Docks+View-Router, Statusleiste, Floating-Panels, Ressourcen-Overlay) — keine
|
||||||
- **Globaler Zustand in einem Store** (`src/state/`, Slices: project/selection/view/layout).
|
Geschäftslogik darin. **Realität (Stand 2026-07-21):** `App.tsx` ist mit
|
||||||
Komponenten lesen Zustand über Store-Hooks statt Prop-Drilling.
|
~7.100 Zeilen die grösste Datei des Projekts und enthält weiterhin
|
||||||
- **Features als eigene Module:** `src/views/` (View-Router-Teile), `src/editors/`
|
View-Umschaltung und Kontextmenü-Aufbau inline — die geplante Auslagerung
|
||||||
(Inline-Editoren), `src/menus/` (Kontextmenü-Builder), `src/panels/`, `src/ui/`.
|
nach `src/views/`/`src/menus/` (unten) ist **nie passiert**, siehe
|
||||||
|
[STATUS.md](STATUS.md) §4.3. Neue, grössere Features sollten trotzdem nicht
|
||||||
|
weiter in `App.tsx` wachsen; wo möglich in `src/panels/`, `src/editors/`
|
||||||
|
oder ein neues Modul auslagern statt die Datei weiter zu vergrössern.
|
||||||
|
- **Globaler Zustand in einem Store** (`src/state/`, eigener Store auf
|
||||||
|
`useSyncExternalStore` — **kein** Zustand/Redux/Immer — mit Slices:
|
||||||
|
project/history/selection/view/layout/site/notify). Komponenten lesen
|
||||||
|
Zustand über `useStore(selector)` statt Prop-Drilling.
|
||||||
|
- **Features als eigene Module:** `src/editors/` (Inline-Editoren),
|
||||||
|
`src/panels/`, `src/ui/`. `src/views/` und `src/menus/` waren geplant, wurden
|
||||||
|
aber nie angelegt — bei Bedarf gilt das als offener Aufräum-Punkt, nicht als
|
||||||
|
bestehende Struktur.
|
||||||
- Ziel: modular + parallel bearbeitbar (verschiedene Features ≠ dieselbe Datei).
|
- Ziel: modular + parallel bearbeitbar (verschiedene Features ≠ dieselbe Datei).
|
||||||
Siehe `docs/design/state-architecture.md`.
|
|
||||||
|
|
||||||
## UI-Konventionen
|
## UI-Konventionen
|
||||||
|
|
||||||
@@ -61,4 +73,6 @@ Rendern angewandt, nie in die Geometrie eingebacken.
|
|||||||
- Ä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.
|
||||||
- Dev-Server läuft via `npm run dev` (Vite, Port 5173).
|
- Dev-Server läuft via `npm run dev` (Vite, Port 5187). Nativer Rahmen
|
||||||
|
plattformabhängig: `npm run tauri:dev` auf macOS, `npm run electron` auf Linux
|
||||||
|
(WebKitGTK kann kein zuverlässiges WebGPU → dort Chromium/Electron).
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
> **Hinweis (2026-07-21):** Dieser Block (Stand 07-09) ist nicht mehr der
|
||||||
|
> neueste Stand — `git log`/[PENDENZEN.md](PENDENZEN.md) reichen bis 07-20/17.
|
||||||
|
> Für den aktuellen Ist-Zustand siehe [STATUS.md](STATUS.md). HANDOVER.md bleibt
|
||||||
|
> ein Append-Log (ältere Sessions unten); es wird nicht rückwirkend aktualisiert
|
||||||
|
> — bei neuen Übergaben oben einen neuen Block ergänzen, nicht diesen editieren.
|
||||||
|
|
||||||
# HANDOVER — Stand 2026-07-09 (autonome Session, ALLES committet)
|
# HANDOVER — Stand 2026-07-09 (autonome Session, ALLES committet)
|
||||||
|
|
||||||
Lange autonome Session (Nutzer-Auftrag: „arbeite die Pendenzen/Funktionen durch,
|
Lange autonome Session (Nutzer-Auftrag: „arbeite die Pendenzen/Funktionen durch,
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
# DOSSIER Standalone
|
# Dossier
|
||||||
|
|
||||||
Open-Source-CAAD (Computer Aided Architecture Design). Modelliert ein Gebäude
|
Open-Source-CAAD (Computer Aided Architecture Design). Modelliert ein Gebäude
|
||||||
aus semantischen Bauteilen und zieht daraus saubere, normgerechte 2D-Pläne —
|
aus semantischen Bauteilen und zieht daraus saubere, normgerechte 2D-Pläne —
|
||||||
ohne Revit. Als Desktop-App und im Browser zugänglich; die Desktop-App ist die
|
ohne Revit. **Desktop-App** — auf macOS über **Tauri**, auf Linux über eine
|
||||||
vollständige Fassung.
|
**Electron**-Shell (weil Tauris Linux-Webview WebKitGTK kein zuverlässiges
|
||||||
|
WebGPU bietet, das die Rendering-Engines brauchen); dieselbe Codebasis läuft
|
||||||
|
auch im Browser (eingeschränkt, ohne native Fenster/Dateisystem-Integration).
|
||||||
|
|
||||||
Das ist die eigenständige Standalone-Variante des Rhino-Plugins
|
Das ist die eigenständige Neuimplementierung des Rhino-Plugins
|
||||||
[DOSSIER](https://git.kgva.ch/karim/DOSSIER): dieselbe Denkweise (Geschosse,
|
[DOSSIER](https://git.openbureau.ch/karim/dossier): dieselbe Denkweise
|
||||||
Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts-Verschneidung), aber als
|
(Geschosse, Kategorie-Ebenen, mehrschichtige Bauteile, Prioritäts-
|
||||||
React-App mit eigener Rendering-Engine (Rust/WASM/WebGPU) statt als Rhino-Aufsatz.
|
Verschneidung), aber mit eigenem Datenmodell + eigener Rendering-Engine
|
||||||
Als Desktop-App (Tauri) läuft sie im eigenen Fenster mit voller Engine-Leistung;
|
(Rust/WASM/WebGPU, intern „Nordstern" genannt) statt Rhino-Aufsatz.
|
||||||
browserseitig ist derselbe Kern zugänglich, die App ist die vollständige Fassung.
|
|
||||||
|
|
||||||
## Grundgedanke
|
## Grundgedanke
|
||||||
|
|
||||||
@@ -22,117 +23,124 @@ in die Geometrie eingebacken. Wer eine Wand verschiebt, verschiebt sie überall.
|
|||||||
Konkret heißt das auch: Der Grundriss entsteht nicht aus einem zerschnittenen
|
Konkret heißt das auch: Der Grundriss entsteht nicht aus einem zerschnittenen
|
||||||
3D-Mesh, sondern **analytisch aus den Bauteil-Parametern** (Wandachsen + Dicken →
|
3D-Mesh, sondern **analytisch aus den Bauteil-Parametern** (Wandachsen + Dicken →
|
||||||
Linien, Öffnungen → Lücken + Symbol). PDF-Export ist derselbe Plan, nur mit
|
Linien, Öffnungen → Lücken + Symbol). PDF-Export ist derselbe Plan, nur mit
|
||||||
echten mm-Stiftstärken statt Bildschirm-Hairlines — kein zweites Rendering.
|
echten mm-Stiftstärken statt Bildschirm-Hairlines. Schnitte und Ansichten laufen
|
||||||
Schnitte und Ansichten brauchen den zweiten Weg — echte 3D-Projektion mit
|
über eine eigene, analytische Rust-Pipeline (kein Hidden-Line-Removal nötig,
|
||||||
verdeckten Kanten (HLR) —, dessen Machbarkeit per Spike bewiesen, aber noch
|
siehe [ARCHITECTURE.md](ARCHITECTURE.md) §4.3) und sind seit Juli live im
|
||||||
nicht ans UI angebunden ist.
|
3D-Viewport verdrahtet, nicht nur ein Spike.
|
||||||
|
|
||||||
## Stand heute
|
## Stand heute
|
||||||
|
|
||||||
Ehrlich eingeordnet: aus dem Risiko-Spike ist ein **benutzbares 2D-CAD mit
|
Aus dem ursprünglichen Risiko-Spike ist binnen weniger Wochen ein
|
||||||
abgeleiteter 3D-Sicht, Vektor-PDF/DXF-Export und einer parametrischen
|
**funktionsreiches Desktop-BIM-Tool** geworden: eigenes semantisches
|
||||||
Wand-Engine** geworden. Der einfachere Teil steht und ist per Screenshot/Probe
|
Gebäudemodell, zwei eigene Rust/WASM-Rendering-Engines, IFC/DXF/PDF/STL/OBJ-
|
||||||
verifiziert; die schwierigen BIM-Kernrisiken sind bewusst noch offen.
|
Export, Swisstopo-Import, SIA-416-Flächen, Materialbibliothek, Layouts/
|
||||||
|
Plansätze, native Tauri-Fenster. Eine vollständige, ehrliche Bestandsaufnahme
|
||||||
|
(inklusive offener Punkte und bekannter technischer Schulden) steht in
|
||||||
|
**[STATUS.md](STATUS.md)**.
|
||||||
|
|
||||||
**Funktioniert:**
|
**Funktioniert (Auszug — volle Liste in STATUS.md §3):**
|
||||||
- Semantisches Modell mit **mehrschichtigen Wänden** und automatischer
|
- Semantisches Modell mit **mehrschichtigen Wänden**, L-Eck-Gehrung **und**
|
||||||
**L-Ecken-Gehrung** (`computeJoins`); dieselbe Logik speist Plan *und* 3D.
|
Prioritäts-T-/X-Stössen (`joinPriority` am Component) — konsistent in
|
||||||
- **Parametrische Wände** (`ParametricWall`-Regelwerke: Grid/Modul/Sequenz/
|
Grundriss, 3D-Viewport und 3D-Live-Schnitt.
|
||||||
Referenzlinie/bedingte Dicke) lösen sich zu konkreten `Wall`-Objekten auf,
|
- **Parametrische Wände**, Decken, Treppen (gerade/L/Wendel), Dächer
|
||||||
statt jede Wand einzeln von Hand zu ziehen.
|
(Flach/Pult/Sattel/Walm/Mansarde/Zelt), Stützen, SIA-416-Räume mit
|
||||||
- **Dokumentmodell wie in DOSSIER:** Zeichnungsebenen (Geschosse + Schnitte/
|
automatischer Bilanz + CSV-Export.
|
||||||
Ansichten/Zeichnungen) × Kategorie-Ebenen (Code-Baum 1:1, `20 Wände`, `30 Decken` …).
|
- **Türen & Fenster** gehostet in Wänden, mit Rahmen/Zarge/Kämpfer/Oberlicht,
|
||||||
- **Zeichnen & Editieren** im Grundriss: Wand, Linie, Polylinie, Rechteck, Kreis,
|
Detailgrad grob/mittel/fein, echte Rechteck-Löcher im 3D-Wandkörper.
|
||||||
Öffnungen, Treppen, Decken, Raumstempel; Snapping (Endpunkt/Mittelpunkt/
|
- Rhino-artiges **Kommandosystem** mit getippten Koordinaten (`5,3` · `r5,3` ·
|
||||||
Schnittpunkt/Lot/Raster/Ortho), Grips, Move/Copy/Offset, Spiegeln/Drehen/Array,
|
`5<45`) und Tab-Feld-Zyklus; Snapping, Grips, Trim/Split/Join, 2D-Booleans.
|
||||||
Trim/Split/Join.
|
- **Live 2D↔3D-Schnitt**: eine Schnittebene im 3D-Viewport folgt derselben
|
||||||
- **Rhino-artiges Befehlssystem** mit getippten Koordinaten (`5,3` · `r5,3` ·
|
Prioritäts-Logik wie der 2D-Plan-Schnitt (Rust-Port, keine Diskrepanz).
|
||||||
`5<45`) und Tab-Feld-Zyklus (Länge → Winkel …).
|
- **Vektor-Export:** PDF (Einzel- **und Mehrseiten-Layouts**), DXF, **IFC4**
|
||||||
- **Vektor-Export:** PDF (A4/A3, Titelblock, echte mm-Stiftstärken nach ISO-Pen-
|
(mit echten Fenster-/Tür-Löchern), STL/OBJ, CSV-Bauteilliste.
|
||||||
Steps, keine Rasterbilder) und DXF — beide aus derselben `Plan`-Struktur wie
|
- **Materialbibliothek**: 13 gebündelte PBR-Starter **plus** Live-Suche der
|
||||||
der Bildschirm.
|
kompletten ambientCG-Bibliothek (1K/2K/4K, On-Demand-Download).
|
||||||
- **PBR-Material-Bibliothek** (ambientCG-Import, `manifest.json`) für die
|
- **Layouts/Plansätze** mit mehreren Viewports pro Blatt, Ausschnitte
|
||||||
3D-Ansicht.
|
(View-Snapshots), Kamera-Presets, Norden-Rotation.
|
||||||
- **Resource Manager** (Component / Hatch / Line) — alles per id referenziert,
|
- **Import:** DXF/DWG, Swisstopo (swissBUILDINGS3D/-ALTI3D/SWISSIMAGE,
|
||||||
zentral änderbar.
|
radiusgenau zugeschnitten), OSM-Kontextimport, `.lin`/`.pat`.
|
||||||
- **Panel-System** (dockbar, stapelbar, floatend), Top-Bar + Statusleiste
|
- **Native Desktop-Integration** (Tauri): eigene randlose Fenster, native
|
||||||
(echter Maßstab 1:N, Cursor X/Y), eigenes Kontextmenü, **i18n** (de/en).
|
Speichern/Öffnen-Dialoge, eigenes `.obp`-Dateiformat mit OS-Level-Lock gegen
|
||||||
- **Import:** DXF/DWG (Konturen/Mesh) → Terrain-TIN, Swisstopo/LV95-Geokontext,
|
Doppelöffnen, mehrere native Zusatzfenster (Resources, Settings, …).
|
||||||
OSM-Kontextimport; `.lin`/`.pat` für Linien/Schraffuren.
|
- **Resource Manager** (Component/Hatch/Line/Typ-Editoren), regelbasierte
|
||||||
|
Overrides, Panel-System (dockbar/floatend), i18n (de/en).
|
||||||
|
|
||||||
**Bewusst noch offen** (die eigentlich harten Teile):
|
**Bewusst noch offen** (Details + volle Liste: STATUS.md §4.7):
|
||||||
- Echte **3D-Booleans** für Öffnungen — Türen/Fenster sind im Plan eine Lücke,
|
- **Echtes Mesh-Boolean für Öffnungen** — funktioniert heute über achsparallele
|
||||||
noch kein geschnittenes Volumen.
|
Rechteck-Löcher; ein generisches CSG-Boolean existiert bereits
|
||||||
- **HLR** (verdeckte Kanten) für Schnitte und Ansichten: Machbarkeit per
|
(`trucksolid`/`csgrs`), ist aber nicht an die Wand-Pipeline angeschlossen.
|
||||||
OCCT-WASM-Spike bewiesen (`docs/welle-c-hlr-spike/`, `src/section/hlr.ts`),
|
- **3D-Griffsystem**: Feld-Controller (Tab-Zyklus) und Snapping fehlen für
|
||||||
aber noch nicht ans UI/Dokumentmodell verdrahtet — Views sind noch Stubs.
|
3D-Grip-Drag (im 2D vollständig vorhanden).
|
||||||
- **Prioritäts-T-/X-Stöße** mehrschichtiger Wände (Beton läuft durch, Putz
|
- **DWG/DXF-Domänenimport** (Entitäten → echte Wände/Öffnungen) — Lesen
|
||||||
verbindet seitlich) — das berüchtigte Risiko #1.
|
funktioniert, das Mapping auf das Modell ist unbegonnen; DWG-Schreiben fehlt.
|
||||||
- **Multi-Page-Layouts/Ausschnitte** (mehrere Viewports pro Blatt) — PDF-Export
|
- **`make2D`**-Kommando (3D-Ansicht → flacher 2D-Plan mit Füllungen).
|
||||||
ist noch single-sheet.
|
- Bekannte, noch nicht bereinigte Doppelspur `Door[]`/`Opening[]` im Modell.
|
||||||
|
|
||||||
Details und die Begründungen stehen im
|
|
||||||
[HANDOVER](HANDOVER.md) (laufendes Arbeitsprotokoll) und in der
|
|
||||||
[ROADMAP](ROADMAP.md) (Vision, Phasen, Backlog).
|
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
Bewusst leichtgewichtig — Three.js ist reiner Display-Layer, kein schwerer
|
|
||||||
Geometrie-Kernel verfrüht eingezogen.
|
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Shell | Electron (eigenes randloses App-Fenster, kein Browser-Tab) |
|
| Shell | **Tauri** auf macOS (WKWebView+WebGPU) · **Electron/Chromium** auf Linux (WebKitGTK kann kein WebGPU) — beide randlos, eigene Titelleiste, gemeinsame React-App |
|
||||||
| Frontend | React + TypeScript + Vite |
|
| Frontend | React + TypeScript + Vite |
|
||||||
| 3D | Three.js, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render3d`) |
|
| 3D | eigener Rust/WASM/WebGPU-Renderer „Nordstern" (`render3d`, Default), Three.js als leichtgewichtiger Zweitpfad |
|
||||||
| 2D-Plan | eigener SVG-Renderer, plus eigener nativer Rust/WASM/WebGPU-Renderer (`render2d`) |
|
| 2D-Plan | eigener Rust/WASM/WebGPU-Renderer (`render2d`), plus ein TS/WebGL2-Renderer (`plan/glPlan`), plus SVG (`PlanView.tsx`, immer als Interaktions-/Fallback-Ebene aktiv) |
|
||||||
| Geometrie | eigener 2D-Kernel (`src/geometry/kernel2d.ts`), `delaunator` fürs Terrain |
|
| Geometrie | eigener 2D-Kernel (`src/geometry/kernel2d.ts`); ein Rust-Port (`src-tauri/kernel2d`) existiert nur als Paritätstest, läuft nicht produktiv |
|
||||||
| Export | `jspdf`/`svg2pdf.js` (Vektor-PDF), eigener DXF-Writer |
|
| CSG/Extrusion | `trucksolid`-Crate (`truck` + `csgrs`), fürs Extrude-Kommando |
|
||||||
|
| Export | eigener IFC4-Writer, `jspdf`/`svg2pdf.js` (Vektor-PDF), eigener DXF-Writer, STL/OBJ |
|
||||||
| Import | `dxf-parser`, `@mlightcad/libredwg-web` (DWG), eigene `.lin`/`.pat`-Parser |
|
| Import | `dxf-parser`, `@mlightcad/libredwg-web` (DWG), eigene `.lin`/`.pat`-Parser |
|
||||||
| Schnitt/HLR-Spike | `opencascade.js` (OCCT-WASM) — isoliert, noch nicht verdrahtet |
|
| Materialien | `jszip` (ambientCG-Zip-Entpacken), `three.js` `TextureLoader` |
|
||||||
|
|
||||||
Geplant, aber noch nicht eingezogen: `rhino3dm` (NURBS / `.3dm`), web-ifc (IFC),
|
`opencascade.js` steht noch als Dependency in `package.json`, wird aber nur
|
||||||
Manifold (exakte 3D-Booleans). OCCT-WASM ist für HLR bereits als Spike da (s.o.),
|
noch von totem Code (`src/section/hlr.ts`, superseded durch die Rust-
|
||||||
aber noch nicht an Dokumentmodell/UI angebunden. Siehe ROADMAP §4.
|
Schnitt-Pipeline) importiert. Details zu allen Rust-Crates (inkl. zwei
|
||||||
|
aktuell unbenutzten WASM-Builds) in [ARCHITECTURE.md](ARCHITECTURE.md) §1
|
||||||
|
und [STATUS.md](STATUS.md) §4.1.
|
||||||
|
|
||||||
## Entwicklung
|
## Entwicklung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm run dev # Vite, http://localhost:5187
|
npm run dev # Vite, http://localhost:5187
|
||||||
npm run electron # Electron-Fenster (Dev-Server + randlose App-Shell)
|
npm run tauri:dev # Tauri-Fenster (macOS — nativer Zielrahmen dort)
|
||||||
|
npm run electron # Electron-Fenster (Linux — WebGPU über Chromium statt WebKitGTK)
|
||||||
npx tsc -b # Typecheck
|
npx tsc -b # Typecheck
|
||||||
npm run build # tsc -b && vite build
|
npm run build # tsc -b && vite build
|
||||||
npm test # vitest run
|
npm test # vitest run
|
||||||
```
|
```
|
||||||
|
|
||||||
Verifiziert wird visuell: `node scripts/probe.mjs` rendert die App headless und
|
WASM-Engines nach Rust-Änderungen neu bauen (nur `.rs` committen,
|
||||||
schreibt `scripts/probe.png` — Screenshot ansehen, Geometrie prüfen. Weitere
|
`src/engine/pkg*/` ist gitignored):
|
||||||
`scripts/probe-*.mjs` decken einzelne Features ab (PDF, Trim, Materialien,
|
|
||||||
Theme, Boolean-Ops …); für Firefox-Fälle gibt es `scripts/probe-ff*.mjs`
|
```bash
|
||||||
(Playwright).
|
npm run build:engine # render2d
|
||||||
|
npm run build:engine3d # render3d
|
||||||
|
npm run build:truck # trucksolid
|
||||||
|
```
|
||||||
|
|
||||||
|
render3d/Wasm3DViewport-Änderungen sind nicht per Puppeteer/Browser
|
||||||
|
verifizierbar — im laufenden `tauri:dev`-Fenster selbst testen.
|
||||||
|
|
||||||
## Aufbau
|
## Aufbau
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
model/ semantisches Modell + Ableitungen (types, parametricWalls, roomStamp, joins, terrain)
|
model/ semantisches Modell (types, joins, parametricWalls, roomStamp, terrain)
|
||||||
geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling, opening, roomArea/-Boundary, stair)
|
geometry/ 2D-Kernel (offset/trim/fillet/intersect, ceiling/opening/roomArea/stair/roof/column)
|
||||||
commands/ Rhino-artiges Befehlssystem (engine, parser, registry, cmds/)
|
commands/ Rhino-artiges Befehlssystem (engine, parser, registry, cmds/)
|
||||||
tools/ interaktive Zeichenwerkzeuge + Snapping + Transformationen
|
tools/ interaktive Zeichenwerkzeuge + Snapping + Transformationen
|
||||||
plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2-Renderer)
|
plan/ Grundriss-Ableitung (generatePlan) + PlanView (SVG) + glPlan (WebGL2) + Rust-Bindings
|
||||||
viewport/ Viewport3D (Three.js)
|
viewport/ Viewport3D (Three.js) + Wasm3DViewport (Rust/wgpu „Nordstern", Default)
|
||||||
section/ HLR-Spike (OCCT-WASM) — Schnitte/Ansichten, noch nicht verdrahtet
|
section/ TOTER Code (OCCT-HLR-Spike) — Schnitt läuft über render3d, siehe ARCHITECTURE.md §4.3
|
||||||
export/ PDF- und DXF-Export aus derselben Plan-Struktur wie der Bildschirm
|
export/ IFC4-, PDF-, DXF-, STL/OBJ-, CSV-Export aus derselben Plan-/Modell-Struktur
|
||||||
materials/ PBR-Material-Bibliothek (ambientCG-Import, Runtime)
|
materials/ ambientCG-Live-Suche + gebündelte Starter-Bibliothek + PBR-Runtime
|
||||||
text/ Rich-Text (Beschriftungen, Textobjekte)
|
|
||||||
panels/ dockbares Panel-System + die einzelnen Paletten
|
panels/ dockbares Panel-System + die einzelnen Paletten
|
||||||
state/ Store + Slices (project/selection/view/layout)
|
state/ eigener Store (useSyncExternalStore) + Slices (project/selection/view/layout/…)
|
||||||
ui/ Top-Bar, Statusleiste, Kontextmenü, Resource Manager, Command-Line
|
native/ Tauri-only: native Fenster, macOS-Menüleiste, Fenster-Chrome
|
||||||
|
ui/ App.tsx-Shell, Top-Bar, Resource Manager, Kontextmenü, Kommandozeile, Ribbon
|
||||||
io/ Import/Export (DXF, DWG, .lin, .pat), Swisstopo/LV95, OSM-Kontext
|
io/ Import/Export (DXF, DWG, .lin, .pat), Swisstopo/LV95, OSM-Kontext
|
||||||
i18n/ Wörterbücher de/en
|
i18n/ Wörterbücher de/en
|
||||||
src-tauri/ Rust-Crates (render2d/render3d) — headless per wasm-pack zu WASM
|
src-tauri/ 6 Rust-Crates (render2d/render3d/geometry/kernel2d/trucksolid/dwgimport,
|
||||||
gebaut (npm run build:engine), Tauri-Host selbst ist ausrangiert
|
je headless UND per wasm-pack baubar) + der Tauri-Host selbst
|
||||||
```
|
```
|
||||||
|
|
||||||
## Konventionen
|
## Konventionen
|
||||||
@@ -141,21 +149,24 @@ src-tauri/ Rust-Crates (render2d/render3d) — headless per wasm-pack zu WAS
|
|||||||
(Design Layer, Component, Hatch, Wall Style). **UI-Texte sind deutsch**, immer
|
(Design Layer, Component, Hatch, Wall Style). **UI-Texte sind deutsch**, immer
|
||||||
über `t('key')` — keine hartcodierten Strings im JSX.
|
über `t('key')` — keine hartcodierten Strings im JSX.
|
||||||
- Intern alles in **Metern**; Anzeige via `formatM`.
|
- Intern alles in **Metern**; Anzeige via `formatM`.
|
||||||
- `App.tsx` bleibt dünner Shell, Zustand lebt im Store. Verbindliches in
|
- Verbindliches in [CONVENTIONS.md](CONVENTIONS.md).
|
||||||
[CONVENTIONS.md](CONVENTIONS.md).
|
|
||||||
|
|
||||||
## Weiterlesen
|
## Weiterlesen
|
||||||
|
|
||||||
- [ROADMAP.md](ROADMAP.md) — Produktvision, Architektur-Entscheidungen, Phasen 0–7, DOSSIER-Backlog
|
- [STATUS.md](STATUS.md) — **vollständige, ehrliche Bestandsaufnahme**: Zahlen,
|
||||||
- [ARCHITECTURE.md](ARCHITECTURE.md) — Technische Architektur im Detail
|
Feature-Inventar, Mist-Liste, Doku-Widersprüche, Tag-1-Vision vs. heute
|
||||||
- [HANDOVER.md](HANDOVER.md) — aktueller Arbeitsstand, Befunde, nächste Schritte
|
- [ARCHITECTURE.md](ARCHITECTURE.md) — technische Architektur im Detail (Ist-Zustand)
|
||||||
- [docs/](docs/) — Design-Specs (Befehlssystem, Zeichenwerkzeuge, Wand-Joins, Backend …)
|
- [ROADMAP.md](ROADMAP.md) — ursprüngliche Produktvision (Tag-1-Stand, historisch)
|
||||||
|
- [HANDOVER.md](HANDOVER.md) / [PENDENZEN.md](PENDENZEN.md) — laufendes
|
||||||
|
Arbeitsprotokoll bzw. Backlog (Single Source of Truth für offene Punkte)
|
||||||
|
- [docs/](docs/) — Design-Specs (teils ebenfalls Tag-1-Vision, siehe Hinweis
|
||||||
|
in `docs/README.md`)
|
||||||
|
|
||||||
## Lizenz
|
## Lizenz
|
||||||
|
|
||||||
Copyright © 2026 Karim Gabriele Varano. Veröffentlicht unter der
|
Copyright © 2026 Karim Gabriele Varano. Veröffentlicht unter der
|
||||||
**GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)** —
|
**GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)** —
|
||||||
siehe [LICENSE](LICENSE). DOSSIER ist Teil der **openbureau**-Suite.
|
siehe [LICENSE](LICENSE). Dossier ist Teil der **openbureau**-Suite.
|
||||||
|
|
||||||
Die AGPL verlangt, dass auch bei Betrieb als Netzwerk-/Webdienst der (ggf.
|
Die AGPL verlangt, dass auch bei Betrieb als Netzwerk-/Webdienst der (ggf.
|
||||||
geänderte) Quellcode für die Nutzer verfügbar gemacht wird. Drittkomponenten
|
geänderte) Quellcode für die Nutzer verfügbar gemacht wird. Drittkomponenten
|
||||||
@@ -164,4 +175,4 @@ behalten ihre jeweiligen Lizenzen (siehe „Über"-Dialog in der App).
|
|||||||
---
|
---
|
||||||
|
|
||||||
Die UI ist deutsch; Schweizer Spezifika (SIA-416-Flächen, Swisstopo-Geodaten)
|
Die UI ist deutsch; Schweizer Spezifika (SIA-416-Flächen, Swisstopo-Geodaten)
|
||||||
sind als Differenzierer eingeplant.
|
sind ein bewusster Differenzierer.
|
||||||
|
|||||||
+11
-1
@@ -1,6 +1,16 @@
|
|||||||
# Browser-BIM für Wohnbau — Architektur & Produkt-Roadmap
|
# Browser-BIM für Wohnbau — Architektur & Produkt-Roadmap
|
||||||
|
|
||||||
> Arbeitstitel: **cad** (Name später)
|
> **Historisches Dokument (Tag-1-Vision, Stand 2026-06-28).** Vieles hier als
|
||||||
|
> „Phase 2–5"/„Backlog" gelistete ist inzwischen längst gebaut (Treppen, Dächer,
|
||||||
|
> Stützen, SIA-416, Swisstopo, OSM, Layouts, Ausschnitte, Kamera-Presets …), und
|
||||||
|
> mehrere Technologie-Entscheidungen liefen anders (eigene Rust/WASM-Engines
|
||||||
|
> statt Three.js/OpenCascade.js/web-ifc, siehe unten §4/§8). Für den aktuellen
|
||||||
|
> Ist-Zustand: **[STATUS.md](STATUS.md)** (Bestandsaufnahme + Mist-Liste) und
|
||||||
|
> **[ARCHITECTURE.md](ARCHITECTURE.md)** (aktuelle Architektur). Dieses Dokument
|
||||||
|
> bleibt als ursprüngliche Produktvision/Phasenplan stehen, wird aber nicht mehr
|
||||||
|
> laufend nachgeführt.
|
||||||
|
>
|
||||||
|
> Arbeitstitel: **cad** (Name später: **Dossier**)
|
||||||
> Ausrichtung: **BIM-first** · Nische: **Wohnbau / Einfamilienhäuser**
|
> Ausrichtung: **BIM-first** · Nische: **Wohnbau / Einfamilienhäuser**
|
||||||
> Stand: 2026-06-28
|
> Stand: 2026-06-28
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
# STATUS — Codebase-Analyse
|
||||||
|
|
||||||
|
> Stand: 2026-07-21 · Vollständige Bestandsaufnahme von Code + Dokumentation.
|
||||||
|
> Ersetzt NICHT [ROADMAP.md](ROADMAP.md)/[ARCHITECTURE.md](ARCHITECTURE.md) (die wurden
|
||||||
|
> im gleichen Zug überarbeitet), sondern begründet die Überarbeitung mit Zahlen und
|
||||||
|
> Befunden. [HANDOVER.md](HANDOVER.md) und [PENDENZEN.md](PENDENZEN.md) bleiben die
|
||||||
|
> laufenden Arbeitsprotokolle (nicht rückwirkend umgeschrieben).
|
||||||
|
|
||||||
|
## 0. TL;DR
|
||||||
|
|
||||||
|
„Dossier" (Arbeitstitel `cad`, Rhino-Vorbild `DOSSIER`) ist in **3 Wochen**
|
||||||
|
(erster Commit 2026-06-30, 362 Commits bis 2026-07-20) von einem Risiko-Spike zu
|
||||||
|
einem **funktionsreichen Desktop-CAD/BIM-Tool** gewachsen: eigenes semantisches
|
||||||
|
Gebäudemodell, zwei eigene Rust/WASM-Rendering-Engines („Nordstern"), ein
|
||||||
|
Rhino-artiges Kommandosystem, IFC/DXF/PDF/STL/OBJ-Export, Swisstopo-Import,
|
||||||
|
SIA-416-Flächen, Materialbibliothek (statisch + live von ambientCG), Layouts/
|
||||||
|
Plansätze, Ausschnitte, native Tauri-Fenster. **~125.000 Zeilen Code** (TS+Rust,
|
||||||
|
inkl. Tests), gebaut über viele autonome Agent-Sessions.
|
||||||
|
|
||||||
|
Die drei zentralen Vision-Dokumente (ARCHITECTURE.md, README.md, ROADMAP.md)
|
||||||
|
stammen aus der **allerersten Woche** (Stand 28./29.6.) und beschreiben einen
|
||||||
|
Plan, der in der Zwischenzeit an vielen Stellen überholt, anders gelöst oder
|
||||||
|
längst umgesetzt wurde (z. B. HLR/OCCT→eigene Rust-Schnitt-Pipeline,
|
||||||
|
„Booleans noch offen"→teilweise längst gelöst). Diese Doku-Drift war der Auslöser
|
||||||
|
für diese Analyse; die Docs sind im gleichen Zug revidiert worden.
|
||||||
|
|
||||||
|
## 1. Kennzahlen
|
||||||
|
|
||||||
|
### Code-Umfang
|
||||||
|
|
||||||
|
| Bereich | Dateien | LOC (ohne Tests) | Tests |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| `src/` (TypeScript, gesamt) | ~230 | 87.736 | 869 (Vitest, 71 Dateien) / 16.855 LOC |
|
||||||
|
| `src-tauri/render3d` („Nordstern" 3D) | 14 | 10.246 | 97 `#[test]` |
|
||||||
|
| `src-tauri/render2d` (2D-WGSL-Renderer) | 11 | 3.530 | 18 `#[test]` |
|
||||||
|
| `src-tauri/kernel2d` (Rust-Geometriekern, Paritätstest) | 1 | 3.383 | 18 `#[test]` |
|
||||||
|
| `src-tauri/geometry` (Wand-Join-Mathe, **unbenutzt**) | 1 | 1.057 | 8 `#[test]` |
|
||||||
|
| `src-tauri/trucksolid` (CSG/Extrusion, `truck`+`csgrs`) | 2 | 758 | 15 `#[test]` |
|
||||||
|
| `src-tauri/dwgimport` (DXF-Parser-Spike, **unbenutzt**) | 1 | 284 | 1 `#[test]` |
|
||||||
|
| `src-tauri/src` (Tauri-Host: Fenster, Dialoge, Lock) | 4 | 1.044 | — |
|
||||||
|
| **Gesamt** | | **~108.000** (ohne Tests) / **~125.000** (mit Tests) | 869 Vitest + 157 Rust-Tests |
|
||||||
|
|
||||||
|
Größte TS-Bereiche: `plan/` (23.626 LOC — Plan-Ableitung + 3 Renderer),
|
||||||
|
`ui/` (12.897 LOC — App-Shell, ResourceManager, Ribbon), `panels/` (9.538 LOC),
|
||||||
|
`model/` (7.043 LOC inkl. Tests), `commands/` (6.973 LOC), `io/` (5.299 LOC),
|
||||||
|
`state/` (5.297 LOC), `geometry/` (5.559 LOC), `viewport/` (5.165 LOC),
|
||||||
|
`export/` (4.280 LOC). `src/App.tsx` allein ist **7.130 Zeilen**.
|
||||||
|
|
||||||
|
### Tempo
|
||||||
|
|
||||||
|
362 Commits in 3 Wochen; Woche 27 (30.6.–6.7.): 233 Commits, Woche 28: 126,
|
||||||
|
danach starker Rückgang (Woche 30 bislang 3) — die Session-Dichte hat spürbar
|
||||||
|
abgenommen, nicht das Projekt gestoppt (siehe PENDENZEN.md, weiterhin aktiv).
|
||||||
|
|
||||||
|
## 2. Architektur, wie sie WIRKLICH ist (nicht wie geplant)
|
||||||
|
|
||||||
|
### 2.1 Datenmodell — kein `Element[]`-Union, sondern typisierte Arrays
|
||||||
|
|
||||||
|
`ARCHITECTURE.md` (alt) plante eine diskriminierte Union `Element = Wall | Door |
|
||||||
|
Window | …`. Tatsächlich hält `Project` (`src/model/types.ts:2095`) **pro
|
||||||
|
Bauteiltyp ein eigenes optionales Array**: `walls`, `ceilings?`, `roofs?`,
|
||||||
|
`doors`, `openings?` (Fenster/Türen gehostet in Wänden, `kind:"window"|"door"`),
|
||||||
|
`stairs?`, `rooms?`, `columns?`, `extrudedSolids?`, `drawings2d`, `context?`
|
||||||
|
(Importe/Terrain), plus die Bibliotheks-/Typtabellen (`lineStyles`, `hatches`,
|
||||||
|
`components`, `wallTypes`, `roofTypes?`, `doorTypes?`, …) und die
|
||||||
|
Dokument-Ebene (`viewSnapshots?`, `layouts?`, `masterLayouts?`, …). Der Typ-Alias
|
||||||
|
`Element` (types.ts:1601) existiert zwar noch, wird aber **nirgends** verwendet
|
||||||
|
(Element-Baum/Selektion arbeiten direkt auf den typisierten Arrays). Praktisch
|
||||||
|
funktioniert das gut (jeder Bauteiltyp hat sein eigenes, spezifisches Interface),
|
||||||
|
ist aber eine bewusste Abweichung vom ursprünglichen Uniform-Union-Plan.
|
||||||
|
|
||||||
|
### 2.2 State — kein Zustand/Redux/Immer, sondern ein Eigenbau
|
||||||
|
|
||||||
|
`docs/design/state-architecture.md` empfahl **Zustand**. Gebaut wurde stattdessen
|
||||||
|
ein **abhängigkeitsfreier Store auf `useSyncExternalStore`** (`src/state/store.ts`,
|
||||||
|
gleiches Muster wie `src/i18n`). `createStore()` komponiert Slice-Fabriken
|
||||||
|
(`projectSlice` inkl. Undo/Redo, `historySlice`, `selectionSlice`, `viewSlice`,
|
||||||
|
`layoutSlice`, `siteSlice`, `notifySlice`) über eine gemeinsame `RootState`.
|
||||||
|
Funktioniert, aber: der geplante Folgeschritt „App.tsx wird dünne Shell,
|
||||||
|
View-Routing nach `src/views/`, Kontextmenüs nach `src/menus/`" ist **nicht**
|
||||||
|
passiert — `src/views/` und `src/menus/` existieren nicht, View-Umschaltung und
|
||||||
|
Kontextmenü-Aufbau liegen weiterhin inline in `App.tsx` (7.130 Zeilen). Das ist
|
||||||
|
der deutlichste Doku-vs-Code-Widerspruch im ganzen Repo (CONVENTIONS.md
|
||||||
|
verlangt explizit das Gegenteil).
|
||||||
|
|
||||||
|
### 2.3 Rendering — drei 2D-Pfade, zwei 3D-Viewports
|
||||||
|
|
||||||
|
**2D-Plan:** es gibt tatsächlich **drei** koexistierende Renderer, nicht einen:
|
||||||
|
1. `PlanView.tsx` (SVG) — Referenz-/Fallback-Pfad, bleibt IMMER im DOM für
|
||||||
|
Hit-Testing/Grips, unabhängig davon was zeichnet.
|
||||||
|
2. `plan/glPlan/` — eigener TypeScript-WebGL2-Renderer (`glPlanCompile/-Render/
|
||||||
|
-Shaders/-Hatch.ts`).
|
||||||
|
3. `useWasmPlanRenderer.ts` → Rust-`render2d`-Crate (WGSL, nativ via wgpu,
|
||||||
|
Web via WebGPU/WebGL2-Fallback, inkl. echtem Text-Rendering via `glyphon`).
|
||||||
|
|
||||||
|
Beide GPU-Pfade fallen bei Initialisierungsfehler still auf SVG zurück.
|
||||||
|
|
||||||
|
**3D:** ebenfalls zwei Viewports: `Viewport3D.tsx` (three.js, „Free"-Stufe) und
|
||||||
|
`Wasm3DViewport.tsx` (Rust/wgpu „Nordstern", editierbar, **Default**). Ein
|
||||||
|
Settings-Schalter wählt die Engine.
|
||||||
|
|
||||||
|
### 2.4 Schnitt/Section — NICHT über HLR, sondern eigene Rust-Pipeline
|
||||||
|
|
||||||
|
`src/section/hlr.ts` + `occt.ts` (der ursprüngliche OpenCascade.js-HLR-Spike aus
|
||||||
|
Phase 0) hat **keinen einzigen Aufrufer mehr** im gesamten `src/` — toter Code.
|
||||||
|
Der tatsächliche, funktionierende Live-Schnitt läuft über einen völlig anderen,
|
||||||
|
analytischen Mechanismus: `App.tsx` (`section3dCutId`/`section3dPlane`) →
|
||||||
|
`Wasm3DViewport.tsx` (`section3d`-Prop → `setSectionPlane`) →
|
||||||
|
`src-tauri/render3d/src/{section.rs, section_boolean.rs, section_fill.rs}`.
|
||||||
|
Die Rust-Seite nutzt aus, dass jedes Bauteil ein Prisma mit konstantem
|
||||||
|
Querschnitt ist — eine Schnittebene liefert dadurch immer ein
|
||||||
|
achsparalleles Rechteck, nie ein Trapez; `section_boolean.rs` ist ein 1:1-Port
|
||||||
|
von `toSection.ts::subtractDominantBands`, damit 2D-Plan-Schnitt und
|
||||||
|
3D-Live-Schnitt exakt übereinstimmen. Kein Worker, kein Comlink (beides war
|
||||||
|
geplant, keines existiert) — läuft synchron/GPU-seitig.
|
||||||
|
|
||||||
|
### 2.5 Öffnungen als Löcher — echt, aber kein Mesh-Boolean
|
||||||
|
|
||||||
|
Fenster/Türen schneiden echte achsparallele Rechteck-Löcher aus dem Wandkörper
|
||||||
|
(`plan/toWalls3d.ts` `RHole`/`subtractSpans`, gespiegelt in
|
||||||
|
`render3d/{mesh.rs,section.rs}`) — funktioniert, ist aber KEIN generisches
|
||||||
|
Mesh-Boolean. Ein echtes CSG-Boolean existiert bereits (`trucksolid::boolean_mesh`,
|
||||||
|
`csgrs`-basiert, 15 Rust-Tests) und wird von `src/engine/truckSolid.ts` für das
|
||||||
|
Extrusions-Kommando genutzt — ist aber **nicht** an die Wand/Öffnungs-Pipeline
|
||||||
|
angeschlossen (bestätigt: `booleanMesh` hat ausserhalb von `truckSolid.ts`
|
||||||
|
keinen Aufrufer).
|
||||||
|
|
||||||
|
### 2.6 Rust-Workspace: sechs unabhängige Crates, nicht ein Workspace
|
||||||
|
|
||||||
|
`src-tauri/Cargo.toml` bindet nur den Tauri-Host (`cad-tauri`) als Workspace-
|
||||||
|
Mitglied; `render2d/render3d/geometry/kernel2d/trucksolid/dwgimport` sind
|
||||||
|
**eigenständige Cargo-Packages**, die dem Host nur optional (Features
|
||||||
|
`native2d`/`native3d`, standardmässig AUS) als Path-Dependency zugespielt
|
||||||
|
werden. Jedes Crate muss headless (`cargo test`) UND per `wasm-pack --features
|
||||||
|
web` bauen, ohne den Tauri-Toolchain-Zwang zu erben — bewusst so geschnitten.
|
||||||
|
Geteilte Abhängigkeiten: `wgpu 29`/`naga 29` (render2d+render3d, versionsgekoppelt
|
||||||
|
wegen `glyphon 0.11`), `truck-modeling`+`csgrs`(gepinnter Git-Rev)+`nalgebra`
|
||||||
|
nur in `trucksolid`.
|
||||||
|
|
||||||
|
### 2.7 Zwei Desktop-Rahmen: Tauri (macOS) + Electron (Linux)
|
||||||
|
|
||||||
|
Die App läuft plattformabhängig in **zwei verschiedenen nativen Rahmen** —
|
||||||
|
das ist Absicht, kein Wildwuchs, und hängt an **WebGPU**:
|
||||||
|
|
||||||
|
- **macOS → Tauri.** WKWebView unterstützt WebGPU, das die render2d/render3d-
|
||||||
|
WASM-Engines brauchen. `src-tauri/tauri.conf.json` (Identifier
|
||||||
|
`ch.dossier.cad`, eigene Titelleiste, `trafficLightPosition`) +
|
||||||
|
`isTauriRuntime()`-Gates an 6+ Stellen in `App.tsx` + vier eigene native
|
||||||
|
Zusatzfenster (`src/native/`: Resources, Settings, DrawingLevels,
|
||||||
|
LayerSettings, ContextImport).
|
||||||
|
- **Linux → Electron.** Tauris Linux-Webview **WebKitGTK unterstützt WebGPU
|
||||||
|
nicht zuverlässig** → dort läuft die App über eine Electron/Chromium-Shell
|
||||||
|
(`scripts/electron-main.cjs` + `electron-preload.cjs`, gestartet via
|
||||||
|
`npm run electron`). Der Kommentar in `electron-main.cjs` sagt es explizit:
|
||||||
|
„Ersetzt WebKitGTK durch Chromium, damit WebGPU zuverlässig läuft."
|
||||||
|
|
||||||
|
Beide teilen sich **dieselbe** React-App und dieselbe randlose eigene
|
||||||
|
Titelleiste; die Laufzeit erkennt den Host über `window.__TAURI__` (Tauri)
|
||||||
|
bzw. `window.dossierWindow` (Electron, per `contextBridge` injiziert). Die
|
||||||
|
Fenstersteuerung (`src/ui/WindowControls.tsx`) ist an beide Wege angebunden.
|
||||||
|
Kein Rust-Backend nötig auf dem Electron-Pfad — `computeJoins` hat einen
|
||||||
|
TS-Fallback (`src/compute/index.ts`). Electron ist also **kein** totes Gleis,
|
||||||
|
sondern der aktive Linux-Zielrahmen.
|
||||||
|
|
||||||
|
## 3. Feature-Inventar (was tatsächlich funktioniert)
|
||||||
|
|
||||||
|
### Modell & Bauteile
|
||||||
|
- Mehrschichtige Wände (`WallType.layers[]`) mit L-Eck-Gehrung UND
|
||||||
|
Prioritäts-T-/X-Stössen (`joinPriority` am Component) — **fertig**, in 2D-Plan,
|
||||||
|
3D-Viewport UND 3D-Live-Schnitt konsistent (Rust-Port `section_boolean.rs`).
|
||||||
|
- Parametrische Wände (`ParametricWall`: Grid/Modul/Sequenz/Referenzlinie/
|
||||||
|
bedingte Dicke) lösen sich zu konkreten `Wall[]` auf.
|
||||||
|
- Decken (Slabs, `ceilings?`) mit Aussparungen, eigenem Typkatalog.
|
||||||
|
- Türen/Fenster gehostet in Wänden, mit Rahmen/Zarge/Blockrahmen, Kämpfer,
|
||||||
|
Oberlicht, Detailgrad grob/mittel/fein (2D UND 3D), Schwenkbogen; daneben
|
||||||
|
existiert weiterhin ein **älteres, separates `Door[]`** neben `Opening[]` —
|
||||||
|
laut PENDENZEN.md explizit als offene Doppelspur/Aufräum-Punkt vermerkt.
|
||||||
|
- Treppen (gerade/L/Wendel), geschossübergreifend, 2D-Symbol mit Lauflinie/Pfeil.
|
||||||
|
- Dächer (Flach/Pult/Sattel/Walm/Mansarde/Zelt) über Rechteck-Umriss, First/
|
||||||
|
Traufe/Grat im 2D-Plan.
|
||||||
|
- Stützen (Column) mit Profilbibliothek (Quadrat/Rechteck/Rund/I/Rohr).
|
||||||
|
- Räume (SIA-416: HNF/NNF/VF/FF/GF/AGF) mit automatischer Bilanz + CSV-Export,
|
||||||
|
Raumstempel-Editor (Drag&Drop-Felder).
|
||||||
|
- Extrudierte Volumenkörper (truck-Integration: konkave Profile, Verjüngung).
|
||||||
|
- Kontext-Layer (Terrain-TIN, importierte Meshes, Konturen) — semantisch getrennt.
|
||||||
|
|
||||||
|
### Zeichnen & Bedienung
|
||||||
|
- Rhino-artiges Kommandosystem (`commands/`): getippte Koordinaten
|
||||||
|
(`5,3`/`r5,3`/`5<45`), Tab-Feld-Zyklus für Präzisionseingabe
|
||||||
|
(`src/ui/CommandLine.tsx`), Alias/Autocomplete, ~25 Kommandos (wall, ceiling,
|
||||||
|
opening, stair, column, roof, room, line/polyline/rect/circle/arc, text,
|
||||||
|
move/mirror/copy/offset/trim/join, extrude, import, terrain, measure,
|
||||||
|
Schnittlinie, Georef).
|
||||||
|
- Snapping (Endpunkt/Mitte/Schnittpunkt/Lot/Raster/Ortho), Grips, Array,
|
||||||
|
Trim/Split/Join, 2D-Booleans (Union/Subtract/Intersect via `polygon-clipping`).
|
||||||
|
- Messwerkzeug (Polygonzug, Länge + Fläche).
|
||||||
|
- Rich-Text-Annotationen (Bold/Kursiv/Hoch-/Tiefstellung).
|
||||||
|
|
||||||
|
### Darstellung / Ressourcen
|
||||||
|
- Resource Manager: Line/Hatch/Component-Manager, Wand-/Decken-/Tür-/Fenster-/
|
||||||
|
Treppen-/Dach-Typeditoren — als eigenständiges natives Fenster (nicht als
|
||||||
|
Dock-Panel).
|
||||||
|
- Materialbibliothek: 13 fest gebündelte PBR-Starter (ambientCG, lokale
|
||||||
|
Texturen) **plus** Live-Suche der kompletten ambientCG-Bibliothek (Auflösung
|
||||||
|
1K/2K/4K, on-demand Download+Entpacken via `jszip`, Proxy wegen CORS) — heute
|
||||||
|
bereinigt (siehe Commit-Historie dieser Session).
|
||||||
|
- Regelbasierte Overrides (Bedingung → Farbe/Strichstärke/Schraffur/Sichtbarkeit).
|
||||||
|
- Detailgrad grob/mittel/fein je Bauteil + Dokument-Override.
|
||||||
|
- Hell-/Dunkel-Theme, Akzentfarben.
|
||||||
|
|
||||||
|
### Pläne / Output
|
||||||
|
- Ausschnitte (View-Snapshots: Kamera, Massstab, Detailgrad, Sichtbarkeiten,
|
||||||
|
Override-Preset) in Ordnerstruktur.
|
||||||
|
- Layout-Blätter (Plansätze): Papierformat/-grösse, mehrere Viewports pro
|
||||||
|
Blatt, Masterlayout-Vererbung (Titelblock), Ordnerstruktur, freie 2D-Annotationen.
|
||||||
|
- Vektor-Export: PDF (Einzelblatt UND **Mehrseiten pro Ordner**,
|
||||||
|
`layoutPdf.ts::buildFolderPdf`), DXF, IFC4 (mit echten Fenster-/Tür-Löchern,
|
||||||
|
deterministischen GUIDs), STL, OBJ, CSV-Bauteil-Schedule (volles Element-Set).
|
||||||
|
- Kamera-Presets (Kardinal + Iso), Norden-Rotation.
|
||||||
|
|
||||||
|
### Import / Kontext
|
||||||
|
- DXF (Konturen), DWG (`@mlightcad/libredwg-web`, WASM), `.lin`/`.pat`.
|
||||||
|
- Swisstopo: swissBUILDINGS3D (radiusgenau zugeschnitten, nicht die ganze
|
||||||
|
STAC-Kachel), swissALTI3D, SWISSIMAGE-Orthofoto-Draping, LV95↔WGS84,
|
||||||
|
Georeferenzierung über EINEN Vermessungspunkt (E/N/H, `geoAnchor`).
|
||||||
|
- OSM/Overpass-Kontextimport (7 Kategorien).
|
||||||
|
- Terrain-Mesh-Generator.
|
||||||
|
|
||||||
|
### Desktop-Integration (Tauri)
|
||||||
|
- Eigene randlose Fenster mit nativer Titelleiste (macOS-Ampel-Position).
|
||||||
|
- Native Speichern/Öffnen-Dialoge (`plugin-fs`/`plugin-dialog`), eigenes
|
||||||
|
`.obp`-Projektdateiformat.
|
||||||
|
- OS-Level-Exklusiv-Lock gegen Doppelöffnen desselben Projekts (`fs4`-Crate).
|
||||||
|
- Vier eigenständige native Zusatzfenster (Resources, Settings, DrawingLevels,
|
||||||
|
LayerSettings, ContextImport) statt Overlay/Modal.
|
||||||
|
- Native macOS-Menüleiste.
|
||||||
|
- i18n de/en durchgängig, eigener `t()`-Mechanismus (kein i18next).
|
||||||
|
|
||||||
|
## 4. Mist-Liste — Befunde, Doku-Widersprüche, offene Fäden
|
||||||
|
|
||||||
|
### 4.1 Verwaiste WASM-Crates (gebaut, aber nirgends importiert)
|
||||||
|
- **`src-tauri/geometry`** (1.057 LOC, 8 Tests) → `pkgGeometry` — **null**
|
||||||
|
Importstellen in `src/`. Wand-Join-Mathe existiert redundant als TS
|
||||||
|
(`src/model/joins.ts`) UND als Rust-Port, aber nur die TS-Version läuft.
|
||||||
|
- **`src-tauri/dwgimport`** (284 LOC) → `pkgDwgImport` — **null** Importstellen;
|
||||||
|
DWG-Import läuft stattdessen über `@mlightcad/libredwg-web` (npm-Paket).
|
||||||
|
- **`src-tauri/kernel2d`** (3.383 LOC, 18 Tests) → `pkgKernel2d` — wird nur von
|
||||||
|
einem Paritätstest (`kernel2d.parity.test.ts`) konsumiert, nicht produktiv.
|
||||||
|
Die TS-Version `src/geometry/kernel2d.ts` (886 Zeilen) ist die tatsächlich
|
||||||
|
laufende Implementierung. Laut PENDENZEN.md bewusst so belassen: ein
|
||||||
|
Join-Benchmark zeigte WASM unter ~100 Wänden **langsamer** als die naive
|
||||||
|
TS-Routine. Kein Bug, aber die drei Crates zusammen sind ~4.700 Zeilen Rust
|
||||||
|
(+44 Tests), die aktuell nichts zur Laufzeit beitragen ausser einem
|
||||||
|
Korrektheits-Cross-Check für kernel2d.
|
||||||
|
|
||||||
|
**Empfehlung:** entweder (a) `geometry`- und `dwgimport`-Crate + ihre
|
||||||
|
`build:*`-Scripts entfernen (kein Nutzen, nur Wartungslast), oder (b)
|
||||||
|
explizit als „Referenzimplementierung/Zukunftsoption" in ARCHITECTURE.md
|
||||||
|
dokumentieren, damit niemand sie für aktiv hält.
|
||||||
|
|
||||||
|
### 4.2 Toter Code
|
||||||
|
- `src/section/hlr.ts` + `occt.ts` + `occt-wasm.d.ts` (473 LOC) — OCCT-WASM-
|
||||||
|
HLR-Spike aus Phase 0, **keine Aufrufer mehr**. Ersetzt durch die analytische
|
||||||
|
Rust-Schnitt-Pipeline (§2.4). `opencascade.js` bleibt als npm-Dependency
|
||||||
|
bestehen, obwohl nur noch dieser tote Code sie importiert.
|
||||||
|
- `src/export/planToPrintSvg.ts` — Kommentar im Code selbst sagt „ERSETZT durch
|
||||||
|
`sceneToPrintSvg.ts`", ist aber noch im Baum.
|
||||||
|
- `Element`-Typalias (`src/model/types.ts:1601`) — definiert, nirgends benutzt.
|
||||||
|
|
||||||
|
### 4.3 Das grösste Doku-vs-Code-Problem: App.tsx
|
||||||
|
CONVENTIONS.md verlangt seit Tag 1 „App.tsx bleibt dünner Shell, keine
|
||||||
|
Geschäftslogik". `docs/design/state-architecture.md` plante explizit die
|
||||||
|
Extraktion nach `src/views/` (View-Routing) und `src/menus/`
|
||||||
|
(Kontextmenü-Aufbau). Beide Ordner **existieren nicht**. `App.tsx` ist mit
|
||||||
|
**7.130 Zeilen** die grösste Einzeldatei des Projekts und enthält weiterhin
|
||||||
|
View-Umschaltung und Kontextmenü-Konstruktion inline. Das ist der genaue
|
||||||
|
„God-Component"-Zustand, den die Doku von Anfang an vermeiden wollte.
|
||||||
|
|
||||||
|
### 4.4 Bekannte Doppelspur: `Door[]` vs. `Opening[]`
|
||||||
|
`Project` führt sowohl ein älteres `doors: Door[]` als auch das neuere,
|
||||||
|
allgemeinere `openings?: Opening[]` (`kind:"door"|"window"`). Laut
|
||||||
|
PENDENZEN.md ist das erkannt und als Aufräum-Punkt vorgemerkt, aber nicht
|
||||||
|
konsolidiert.
|
||||||
|
|
||||||
|
### 4.5 Doku-Widersprüche (README/ARCHITECTURE/CONVENTIONS vs. Realität)
|
||||||
|
| Dokument | Behauptung | Realität |
|
||||||
|
|---|---|---|
|
||||||
|
| README.md | Shell = Electron, Tauri „ausrangiert" | Falsch andersrum gedacht: **beide** sind aktiv — Tauri auf macOS (WKWebView+WebGPU), Electron auf Linux (WebKitGTK kann kein WebGPU); siehe §2.7 |
|
||||||
|
| README.md | „HLR noch nicht ans UI verdrahtet, Views sind Stubs" | Der OCCT-HLR-Pfad stimmt (tot), aber Schnitte/Ansichten funktionieren real über eine andere, eigene Rust-Pipeline |
|
||||||
|
| README.md | „Prioritäts-T-/X-Stösse … Risiko #1" unter „bewusst offen" | Seit 7.7. erledigt, inkl. Rust-Port |
|
||||||
|
| README.md | „PDF-Export ist noch single-sheet" | `layoutPdf.ts::buildFolderPdf` erzeugt echte Mehrseiten-PDFs pro Ordner |
|
||||||
|
| README.md | Engine-Liste nennt nur render2d/render3d | Es gibt 6 Rust-Crates (+kernel2d/geometry/trucksolid/dwgimport) |
|
||||||
|
| CONVENTIONS.md | Struktur-Ziel `src/views/`, `src/menus/` | Existieren nicht; Logik liegt in `App.tsx` |
|
||||||
|
| CONVENTIONS.md | Dev-Port 5173 | Tatsächlich 5187 (`vite.config.ts`, `tauri.conf.json`) |
|
||||||
|
| ARCHITECTURE.md | Ziel-Struktur `store/`, `sheets/`, `workers/geometry.worker.ts` (Comlink) | Tatsächlich `state/`, `panels/layoutModel.ts`; kein Worker/Comlink irgendwo im Projekt |
|
||||||
|
| ARCHITECTURE.md | HLR „im Web Worker (Comlink)" | Kein Comlink im Projekt; Schnitt läuft synchron GPU-seitig in Rust |
|
||||||
|
| HANDOVER.md | Neuester Block: „Stand 2026-07-09" | Jüngster Commit + PENDENZEN.md sind vom 17.–20.7. — 8+ Tage/mehrere Sessions veraltet |
|
||||||
|
|
||||||
|
### 4.6 Codequalität — besser als der Tempo vermuten lässt
|
||||||
|
Trotz 3 Wochen / 362 Commits über viele autonome Sessions: **keine** FIXME/HACK/
|
||||||
|
XXX-Marker im ganzen Projekt; nur 2 TODOs (beide bekannt/harmlos: Geländer in
|
||||||
|
`Viewport3D.tsx:2649`, Kanten-Tiefe in `toSection.ts:1093`); `eslint-disable`
|
||||||
|
fast ausschliesslich `react-hooks/exhaustive-deps` (bewusst); keine
|
||||||
|
`_v2`/`_old`/`_backup`-Dateileichen. Die eigentliche Aufgabenliste lebt
|
||||||
|
diszipliniert in PENDENZEN.md statt in Code-Kommentaren verstreut — gesünder
|
||||||
|
als der Durchschnitt für dieses Bau-Tempo.
|
||||||
|
|
||||||
|
### 4.7 Genuine offene Punkte (Auszug aus PENDENZEN.md, Details dort)
|
||||||
|
- Geo-Block: Layer-Zuordnung für importierte Gebäude/Terrain, reale Höhen +
|
||||||
|
Projekt-müM-Draping, SWISSIMAGE-Draping.
|
||||||
|
- 3D-Feinschliff: Fensterrahmen-Ecken bei „fein" überlappen (kein Gehrungs-
|
||||||
|
Union), Dach-First/Grat hat unverschmolzene Dreiecke, unerklärte
|
||||||
|
Vertikalstreifen auf oberen Wandflächen (undiagnostiziert).
|
||||||
|
- truck-Boolean nicht an die Wand/Öffnungs-Pipeline angeschlossen (Scope-
|
||||||
|
Entscheid mit Nutzer ausstehend).
|
||||||
|
- Feld-Controller (Tab-Zyklus) fehlt für Body-Move und für 3D-Griffe generell
|
||||||
|
(nur 2D-Einzelpunkt-Drag hat ihn); 3D-Griff-Drag hat gar kein Snapping.
|
||||||
|
- `make2D`-Kommando (3D→flacher 2D-Plan mit Füllungen) ungebaut.
|
||||||
|
- Ribbon-3D-Tab leer; einige Punkte visuell noch nicht in Tauri abgenommen
|
||||||
|
(u. a. Materialfarben-Textur-Array, Schraffur-Schnittfüllung — laut
|
||||||
|
PENDENZEN als „[~] implementiert, aber unverifiziert" markiert).
|
||||||
|
- DWG/DXF-Domänen-Mapping (Entitäten → Wände/Öffnungen) unbegonnen; DWG-Schreiben
|
||||||
|
fehlt (Lesen über `libredwg-web` vorhanden).
|
||||||
|
- Teamwork/Kollaboration (Supabase) bewusst nicht begonnen, gilt als späte Phase.
|
||||||
|
|
||||||
|
## 5. Vergleich: Tag-1-Vision vs. heute
|
||||||
|
|
||||||
|
| Vision (28./29.6.) | Heute |
|
||||||
|
|---|---|
|
||||||
|
| Three.js als einziger 3D-Renderer, OpenCascade.js für Booleans/HLR | Eigene Rust/WASM-Engines („Nordstern") für 2D+3D; three.js nur noch „Free"-Fallback; OCCT-Pfad tot |
|
||||||
|
| Ein `Element[]`-Union | Typisierte Arrays pro Bauteiltyp auf `Project` |
|
||||||
|
| Zustand-Store | Eigener `useSyncExternalStore`-Store |
|
||||||
|
| Web Worker + Comlink für HLR | Synchrone, analytische Rust-GPU-Schnitt-Pipeline |
|
||||||
|
| „Booleans: Entscheidung in Phase 0" | Trucksolid/csgrs-CSG existiert, ist getestet, aber nicht an Wände angeschlossen |
|
||||||
|
| web-ifc für IFC | Eigener IFC4-Writer (`exportIfc.ts`) |
|
||||||
|
| Phase 2–5 grösstenteils „Backlog" | Treppen, Dächer, Stützen, SIA-416, Swisstopo, OSM, Kamera-Presets, Layouts, Ausschnitte, Terrain — alles bereits gebaut |
|
||||||
|
|
||||||
|
Kurz: die **Prinzipien** (ein Modell, viele Ableitungen; Darstellung erst beim
|
||||||
|
Rendern; keine Cache-Stale-Bugs) haben gehalten und wurden korrekt umgesetzt.
|
||||||
|
Die **konkreten Technologie-Entscheidungen** sind fast durchgängig anders
|
||||||
|
gelaufen als geplant — meist zugunsten einer eigenen, schnelleren Rust/WASM-
|
||||||
|
Lösung statt einer Drittbibliothek.
|
||||||
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
# Dokumentation — Standalone Browser-BIM (cad)
|
# Dokumentation — Standalone Browser-BIM (cad)
|
||||||
|
|
||||||
> Stand: 2026-06-29 · Übergeordnet: [ROADMAP.md](../ROADMAP.md) (Vision & Phasen) ·
|
> Stand: 2026-06-29 · **Historische Recherche-/Design-Dokumente aus der ersten
|
||||||
|
> Woche.** Mehrere Kern-Empfehlungen hier (replicad/OCCT als B-Rep-Kernel,
|
||||||
|
> Manifold, web-ifc, `three/webgpu`) wurden im tatsächlichen Bau **nicht**
|
||||||
|
> umgesetzt — stattdessen entstanden eigene Rust/WASM-Rendering-Engines
|
||||||
|
> („Nordstern"). Für den aktuellen Ist-Zustand: **[../STATUS.md](../STATUS.md)**
|
||||||
|
> und **[../ARCHITECTURE.md](../ARCHITECTURE.md)**. Die Dokumente unten sind als
|
||||||
|
> Recherche-Hintergrund weiterhin lesenswert, aber nicht mehr aktueller Plan.
|
||||||
|
> Übergeordnet: [ROADMAP.md](../ROADMAP.md) (Vision & Phasen, ebenfalls historisch) ·
|
||||||
> [CONVENTIONS.md](../CONVENTIONS.md) (Konventionen) · [ARCHITECTURE.md](../ARCHITECTURE.md).
|
> [CONVENTIONS.md](../CONVENTIONS.md) (Konventionen) · [ARCHITECTURE.md](../ARCHITECTURE.md).
|
||||||
|
|
||||||
Dieses Verzeichnis bündelt die Recherche- und Design-Dokumente für `cad`, die
|
Dieses Verzeichnis bündelt die Recherche- und Design-Dokumente für `cad`, die
|
||||||
|
|||||||
@@ -154,889 +154,6 @@
|
|||||||
"displacement": "/assets/materials/Ground103/displacement.jpg",
|
"displacement": "/assets/materials/Ground103/displacement.jpg",
|
||||||
"ao": "/assets/materials/Ground103/ao.jpg"
|
"ao": "/assets/materials/Ground103/ao.jpg"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Asphalt023S",
|
|
||||||
"name": "Asphalt 1",
|
|
||||||
"category": "Asphalt",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Asphalt023S/color.jpg",
|
|
||||||
"normal": "/assets/materials/Asphalt023S/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Asphalt023S/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Asphalt023S/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Asphalt023S/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Asphalt025C",
|
|
||||||
"name": "Asphalt 2",
|
|
||||||
"category": "Asphalt",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Asphalt025C/color.jpg",
|
|
||||||
"normal": "/assets/materials/Asphalt025C/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Asphalt025C/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Asphalt025C/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Asphalt025C/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Asphalt031",
|
|
||||||
"name": "Asphalt 3",
|
|
||||||
"category": "Asphalt",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Asphalt031/color.jpg",
|
|
||||||
"normal": "/assets/materials/Asphalt031/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Asphalt031/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Asphalt031/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Asphalt031/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks051",
|
|
||||||
"name": "Backstein 1",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks051/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks051/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks051/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks051/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks059",
|
|
||||||
"name": "Backstein 2",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks059/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks059/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks059/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks059/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks059/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks060",
|
|
||||||
"name": "Backstein 3",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks060/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks060/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks060/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks060/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks060/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks066",
|
|
||||||
"name": "Backstein 4",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks066/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks066/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks066/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks066/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks066/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks074",
|
|
||||||
"name": "Backstein 5",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks074/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks074/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks074/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks074/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks074/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks075A",
|
|
||||||
"name": "Backstein 6",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks075A/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks075A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks075A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks075A/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks075A/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks084",
|
|
||||||
"name": "Backstein 7",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks084/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks084/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks084/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks084/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks084/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks085",
|
|
||||||
"name": "Backstein 8",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks085/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks085/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks085/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks085/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks085/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks089",
|
|
||||||
"name": "Backstein 9",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks089/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks089/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks089/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks089/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks089/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks090",
|
|
||||||
"name": "Backstein 10",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks090/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks090/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks090/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks090/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks090/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks091",
|
|
||||||
"name": "Backstein 11",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks091/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks091/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks091/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks091/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks091/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks092",
|
|
||||||
"name": "Backstein 12",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks092/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks092/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks092/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks092/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks092/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks093",
|
|
||||||
"name": "Backstein 13",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks093/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks093/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks093/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks093/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks093/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks094",
|
|
||||||
"name": "Backstein 14",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks094/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks094/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks094/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks094/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks094/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks097",
|
|
||||||
"name": "Backstein 15",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks097/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks097/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks097/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks097/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks097/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks100",
|
|
||||||
"name": "Backstein 16",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks100/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks100/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks100/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks100/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks100/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks101",
|
|
||||||
"name": "Backstein 17",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks101/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks101/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks101/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks101/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks101/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks102",
|
|
||||||
"name": "Backstein 18",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks102/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks102/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks102/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks102/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks102/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Bricks103",
|
|
||||||
"name": "Backstein 19",
|
|
||||||
"category": "Bricks",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Bricks103/color.jpg",
|
|
||||||
"normal": "/assets/materials/Bricks103/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Bricks103/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Bricks103/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Bricks103/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Chipboard004",
|
|
||||||
"name": "Sperrholz 1",
|
|
||||||
"category": "Chipboard",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Chipboard004/color.jpg",
|
|
||||||
"normal": "/assets/materials/Chipboard004/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Chipboard004/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Chipboard004/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Chipboard005",
|
|
||||||
"name": "Sperrholz 2",
|
|
||||||
"category": "Chipboard",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Chipboard005/color.jpg",
|
|
||||||
"normal": "/assets/materials/Chipboard005/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Chipboard005/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Chipboard005/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Chipboard007",
|
|
||||||
"name": "Sperrholz 3",
|
|
||||||
"category": "Chipboard",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Chipboard007/color.jpg",
|
|
||||||
"normal": "/assets/materials/Chipboard007/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Chipboard007/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Chipboard007/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete024",
|
|
||||||
"name": "Beton 1",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete024/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete024/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete024/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete024/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete025",
|
|
||||||
"name": "Beton 2",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete025/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete025/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete025/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete025/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete025/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete030",
|
|
||||||
"name": "Beton 3",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete030/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete030/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete030/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete030/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete030/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete031",
|
|
||||||
"name": "Beton 4",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete031/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete031/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete031/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete031/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete031/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete032",
|
|
||||||
"name": "Beton 5",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete032/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete032/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete032/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete032/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete032/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete033",
|
|
||||||
"name": "Beton 6",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete033/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete033/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete033/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete033/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete033/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete034",
|
|
||||||
"name": "Beton 7",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete034/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete034/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete034/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete034/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete036",
|
|
||||||
"name": "Beton 8",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete036/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete036/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete036/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete036/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete040",
|
|
||||||
"name": "Beton 9",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete040/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete040/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete040/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete040/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete040/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete042A",
|
|
||||||
"name": "Beton 10",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete042A/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete042A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete042A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete042A/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete042A/ao.jpg",
|
|
||||||
"metalness": "/assets/materials/Concrete042A/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete044C",
|
|
||||||
"name": "Beton 11",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete044C/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete044C/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete044C/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete044C/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete044C/ao.jpg",
|
|
||||||
"metalness": "/assets/materials/Concrete044C/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete044D",
|
|
||||||
"name": "Beton 12",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete044D/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete044D/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete044D/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete044D/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete044D/ao.jpg",
|
|
||||||
"metalness": "/assets/materials/Concrete044D/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete045",
|
|
||||||
"name": "Beton 13",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete045/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete045/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete045/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete045/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete045/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete046",
|
|
||||||
"name": "Beton 14",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete046/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete046/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete046/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete046/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Concrete047A",
|
|
||||||
"name": "Beton 15",
|
|
||||||
"category": "Concrete",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Concrete047A/color.jpg",
|
|
||||||
"normal": "/assets/materials/Concrete047A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Concrete047A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Concrete047A/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Concrete047A/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Fabric061",
|
|
||||||
"name": "Stoff 1",
|
|
||||||
"category": "Fabric",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Fabric061/color.jpg",
|
|
||||||
"normal": "/assets/materials/Fabric061/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Fabric061/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Fabric061/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Fabric061/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Fabric066",
|
|
||||||
"name": "Stoff 2",
|
|
||||||
"category": "Fabric",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Fabric066/color.jpg",
|
|
||||||
"normal": "/assets/materials/Fabric066/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Fabric066/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Fabric066/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Fabric066/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Fabric081C",
|
|
||||||
"name": "Stoff 3",
|
|
||||||
"category": "Fabric",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Fabric081C/color.jpg",
|
|
||||||
"normal": "/assets/materials/Fabric081C/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Fabric081C/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Fabric081C/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Fabric081C/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Gravel023",
|
|
||||||
"name": "Kies 1",
|
|
||||||
"category": "Gravel",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Gravel023/color.jpg",
|
|
||||||
"normal": "/assets/materials/Gravel023/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Gravel023/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Gravel023/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Gravel023/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Gravel041",
|
|
||||||
"name": "Kies 2",
|
|
||||||
"category": "Gravel",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Gravel041/color.jpg",
|
|
||||||
"normal": "/assets/materials/Gravel041/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Gravel041/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Gravel041/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Gravel041/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Ground037",
|
|
||||||
"name": "Boden 1",
|
|
||||||
"category": "Ground",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Ground037/color.jpg",
|
|
||||||
"normal": "/assets/materials/Ground037/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Ground037/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Ground037/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Ground037/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Ground054",
|
|
||||||
"name": "Boden 2",
|
|
||||||
"category": "Ground",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Ground054/color.jpg",
|
|
||||||
"normal": "/assets/materials/Ground054/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Ground054/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Ground054/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Ground054/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Marble016",
|
|
||||||
"name": "Marmor 1",
|
|
||||||
"category": "Marble",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Marble016/color.jpg",
|
|
||||||
"normal": "/assets/materials/Marble016/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Marble016/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Marble016/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Marble021",
|
|
||||||
"name": "Marmor 2",
|
|
||||||
"category": "Marble",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Marble021/color.jpg",
|
|
||||||
"normal": "/assets/materials/Marble021/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Marble021/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Marble021/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Metal049A",
|
|
||||||
"name": "Metall 1",
|
|
||||||
"category": "Metal",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Metal049A/color.jpg",
|
|
||||||
"normal": "/assets/materials/Metal049A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Metal049A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Metal049A/displacement.jpg",
|
|
||||||
"metalness": "/assets/materials/Metal049A/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Metal055A",
|
|
||||||
"name": "Metall 2",
|
|
||||||
"category": "Metal",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Metal055A/color.jpg",
|
|
||||||
"normal": "/assets/materials/Metal055A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Metal055A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Metal055A/displacement.jpg",
|
|
||||||
"metalness": "/assets/materials/Metal055A/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "OfficeCeiling001",
|
|
||||||
"name": "Bürodecke 1",
|
|
||||||
"category": "Office Ceiling",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/OfficeCeiling001/color.jpg",
|
|
||||||
"normal": "/assets/materials/OfficeCeiling001/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/OfficeCeiling001/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/OfficeCeiling001/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/OfficeCeiling001/ao.jpg",
|
|
||||||
"metalness": "/assets/materials/OfficeCeiling001/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "OfficeCeiling002",
|
|
||||||
"name": "Bürodecke 2",
|
|
||||||
"category": "Office Ceiling",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/OfficeCeiling002/color.jpg",
|
|
||||||
"normal": "/assets/materials/OfficeCeiling002/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/OfficeCeiling002/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/OfficeCeiling002/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/OfficeCeiling002/ao.jpg",
|
|
||||||
"metalness": "/assets/materials/OfficeCeiling002/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "OfficeCeiling003",
|
|
||||||
"name": "Bürodecke 3",
|
|
||||||
"category": "Office Ceiling",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/OfficeCeiling003/color.jpg",
|
|
||||||
"normal": "/assets/materials/OfficeCeiling003/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/OfficeCeiling003/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/OfficeCeiling003/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/OfficeCeiling003/ao.jpg",
|
|
||||||
"metalness": "/assets/materials/OfficeCeiling003/metalness.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "PavingStones128",
|
|
||||||
"name": "Pflasterstein 1",
|
|
||||||
"category": "Paving Stones",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/PavingStones128/color.jpg",
|
|
||||||
"normal": "/assets/materials/PavingStones128/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/PavingStones128/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/PavingStones128/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/PavingStones128/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "PavingStones138",
|
|
||||||
"name": "Pflasterstein 2",
|
|
||||||
"category": "Paving Stones",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/PavingStones138/color.jpg",
|
|
||||||
"normal": "/assets/materials/PavingStones138/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/PavingStones138/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/PavingStones138/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/PavingStones138/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Plaster002",
|
|
||||||
"name": "Putz 1",
|
|
||||||
"category": "Plaster",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Plaster002/color.jpg",
|
|
||||||
"normal": "/assets/materials/Plaster002/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Plaster002/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Plaster002/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Plaster007",
|
|
||||||
"name": "Putz 2",
|
|
||||||
"category": "Plaster",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Plaster007/color.jpg",
|
|
||||||
"normal": "/assets/materials/Plaster007/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Plaster007/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Plaster007/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Plaster007/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Rock058",
|
|
||||||
"name": "Naturstein 1",
|
|
||||||
"category": "Rock",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Rock058/color.jpg",
|
|
||||||
"normal": "/assets/materials/Rock058/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Rock058/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Rock058/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Rock058/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Rock063",
|
|
||||||
"name": "Naturstein 2",
|
|
||||||
"category": "Rock",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Rock063/color.jpg",
|
|
||||||
"normal": "/assets/materials/Rock063/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Rock063/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Rock063/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Rock063/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Rock064",
|
|
||||||
"name": "Naturstein 3",
|
|
||||||
"category": "Rock",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Rock064/color.jpg",
|
|
||||||
"normal": "/assets/materials/Rock064/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Rock064/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Rock064/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/Rock064/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "RoofingTiles012A",
|
|
||||||
"name": "Dach 1",
|
|
||||||
"category": "Roofing Tiles",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/RoofingTiles012A/color.jpg",
|
|
||||||
"normal": "/assets/materials/RoofingTiles012A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/RoofingTiles012A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/RoofingTiles012A/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/RoofingTiles012A/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "RoofingTiles014A",
|
|
||||||
"name": "Dach 3",
|
|
||||||
"category": "Roofing Tiles",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/RoofingTiles014A/color.jpg",
|
|
||||||
"normal": "/assets/materials/RoofingTiles014A/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/RoofingTiles014A/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/RoofingTiles014A/displacement.jpg",
|
|
||||||
"ao": "/assets/materials/RoofingTiles014A/ao.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Terrazzo005",
|
|
||||||
"name": "Terrazzo 1",
|
|
||||||
"category": "Terrazzo",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Terrazzo005/color.jpg",
|
|
||||||
"normal": "/assets/materials/Terrazzo005/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Terrazzo005/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Terrazzo005/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Terrazzo013",
|
|
||||||
"name": "Terrazzo 2",
|
|
||||||
"category": "Terrazzo",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Terrazzo013/color.jpg",
|
|
||||||
"normal": "/assets/materials/Terrazzo013/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Terrazzo013/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Terrazzo013/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Terrazzo018",
|
|
||||||
"name": "Terrazzo 3",
|
|
||||||
"category": "Terrazzo",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Terrazzo018/color.jpg",
|
|
||||||
"normal": "/assets/materials/Terrazzo018/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Terrazzo018/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Terrazzo018/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Tiles139",
|
|
||||||
"name": "Fliesen 1",
|
|
||||||
"category": "Tiles",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Tiles139/color.jpg",
|
|
||||||
"normal": "/assets/materials/Tiles139/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Tiles139/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Tiles139/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Tiles140",
|
|
||||||
"name": "Fliesen 2",
|
|
||||||
"category": "Tiles",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Tiles140/color.jpg",
|
|
||||||
"normal": "/assets/materials/Tiles140/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Tiles140/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Tiles140/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Wood049",
|
|
||||||
"name": "Holz 1",
|
|
||||||
"category": "Wood",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Wood049/color.jpg",
|
|
||||||
"normal": "/assets/materials/Wood049/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Wood049/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Wood049/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Wood051",
|
|
||||||
"name": "Holz 2",
|
|
||||||
"category": "Wood",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Wood051/color.jpg",
|
|
||||||
"normal": "/assets/materials/Wood051/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Wood051/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Wood051/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Wood066",
|
|
||||||
"name": "Holz 3",
|
|
||||||
"category": "Wood",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Wood066/color.jpg",
|
|
||||||
"normal": "/assets/materials/Wood066/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Wood066/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Wood066/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Wood092",
|
|
||||||
"name": "Holz 4",
|
|
||||||
"category": "Wood",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Wood092/color.jpg",
|
|
||||||
"normal": "/assets/materials/Wood092/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Wood092/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Wood092/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Wood094",
|
|
||||||
"name": "Holz 5",
|
|
||||||
"category": "Wood",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/Wood094/color.jpg",
|
|
||||||
"normal": "/assets/materials/Wood094/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/Wood094/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/Wood094/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "WoodFloor064",
|
|
||||||
"name": "Holzboden 1",
|
|
||||||
"category": "Wood Floor",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/WoodFloor064/color.jpg",
|
|
||||||
"normal": "/assets/materials/WoodFloor064/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/WoodFloor064/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/WoodFloor064/displacement.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "WoodFloor070",
|
|
||||||
"name": "Holzboden 2",
|
|
||||||
"category": "Wood Floor",
|
|
||||||
"maps": {
|
|
||||||
"color": "/assets/materials/WoodFloor070/color.jpg",
|
|
||||||
"normal": "/assets/materials/WoodFloor070/normal.jpg",
|
|
||||||
"roughness": "/assets/materials/WoodFloor070/roughness.jpg",
|
|
||||||
"displacement": "/assets/materials/WoodFloor070/displacement.jpg"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,297 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* fetch-materials.mjs — Copies mirrored materials from DOSSIER-LIBRARY into
|
|
||||||
* public/assets/materials/ and regenerates src/materials/library.ts and
|
|
||||||
* public/assets/materials/manifest.json.
|
|
||||||
*
|
|
||||||
* The 12 hand-authored starter entries in library.ts / manifest.json are
|
|
||||||
* preserved (they appear first). Broad-set entries are appended/merged
|
|
||||||
* (existing broad entries are replaced if the source has changed).
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/fetch-materials.mjs --library /home/karim/DOSSIER-LIBRARY
|
|
||||||
* node scripts/fetch-materials.mjs --library ../DOSSIER-LIBRARY --dry-run
|
|
||||||
*
|
|
||||||
* The --library flag (or DOSSIER_LIBRARY env var) points to the local mirror
|
|
||||||
* root. When a git remote is set up, pass a local clone path here.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
||||||
import { readdir } from 'fs/promises';
|
|
||||||
import { dirname, join, resolve } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
const CAD_ROOT = join(__dirname, '..');
|
|
||||||
|
|
||||||
// ── CLI ───────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
function parseArgs() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
let libraryPath = process.env.DOSSIER_LIBRARY ?? null;
|
|
||||||
let dryRun = false;
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
|
||||||
if ((args[i] === '--library' || args[i] === '-l') && args[i + 1]) {
|
|
||||||
libraryPath = args[++i];
|
|
||||||
} else if (args[i] === '--dry-run') {
|
|
||||||
dryRun = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!libraryPath) {
|
|
||||||
console.error('Error: --library <path> is required (or set DOSSIER_LIBRARY env var)');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
return { libraryPath: resolve(libraryPath), dryRun };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── German category labels ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const CATEGORY_GERMAN = {
|
|
||||||
Bricks: 'Backstein',
|
|
||||||
Concrete: 'Beton',
|
|
||||||
Wood: 'Holz',
|
|
||||||
WoodFloor: 'Holzboden',
|
|
||||||
Plaster: 'Putz',
|
|
||||||
Metal: 'Metall',
|
|
||||||
Marble: 'Marmor',
|
|
||||||
Rock: 'Naturstein',
|
|
||||||
PavingStones: 'Pflasterstein',
|
|
||||||
Tiles: 'Fliesen',
|
|
||||||
Ground: 'Boden',
|
|
||||||
Asphalt: 'Asphalt',
|
|
||||||
Roof: 'Dach',
|
|
||||||
Roofing: 'Dach',
|
|
||||||
Plywood: 'Sperrholz',
|
|
||||||
Fabric: 'Stoff',
|
|
||||||
Gravel: 'Kies',
|
|
||||||
Terrazzo: 'Terrazzo',
|
|
||||||
OfficeCeiling: 'Bürodecke',
|
|
||||||
Grass: 'Gras',
|
|
||||||
// ambientCG displayCategory strings (as stored in index.json) — spaced /
|
|
||||||
// substituted names that differ from the API category keys above.
|
|
||||||
'Wood Floor': 'Holzboden',
|
|
||||||
'Paving Stones': 'Pflasterstein',
|
|
||||||
'Roofing Tiles': 'Dach',
|
|
||||||
'Roofing': 'Dach',
|
|
||||||
'Chipboard': 'Sperrholz',
|
|
||||||
'Office Ceiling': 'Bürodecke',
|
|
||||||
};
|
|
||||||
|
|
||||||
/** German label with a numeric suffix to disambiguate within the same category. */
|
|
||||||
function germanLabel(category, indexWithinCategory) {
|
|
||||||
const base = CATEGORY_GERMAN[category] ?? category;
|
|
||||||
return `${base} ${indexWithinCategory + 1}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Starter material IDs (must never be replaced/removed) ─────────────────────
|
|
||||||
|
|
||||||
const STARTER_IDS = new Set([
|
|
||||||
'Concrete048', 'Plaster001', 'Wood095', 'WoodFloor051',
|
|
||||||
'Bricks104', 'Tiles141', 'Marble012', 'PavingStones150',
|
|
||||||
'Metal063', 'Gravel043', 'Grass005', 'Ground103',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ── Map files ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const CANONICAL_MAPS = ['color', 'normal', 'roughness', 'displacement', 'ao', 'metalness'];
|
|
||||||
|
|
||||||
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const { libraryPath, dryRun } = parseArgs();
|
|
||||||
|
|
||||||
const ambientcgDir = join(libraryPath, 'ambientcg');
|
|
||||||
const indexPath = join(ambientcgDir, 'index.json');
|
|
||||||
|
|
||||||
if (!existsSync(indexPath)) {
|
|
||||||
console.error(`No index.json found at: ${indexPath}`);
|
|
||||||
console.error('Run scripts/mirror-ambientcg.mjs in DOSSIER-LIBRARY first.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = JSON.parse(readFileSync(indexPath, 'utf8'));
|
|
||||||
const mirroredAssets = index.assets ?? [];
|
|
||||||
console.log(`DOSSIER-LIBRARY: ${mirroredAssets.length} assets in index`);
|
|
||||||
if (dryRun) console.log('(dry-run mode — no files written)');
|
|
||||||
|
|
||||||
// ── Copy texture files ────────────────────────────────────────────────────
|
|
||||||
const destBase = join(CAD_ROOT, 'public', 'assets', 'materials');
|
|
||||||
let copied = 0;
|
|
||||||
let skipped = 0;
|
|
||||||
const broadEntries = []; // { assetId, category, displayName, maps }
|
|
||||||
|
|
||||||
// Track German label counters per category
|
|
||||||
const categoryCounters = {};
|
|
||||||
|
|
||||||
for (const asset of mirroredAssets) {
|
|
||||||
const { assetId, category, displayName, maps: mapKinds } = asset;
|
|
||||||
|
|
||||||
// Never overwrite starter materials
|
|
||||||
if (STARTER_IDS.has(assetId)) {
|
|
||||||
skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const srcDir = join(ambientcgDir, assetId);
|
|
||||||
const dstDir = join(destBase, assetId);
|
|
||||||
|
|
||||||
if (!existsSync(srcDir)) {
|
|
||||||
console.warn(` [warn] Source missing: ${srcDir}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
mkdirSync(dstDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedMaps = {};
|
|
||||||
for (const kind of CANONICAL_MAPS) {
|
|
||||||
const srcFile = join(srcDir, `${kind}.jpg`);
|
|
||||||
const dstFile = join(dstDir, `${kind}.jpg`);
|
|
||||||
if (existsSync(srcFile)) {
|
|
||||||
if (!dryRun) {
|
|
||||||
copyFileSync(srcFile, dstFile);
|
|
||||||
}
|
|
||||||
resolvedMaps[kind] = `/assets/materials/${assetId}/${kind}.jpg`;
|
|
||||||
copied++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(resolvedMaps).length === 0) {
|
|
||||||
console.warn(` [warn] No maps found for ${assetId}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build German label
|
|
||||||
const catKey = category ?? 'Other';
|
|
||||||
categoryCounters[catKey] = (categoryCounters[catKey] ?? 0);
|
|
||||||
const label = germanLabel(catKey, categoryCounters[catKey]);
|
|
||||||
categoryCounters[catKey]++;
|
|
||||||
|
|
||||||
broadEntries.push({
|
|
||||||
id: assetId,
|
|
||||||
name: label,
|
|
||||||
category: catKey,
|
|
||||||
maps: resolvedMaps,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Copied ${copied} map files; ${skipped} starter materials unchanged`);
|
|
||||||
console.log(`Broad set: ${broadEntries.length} materials`);
|
|
||||||
|
|
||||||
// ── Regenerate library.ts ─────────────────────────────────────────────────
|
|
||||||
const libraryPath_ = join(CAD_ROOT, 'src', 'materials', 'library.ts');
|
|
||||||
const existingLibrary = readFileSync(libraryPath_, 'utf8');
|
|
||||||
|
|
||||||
// Extract starter entries from the existing file (everything in MATERIAL_LIBRARY[])
|
|
||||||
// We re-use the static text of the 12 starter entries verbatim.
|
|
||||||
const starterEntriesMatch = existingLibrary.match(
|
|
||||||
/export const MATERIAL_LIBRARY: MaterialAsset\[\] = \[([\s\S]*?)\];\s*\n\nexport default/
|
|
||||||
);
|
|
||||||
|
|
||||||
let starterBlock = '';
|
|
||||||
if (starterEntriesMatch) {
|
|
||||||
// Keep the existing starter block exactly as-is
|
|
||||||
starterBlock = starterEntriesMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build broad entries as TS object literals
|
|
||||||
function mapsToTs(maps, indent) {
|
|
||||||
const lines = Object.entries(maps).map(
|
|
||||||
([k, v]) => `${indent} ${k}: "${v}",`
|
|
||||||
);
|
|
||||||
return `{\n${lines.join('\n')}\n${indent}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const broadTs = broadEntries.map(e => {
|
|
||||||
return ` {
|
|
||||||
id: "${e.id}",
|
|
||||||
name: "${e.name}",
|
|
||||||
category: "${e.category}",
|
|
||||||
maps: ${mapsToTs(e.maps, ' ')},
|
|
||||||
}`;
|
|
||||||
}).join(',\n');
|
|
||||||
|
|
||||||
const separator = broadEntries.length > 0
|
|
||||||
? `\n\n // ── Broad set (ambientCG 2K — populated by scripts/fetch-materials.mjs) ──\n // Run "node scripts/fetch-materials.mjs --library <DOSSIER-LIBRARY-path>" to refresh.\n`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const newLibrary = `// Auto-generated built-in material library (ambientCG, CC0 1.0).
|
|
||||||
// Static asset paths under /public; loadable directly via three.js TextureLoader.
|
|
||||||
// Source data mirrors public/assets/materials/manifest.json.
|
|
||||||
// Broad set populated by: node scripts/fetch-materials.mjs --library <DOSSIER-LIBRARY-path>
|
|
||||||
|
|
||||||
export type MaterialMapKind =
|
|
||||||
| 'color'
|
|
||||||
| 'normal'
|
|
||||||
| 'roughness'
|
|
||||||
| 'metalness'
|
|
||||||
| 'displacement'
|
|
||||||
| 'ao';
|
|
||||||
|
|
||||||
export interface MaterialAsset {
|
|
||||||
/** ambientCG asset id, e.g. 'Concrete048'. */
|
|
||||||
id: string;
|
|
||||||
/** Localized display name (German). */
|
|
||||||
name: string;
|
|
||||||
/** ambientCG category key, e.g. 'Concrete'. */
|
|
||||||
category: string;
|
|
||||||
/** Absolute public paths to the available texture maps. */
|
|
||||||
maps: Partial<Record<MaterialMapKind, string>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MATERIAL_LIBRARY_SOURCE = "ambientCG (ambientcg.com)";
|
|
||||||
export const MATERIAL_LIBRARY_LICENSE = "CC0 1.0 Universal (Public Domain)";
|
|
||||||
export const MATERIAL_LIBRARY_RESOLUTION = "2K";
|
|
||||||
|
|
||||||
export const MATERIAL_LIBRARY: MaterialAsset[] = [${starterBlock}${separator}${broadEntries.length > 0 ? broadTs + ',\n' : ''}];
|
|
||||||
|
|
||||||
export default MATERIAL_LIBRARY;
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
writeFileSync(libraryPath_, newLibrary, 'utf8');
|
|
||||||
console.log(`Written: ${libraryPath_}`);
|
|
||||||
} else {
|
|
||||||
console.log(`[dry-run] Would write: ${libraryPath_}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Regenerate manifest.json ──────────────────────────────────────────────
|
|
||||||
const manifestPath = join(destBase, 'manifest.json');
|
|
||||||
const existingManifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
||||||
|
|
||||||
// Keep starter entries from existing manifest
|
|
||||||
const starterManifestEntries = existingManifest.materials.filter(
|
|
||||||
m => STARTER_IDS.has(m.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
const broadManifestEntries = broadEntries.map(e => ({
|
|
||||||
id: e.id,
|
|
||||||
name: e.name,
|
|
||||||
category: e.category,
|
|
||||||
maps: e.maps,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const newManifest = {
|
|
||||||
source: 'ambientCG (ambientcg.com)',
|
|
||||||
license: 'CC0 1.0 Universal (Public Domain)',
|
|
||||||
resolution: '2K',
|
|
||||||
materials: [...starterManifestEntries, ...broadManifestEntries],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!dryRun) {
|
|
||||||
writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2) + '\n', 'utf8');
|
|
||||||
console.log(`Written: ${manifestPath}`);
|
|
||||||
} else {
|
|
||||||
console.log(`[dry-run] Would write: ${manifestPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\nTotal in MATERIAL_LIBRARY: ${starterManifestEntries.length} starters + ${broadEntries.length} broad = ${starterManifestEntries.length + broadEntries.length}`);
|
|
||||||
console.log('\nDone. Next: npx tsc -b && npm run build');
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(err => {
|
|
||||||
console.error('Fatal:', err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
+5
-890
@@ -1,7 +1,9 @@
|
|||||||
// Auto-generated built-in material library (ambientCG, CC0 1.0).
|
// Eingebaute Material-Bibliothek (ambientCG, CC0 1.0) — 13 fest gebündelte
|
||||||
// Static asset paths under /public; loadable directly via three.js TextureLoader.
|
// Starter-Materialien (Bilddateien liegen unter public/assets/materials/).
|
||||||
|
// Alles darüber hinaus läuft über die Live-Suche (src/materials/ambientcg.ts,
|
||||||
|
// Tab "Bibliothek durchsuchen"), die die komplette ambientCG-Bibliothek
|
||||||
|
// online durchsucht statt Texturen zu bündeln.
|
||||||
// Source data mirrors public/assets/materials/manifest.json.
|
// Source data mirrors public/assets/materials/manifest.json.
|
||||||
// Broad set populated by: node scripts/fetch-materials.mjs --library <DOSSIER-LIBRARY-path>
|
|
||||||
|
|
||||||
export type MaterialMapKind =
|
export type MaterialMapKind =
|
||||||
| 'color'
|
| 'color'
|
||||||
@@ -179,893 +181,6 @@ export const MATERIAL_LIBRARY: MaterialAsset[] = [
|
|||||||
ao: "/assets/materials/Ground103/ao.jpg",
|
ao: "/assets/materials/Ground103/ao.jpg",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// ── Broad set (ambientCG 2K — populated by scripts/fetch-materials.mjs) ──
|
|
||||||
// Run "node scripts/fetch-materials.mjs --library <DOSSIER-LIBRARY-path>" to refresh.
|
|
||||||
{
|
|
||||||
id: "Asphalt023S",
|
|
||||||
name: "Asphalt 1",
|
|
||||||
category: "Asphalt",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Asphalt023S/color.jpg",
|
|
||||||
normal: "/assets/materials/Asphalt023S/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Asphalt023S/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Asphalt023S/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Asphalt023S/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Asphalt025C",
|
|
||||||
name: "Asphalt 2",
|
|
||||||
category: "Asphalt",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Asphalt025C/color.jpg",
|
|
||||||
normal: "/assets/materials/Asphalt025C/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Asphalt025C/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Asphalt025C/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Asphalt025C/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Asphalt031",
|
|
||||||
name: "Asphalt 3",
|
|
||||||
category: "Asphalt",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Asphalt031/color.jpg",
|
|
||||||
normal: "/assets/materials/Asphalt031/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Asphalt031/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Asphalt031/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Asphalt031/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks051",
|
|
||||||
name: "Backstein 1",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks051/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks051/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks051/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks051/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks059",
|
|
||||||
name: "Backstein 2",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks059/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks059/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks059/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks059/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks059/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks060",
|
|
||||||
name: "Backstein 3",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks060/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks060/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks060/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks060/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks060/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks066",
|
|
||||||
name: "Backstein 4",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks066/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks066/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks066/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks066/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks066/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks074",
|
|
||||||
name: "Backstein 5",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks074/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks074/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks074/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks074/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks074/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks075A",
|
|
||||||
name: "Backstein 6",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks075A/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks075A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks075A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks075A/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks075A/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks084",
|
|
||||||
name: "Backstein 7",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks084/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks084/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks084/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks084/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks084/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks085",
|
|
||||||
name: "Backstein 8",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks085/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks085/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks085/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks085/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks085/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks089",
|
|
||||||
name: "Backstein 9",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks089/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks089/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks089/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks089/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks089/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks090",
|
|
||||||
name: "Backstein 10",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks090/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks090/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks090/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks090/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks090/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks091",
|
|
||||||
name: "Backstein 11",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks091/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks091/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks091/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks091/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks091/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks092",
|
|
||||||
name: "Backstein 12",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks092/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks092/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks092/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks092/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks092/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks093",
|
|
||||||
name: "Backstein 13",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks093/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks093/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks093/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks093/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks093/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks094",
|
|
||||||
name: "Backstein 14",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks094/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks094/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks094/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks094/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks094/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks097",
|
|
||||||
name: "Backstein 15",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks097/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks097/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks097/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks097/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks097/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks100",
|
|
||||||
name: "Backstein 16",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks100/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks100/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks100/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks100/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks100/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks101",
|
|
||||||
name: "Backstein 17",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks101/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks101/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks101/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks101/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks101/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks102",
|
|
||||||
name: "Backstein 18",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks102/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks102/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks102/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks102/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks102/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Bricks103",
|
|
||||||
name: "Backstein 19",
|
|
||||||
category: "Bricks",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Bricks103/color.jpg",
|
|
||||||
normal: "/assets/materials/Bricks103/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Bricks103/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Bricks103/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Bricks103/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Chipboard004",
|
|
||||||
name: "Sperrholz 1",
|
|
||||||
category: "Chipboard",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Chipboard004/color.jpg",
|
|
||||||
normal: "/assets/materials/Chipboard004/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Chipboard004/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Chipboard004/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Chipboard005",
|
|
||||||
name: "Sperrholz 2",
|
|
||||||
category: "Chipboard",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Chipboard005/color.jpg",
|
|
||||||
normal: "/assets/materials/Chipboard005/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Chipboard005/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Chipboard005/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Chipboard007",
|
|
||||||
name: "Sperrholz 3",
|
|
||||||
category: "Chipboard",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Chipboard007/color.jpg",
|
|
||||||
normal: "/assets/materials/Chipboard007/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Chipboard007/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Chipboard007/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete024",
|
|
||||||
name: "Beton 1",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete024/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete024/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete024/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete024/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete025",
|
|
||||||
name: "Beton 2",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete025/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete025/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete025/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete025/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete025/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete030",
|
|
||||||
name: "Beton 3",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete030/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete030/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete030/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete030/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete030/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete031",
|
|
||||||
name: "Beton 4",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete031/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete031/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete031/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete031/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete031/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete032",
|
|
||||||
name: "Beton 5",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete032/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete032/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete032/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete032/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete032/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete033",
|
|
||||||
name: "Beton 6",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete033/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete033/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete033/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete033/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete033/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete034",
|
|
||||||
name: "Beton 7",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete034/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete034/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete034/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete034/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete036",
|
|
||||||
name: "Beton 8",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete036/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete036/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete036/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete036/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete040",
|
|
||||||
name: "Beton 9",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete040/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete040/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete040/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete040/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete040/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete042A",
|
|
||||||
name: "Beton 10",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete042A/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete042A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete042A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete042A/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete042A/ao.jpg",
|
|
||||||
metalness: "/assets/materials/Concrete042A/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete044C",
|
|
||||||
name: "Beton 11",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete044C/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete044C/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete044C/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete044C/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete044C/ao.jpg",
|
|
||||||
metalness: "/assets/materials/Concrete044C/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete044D",
|
|
||||||
name: "Beton 12",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete044D/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete044D/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete044D/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete044D/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete044D/ao.jpg",
|
|
||||||
metalness: "/assets/materials/Concrete044D/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete045",
|
|
||||||
name: "Beton 13",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete045/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete045/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete045/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete045/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete045/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete046",
|
|
||||||
name: "Beton 14",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete046/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete046/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete046/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete046/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Concrete047A",
|
|
||||||
name: "Beton 15",
|
|
||||||
category: "Concrete",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Concrete047A/color.jpg",
|
|
||||||
normal: "/assets/materials/Concrete047A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Concrete047A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Concrete047A/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Concrete047A/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Fabric061",
|
|
||||||
name: "Stoff 1",
|
|
||||||
category: "Fabric",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Fabric061/color.jpg",
|
|
||||||
normal: "/assets/materials/Fabric061/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Fabric061/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Fabric061/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Fabric061/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Fabric066",
|
|
||||||
name: "Stoff 2",
|
|
||||||
category: "Fabric",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Fabric066/color.jpg",
|
|
||||||
normal: "/assets/materials/Fabric066/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Fabric066/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Fabric066/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Fabric066/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Fabric081C",
|
|
||||||
name: "Stoff 3",
|
|
||||||
category: "Fabric",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Fabric081C/color.jpg",
|
|
||||||
normal: "/assets/materials/Fabric081C/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Fabric081C/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Fabric081C/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Fabric081C/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Gravel023",
|
|
||||||
name: "Kies 1",
|
|
||||||
category: "Gravel",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Gravel023/color.jpg",
|
|
||||||
normal: "/assets/materials/Gravel023/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Gravel023/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Gravel023/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Gravel023/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Gravel041",
|
|
||||||
name: "Kies 2",
|
|
||||||
category: "Gravel",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Gravel041/color.jpg",
|
|
||||||
normal: "/assets/materials/Gravel041/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Gravel041/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Gravel041/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Gravel041/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Ground037",
|
|
||||||
name: "Boden 1",
|
|
||||||
category: "Ground",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Ground037/color.jpg",
|
|
||||||
normal: "/assets/materials/Ground037/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Ground037/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Ground037/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Ground037/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Ground054",
|
|
||||||
name: "Boden 2",
|
|
||||||
category: "Ground",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Ground054/color.jpg",
|
|
||||||
normal: "/assets/materials/Ground054/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Ground054/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Ground054/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Ground054/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Marble016",
|
|
||||||
name: "Marmor 1",
|
|
||||||
category: "Marble",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Marble016/color.jpg",
|
|
||||||
normal: "/assets/materials/Marble016/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Marble016/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Marble016/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Marble021",
|
|
||||||
name: "Marmor 2",
|
|
||||||
category: "Marble",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Marble021/color.jpg",
|
|
||||||
normal: "/assets/materials/Marble021/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Marble021/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Marble021/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Metal049A",
|
|
||||||
name: "Metall 1",
|
|
||||||
category: "Metal",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Metal049A/color.jpg",
|
|
||||||
normal: "/assets/materials/Metal049A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Metal049A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Metal049A/displacement.jpg",
|
|
||||||
metalness: "/assets/materials/Metal049A/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Metal055A",
|
|
||||||
name: "Metall 2",
|
|
||||||
category: "Metal",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Metal055A/color.jpg",
|
|
||||||
normal: "/assets/materials/Metal055A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Metal055A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Metal055A/displacement.jpg",
|
|
||||||
metalness: "/assets/materials/Metal055A/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "OfficeCeiling001",
|
|
||||||
name: "Bürodecke 1",
|
|
||||||
category: "Office Ceiling",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/OfficeCeiling001/color.jpg",
|
|
||||||
normal: "/assets/materials/OfficeCeiling001/normal.jpg",
|
|
||||||
roughness: "/assets/materials/OfficeCeiling001/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/OfficeCeiling001/displacement.jpg",
|
|
||||||
ao: "/assets/materials/OfficeCeiling001/ao.jpg",
|
|
||||||
metalness: "/assets/materials/OfficeCeiling001/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "OfficeCeiling002",
|
|
||||||
name: "Bürodecke 2",
|
|
||||||
category: "Office Ceiling",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/OfficeCeiling002/color.jpg",
|
|
||||||
normal: "/assets/materials/OfficeCeiling002/normal.jpg",
|
|
||||||
roughness: "/assets/materials/OfficeCeiling002/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/OfficeCeiling002/displacement.jpg",
|
|
||||||
ao: "/assets/materials/OfficeCeiling002/ao.jpg",
|
|
||||||
metalness: "/assets/materials/OfficeCeiling002/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "OfficeCeiling003",
|
|
||||||
name: "Bürodecke 3",
|
|
||||||
category: "Office Ceiling",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/OfficeCeiling003/color.jpg",
|
|
||||||
normal: "/assets/materials/OfficeCeiling003/normal.jpg",
|
|
||||||
roughness: "/assets/materials/OfficeCeiling003/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/OfficeCeiling003/displacement.jpg",
|
|
||||||
ao: "/assets/materials/OfficeCeiling003/ao.jpg",
|
|
||||||
metalness: "/assets/materials/OfficeCeiling003/metalness.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PavingStones128",
|
|
||||||
name: "Pflasterstein 1",
|
|
||||||
category: "Paving Stones",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/PavingStones128/color.jpg",
|
|
||||||
normal: "/assets/materials/PavingStones128/normal.jpg",
|
|
||||||
roughness: "/assets/materials/PavingStones128/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/PavingStones128/displacement.jpg",
|
|
||||||
ao: "/assets/materials/PavingStones128/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PavingStones138",
|
|
||||||
name: "Pflasterstein 2",
|
|
||||||
category: "Paving Stones",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/PavingStones138/color.jpg",
|
|
||||||
normal: "/assets/materials/PavingStones138/normal.jpg",
|
|
||||||
roughness: "/assets/materials/PavingStones138/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/PavingStones138/displacement.jpg",
|
|
||||||
ao: "/assets/materials/PavingStones138/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Plaster002",
|
|
||||||
name: "Putz 1",
|
|
||||||
category: "Plaster",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Plaster002/color.jpg",
|
|
||||||
normal: "/assets/materials/Plaster002/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Plaster002/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Plaster002/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Plaster007",
|
|
||||||
name: "Putz 2",
|
|
||||||
category: "Plaster",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Plaster007/color.jpg",
|
|
||||||
normal: "/assets/materials/Plaster007/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Plaster007/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Plaster007/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Plaster007/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Rock058",
|
|
||||||
name: "Naturstein 1",
|
|
||||||
category: "Rock",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Rock058/color.jpg",
|
|
||||||
normal: "/assets/materials/Rock058/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Rock058/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Rock058/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Rock058/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Rock063",
|
|
||||||
name: "Naturstein 2",
|
|
||||||
category: "Rock",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Rock063/color.jpg",
|
|
||||||
normal: "/assets/materials/Rock063/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Rock063/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Rock063/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Rock063/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Rock064",
|
|
||||||
name: "Naturstein 3",
|
|
||||||
category: "Rock",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Rock064/color.jpg",
|
|
||||||
normal: "/assets/materials/Rock064/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Rock064/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Rock064/displacement.jpg",
|
|
||||||
ao: "/assets/materials/Rock064/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "RoofingTiles012A",
|
|
||||||
name: "Dach 1",
|
|
||||||
category: "Roofing Tiles",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/RoofingTiles012A/color.jpg",
|
|
||||||
normal: "/assets/materials/RoofingTiles012A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/RoofingTiles012A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/RoofingTiles012A/displacement.jpg",
|
|
||||||
ao: "/assets/materials/RoofingTiles012A/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "RoofingTiles014A",
|
|
||||||
name: "Dach 3",
|
|
||||||
category: "Roofing Tiles",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/RoofingTiles014A/color.jpg",
|
|
||||||
normal: "/assets/materials/RoofingTiles014A/normal.jpg",
|
|
||||||
roughness: "/assets/materials/RoofingTiles014A/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/RoofingTiles014A/displacement.jpg",
|
|
||||||
ao: "/assets/materials/RoofingTiles014A/ao.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Terrazzo005",
|
|
||||||
name: "Terrazzo 1",
|
|
||||||
category: "Terrazzo",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Terrazzo005/color.jpg",
|
|
||||||
normal: "/assets/materials/Terrazzo005/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Terrazzo005/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Terrazzo005/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Terrazzo013",
|
|
||||||
name: "Terrazzo 2",
|
|
||||||
category: "Terrazzo",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Terrazzo013/color.jpg",
|
|
||||||
normal: "/assets/materials/Terrazzo013/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Terrazzo013/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Terrazzo013/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Terrazzo018",
|
|
||||||
name: "Terrazzo 3",
|
|
||||||
category: "Terrazzo",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Terrazzo018/color.jpg",
|
|
||||||
normal: "/assets/materials/Terrazzo018/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Terrazzo018/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Terrazzo018/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Tiles139",
|
|
||||||
name: "Fliesen 1",
|
|
||||||
category: "Tiles",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Tiles139/color.jpg",
|
|
||||||
normal: "/assets/materials/Tiles139/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Tiles139/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Tiles139/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Tiles140",
|
|
||||||
name: "Fliesen 2",
|
|
||||||
category: "Tiles",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Tiles140/color.jpg",
|
|
||||||
normal: "/assets/materials/Tiles140/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Tiles140/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Tiles140/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Wood049",
|
|
||||||
name: "Holz 1",
|
|
||||||
category: "Wood",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Wood049/color.jpg",
|
|
||||||
normal: "/assets/materials/Wood049/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Wood049/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Wood049/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Wood051",
|
|
||||||
name: "Holz 2",
|
|
||||||
category: "Wood",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Wood051/color.jpg",
|
|
||||||
normal: "/assets/materials/Wood051/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Wood051/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Wood051/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Wood066",
|
|
||||||
name: "Holz 3",
|
|
||||||
category: "Wood",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Wood066/color.jpg",
|
|
||||||
normal: "/assets/materials/Wood066/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Wood066/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Wood066/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Wood092",
|
|
||||||
name: "Holz 4",
|
|
||||||
category: "Wood",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Wood092/color.jpg",
|
|
||||||
normal: "/assets/materials/Wood092/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Wood092/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Wood092/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Wood094",
|
|
||||||
name: "Holz 5",
|
|
||||||
category: "Wood",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/Wood094/color.jpg",
|
|
||||||
normal: "/assets/materials/Wood094/normal.jpg",
|
|
||||||
roughness: "/assets/materials/Wood094/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/Wood094/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "WoodFloor064",
|
|
||||||
name: "Holzboden 1",
|
|
||||||
category: "Wood Floor",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/WoodFloor064/color.jpg",
|
|
||||||
normal: "/assets/materials/WoodFloor064/normal.jpg",
|
|
||||||
roughness: "/assets/materials/WoodFloor064/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/WoodFloor064/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "WoodFloor070",
|
|
||||||
name: "Holzboden 2",
|
|
||||||
category: "Wood Floor",
|
|
||||||
maps: {
|
|
||||||
color: "/assets/materials/WoodFloor070/color.jpg",
|
|
||||||
normal: "/assets/materials/WoodFloor070/normal.jpg",
|
|
||||||
roughness: "/assets/materials/WoodFloor070/roughness.jpg",
|
|
||||||
displacement: "/assets/materials/WoodFloor070/displacement.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default MATERIAL_LIBRARY;
|
export default MATERIAL_LIBRARY;
|
||||||
|
|||||||
Reference in New Issue
Block a user