chore: checkpoint premium 3d stages 0 and 1

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Даня Архипов
2026-07-29 19:05:27 +00:00
parent 5cc27c6fc2
commit 394c513037
79 changed files with 6221 additions and 612 deletions

View File

@@ -1,16 +1,16 @@
import { Suspense } from 'react';
import { useEffect, useMemo } from 'react';
import * as THREE from 'three';
import type { SimulatedItem, SimulationState } from '../../domain/types';
import { itemPosition3D, ROUTE_COLORS } from './itemMotion';
import { DIMENSION_LIMITS } from '../../domain/classifier';
import { getModelAsset } from '../../data/modelAssets';
import STLModel from './STLModel';
import { getRenderedItemDimensions } from '../../domain/physicalLayout';
import RealItemModel from './RealItemModel';
/**
* Visual scale multiplier for 3D demo visibility.
* Physical dimensions remain unchanged in data/proof panel.
* This only affects the rendered mesh size so item is visible in scene.
* Stage 1: VISUAL_SCALE_MULTIPLIER (2.5x) and the 0.12–0.45m clamps removed.
* `/details` now renders the same true physical scale as `/` (1 unit = 1m),
* with the shared manifest transform (bottom-center pivot, mm→m uniform).
*/
const VISUAL_SCALE_MULTIPLIER = 2.5;
interface Props {
simulation: SimulationState;
@@ -36,38 +36,20 @@ function FallbackPrimitive({
roughness: number;
emissiveIntensity?: number;
}) {
if (primitive === 'cylinder') {
return (
<mesh>
<cylinderGeometry args={[Math.min(sx, sz) / 2, Math.min(sx, sz) / 2, sy, 24]} />
<meshStandardMaterial
color={color}
emissive={emissive}
emissiveIntensity={emissiveIntensity}
roughness={roughness}
/>
</mesh>
);
}
const geometry = useMemo<THREE.BufferGeometry>(() => {
if (primitive === 'cylinder') {
return new THREE.CylinderGeometry(Math.min(sx, sz) / 2, Math.min(sx, sz) / 2, sy, 24);
}
if (primitive === 'sphere') {
return new THREE.SphereGeometry(Math.max(sx, sy, sz) / 2, 24, 16);
}
return new THREE.BoxGeometry(sx, sy, sz);
}, [primitive, sx, sy, sz]);
useEffect(() => () => geometry.dispose(), [geometry]);
if (primitive === 'sphere') {
return (
<mesh>
<sphereGeometry args={[Math.max(sx, sy, sz) / 2, 24, 16]} />
<meshStandardMaterial
color={color}
emissive={emissive}
emissiveIntensity={emissiveIntensity}
roughness={roughness}
/>
</mesh>
);
}
// default: box
// center-pivot primitives are lifted so their bottom touches the surface
return (
<mesh>
<boxGeometry args={[sx, sy, sz]} />
<mesh geometry={geometry} position={[0, sy / 2, 0]}>
<meshStandardMaterial
color={color}
emissive={emissive}
@@ -86,90 +68,58 @@ export default function Item3D({ simulation, currentItem }: Props) {
const [x, y, z] = itemPosition3D(simulation);
const category = currentItem.classification.category;
const color = ROUTE_COLORS[category] ?? '#38bdf8';
const dims = currentItem.item.dimensionsMm;
// Physical scale to Three.js units (1 unit = 1 meter, dimensions in mm)
const physicalSx = Math.max(0.12, Math.min(0.45, dims.width / 1000));
const physicalSy = Math.max(0.1, Math.min(0.4, dims.height / 1000));
const physicalSz = Math.max(0.12, Math.min(0.45, dims.depth / 1000));
// Apply visual multiplier for demo visibility (does not affect proof panel data)
const sx = physicalSx * VISUAL_SCALE_MULTIPLIER;
const sy = physicalSy * VISUAL_SCALE_MULTIPLIER;
const sz = physicalSz * VISUAL_SCALE_MULTIPLIER;
const dims = getRenderedItemDimensions(currentItem.item.dimensionsMm);
const sx = dims.width;
const sy = dims.height;
const sz = dims.depth;
const detecting = simulation.machineState === 'DETECTING';
const moving = simulation.machineState.startsWith('ROUTE_TO_') ||
const moving = simulation.machineState.startsWith('ROUTE_TO_') ||
simulation.machineState === 'MOVING_TO_CAMERA' ||
simulation.machineState === 'MOVING_TO_GATE';
const fault = simulation.machineState === 'FAULT' || simulation.machineState === 'EMERGENCY_STOP';
// Get model asset for this item
const asset = getModelAsset(currentItem.item.id);
const finalColor = fault ? '#fb3d4e' : color;
const finalEmissive = fault ? '#fb3d4e' : color;
const finalRoughness = asset?.loaderType === 'stl' ? 0.35 : 0.4;
// Brighter emissive when moving for visibility
const finalRoughness = asset?.defaultRealAsset ? 0.35 : 0.4;
const emissiveBoost = moving ? 0.55 : 0.35;
// Fallback primitive
const fallbackPrimitive = asset?.fallbackPrimitive ?? 'box';
const useReal = Boolean(asset?.defaultRealAsset && asset?.runtimePath);
const fallback = (
<FallbackPrimitive
primitive={fallbackPrimitive}
sx={sx}
sy={sy}
sz={sz}
color={finalColor}
emissive={finalEmissive}
roughness={finalRoughness}
emissiveIntensity={emissiveBoost}
/>
);
return (
<group position={[x, y, z]}>
{asset && asset.loaderType === 'stl' && asset.frontendAssetPath ? (
<Suspense
fallback={
<FallbackPrimitive
primitive={fallbackPrimitive}
sx={sx}
sy={sy}
sz={sz}
color={finalColor}
emissive={finalEmissive}
roughness={finalRoughness}
emissiveIntensity={emissiveBoost}
/>
}
>
<STLModel
path={asset.frontendAssetPath}
scale={[sx, sy, sz]}
rotation={[0, 0, 0]}
position={[0, 0, 0]}
color={finalColor}
emissive={finalEmissive}
emissiveIntensity={emissiveBoost}
roughness={finalRoughness}
fallback={
<FallbackPrimitive
primitive={fallbackPrimitive}
sx={sx}
sy={sy}
sz={sz}
color={finalColor}
emissive={finalEmissive}
roughness={finalRoughness}
emissiveIntensity={emissiveBoost}
/>
}
/>
</Suspense>
) : (
<FallbackPrimitive
primitive={fallbackPrimitive}
sx={sx}
sy={sy}
sz={sz}
color={finalColor}
emissive={finalEmissive}
roughness={finalRoughness}
emissiveIntensity={emissiveBoost}
{useReal && asset ? (
<RealItemModel
asset={asset}
material={{
color: finalColor,
emissive: finalEmissive,
emissiveIntensity: emissiveBoost,
roughness: finalRoughness,
}}
fallback={fallback}
/>
) : (
fallback
)}
{/* Glow ring under item for visibility */}
<mesh position={[0, -sy / 2 + 0.01, 0]} rotation={[-Math.PI / 2, 0, 0]}>
{/* Glow ring at the contact plane under the item */}
<mesh position={[0, 0.01, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<ringGeometry args={[Math.max(sx, sz) * 0.4, Math.max(sx, sz) * 0.6, 32]} />
<meshStandardMaterial
color={finalColor}
@@ -181,7 +131,7 @@ export default function Item3D({ simulation, currentItem }: Props) {
</mesh>
{detecting ? (
<mesh>
<mesh position={[0, sy / 2, 0]}>
<boxGeometry args={[sx + 0.12, sy + 0.12, sz + 0.12]} />
<meshStandardMaterial color="#38bdf8" wireframe transparent opacity={0.9} />
</mesh>