diff --git a/src/components/three/XRControllerMeasure.tsx b/src/components/three/XRControllerMeasure.tsx index 49450c6..de52e40 100644 --- a/src/components/three/XRControllerMeasure.tsx +++ b/src/components/three/XRControllerMeasure.tsx @@ -2,6 +2,7 @@ import { useRef, useState } from 'react'; import { useFrame, useThree } from '@react-three/fiber'; import * as THREE from 'three'; import { useModelStore } from '@/stores/useModelStore'; +import { sendRemoteLog } from '@/lib/remoteLogger'; import { resolveSnap, detectCircularEdgeAtPoint, @@ -269,6 +270,7 @@ export function XRControllerMeasure() { } } } else if (snappedPoint) { + sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint); st.addMeasurePoint({ x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z, }); diff --git a/src/lib/remoteLogger.ts b/src/lib/remoteLogger.ts index d7fea9c..3e19b1a 100644 --- a/src/lib/remoteLogger.ts +++ b/src/lib/remoteLogger.ts @@ -1,26 +1,55 @@ import { supabase } from '@/integrations/supabase/client'; +const logBuffer: any[] = []; +let channelSubscribed = false; + const logChannel = supabase.channel('global-app-logs', { config: { broadcast: { self: true, ack: false } }, }); -logChannel.subscribe(); +logChannel.subscribe((status) => { + console.log(`[RemoteLogger] Status do canal de log: ${status}`); + if (status === 'SUBSCRIBED') { + channelSubscribed = true; + console.log(`[RemoteLogger] Inscrito com sucesso. Enviando ${logBuffer.length} logs acumulados...`); + // Envia o buffer acumulado + while (logBuffer.length > 0) { + const payload = logBuffer.shift(); + logChannel.send({ + type: 'broadcast', + event: 'log', + payload, + }).catch(err => { + console.error('[RemoteLogger] Erro ao enviar log em buffer:', err); + }); + } + } +}); export function sendRemoteLog(level: 'info' | 'warn' | 'error', message: string, data?: any) { - console.log(`[RemoteLog] [${level}] ${message}`, data || ''); - logChannel.send({ - type: 'broadcast', - event: 'log', - payload: { - level, - message, - data: data ? JSON.parse(JSON.stringify(data)) : null, - timestamp: new Date().toISOString(), - userAgent: navigator.userAgent, - }, - }).catch(err => { - console.error('Falha ao enviar log remoto:', err); - }); + const consoleFn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log; + consoleFn(`[RemoteLog] [${level}] ${message}`, data || ''); + + const payload = { + level, + message, + data: data ? JSON.parse(JSON.stringify(data)) : null, + timestamp: new Date().toISOString(), + userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'NodeJS', + }; + + if (channelSubscribed) { + logChannel.send({ + type: 'broadcast', + event: 'log', + payload, + }).catch(err => { + console.error('[RemoteLogger] Falha ao enviar log remoto, enfileirando...', err); + logBuffer.push(payload); + }); + } else { + logBuffer.push(payload); + } } // Captura global de erros em tempo de execução diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index d29c219..c020dcc 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -327,7 +327,7 @@ const Index = () => { {/* Footer */}

- TrackSteelXR v1.04 — Q.C. Inspection + TrackSteelXR v1.05 — Q.C. Inspection

{showLogs && ( diff --git a/src/pages/XRSession.tsx b/src/pages/XRSession.tsx index f15b985..cc59e3d 100644 --- a/src/pages/XRSession.tsx +++ b/src/pages/XRSession.tsx @@ -215,6 +215,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore'). scale={[s, s, s]} > + ); @@ -375,6 +376,66 @@ function XRMeasurementText({ text, color }: { text: string; color: string }) { ); } +// Componente de linha nativa seguro para WebXR +export function XRSafeLine({ a, b }: { a: [number, number, number]; b: [number, number, number] }) { + const points = useMemo(() => [ + new THREE.Vector3(a[0], a[1], a[2]), + new THREE.Vector3(b[0], b[1], b[2]) + ], [a, b]); + + const geometry = useMemo(() => { + return new THREE.BufferGeometry().setFromPoints(points); + }, [points]); + + return ( + + + + ); +} + +// Componente para renderizar medições que pertencem a um modelo específico +export function XRLocalModelMeasurements({ modelId }: { modelId: string }) { + const measurements = useModelStore((s) => s.measurements); + const modelMeasurements = useMemo(() => { + return measurements.filter(m => m.modelId === modelId); + }, [measurements, modelId]); + + return ( + <> + {modelMeasurements.map((m) => { + const a: [number, number, number] = [m.pointA.x, m.pointA.y, m.pointA.z]; + const b: [number, number, number] = [m.pointB.x, m.pointB.y, m.pointB.z]; + const mid: [number, number, number] = [(a[0]+b[0])/2, (a[1]+b[1])/2, (a[2]+b[2])/2]; + const isHole = m.kind === 'hole'; + const color = isHole ? '#f59e0b' : '#3b82f6'; + const text = m.label ?? `${m.distanceMM.toFixed(1)} mm`; + + return ( + + {!isHole && ( + <> + + + + + + + + + + + )} + + + + + ); + })} + + ); +} + // ─── Measurement overlay (reused from ModelViewer) ───── function XRMeasurementOverlay() { const measurements = useModelStore((s) => s.measurements); @@ -382,6 +443,10 @@ function XRMeasurementOverlay() { const snapPoint = useModelStore((s) => s.snapPoint); const measureMode = useModelStore((s) => s.measureMode); + const globalMeasurements = useMemo(() => { + return measurements.filter(m => !m.modelId); + }, [measurements]); + return ( <> {measureMode && snapPoint && ( @@ -396,20 +461,27 @@ function XRMeasurementOverlay() { )} - {measurements.map((m) => { + {globalMeasurements.map((m) => { const a: [number, number, number] = [m.pointA.x, m.pointA.y, m.pointA.z]; const b: [number, number, number] = [m.pointB.x, m.pointB.y, m.pointB.z]; const mid: [number, number, number] = [(a[0]+b[0])/2, (a[1]+b[1])/2, (a[2]+b[2])/2]; const isHole = m.kind === 'hole'; const color = isHole ? '#f59e0b' : '#3b82f6'; const text = m.label ?? `${m.distanceMM.toFixed(1)} mm`; + return ( {!isHole && ( <> - - - + + + + + + + + + )} diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts index 3a15175..e07045f 100644 --- a/src/stores/useModelStore.ts +++ b/src/stores/useModelStore.ts @@ -1,5 +1,6 @@ import { create } from 'zustand'; import { worldToModelLocal } from '@/lib/modelTransforms'; +import { sendRemoteLog } from '@/lib/remoteLogger'; // ── Persistência de placement (fineTuning + escala) e flags WebXR ───── const PLACEMENT_KEY = 'tsxr_placements_v1'; @@ -553,6 +554,7 @@ export const useModelStore = create((set, get) => ({ }), measurePoints: [], addMeasurePoint: (p) => set((state) => { + sendRemoteLog('info', `addMeasurePoint acionado: ponto atual ${state.measurePoints.length + 1}`, p); const points = [...state.measurePoints, p]; if (points.length === 2) { const [a, b] = points; @@ -562,11 +564,17 @@ export const useModelStore = create((set, get) => ({ const distanceWorldMM = Math.sqrt(dx * dx + dy * dy + dz * dz); const factor = state.scaleRatio?.factor ?? 1; const distanceMM = distanceWorldMM / factor; - // Convert world points to active model's local frame so they follow the model. const modelId = state.activeModelId ?? undefined; const aConv = modelId ? worldToModelLocal(modelId, a) : null; const bConv = modelId ? worldToModelLocal(modelId, b) : null; const attached = !!(aConv && bConv); + sendRemoteLog('info', `addMeasurePoint: calculando distância`, { + distanceWorldMM, + distanceMM, + factor, + modelId, + attached + }); const measurement: Measurement = { id: `m_${Date.now()}`, pointA: attached ? aConv! : a, @@ -574,6 +582,7 @@ export const useModelStore = create((set, get) => ({ distanceMM, modelId: attached ? modelId : undefined, }; + sendRemoteLog('info', `addMeasurePoint: medição gerada com sucesso`, measurement); return { measurePoints: [], measurements: [...state.measurements, measurement] }; } return { measurePoints: points };