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 };