🚀 Auto-deploy: melhoria no snap e medição AR em 22/05/2026 21:38:44

This commit is contained in:
2026-05-22 21:38:44 +00:00
parent c85dc85936
commit c93e56640d
5 changed files with 133 additions and 21 deletions
@@ -2,6 +2,7 @@ import { useRef, useState } from 'react';
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three'; import * as THREE from 'three';
import { useModelStore } from '@/stores/useModelStore'; import { useModelStore } from '@/stores/useModelStore';
import { sendRemoteLog } from '@/lib/remoteLogger';
import { import {
resolveSnap, resolveSnap,
detectCircularEdgeAtPoint, detectCircularEdgeAtPoint,
@@ -269,6 +270,7 @@ export function XRControllerMeasure() {
} }
} }
} else if (snappedPoint) { } else if (snappedPoint) {
sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint);
st.addMeasurePoint({ st.addMeasurePoint({
x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z, x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
}); });
+44 -15
View File
@@ -1,26 +1,55 @@
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
const logBuffer: any[] = [];
let channelSubscribed = false;
const logChannel = supabase.channel('global-app-logs', { const logChannel = supabase.channel('global-app-logs', {
config: { broadcast: { self: true, ack: false } }, 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) { export function sendRemoteLog(level: 'info' | 'warn' | 'error', message: string, data?: any) {
console.log(`[RemoteLog] [${level}] ${message}`, data || ''); const consoleFn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
logChannel.send({ consoleFn(`[RemoteLog] [${level}] ${message}`, data || '');
type: 'broadcast',
event: 'log', const payload = {
payload: { level,
level, message,
message, data: data ? JSON.parse(JSON.stringify(data)) : null,
data: data ? JSON.parse(JSON.stringify(data)) : null, timestamp: new Date().toISOString(),
timestamp: new Date().toISOString(), userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'NodeJS',
userAgent: navigator.userAgent, };
},
}).catch(err => { if (channelSubscribed) {
console.error('Falha ao enviar log remoto:', err); 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 // Captura global de erros em tempo de execução
+1 -1
View File
@@ -327,7 +327,7 @@ const Index = () => {
{/* Footer */} {/* Footer */}
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50"> <p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
TrackSteelXR v1.04 Q.C. Inspection TrackSteelXR v1.05 Q.C. Inspection
</p> </p>
{showLogs && ( {showLogs && (
+76 -4
View File
@@ -215,6 +215,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
scale={[s, s, s]} scale={[s, s, s]}
> >
<primitive object={scene} /> <primitive object={scene} />
<XRLocalModelMeasurements modelId={sceneModel.id} />
</group> </group>
</group> </group>
); );
@@ -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 (
<line geometry={geometry}>
<lineBasicMaterial color="#22c55e" linewidth={2} />
</line>
);
}
// 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 (
<group key={m.id}>
{!isHole && (
<>
<mesh position={a}>
<sphereGeometry args={[0.003, 16, 16]} />
<meshBasicMaterial color="#22c55e" />
</mesh>
<mesh position={b}>
<sphereGeometry args={[0.003, 16, 16]} />
<meshBasicMaterial color="#22c55e" />
</mesh>
<XRSafeLine a={a} b={b} />
</>
)}
<group position={mid}>
<XRMeasurementText text={text} color={color} />
</group>
</group>
);
})}
</>
);
}
// ─── Measurement overlay (reused from ModelViewer) ───── // ─── Measurement overlay (reused from ModelViewer) ─────
function XRMeasurementOverlay() { function XRMeasurementOverlay() {
const measurements = useModelStore((s) => s.measurements); const measurements = useModelStore((s) => s.measurements);
@@ -382,6 +443,10 @@ function XRMeasurementOverlay() {
const snapPoint = useModelStore((s) => s.snapPoint); const snapPoint = useModelStore((s) => s.snapPoint);
const measureMode = useModelStore((s) => s.measureMode); const measureMode = useModelStore((s) => s.measureMode);
const globalMeasurements = useMemo(() => {
return measurements.filter(m => !m.modelId);
}, [measurements]);
return ( return (
<> <>
{measureMode && snapPoint && ( {measureMode && snapPoint && (
@@ -396,20 +461,27 @@ function XRMeasurementOverlay() {
<meshBasicMaterial color="#e8a838" /> <meshBasicMaterial color="#e8a838" />
</mesh> </mesh>
)} )}
{measurements.map((m) => { {globalMeasurements.map((m) => {
const a: [number, number, number] = [m.pointA.x, m.pointA.y, m.pointA.z]; 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 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 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 isHole = m.kind === 'hole';
const color = isHole ? '#f59e0b' : '#3b82f6'; const color = isHole ? '#f59e0b' : '#3b82f6';
const text = m.label ?? `${m.distanceMM.toFixed(1)} mm`; const text = m.label ?? `${m.distanceMM.toFixed(1)} mm`;
return ( return (
<group key={m.id}> <group key={m.id}>
{!isHole && ( {!isHole && (
<> <>
<mesh position={a}><sphereGeometry args={[0.003, 16, 16]} /><meshBasicMaterial color="#22c55e" /></mesh> <mesh position={a}>
<mesh position={b}><sphereGeometry args={[0.003, 16, 16]} /><meshBasicMaterial color="#22c55e" /></mesh> <sphereGeometry args={[0.003, 16, 16]} />
<Line points={[a, b]} color="#22c55e" lineWidth={2} /> <meshBasicMaterial color="#22c55e" />
</mesh>
<mesh position={b}>
<sphereGeometry args={[0.003, 16, 16]} />
<meshBasicMaterial color="#22c55e" />
</mesh>
<XRSafeLine a={a} b={b} />
</> </>
)} )}
<group position={mid}> <group position={mid}>
+10 -1
View File
@@ -1,5 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { worldToModelLocal } from '@/lib/modelTransforms'; import { worldToModelLocal } from '@/lib/modelTransforms';
import { sendRemoteLog } from '@/lib/remoteLogger';
// ── Persistência de placement (fineTuning + escala) e flags WebXR ───── // ── Persistência de placement (fineTuning + escala) e flags WebXR ─────
const PLACEMENT_KEY = 'tsxr_placements_v1'; const PLACEMENT_KEY = 'tsxr_placements_v1';
@@ -553,6 +554,7 @@ export const useModelStore = create<ModelStore>((set, get) => ({
}), }),
measurePoints: [], measurePoints: [],
addMeasurePoint: (p) => set((state) => { addMeasurePoint: (p) => set((state) => {
sendRemoteLog('info', `addMeasurePoint acionado: ponto atual ${state.measurePoints.length + 1}`, p);
const points = [...state.measurePoints, p]; const points = [...state.measurePoints, p];
if (points.length === 2) { if (points.length === 2) {
const [a, b] = points; const [a, b] = points;
@@ -562,11 +564,17 @@ export const useModelStore = create<ModelStore>((set, get) => ({
const distanceWorldMM = Math.sqrt(dx * dx + dy * dy + dz * dz); const distanceWorldMM = Math.sqrt(dx * dx + dy * dy + dz * dz);
const factor = state.scaleRatio?.factor ?? 1; const factor = state.scaleRatio?.factor ?? 1;
const distanceMM = distanceWorldMM / factor; const distanceMM = distanceWorldMM / factor;
// Convert world points to active model's local frame so they follow the model.
const modelId = state.activeModelId ?? undefined; const modelId = state.activeModelId ?? undefined;
const aConv = modelId ? worldToModelLocal(modelId, a) : null; const aConv = modelId ? worldToModelLocal(modelId, a) : null;
const bConv = modelId ? worldToModelLocal(modelId, b) : null; const bConv = modelId ? worldToModelLocal(modelId, b) : null;
const attached = !!(aConv && bConv); const attached = !!(aConv && bConv);
sendRemoteLog('info', `addMeasurePoint: calculando distância`, {
distanceWorldMM,
distanceMM,
factor,
modelId,
attached
});
const measurement: Measurement = { const measurement: Measurement = {
id: `m_${Date.now()}`, id: `m_${Date.now()}`,
pointA: attached ? aConv! : a, pointA: attached ? aConv! : a,
@@ -574,6 +582,7 @@ export const useModelStore = create<ModelStore>((set, get) => ({
distanceMM, distanceMM,
modelId: attached ? modelId : undefined, modelId: attached ? modelId : undefined,
}; };
sendRemoteLog('info', `addMeasurePoint: medição gerada com sucesso`, measurement);
return { measurePoints: [], measurements: [...state.measurements, measurement] }; return { measurePoints: [], measurements: [...state.measurements, measurement] };
} }
return { measurePoints: points }; return { measurePoints: points };