🚀 Auto-deploy: melhoria no snap e medição AR em 28/05/2026 21:16:09
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user