🚀 Auto-deploy: melhoria no snap e medição AR em 22/05/2026 21:38:44
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
+44
-15
@@ -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
|
||||
|
||||
+1
-1
@@ -327,7 +327,7 @@ const Index = () => {
|
||||
|
||||
{/* Footer */}
|
||||
<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>
|
||||
|
||||
{showLogs && (
|
||||
|
||||
+76
-4
@@ -215,6 +215,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
|
||||
scale={[s, s, s]}
|
||||
>
|
||||
<primitive object={scene} />
|
||||
<XRLocalModelMeasurements modelId={sceneModel.id} />
|
||||
</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) ─────
|
||||
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() {
|
||||
<meshBasicMaterial color="#e8a838" />
|
||||
</mesh>
|
||||
)}
|
||||
{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 (
|
||||
<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>
|
||||
<Line points={[a, b]} color="#22c55e" lineWidth={2} />
|
||||
<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}>
|
||||
|
||||
@@ -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<ModelStore>((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<ModelStore>((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<ModelStore>((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 };
|
||||
|
||||
Reference in New Issue
Block a user