feat(synoptik): echte GLB-Modelle eingehängt (Asset-Swap + Auto-Normalisierung) - #55
Conversation
📝 WalkthroughWalkthroughDie PR fügt eine GLB-basierte Modellpipeline für Synoptik hinzu: Modelle werden per Manifest auf GLB-Assets gemappt, passend skaliert und am Boden zentriert, im Scene-Loader asynchron geladen und mit Entsorgung sowie Pick-Target-Anbindung verarbeitet. Dazu kommen Asset-Regeln und Draco-Decoder-Bundling. ChangesSynoptik GLB- und Decoder-Pipeline
Sequence Diagram(s)sequenceDiagram
participant SynoptikScene
participant resolveModelSource
participant loadGlbForMachine
participant createModelLoader
participant placeGlb
participant PlaceholderHandle
participant registerPickables
SynoptikScene->>resolveModelSource: machineClass
resolveModelSource-->>SynoptikScene: ModelSource
SynoptikScene->>loadGlbForMachine: source.kind === "glb"
loadGlbForMachine->>createModelLoader: load(model source)
createModelLoader-->>loadGlbForMachine: Object3D
loadGlbForMachine->>placeGlb: fit model to targetHeight
loadGlbForMachine->>PlaceholderHandle: attachGlb(model)
PlaceholderHandle-->>loadGlbForMachine: mesh list
loadGlbForMachine->>registerPickables: add meshes with machineId
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
f12d456 to
8352755
Compare
|
ℹ️ CodeRabbit hat diesen PR übersprungen ("Auto reviews are disabled on base/target branches other than the default branch"), weil er auf Dieser PR ist auf den gefixten Stand von #54 ( Gates grün: |
8352755 to
86fdec8
Compare
0fa961e to
ee90192
Compare
86fdec8 to
9138716
Compare
…lisierung) Aktiviert die GLB-Swap-Naht: die fünf Maschinenklassen tragen jetzt optimierte GLBs (Draco) statt Platzhalter — über das Klasse→Modell-Manifest, ohne Umbau am Datenfluss oder Klick-Vertrag. mixing_unit (kein Asset) bleibt Platzhalter. - Auto-Normalisierung (lib/synoptic3d/fit.ts, rein/getestet): die nativen Hersteller-GLBs kommen in völlig verschiedenen Einheiten (feeder ~0,08 vs. servo_press ~217 „units") und Pivots (zentriert/schwebend). placeGlb skaliert jedes Modell uniform auf die Klassen-Zielhöhe und verankert es am Boden-Zentrum — der eigentliche Render-Kohärenz-Schritt. So sitzt auch das Status-Beacon (h + 0,3) automatisch richtig. - Renderer-Naht vervollständigt (adressiert die 3 verschobenen Review-Findings): vollständiges GLB-Disposal (Geometrie/Material/Texturen), Unmount/Rebuild-Race- Schutz, und die GLB-Meshes werden als Raycast-Ziele mit machineId registriert (der Klick → kanonische Karte bleibt erhalten). - Assets unter public/synoptik/models/ (plain .glb, Draco); Draco-Decoder lokal unter public/synoptik/decoders/draco/ (WASM-Paar, kein Runtime-CDN; KTX2/Basis nicht nötig). public/** aus dem Lint genommen; *.glb/*.wasm als binär markiert. Offen (visuell, in der Laufumgebung): Ausrichtung je Modell (ModelTransform.rotationY, Default 0) und der 60fps-/Optik-Sichtcheck. Gates grün: tsc 0, eslint 0, vitest 717, tokens synchron, next build ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9138716 to
6c23e86
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
frontend/components/synoptik/synoptik-scene.tsx (1)
66-70: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winLadefehler still verschluckt — Diagnose erschwert.
Der
catch-Block verwirft den Fehler ohne jede Spur. Die Platzhalter-Degradation ist gewollt, aber ohne Log bleibt ein fehlendes oder defektes GLB-Asset (404, Draco-Decode-Fehler) in Produktion unsichtbar. Einconsole.warnmitsource.urlwürde die Fehlersuche deutlich erleichtern.🔧 Vorschlag
try { model = await loader.loadGlb(source.url); - } catch { + } catch (error) { + console.warn(`Synoptik: GLB-Laden fehlgeschlagen (${source.url}) — Platzhalter bleibt.`, error); return; // Ladefehler → Platzhalter bleibt stehen (ehrliche Degradation). }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/synoptik/synoptik-scene.tsx` around lines 66 - 70, The try/catch around loader.loadGlb in synoptik-scene.tsx swallows GLB load failures silently, making missing or broken assets hard to diagnose. Update the catch block in the scene loading flow to keep the graceful fallback but also emit a warning that includes source.url (for example via console.warn) so failures are visible in production logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/components/synoptik/scene/glb.ts`:
- Around line 22-32: The fit calculation in the GLB scene setup is using a
bounding box computed before the model’s final Y rotation, which causes x/z
offset drift when rotationY is non-zero. Update the transform flow in the scene
logic around the model positioning so model.rotation.y is applied before calling
new THREE.Box3().setFromObject(model), then keep computeFit and the subsequent
scale/position assignment using that rotated bounding box.
---
Nitpick comments:
In `@frontend/components/synoptik/synoptik-scene.tsx`:
- Around line 66-70: The try/catch around loader.loadGlb in synoptik-scene.tsx
swallows GLB load failures silently, making missing or broken assets hard to
diagnose. Update the catch block in the scene loading flow to keep the graceful
fallback but also emit a warning that includes source.url (for example via
console.warn) so failures are visible in production logs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0345f49c-9f47-4794-8f33-00d2d1af2661
⛔ Files ignored due to path filters (1)
frontend/public/synoptik/decoders/draco/draco_decoder.wasmis excluded by!**/*.wasm
📒 Files selected for processing (16)
.gitattributesfrontend/components/synoptik/scene/glb.test.tsfrontend/components/synoptik/scene/glb.tsfrontend/components/synoptik/scene/placeholders.tsfrontend/components/synoptik/synoptik-scene.tsxfrontend/eslint.config.mjsfrontend/lib/synoptic3d/fit.test.tsfrontend/lib/synoptic3d/fit.tsfrontend/lib/synoptic3d/manifest.test.tsfrontend/lib/synoptic3d/manifest.tsfrontend/public/synoptik/decoders/draco/draco_wasm_wrapper.jsfrontend/public/synoptik/models/feeder.glbfrontend/public/synoptik/models/robot.glbfrontend/public/synoptik/models/servo_axis.glbfrontend/public/synoptik/models/servo_press.glbfrontend/public/synoptik/models/vision_station.glb
| const box = new THREE.Box3().setFromObject(model); | ||
| const effectiveHeight = targetHeight * transform.scale; | ||
| const fit = computeFit({ min: box.min, max: box.max }, effectiveHeight); | ||
|
|
||
| model.scale.setScalar(fit.scale); | ||
| model.position.set( | ||
| fit.offset.x + transform.offset.x, | ||
| fit.offset.y + transform.offset.y, | ||
| fit.offset.z + transform.offset.z, | ||
| ); | ||
| model.rotation.y = transform.rotationY; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🌐 Web query:
Three.js Object3D matrix composition order translation rotation scale TRS
💡 Result:
In Three.js, an Object3D's local transformation matrix is composed using the order Translation, Rotation, and Scale (TRS) [1][2]. When the engine automatically updates the local matrix (default behavior when matrixAutoUpdate is true), it calls internal methods that effectively combine these transformations into the local matrix property [1][3]. Specifically, the Matrix4.compose(position, quaternion, scale) method performs this operation [4][3]. Key aspects of this matrix composition include: 1. Local Matrix (matrix): This matrix represents the object's transformation relative to its parent [2][3]. It is composed from the object's position (Translation), quaternion (Rotation), and scale (Scale) [2][4]. 2. World Matrix (matrixWorld): This represents the object's transformation in world space [1][2]. For a root object, it is identical to the local matrix; for a child object, it is calculated by multiplying the parent's world matrix by the child's local matrix (ParentWorldMatrix * ChildLocalMatrix) [3]. 3. Manual Control: If matrixAutoUpdate is set to false, you are responsible for calling updateMatrix to refresh the local matrix after modifying position, quaternion, or scale [1][5]. Similarly, if matrixWorldAutoUpdate is false, you must manage the world matrix manually [1][6]. When manually constructing matrices using methods like Matrix4.multiply, remember that matrix multiplication is non-commutative [7]. The order in which you apply transformations depends on whether you are multiplying on the left or the right; for hierarchical or sequential transformations, applying them in the correct mathematical order is essential to achieving the intended result [7].
Citations:
- 1: https://threejs.org/docs/pages/Object3D.html
- 2: https://threejs.org/manual/en/matrix-transformations.html
- 3: https://readoss.com/en/mrdoob/three.js/scene-graph-object3d-transforms-geometry-material-contract
- 4: https://threejs.org/docs/pages/Matrix4.html
- 5: https://discourse.threejs.org/t/issues-with-updating-transformation-matrices/4319
- 6: Object3D: Respect matrixWorldAutoUpdate in matrix update methods mrdoob/three.js#28533
- 7: https://stackoverflow.com/questions/22178476/threejs-transform-matrix-ordering
🏁 Script executed:
sed -n '1,220p' frontend/components/synoptik/scene/glb.tsRepository: patricznr1/foreman
Length of output: 2022
Rotation vor der Fit-Berechnung setzen Bei rotationY ≠ 0 driften die x/z-Offsets, weil die Bounding-Box vor der Rotation berechnet wird. model.rotation.y vor new THREE.Box3().setFromObject(model) setzen, damit computeFit die finale Ausrichtung berücksichtigt.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/components/synoptik/scene/glb.ts` around lines 22 - 32, The fit
calculation in the GLB scene setup is using a bounding box computed before the
model’s final Y rotation, which causes x/z offset drift when rotationY is
non-zero. Update the transform flow in the scene logic around the model
positioning so model.rotation.y is applied before calling new
THREE.Box3().setFromObject(model), then keep computeFit and the subsequent
scale/position assignment using that rotated bounding box.
…04) (#56) Next.js output:"standalone" übernimmt public/ NICHT automatisch; das Dockerfile kopierte bisher nur das Standalone-Bundle + die statischen Assets (Kommentar: "kein public/"). Seit der 3D-Linie (#55) liegen die Maschinen-GLBs + der Draco-Decoder unter frontend/public/synoptik/ → auf dem Deploy 404, der Renderer fällt still auf Platzhalter zurück. Fix: public/ explizit ins Runner-Stage kopieren. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Was
Aktiviert die GLB-Swap-Naht der Live-3D-Linie: die fünf Maschinenklassen tragen jetzt echte, optimierte GLBs (Draco) statt Platzhalter — über das Klasse→Modell-Manifest, ohne Umbau am Datenfluss oder Klick-Vertrag. Das ist der „Asset-Swap, kein Renderer-Umbau", den die Platzhalter-Phase versprochen hat.
Warum eine Auto-Normalisierung (statt blinder Transforms)
Die GLB-Inspektion zeigte völlig inkonsistente native Maßstäbe und Pivots:
Ein Förderer mit 8 cm neben einer Presse mit 217 „units" wäre unbrauchbar. Statt fünf hand-getunter Transforms blind zu raten, normalisiert
placeGlb(lib/synoptic3d/fit.ts, rein + getestet) jedes Modell uniform auf die Klassen-Zielhöhe und verankert es am Boden-Zentrum. Das ist der Render-Kohärenz-Schritt — und weil die effektive Höhe == Klassen-hist, sitzt das Status-Beacon (h + 0,3) automatisch richtig.Renderer-Naht vervollständigt (die 3 aus #54 verschobenen Review-Findings)
machineIdals Raycast-Ziele registriert → Klick auf das Modell führt weiter zur kanonischen Karte.Assets & Decoder
public/synoptik/models/(plain-Variante, Draco-komprimiert).public/synoptik/decoders/draco/(nur das WASM-Paar — kein Runtime-CDN, keine externe Abhängigkeit). KTX2/Basis nicht nötig (plain-Variante, WebP-Texturen decodiert der Browser nativ).public/**aus dem Lint genommen (statische/vendorte Assets);*.glb/*.wasmin.gitattributesals binär markiert (kein CRLF-Schaden).Tests & Gates
lib/synoptic3d/fit.test.ts(Normalisierungs-Mathematik),components/synoptik/scene/glb.test.ts(Einsetzen/Dispose/Boden-Verankerung ohne WebGL),manifest.test.tsan die GLB-Einträge angepasst.tsc0 ·eslint0 ·vitest717 ·tokens:checksynchron ·next buildok.Offen (visuell, in der Laufumgebung)
ModelTransform.rotationY, heute Default 0) — falls ein Modell quer zur Flussrichtung steht, ist das ein Ein-Wert-Tweak im Manifest.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests