🚀 Auto-deploy: melhoria no snap e medição AR em 28/05/2026 21:16:09

This commit is contained in:
2026-05-28 21:16:09 +00:00
parent aff19146f0
commit 9b7f237afc
4 changed files with 494 additions and 18 deletions
+133 -3
View File
@@ -12,8 +12,16 @@ import {
stopRecording, captureScreenshot, formatElapsed,
} from '@/hooks/useCanvasRecorder';
import { getAllModelLocalGroups } from '@/lib/modelTransforms';
import { toast } from 'sonner';
import {
xrCalibration,
startXRCalibration,
cancelXRCalibration,
subscribeXRCalibration,
} from './xrCalibrationBus';
import { XRViewCube } from './XRViewCube';
type Tab = 'scene' | 'tools' | 'cuts' | 'inspection' | 'share' | 'capture' | 'webxr';
type Tab = 'scene' | 'tools' | 'calib' | 'cuts' | 'inspection' | 'share' | 'capture' | 'webxr';
interface XRHudInWorldProps {
freeMove: boolean;
@@ -225,6 +233,7 @@ function FloatingPanel({
const tabs: { id: Tab; label: string; icon: string }[] = [
{ id: 'scene', label: 'Cena', icon: '🧩' },
{ id: 'tools', label: 'Ferram.', icon: '🛠' },
{ id: 'calib', label: 'Calibrar', icon: '🎯' },
{ id: 'cuts', label: 'Cortes', icon: '✂' },
{ id: 'inspection', label: 'Inspeção', icon: '✓' },
{ id: 'capture', label: 'Captura', icon: '🎥' },
@@ -257,15 +266,16 @@ function FloatingPanel({
<group position={[0, H / 2 - 0.045, 0.001]}>
{tabs.map((t, i) => (
<XR3DButton key={t.id}
position={[-W / 2 + 0.038 + i * 0.072, 0, 0]} size={[0.066, 0.022]}
position={[-W / 2 + 0.0495 + i * 0.063, 0, 0]} size={[0.058, 0.020]}
label={t.label} icon={t.icon} active={tab === t.id}
onClick={() => setTab(t.id)} fontSize={0.0068} />
onClick={() => setTab(t.id)} fontSize={0.0062} />
))}
</group>
<group position={[0, -0.018, 0.001]}>
{tab === 'scene' && <SceneTab />}
{tab === 'tools' && <ToolsTab {...p} />}
{tab === 'calib' && <CalibrationTab />}
{tab === 'cuts' && <CutsTab />}
{tab === 'inspection' && <InspectionTab />}
{tab === 'capture' && <CaptureTab />}
@@ -879,3 +889,123 @@ function RecIndicator() {
</group>
);
}
function CalibrationTab() {
const activeModelId = useModelStore((s) => s.activeModelId);
const models = useModelStore((s) => s.models);
const setCalibrationStore = useModelStore((s) => s.setCalibration);
const active = models.find((m) => m.id === activeModelId);
// Escuta o barramento de calibração do XR
const [, force] = useState(0);
useEffect(() => subscribeXRCalibration(() => force((t) => t + 1)), []);
const isCalibrating = xrCalibration.step !== 'idle' && xrCalibration.step !== 'done';
const isDone = xrCalibration.step === 'done';
const progress = isDone ? 1 : xrCalibration.progress;
const onClickCalibrar = () => {
if (isCalibrating || isDone) {
cancelXRCalibration();
return;
}
if (!active) {
toast.error('Selecione uma peça antes de calibrar');
return;
}
if (active.locked) {
toast.error('Peça travada — destranque a peça para calibrar');
return;
}
startXRCalibration(active.id);
};
const onResetCalibration = () => {
if (!active) return;
setCalibrationStore(active.id, null);
toast.success('Calibração removida');
};
const STEP_HINTS_AR: Record<string, string> = {
'await-cube-1': '1/3 - Clique numa face do Cubo à direita',
'await-model-1': '1/3 - Aponte e clique na face correspondente da peça',
'await-cube-2': '2/3 - Clique em outra face do Cubo',
'await-model-2': '2/3 - Clique na face correspondente da peça',
'await-cube-3': '3/3 (Verificar) - Clique em uma 3ª face do Cubo (opcional)',
'await-model-3': '3/3 - Clique na face correspondente da peça',
'done': 'Calibração concluída com sucesso!',
};
const currentHint = STEP_HINTS_AR[xrCalibration.step] ?? 'Clique em "Iniciar Calibrar" para alinhar o modelo';
return (
<group>
{/* Coluna da esquerda: Informações e Fluxo */}
<Text position={[-0.24, 0.11, 0]} fontSize={0.012} color="#3b82f6" anchorX="left" anchorY="middle" fontStyle="bold">
Calibração 3D da Peça
</Text>
<Text position={[-0.24, 0.07, 0]} fontSize={0.008} color="#cbd5e1" anchorX="left" anchorY="top" maxWidth={0.28}>
Alinhe os eixos intrínsecos do modelo 3D com a estrutura física correspondente no ambiente.
</Text>
{/* Caixa de status do passo atual */}
<group position={[-0.24, 0.00, 0]}>
<mesh position={[0.13, 0, -0.0005]}>
<planeGeometry args={[0.26, 0.05]} />
<meshBasicMaterial color="#111827" transparent opacity={0.65} />
</mesh>
<Text position={[0.01, 0, 0.001]} fontSize={0.0085} color={isDone ? '#22c55e' : isCalibrating ? '#f59e0b' : '#ffffff'} anchorX="left" anchorY="middle" maxWidth={0.24}>
{currentHint}
</Text>
{isDone && Number.isFinite(xrCalibration.verifyErrorDeg) && (
<Text position={[0.01, -0.016, 0.001]} fontSize={0.007} color="#22c55e" anchorX="left" anchorY="middle">
Erro residual calculado: {xrCalibration.verifyErrorDeg.toFixed(1)}°
</Text>
)}
</group>
{/* Botões na parte inferior esquerda */}
<group position={[-0.24, -0.06, 0]}>
<XR3DButton
position={[0.06, 0, 0]}
size={[0.11, 0.026]}
label={isDone ? 'Concluído' : isCalibrating ? 'Cancelar' : 'Iniciar Calibrar'}
active={isCalibrating || isDone}
color={isDone ? '#22c55e' : isCalibrating ? '#d97706' : '#3b82f6'}
onClick={onClickCalibrar}
fontSize={0.008}
/>
{active?.calibrationQuat && !isCalibrating && (
<XR3DButton
position={[0.18, 0, 0]}
size={[0.09, 0.026]}
label="Remover Cal."
color="#dc2626"
onClick={onResetCalibration}
fontSize={0.008}
/>
)}
</group>
<Text position={[-0.24, -0.11, 0]} fontSize={0.007} color="#64748b" anchorX="left" maxWidth={0.28}>
{active ? `Peça ativa: ${active.fileName}` : 'Selecione uma peça na aba "Cena"'}
</Text>
{/* Coluna da direita: Cubo de views 3D interativo */}
<group position={[0.14, -0.01, 0.04]}>
<XRViewCube activeModelId={activeModelId} size={0.075} />
{/* Tripé dos eixos abaixo do cubo */}
<group position={[0, -0.06, -0.04]} scale={[0.035, 0.035, 0.035]}>
<arrowHelper args={[new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0), 0.7, 0xff4444, 0.18, 0.12]} />
<arrowHelper args={[new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), 0.7, 0x44ff66, 0.18, 0.12]} />
<arrowHelper args={[new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), 0.7, 0x4488ff, 0.18, 0.12]} />
</group>
<Text position={[0, 0.065, -0.04]} fontSize={0.008} color="#cbd5e1" anchorX="center" anchorY="middle">
Cubo de Calibração
</Text>
</group>
</group>
);
}