🚀 Auto-deploy: melhoria no snap e medição AR em 02/06/2026 21:42:49
This commit is contained in:
+1
-1
@@ -365,7 +365,7 @@ const Index = () => {
|
||||
|
||||
{/* Footer */}
|
||||
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
|
||||
SteelXR v1.11 — Q.C. Inspection
|
||||
SteelXR v1.12 — Q.C. Inspection
|
||||
</p>
|
||||
|
||||
{showLogs && (
|
||||
|
||||
+1
-26
@@ -259,37 +259,12 @@ const Viewer = () => {
|
||||
|
||||
const [enteringXR, setEnteringXR] = useState(false);
|
||||
|
||||
const handleEnterXR = useCallback(async () => {
|
||||
const handleEnterXR = useCallback(() => {
|
||||
if (!xrSupported) {
|
||||
toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3.");
|
||||
return;
|
||||
}
|
||||
setEnteringXR(true);
|
||||
|
||||
// Timeout de segurança — se não entrar em 15s, cancela
|
||||
const timeoutId = setTimeout(() => {
|
||||
setEnteringXR(false);
|
||||
toast.error("Tempo limite ao iniciar modo AR. Verifique se o Meta Quest Browser está conectado.");
|
||||
}, 15000);
|
||||
|
||||
try {
|
||||
// Marca que está entrando no AR antes de navegar — bloqueia auto-return prematuro
|
||||
(window as unknown as { __setXrEntering?: (v: boolean) => void }).__setXrEntering?.(true);
|
||||
|
||||
// Chamada síncrona de enterAR para preservar o User Gesture exigido pelo WebXR
|
||||
const enterPromise = xrStore.enterAR();
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
navigate("/xr");
|
||||
|
||||
await enterPromise;
|
||||
} catch (e) {
|
||||
clearTimeout(timeoutId);
|
||||
setEnteringXR(false);
|
||||
(window as unknown as { __setXrEntering?: (v: boolean) => void }).__setXrEntering?.(false);
|
||||
console.error('[Viewer] XR enterAR error:', e);
|
||||
toast.error("Falha ao iniciar modo AR. Verifique se o Meta Quest Browser está conectado.");
|
||||
}
|
||||
}, [xrSupported, navigate]);
|
||||
|
||||
const handleEnterMeeting = () => {
|
||||
|
||||
+82
-5
@@ -7,7 +7,7 @@ import { xrStore as store } from '@/stores/useXRStore';
|
||||
import * as THREE from 'three';
|
||||
import { useModelStore } from '@/stores/useModelStore';
|
||||
import { toast } from 'sonner';
|
||||
import { ArrowLeft, Download, QrCode, Crosshair, Home } from 'lucide-react';
|
||||
import { ArrowLeft, Download, QrCode, Crosshair, Home, Glasses } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { generateMarkerDownloadURL } from '@/lib/trackingMarker';
|
||||
import { XRHud } from '@/components/XRHud';
|
||||
@@ -1050,6 +1050,7 @@ const XRSession = () => {
|
||||
const effectiveInXR = inXR || simXR;
|
||||
// Indica que enterAR() foi chamado mas a sessão ainda não começou — previne auto-return prematuro
|
||||
const [isEnteringAR, setIsEnteringAR] = useState(false);
|
||||
const [hasClickedEnter, setHasClickedEnter] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!model) navigate('/');
|
||||
@@ -1098,12 +1099,10 @@ const XRSession = () => {
|
||||
toast.success('Marcador baixado — Imprima em 15×15cm');
|
||||
}, []);
|
||||
|
||||
if (!model) return null;
|
||||
|
||||
// Auto-return to viewer if user exits AR session
|
||||
useEffect(() => {
|
||||
// Don't auto-return while we're waiting for the XR onboarding dialog to complete
|
||||
if (!inXR) {
|
||||
if (hasClickedEnter && !inXR) {
|
||||
const timer = setTimeout(() => {
|
||||
// Only return if the session is truly absent AND we're not in the middle of entering AR
|
||||
if (!store.getState().session) {
|
||||
@@ -1112,7 +1111,9 @@ const XRSession = () => {
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [inXR, navigate]);
|
||||
}, [inXR, hasClickedEnter, navigate]);
|
||||
|
||||
if (!model) return null;
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col bg-background">
|
||||
@@ -1262,6 +1263,82 @@ const XRSession = () => {
|
||||
</XR>
|
||||
</Canvas>
|
||||
|
||||
{/* Onboarding do AR */}
|
||||
{!effectiveInXR && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center bg-slate-950/85 backdrop-blur-md p-6">
|
||||
<div className="w-full max-w-md border border-primary/20 bg-slate-900/95 rounded-2xl p-8 shadow-2xl text-center space-y-6 glow-primary">
|
||||
<div className="flex justify-center">
|
||||
<div className="h-16 w-16 items-center justify-center flex rounded-full bg-primary/10 border border-primary/30 animate-pulse">
|
||||
<Glasses className="h-8 w-8 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-xl font-semibold tracking-tight text-foreground font-mono">
|
||||
Pronto para entrar no <span className="text-primary">Modo AR</span>?
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Você está prestes a carregar o modelo em escala real 1:1 no seu ambiente físico.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Informações da Peça */}
|
||||
<div className="rounded-lg border border-border bg-slate-950/50 p-4 text-left space-y-2 font-mono text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Modelo Ativo:</span>
|
||||
<span className="text-foreground truncate max-w-[200px]" title={model.fileName}>{model.fileName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Tamanho:</span>
|
||||
<span className="text-foreground">{(model.fileSize / (1024 * 1024)).toFixed(2)} MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dicas Rápidas */}
|
||||
<div className="text-left space-y-2.5 rounded-lg bg-primary/5 border border-primary/15 p-4 text-xs">
|
||||
<h3 className="font-semibold text-primary font-mono flex items-center gap-1.5">
|
||||
💡 Guia de Controles Rápidos:
|
||||
</h3>
|
||||
<ul className="space-y-1.5 text-muted-foreground list-disc pl-4">
|
||||
<li><strong>Teletransporte:</strong> Thumbstick para frente. Solte para teleportar.</li>
|
||||
<li><strong>Escala Real (1:1):</strong> Aponte o feixe para a peça e teletransporte nela.</li>
|
||||
<li><strong>Mover Peça:</strong> Segure o Grip de qualquer controle para arrastar.</li>
|
||||
<li><strong>Girar/Zoom:</strong> Ambas as miras na peça, segure ambos os Grips e mova as mãos.</li>
|
||||
<li><strong>Menu do App:</strong> Botões A/B/X/Y abrem/fecham o menu flutuante.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Ações */}
|
||||
<div className="flex flex-col gap-3 pt-2">
|
||||
<Button
|
||||
className="h-12 text-sm font-semibold glow-primary w-full gap-2 text-primary-foreground"
|
||||
onClick={async () => {
|
||||
setHasClickedEnter(true);
|
||||
try {
|
||||
await store.enterAR();
|
||||
} catch (err) {
|
||||
console.error("[XR] Falha ao iniciar AR:", err);
|
||||
toast.error("Não foi possível iniciar o modo AR. Verifique se o Quest 3 está conectado.");
|
||||
setHasClickedEnter(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Glasses className="h-5 w-5 animate-bounce" />
|
||||
Iniciar Visualização AR
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-12 text-sm font-medium border-muted-foreground/20 text-muted-foreground hover:bg-slate-800 hover:text-foreground w-full"
|
||||
onClick={() => navigate('/viewer')}
|
||||
>
|
||||
Voltar ao Viewer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Floating DOM HUD overlay — only visible OUTSIDE passthrough.
|
||||
Inside AR, the DOM is occluded by the headset compositor; the
|
||||
in-world XRHudInWorld replaces it. */}
|
||||
|
||||
Reference in New Issue
Block a user