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

This commit is contained in:
2026-05-22 21:20:32 +00:00
parent 63ad53edfb
commit c85dc85936
6 changed files with 181 additions and 78 deletions
+60
View File
@@ -4,6 +4,7 @@ import { Upload, Glasses, CheckCircle, XCircle, Loader2, Box, Package } from 'lu
import { Button } from '@/components/ui/button';
import { useModelStore } from '@/stores/useModelStore';
import { toast } from 'sonner';
import { supabase } from '@/integrations/supabase/client';
import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from '@/lib/convertToGLB';
import { convertIFCtoGLB } from '@/lib/convertIFC';
import { validateModelFile } from '@/lib/validateModelFile';
@@ -23,6 +24,22 @@ const Index = () => {
const [converting, setConverting] = useState(false);
const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore();
// --- Painel de Logs Remotos em Tempo Real ---
const [debugLogs, setDebugLogs] = useState<{ level: 'info' | 'warn' | 'error'; message: string; timestamp: string; data?: any }[]>([]);
const [showLogs, setShowLogs] = useState(() => new URLSearchParams(window.location.search).get('viewlogs') === 'true');
useEffect(() => {
if (!showLogs) return;
const logChannel = supabase.channel('global-app-logs-viewer');
logChannel.on('broadcast', { event: 'log' }, ({ payload }) => {
setDebugLogs(prev => [payload, ...prev].slice(0, 100));
}).subscribe();
return () => {
logChannel.unsubscribe();
};
}, [showLogs]);
const [ifcConvert, setIfcConvert] = useState(() => {
const saved = localStorage.getItem('tsxr_ifc_convert');
return saved !== 'false';
@@ -312,6 +329,49 @@ const Index = () => {
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
TrackSteelXR v1.04 Q.C. Inspection
</p>
{showLogs && (
<div className="fixed bottom-4 right-4 z-50 flex h-[400px] w-[500px] flex-col rounded-lg border border-border bg-slate-950/95 p-4 shadow-2xl backdrop-blur font-mono text-xs text-slate-200">
<div className="flex items-center justify-between border-b border-border pb-2 mb-2">
<span className="font-bold text-primary flex items-center gap-2">
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-ping" />
Console de Logs Remotos (Quest 3)
</span>
<div className="flex gap-2">
<Button size="sm" variant="outline" className="h-6 px-2 text-[10px]" onClick={() => setDebugLogs([])}>
Limpar
</Button>
<Button size="sm" variant="outline" className="h-6 px-2 text-[10px] text-destructive" onClick={() => setShowLogs(false)}>
Fechar
</Button>
</div>
</div>
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
{debugLogs.length === 0 ? (
<p className="text-slate-500 text-center mt-12">Aguardando logs do headset Quest 3...</p>
) : (
debugLogs.map((log, i) => (
<div key={i} className={`p-2 rounded border ${
log.level === 'error' ? 'bg-red-950/40 border-red-500/30 text-red-300' :
log.level === 'warn' ? 'bg-amber-950/40 border-amber-500/30 text-amber-300' :
'bg-slate-900/60 border-slate-700/30 text-slate-300'
}`}>
<div className="flex justify-between text-[9px] text-slate-400 mb-1">
<span>{new Date(log.timestamp).toLocaleTimeString()}</span>
<span className="uppercase font-bold">{log.level}</span>
</div>
<p className="break-all whitespace-pre-wrap">{log.message}</p>
{log.data && (
<pre className="mt-1 text-[10px] bg-black/40 p-1 rounded overflow-x-auto max-h-24 text-slate-400">
{JSON.stringify(log.data, null, 2)}
</pre>
)}
</div>
))
)}
</div>
</div>
)}
</div>);
};
+20 -6
View File
@@ -23,6 +23,7 @@ import { useDevKit } from '@/devkit/useDevKit';
import { DevPanel } from '@/devkit/DevPanel';
import { FakeControllers } from '@/devkit/FakeControllers';
import { VisibilityApplier } from '@/components/three/ModelViewer';
import { registerModelLocalGroup, unregisterModelLocalGroup } from '@/lib/modelTransforms';
import { parseIFCtoThree } from '@/lib/convertIFC';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
@@ -99,6 +100,13 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
}, [sceneModel.url, sceneModel.fileName]);
const ref = useRef<THREE.Group>(null);
useEffect(() => {
const g = ref.current;
if (!g) return;
registerModelLocalGroup(sceneModel.id, g);
return () => unregisterModelLocalGroup(sceneModel.id, g);
}, [sceneModel.id]);
const fineTuning = sceneModel.fineTuning;
const opacity = useModelStore((s) => s.opacity);
const renderMode = useModelStore((s) => s.renderMode);
@@ -106,6 +114,7 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
const wireframeThickness = useModelStore((s) => s.wireframeThickness);
const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle);
const checklist = useModelStore((s) => s.checklist);
const measureMode = useModelStore((s) => s.measureMode);
const modelInfo = useMemo(() => {
if (!scene) return { size: new THREE.Vector3(), center: new THREE.Vector3() };
@@ -145,12 +154,13 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
const materials = Array.isArray(child.material) ? child.material : [child.material];
materials.forEach((mat: THREE.Material) => {
if (mat instanceof THREE.MeshStandardMaterial) {
if (renderMode === 'edges') {
if (renderMode === 'edges' && !measureMode) {
mat.visible = false;
} else {
mat.visible = true;
mat.transparent = opacity < 1;
mat.opacity = opacity;
const targetOpacity = measureMode ? 0.25 : opacity;
mat.transparent = targetOpacity < 1;
mat.opacity = targetOpacity;
mat.wireframe = renderMode === 'wireframe';
if (renderMode === 'wireframe') {
mat.wireframeLinewidth = wireframeThickness;
@@ -167,16 +177,19 @@ function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').
}
});
if (renderMode === 'edges' && child.geometry) {
if ((renderMode === 'edges' || measureMode) && child.geometry) {
const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
const lineMat = new THREE.LineBasicMaterial({ color: wireframeColor, linewidth: wireframeThickness });
const lineMat = new THREE.LineBasicMaterial({
color: measureMode ? '#00f3ff' : wireframeColor,
linewidth: measureMode ? 2 : wireframeThickness,
});
const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
lineSegments.userData.__edgeLine = true;
child.add(lineSegments);
}
}
});
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color]);
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color, measureMode]);
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
@@ -457,6 +470,7 @@ function XRSnapHandler() {
// Click to measure
useEffect(() => {
if (!measureMode) return;
if (gl.xr.isPresenting) return; // Ignora cliques simulados do DOM quando o WebXR está ativo
const onClick = (e: MouseEvent) => {
if (snapPoint) {
addMeasurePoint({ x: snapPoint.x, y: snapPoint.y, z: snapPoint.z });