import React, { useRef } from 'react'; import { Canvas, useFrame } from '@react-three/fiber'; import { OrbitControls, Grid, OrthographicCamera, Text, Box, Environment, RoundedBox, Cylinder, Plane } from '@react-three/drei'; import * as THREE from 'three'; import { RobotTarget, IOPoint } from '../types'; interface Machine3DProps { target: RobotTarget; ioState: IOPoint[]; doorStates: { front: boolean; right: boolean; left: boolean; back: boolean; }; } // Reusable Aluminum Profile Component const Profile = ({ position, size, rotation = [0, 0, 0] }: { position: [number, number, number], size: [number, number, number], rotation?: [number, number, number] }) => ( ); // Industrial Linear Actuator Rail (Black body + Silver guides) const RobotRail = ({ length, orientation = 'horizontal', profile = [0.25, 0.25] }: { length: number, orientation?: 'horizontal' | 'vertical', profile?: [number, number] }) => { const isHoriz = orientation === 'horizontal'; const [dim1, dim2] = profile; const size = isHoriz ? [length, dim1, dim2] : [dim1, length, dim2]; const railW = isHoriz ? length : dim1 * 0.4; const railH = isHoriz ? dim1 * 0.4 : length; const railD = 0.02; const guideZ = dim2 / 2 + 0.005; return ( ); }; // Detailed Industrial Camera Component const IndustrialCamera = ({ position, rotation = [0, 0, 0], isActive = false }: { position: [number, number, number], rotation?: [number, number, number], isActive?: boolean }) => { return ( MA-D200B DC 12V {isActive && ( )} ); }; // SATO CL4NX Plus Printer Component const SatoPrinter = ({ position, rotation = [0, 0, 0] }: { position: [number, number, number], rotation?: [number, number, number] }) => { return ( SATO CL4NX Plus ); }; // Industrial Cart Component const Cart = ({ position, label }: { position: [number, number, number], label?: string }) => { return ( {Array.from({ length: 15 }).map((_, i) => ( ))} {[0, 120, 240].map((angle, idx) => { const rad = 0.65; const x = Math.cos(angle * Math.PI / 180) * rad; const z = Math.sin(angle * Math.PI / 180) * rad; return ( ); })} {label && ( {label} )} ); }; // Glass Door Component const Door = ({ position, size, isOpen, hingeDirection = 1 }: { position: [number, number, number], size: [number, number, number], isOpen: boolean, hingeDirection?: number }) => { const meshRef = useRef(null); useFrame(() => { if (meshRef.current) { const targetRotation = isOpen ? Math.PI / 2 * hingeDirection : 0; meshRef.current.rotation.y = THREE.MathUtils.lerp(meshRef.current.rotation.y, targetRotation, 0.1); } }); return ( ); }; // Reusable Labeler Unit Component const LabelerUnit = ({ side, posY, posZ }: { side: 'left' | 'right', posY: number, posZ: number }) => { const isLeft = side === 'left'; const wallOffsetX = isLeft ? -1.4 : 1.4; const printerOffset = isLeft ? 0.3 : -0.3; return ( ); }; // Tower Lamp Component const TowerLamp = ({ ioState }: { ioState: IOPoint[] }) => { const redOn = ioState.find(io => io.id === 0 && io.type === 'output')?.state; const yellowOn = ioState.find(io => io.id === 1 && io.type === 'output')?.state; const greenOn = ioState.find(io => io.id === 2 && io.type === 'output')?.state; const redMat = useRef(null); const yelMat = useRef(null); const grnMat = useRef(null); useFrame((state) => { const time = state.clock.elapsedTime; const pulse = (Math.sin(time * 6) * 0.5 + 0.5); const intensity = 0.5 + (pulse * 3.0); if (redMat.current) { redMat.current.emissiveIntensity = redOn ? intensity : 0; redMat.current.opacity = redOn ? 1.0 : 0.3; redMat.current.color.setHex(redOn ? 0xff0000 : 0x550000); } if (yelMat.current) { yelMat.current.emissiveIntensity = yellowOn ? intensity : 0; yelMat.current.opacity = yellowOn ? 1.0 : 0.3; yelMat.current.color.setHex(yellowOn ? 0xffff00 : 0x555500); } if (grnMat.current) { grnMat.current.emissiveIntensity = greenOn ? intensity : 0; grnMat.current.opacity = greenOn ? 1.0 : 0.3; grnMat.current.color.setHex(greenOn ? 0x00ff00 : 0x005500); } }); return ( ); }; // Main Machine Model const MachineModel = ({ target, doorStates }: { target: RobotTarget, doorStates: Machine3DProps['doorStates'] }) => { const FRAME_WIDTH = 10; const TOTAL_HEIGHT = 6.5; const DOCK_HEIGHT = 2.5; const OPERATIONAL_HEIGHT = TOTAL_HEIGHT - DOCK_HEIGHT; const FRAME_DEPTH = 3.0; const CONVEYOR_Y = DOCK_HEIGHT + 0.2; const LABELER_BASE_Y = CONVEYOR_Y; const PICKER_Y = 4.8; const minZLength = 0.6; const zRailLength = Math.max(minZLength, target.z + 0.4); const Z_XRAIL = -0.6; const Z_CARRIAGE = -0.45; const Z_ZRAIL = -0.3; const Z_HEAD = 0; // Convert target coordinates const pickerX = target.x; const pickerZ = target.z; const leftLabelY = target.y; const leftLabelZ = 0.5; const rightLabelY = target.y; const rightLabelZ = 0.5; return ( {/* --- STRUCTURAL FRAME --- */} {/* --- DOCKING AREA (LOWER LEVEL) --- */} {[-3.5, 0, 3.5].map((xPos, idx) => ( {idx === 0 ? "SPARE L" : idx === 1 ? "LOADING" : "SPARE R"} ))} {/* --- DOORS (UPPER LEVEL ONLY) --- */} {/* --- TOP COVER (ROOF) --- */} {/* --- CONVEYORS (Left & Right - UPPER LEVEL) --- */} Left Conveyor PLACE REEL Right Conveyor PLACE REEL {/* --- MAIN PICKER (Robot Design) --- */} {Array.from({ length: 8 }).map((_, i) => { const angle = (i / 8) * Math.PI * 2; const radius = 0.4; const x = Math.cos(angle) * radius; const z = Math.sin(angle) * radius; return ( ); })} {/* --- LABELER UNITS --- */} {/* --- CAMERAS (Roof Mounted) --- */} {/* --- REAR DISCHARGE CHUTES --- */} {/* Grid Floor */} ); }; export const Machine3D: React.FC = ({ target, ioState, doorStates }) => { return ( {/* 정면에서 바라보는 직교 카메라 (왜곡 없음) */} ); };