import { useCadStore } from "@/lib/store/cad-store"; import { cn } from "@/lib/utils/cn"; import { Environment, GizmoHelper, GizmoViewport, Grid, OrbitControls, } from "@react-three/drei"; import { Canvas } from "@react-three/fiber"; import { useCallback, useMemo, useRef } from "react"; import * as THREE from "three"; function CadMesh() { const meshData = useCadStore((s) => s.meshData); const viewportSettings = useCadStore((s) => s.viewportSettings); const selectedFaceIndex = useCadStore((s) => s.selectedFaceIndex); const selectFace = useCadStore((s) => s.selectFace); const meshRef = useRef(null); const handleClick = useCallback( (event: { faceIndex?: number | null; stopPropagation: () => void }) => { event.stopPropagation(); if (event.faceIndex != null) { selectFace( event.faceIndex === selectedFaceIndex ? null : event.faceIndex, ); } }, [selectFace, selectedFaceIndex], ); const faceGeometry = useMemo(() => { if (!meshData?.faces) return null; const geom = new THREE.BufferGeometry(); geom.setAttribute( "position", new THREE.BufferAttribute(meshData.faces.vertices, 3), ); geom.setAttribute( "normal", new THREE.BufferAttribute(meshData.faces.normals, 3), ); geom.setIndex(new THREE.BufferAttribute(meshData.faces.triangles, 1)); return geom; }, [meshData]); const edgeGeometry = useMemo(() => { if (!meshData?.edges?.vertices?.length) return null; const geom = new THREE.BufferGeometry(); geom.setAttribute( "position", new THREE.BufferAttribute(meshData.edges.vertices, 3), ); return geom; }, [meshData]); if (!faceGeometry) return null; return ( {/* Solid mesh */} {/* biome-ignore lint/a11y/useKeyWithClickEvents: R3F mesh element, not a DOM element */} {/* Selected face highlight */} {selectedFaceIndex !== null && faceGeometry && ( )} {/* Wireframe overlay */} {viewportSettings.showWireframe && ( )} {/* Edge lines from replicad meshEdges */} {viewportSettings.showEdges && edgeGeometry && ( )} ); } function SceneSetup() { const viewportSettings = useCadStore((s) => s.viewportSettings); return ( <> {viewportSettings.showGrid && ( )} {viewportSettings.showAxes && } ); } interface CadViewportProps { className?: string; } export function CadViewport({ className }: CadViewportProps) { const isEngineReady = useCadStore((s) => s.isEngineReady); const isProcessing = useCadStore((s) => s.isProcessing); const viewportSettings = useCadStore((s) => s.viewportSettings); const containerRef = useRef(null); const handleCreated = useCallback( ({ gl }: { gl: THREE.WebGLRenderer }) => { gl.setClearColor(viewportSettings.backgroundColor); gl.toneMapping = THREE.ACESFilmicToneMapping; gl.toneMappingExposure = 1.0; }, [viewportSettings.backgroundColor], ); return (
{/* Status overlay */}
{isEngineReady ? "Engine Ready" : "Loading Engine..."}
{/* Processing overlay */} {isProcessing && (
Processing...
)} {/* Viewport toolbar */}
useCadStore.setState((s) => ({ viewportSettings: { ...s.viewportSettings, showGrid: !s.viewportSettings.showGrid, }, })) } /> useCadStore.setState((s) => ({ viewportSettings: { ...s.viewportSettings, showEdges: !s.viewportSettings.showEdges, }, })) } /> useCadStore.setState((s) => ({ viewportSettings: { ...s.viewportSettings, showWireframe: !s.viewportSettings.showWireframe, }, })) } />
); } function ViewportButton({ label, active, onClick, }: { label: string; active: boolean; onClick: () => void }) { return ( ); }