feat: 3D digital twin major rework - real models, clean labels, improved geometry

- Real STL Models: 6 models loaded (55%), fallback for 5 (45%)
- Clean Labels: 12+ → 5 default, step-based, technical toggle
- Geometry: Gate lift, pusher plates, conveyor rollers, roll-cage wireframes
- Demo Director: 12 steps, camera presets, auto-play logic (not integrated)
- Tests: +16 new (43/43 green), Build ✅, Docker ✅, Domains ✅

Remaining: Auto demo UI integration, camera transitions, docs updates, Visual QA
This commit is contained in:
root
2026-07-04 16:45:43 +02:00
parent a700a146a3
commit 3e1f10a01a
20 changed files with 3584 additions and 158 deletions

View File

@@ -1,3 +1,4 @@
import * as THREE from 'three';
import type { Category } from '../../domain/types';
import { ROUTE_COLORS, TWIN_LAYOUT } from './itemMotion';
@@ -31,6 +32,54 @@ function ZoneBox({
);
}
/** Roll-cage with wireframe edges for C/D zones */
function RollCage({
position,
size,
color,
active,
}: {
position: [number, number, number];
size: [number, number, number];
color: string;
active: boolean;
}) {
return (
<group position={position}>
{/* Cage body (transparent) */}
<mesh>
<boxGeometry args={size} />
<meshStandardMaterial
color={color}
transparent
opacity={active ? 0.25 : 0.1}
emissive={color}
emissiveIntensity={active ? 0.3 : 0.08}
/>
</mesh>
{/* Cage wireframe edges (visible frame) */}
<lineSegments>
<edgesGeometry args={[new THREE.BoxGeometry(...size)]} />
<lineBasicMaterial color={color} linewidth={active ? 2 : 1} opacity={active ? 1 : 0.6} transparent />
</lineSegments>
{/* Vertical posts (corners) for visual emphasis */}
{[
[-size[0] / 2, 0, -size[2] / 2],
[size[0] / 2, 0, -size[2] / 2],
[-size[0] / 2, 0, size[2] / 2],
[size[0] / 2, 0, size[2] / 2],
].map((offset, i) => (
<mesh key={i} position={offset as [number, number, number]}>
<boxGeometry args={[0.04, size[1], 0.04]} />
<meshStandardMaterial color={color} metalness={0.3} roughness={0.7} />
</mesh>
))}
</group>
);
}
export default function SortingZones3D({ activeCategory, activeRoute }: Props) {
const active = activeRoute ?? activeCategory;
const cage = TWIN_LAYOUT.rollCageSize;
@@ -53,16 +102,16 @@ export default function SortingZones3D({ activeCategory, activeRoute }: Props) {
active={active === 'B'}
/>
{/* Roll-cage C 1200×800×800 mm — orange */}
<ZoneBox
{/* Roll-cage C 1200×800×800 mm — orange with wireframe */}
<RollCage
position={[TWIN_LAYOUT.gateX + 0.4, cage.y / 2, TWIN_LAYOUT.zoneCZ]}
size={[cage.x, cage.y, cage.z]}
color={ROUTE_COLORS.C}
active={active === 'C'}
/>
{/* Roll-cage D 1200×800×800 mm — purple */}
<ZoneBox
{/* Roll-cage D 1200×800×800 mm — purple with wireframe */}
<RollCage
position={[TWIN_LAYOUT.gateX + 0.4, cage.y / 2, TWIN_LAYOUT.zoneDZ]}
size={[cage.x, cage.y, cage.z]}
color={ROUTE_COLORS.D}