🚀 Auto-deploy: melhoria no snap e medição AR em 24/05/2026 18:28:15
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useModelStore } from '@/stores/useModelStore';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RotateCw, RefreshCw, Move, HelpCircle } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function ViewCube() {
|
||||
const { activeModelId, models, setFineTuning } = useModelStore();
|
||||
const activeModel = models.find((m) => m.id === activeModelId);
|
||||
|
||||
// Estados locais para controlar uma rotação interativa ou visualização do cubo
|
||||
const [hoveredFace, setHoveredFace] = useState<string | null>(null);
|
||||
|
||||
if (!activeModelId || !activeModel) return null;
|
||||
|
||||
const alignToFace = (face: string) => {
|
||||
let rotX = 0;
|
||||
let rotY = 0;
|
||||
let rotZ = 0;
|
||||
|
||||
switch (face) {
|
||||
case 'TOPO':
|
||||
rotX = 0;
|
||||
rotY = 0;
|
||||
rotZ = 0;
|
||||
break;
|
||||
case 'FRENTE':
|
||||
rotX = 90;
|
||||
rotY = 0;
|
||||
rotZ = 0;
|
||||
break;
|
||||
case 'DIREITA':
|
||||
rotX = 90;
|
||||
rotY = 0;
|
||||
rotZ = -90;
|
||||
break;
|
||||
case 'ESQUERDA':
|
||||
rotX = 90;
|
||||
rotY = 0;
|
||||
rotZ = 90;
|
||||
break;
|
||||
case 'ATRÁS':
|
||||
rotX = -90;
|
||||
rotY = 180;
|
||||
rotZ = 0;
|
||||
break;
|
||||
case 'BASE':
|
||||
rotX = 180;
|
||||
rotY = 0;
|
||||
rotZ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
setFineTuning({ rotX, rotY, rotZ });
|
||||
toast.success(`Alinhado para vista ${face}`);
|
||||
};
|
||||
|
||||
// Ajusta a rotação para o múltiplo de 90 graus mais próximo
|
||||
const snapToNearest90 = () => {
|
||||
const ft = activeModel.fineTuning;
|
||||
const rotX = Math.round(ft.rotX / 90) * 90;
|
||||
const rotY = Math.round(ft.rotY / 90) * 90;
|
||||
const rotZ = Math.round(ft.rotZ / 90) * 90;
|
||||
setFineTuning({ rotX, rotY, rotZ });
|
||||
toast.success(`Snap Ortogonal (90°): [${rotX}°, ${rotY}°, ${rotZ}°]`);
|
||||
};
|
||||
|
||||
const resetAllTransforms = () => {
|
||||
setFineTuning({ posX: 0, posY: 0, posZ: 0, rotX: 0, rotY: 0, rotZ: 0, scale: 1 });
|
||||
toast.success('Posição e rotação restauradas para a origem');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="absolute top-4 right-4 z-20 flex flex-col items-center gap-3 rounded-xl border border-white/10 bg-black/50 p-4 shadow-2xl backdrop-blur-md select-none text-white w-40">
|
||||
{/* Título do Widget */}
|
||||
<span className="font-mono text-[9px] font-semibold text-muted-foreground uppercase tracking-widest text-center">
|
||||
Alinhamento 3D
|
||||
</span>
|
||||
|
||||
{/* Cubo 3D Visual */}
|
||||
<div className="relative h-20 w-20 flex items-center justify-center my-2 group">
|
||||
{/* Container do Cubo Isométrico */}
|
||||
<div
|
||||
className="h-14 w-14 transition-transform duration-500 ease-out"
|
||||
style={{
|
||||
transformStyle: 'preserve-3d',
|
||||
transform: 'rotateX(-30deg) rotateY(45deg)',
|
||||
}}
|
||||
>
|
||||
{/* TOPO (Z+) */}
|
||||
<div
|
||||
onClick={() => alignToFace('TOPO')}
|
||||
onMouseEnter={() => setHoveredFace('TOPO')}
|
||||
onMouseLeave={() => setHoveredFace(null)}
|
||||
className={`absolute h-14 w-14 border border-white/20 flex items-center justify-center font-mono text-[9px] font-bold cursor-pointer transition-all duration-200 ${
|
||||
hoveredFace === 'TOPO' ? 'bg-primary/40 border-primary text-white shadow-[0_0_10px_rgba(59,130,246,0.5)]' : 'bg-zinc-800/80 text-zinc-300'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'rotateX(90deg) translateZ(28px)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
TOPO
|
||||
</div>
|
||||
|
||||
{/* FRENTE (Y-) */}
|
||||
<div
|
||||
onClick={() => alignToFace('FRENTE')}
|
||||
onMouseEnter={() => setHoveredFace('FRENTE')}
|
||||
onMouseLeave={() => setHoveredFace(null)}
|
||||
className={`absolute h-14 w-14 border border-white/20 flex items-center justify-center font-mono text-[9px] font-bold cursor-pointer transition-all duration-200 ${
|
||||
hoveredFace === 'FRENTE' ? 'bg-primary/40 border-primary text-white shadow-[0_0_10px_rgba(59,130,246,0.5)]' : 'bg-zinc-700/80 text-zinc-300'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'translateZ(28px)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
FRENTE
|
||||
</div>
|
||||
|
||||
{/* DIREITA (X+) */}
|
||||
<div
|
||||
onClick={() => alignToFace('DIREITA')}
|
||||
onMouseEnter={() => setHoveredFace('DIREITA')}
|
||||
onMouseLeave={() => setHoveredFace(null)}
|
||||
className={`absolute h-14 w-14 border border-white/20 flex items-center justify-center font-mono text-[9px] font-bold cursor-pointer transition-all duration-200 ${
|
||||
hoveredFace === 'DIREITA' ? 'bg-primary/40 border-primary text-white shadow-[0_0_10px_rgba(59,130,246,0.5)]' : 'bg-zinc-850/80 text-zinc-300'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'rotateY(90deg) translateZ(28px)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
DIR
|
||||
</div>
|
||||
|
||||
{/* ESQUERDA (X-) */}
|
||||
<div
|
||||
onClick={() => alignToFace('ESQUERDA')}
|
||||
onMouseEnter={() => setHoveredFace('ESQUERDA')}
|
||||
onMouseLeave={() => setHoveredFace(null)}
|
||||
className={`absolute h-14 w-14 border border-white/20 flex items-center justify-center font-mono text-[9px] font-bold cursor-pointer transition-all duration-200 ${
|
||||
hoveredFace === 'ESQUERDA' ? 'bg-primary/40 border-primary text-white shadow-[0_0_10px_rgba(59,130,246,0.5)]' : 'bg-zinc-850/80 text-zinc-300'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'rotateY(-90deg) translateZ(28px)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
ESQ
|
||||
</div>
|
||||
|
||||
{/* ATRÁS (Y+) */}
|
||||
<div
|
||||
onClick={() => alignToFace('ATRÁS')}
|
||||
onMouseEnter={() => setHoveredFace('ATRÁS')}
|
||||
onMouseLeave={() => setHoveredFace(null)}
|
||||
className={`absolute h-14 w-14 border border-white/20 flex items-center justify-center font-mono text-[9px] font-bold cursor-pointer transition-all duration-200 ${
|
||||
hoveredFace === 'ATRÁS' ? 'bg-primary/40 border-primary text-white shadow-[0_0_10px_rgba(59,130,246,0.5)]' : 'bg-zinc-750/80 text-zinc-300'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'rotateY(180deg) translateZ(28px)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
ATRÁS
|
||||
</div>
|
||||
|
||||
{/* BASE (Z-) */}
|
||||
<div
|
||||
onClick={() => alignToFace('BASE')}
|
||||
onMouseEnter={() => setHoveredFace('BASE')}
|
||||
onMouseLeave={() => setHoveredFace(null)}
|
||||
className={`absolute h-14 w-14 border border-white/20 flex items-center justify-center font-mono text-[9px] font-bold cursor-pointer transition-all duration-200 ${
|
||||
hoveredFace === 'BASE' ? 'bg-primary/40 border-primary text-white shadow-[0_0_10px_rgba(59,130,246,0.5)]' : 'bg-zinc-900/80 text-zinc-300'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'rotateX(-90deg) translateZ(28px)',
|
||||
backfaceVisibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
BASE
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botões Rápidos de Alinhamento */}
|
||||
<div className="flex flex-col gap-1.5 w-full mt-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full text-[10px] h-7 gap-1 bg-white/5 border-white/10 hover:bg-white/10 hover:text-white"
|
||||
onClick={snapToNearest90}
|
||||
title="Corrige a orientação aproximando para o múltiplo de 90° mais próximo"
|
||||
>
|
||||
<RotateCw className="h-3 w-3 text-primary animate-pulse" />
|
||||
<span>Alinhar 90° (Snap)</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full text-[10px] h-7 gap-1 bg-white/5 border-white/10 hover:bg-white/10 hover:text-white"
|
||||
onClick={resetAllTransforms}
|
||||
title="Zera a posição e a rotação da peça atual"
|
||||
>
|
||||
<RefreshCw className="h-3 w-3 text-destructive" />
|
||||
<span>Resetar Origem</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Info compacta */}
|
||||
<div className="flex items-center gap-1.5 font-mono text-[8px] text-muted-foreground mt-0.5 select-none justify-center">
|
||||
<span>X: {Math.round(activeModel.fineTuning.rotX)}°</span>
|
||||
<span>Y: {Math.round(activeModel.fineTuning.rotY)}°</span>
|
||||
<span>Z: {Math.round(activeModel.fineTuning.rotZ)}°</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user