diff --git a/src/components/viewer3d/AnalyzerViewer3D.tsx b/src/components/viewer3d/AnalyzerViewer3D.tsx index 7dfa242..a7232e7 100644 --- a/src/components/viewer3d/AnalyzerViewer3D.tsx +++ b/src/components/viewer3d/AnalyzerViewer3D.tsx @@ -122,15 +122,45 @@ export const AnalyzerViewer3D: React.FC = ({ dirLight2.position.set(-80, -40, -80); scene.add(dirLight2); - // Grid no piso - const isDark = theme === 'dark' || document.documentElement.classList.contains('dark'); - const gridColor1 = isDark ? 0x475569 : 0x94a3b8; - const gridColor2 = isDark ? 0x1e293b : 0xe2e8f0; + // Grid no piso com cores refinadas para modo claro e escuro + const isDarkMode = document.documentElement.classList.contains('dark'); + const gridColor1 = isDarkMode ? 0x475569 : 0x94a3b8; + const gridColor2 = isDarkMode ? 0x1e293b : 0xe2e8f0; const grid = new THREE.GridHelper(100, 50, gridColor1, gridColor2); grid.position.y = -0.01; + if (grid.material && (grid.material as THREE.Material)) { + (grid.material as THREE.Material).transparent = true; + (grid.material as THREE.Material).opacity = 0.55; + } scene.add(grid); gridRef.current = grid; + // Observador para atualizar cores do grid automaticamente quando o usuário alternar tema + const themeObserver = new MutationObserver(() => { + const isDarkNow = document.documentElement.classList.contains('dark'); + const centerC = isDarkNow ? new THREE.Color(0x475569) : new THREE.Color(0x94a3b8); + const lineC = isDarkNow ? new THREE.Color(0x1e293b) : new THREE.Color(0xe2e8f0); + + if (gridRef.current) { + const colors = gridRef.current.geometry.attributes.color as THREE.BufferAttribute; + if (colors) { + const arr = colors.array as Float32Array; + for (let i = 0; i < arr.length; i += 6) { + const isCenter = i === Math.floor(arr.length / 2); + const c = isCenter ? centerC : lineC; + arr[i] = c.r; + arr[i + 1] = c.g; + arr[i + 2] = c.b; + arr[i + 3] = c.r; + arr[i + 4] = c.g; + arr[i + 5] = c.b; + } + colors.needsUpdate = true; + } + } + }); + themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); + let animId: number; const animate = () => { animId = requestAnimationFrame(animate); @@ -156,12 +186,13 @@ export const AnalyzerViewer3D: React.FC = ({ cancelAnimationFrame(animId); window.removeEventListener('resize', handleResize); observer.disconnect(); + themeObserver.disconnect(); renderer.dispose(); controls.dispose(); }; }, [theme]); - // 2. Carrega o modelo na cena + // 2. Carrega o modelo na cena diretamente sem deep clone circular useEffect(() => { const scene = sceneRef.current; if (!scene) return; @@ -173,7 +204,8 @@ export const AnalyzerViewer3D: React.FC = ({ if (!modelData || !modelData.sceneGroup) return; - const group = modelData.sceneGroup.clone(); + // Usa diretamente a sceneGroup da carga sem clone para evitar erro de circular JSON + const group = modelData.sceneGroup; modelGroupRef.current = group; scene.add(group); @@ -339,14 +371,50 @@ export const AnalyzerViewer3D: React.FC = ({ controlsRef.current.update(); }; + // ViewCube Switcher de Direções + const handleViewCubeSelect = useCallback( + (view: 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso') => { + if (!controlsRef.current || !cameraRef.current) return; + const target = controlsRef.current.target; + const camera = cameraRef.current; + const distance = camera.position.distanceTo(target); + + const newPos = target.clone(); + switch (view) { + case 'top': + newPos.y += distance; + break; + case 'bottom': + newPos.y -= distance; + break; + case 'front': + newPos.z += distance; + break; + case 'back': + newPos.z -= distance; + break; + case 'left': + newPos.x -= distance; + break; + case 'right': + newPos.x += distance; + break; + case 'iso': + newPos.x += distance * 0.7; + newPos.y += distance * 0.7; + newPos.z += distance * 0.7; + break; + } + camera.position.copy(newPos); + controlsRef.current.update(); + }, + [] + ); + return (
{/* Container Three.js */}
= ({ {/* ViewCube Superior Direito */} {cameraRef.current && controlsRef.current && ( - + } + /> )} {/* Barra de Ferramentas Flutuante Superior */} -
+
-
+
@@ -409,21 +480,21 @@ export const AnalyzerViewer3D: React.FC = ({
{/* Legenda de Cores Flutuante Inferior */} -
- Legenda: +
+ Legenda: {colorizationMode === 'status_defeito' && ( <>
- Conforme + Conforme
- Alerta (Deslocado) + Alerta (Deslocado)
- Crítico (Furo Cego/Colisão) + Crítico (Furo Cego/Colisão)
)} @@ -431,19 +502,19 @@ export const AnalyzerViewer3D: React.FC = ({ <>
- Inalterada + Inalterada
- Modificada + Modificada
- Nova + Nova
- Excluída + Excluída
)} @@ -451,15 +522,15 @@ export const AnalyzerViewer3D: React.FC = ({ <>
- Baixo Risco (Sem Corte) + Baixo Risco (Sem Corte)
- Médio (Em Corte) + Médio (Em Corte)
- Crítico (Soldada/Expedida) + Crítico (Soldada/Expedida)
)} @@ -467,17 +538,17 @@ export const AnalyzerViewer3D: React.FC = ({ {/* Alerta de elemento selecionado flutuante */} {selectedPieceMark && ( -
- - - Foco Ativo: {selectedPieceMark} +
+ + + Foco Ativo: {selectedPieceMark}