🚀 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 { 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
@@ -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
@@ -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
@@ -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}>
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user