🚀 Auto-deploy: BrainWind atualizado em 13/07/2026 15:00:47

This commit is contained in:
2026-07-13 15:00:47 +00:00
parent 314c9b7490
commit ab8f89e807
11 changed files with 567 additions and 10 deletions
+121
View File
@@ -0,0 +1,121 @@
import { Grid, Environment } from '@react-three/drei';
import { ViewerOrbitControls as OrbitControls } from './ViewerOrbitControls';
import SceneCanvas from '../SceneCanvas';
import { WindArrow } from './WindArrow';
import { useCanvasTheme } from '@/lib/theme';
import type { PipeDef } from '../../lib/modules/piperack';
export interface Piperack3DInput {
width: number;
height: number;
elevation: number;
spacing: number;
numFrames: number;
pipes: PipeDef[];
phiTotal: number;
eta: number;
}
function PiperackModel({
width,
height,
elevation,
spacing,
numFrames,
pipes,
phiTotal,
}: Piperack3DInput) {
const theme = useCanvasTheme();
const isDark = theme === 'dark';
const halfW = width / 2;
const rackTop = elevation + height / 2;
const steelColor = isDark ? '#94a3b8' : '#64748b';
const pipeColors = ['#ef4444', '#3b82f6', '#f59e0b', '#10b981', '#8b5cf6'];
// Gera os pórticos
const frames = [];
for (let i = 0; i < numFrames; i++) {
const z = -i * spacing;
frames.push(
<group key={`frame-${i}`} position={[0, 0, z]}>
{/* Coluna Esquerda */}
<mesh position={[-halfW, rackTop / 2, 0]} receiveShadow castShadow>
<boxGeometry args={[0.3, rackTop, 0.3]} />
<meshStandardMaterial color={steelColor} metalness={0.6} roughness={0.4} />
</mesh>
{/* Coluna Direita */}
<mesh position={[halfW, rackTop / 2, 0]} receiveShadow castShadow>
<boxGeometry args={[0.3, rackTop, 0.3]} />
<meshStandardMaterial color={steelColor} metalness={0.6} roughness={0.4} />
</mesh>
{/* Viga Transversal (Reticulado simplificado) */}
<mesh position={[0, elevation, 0]} receiveShadow castShadow>
<boxGeometry args={[width, height, 0.3]} />
<meshStandardMaterial color={steelColor} metalness={0.6} roughness={0.4} transparent opacity={phiTotal} />
</mesh>
</group>
);
}
// Gera as tubulações correndo longitudinalmente
const rackLength = spacing * Math.max(numFrames - 1, 0.5); // Garante comprimento visual mesmo com 1 pórtico
const pipeZCenter = -(spacing * (numFrames - 1)) / 2;
const pipeMeshes = pipes.map((pipe, idx) => {
// Distribui os tubos ao longo da largura do pórtico
const xPos = -halfW + 0.5 + (idx % Math.max(pipes.length, 1)) * (width - 1) / Math.max(pipes.length - 1, 1) || 0;
const yPos = elevation + (idx % 2 === 0 ? 0.2 : -0.2); // Leve variação de altura
const color = pipeColors[idx % pipeColors.length];
return (
<mesh key={`pipe-${pipe.id}`} position={[xPos, yPos, pipeZCenter]} rotation={[Math.PI / 2, 0, 0]} receiveShadow castShadow>
<cylinderGeometry args={[pipe.diameter / 2, pipe.diameter / 2, rackLength + 1, 16]} />
<meshStandardMaterial color={color} metalness={0.3} roughness={0.2} />
</mesh>
);
});
return (
<group>
{frames}
{pipeMeshes}
{/* Vento batendo lateralmente (transversal ao Pipe-rack) */}
<WindArrow
direction={[0, 0, 1]}
target={[0, elevation, spacing * 0.5]}
/>
</group>
);
}
export function Piperack3D(props: Piperack3DInput) {
const isDark = useCanvasTheme() === 'dark';
return (
<SceneCanvas moduleId="piperack" fallback={<div />}>
<ambientLight intensity={isDark ? 0.3 : 0.6} />
<directionalLight position={[10, 15, 10]} intensity={isDark ? 1 : 1.5} castShadow shadow-bias={-0.001} />
<Environment preset="city" />
<PiperackModel {...props} />
<Grid
args={[40, 40]}
position={[0, -0.01, 0]}
cellColor={isDark ? '#334155' : '#cbd5e1'}
sectionColor={isDark ? '#475569' : '#94a3b8'}
fadeDistance={30}
/>
<OrbitControls
minPolarAngle={0}
maxPolarAngle={Math.PI / 2 - 0.05}
minDistance={5}
maxDistance={50}
target={[0, props.elevation / 2, -(props.spacing * (props.numFrames - 1)) / 2]}
/>
</SceneCanvas>
);
}