🚀 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -363,75 +363,125 @@ function ToolsTab(p: XRHudInWorldProps) {
|
|||||||
const resetScale = useModelStore((s) => s.resetScale);
|
const resetScale = useModelStore((s) => s.resetScale);
|
||||||
const clearMeasurements = useModelStore((s) => s.clearMeasurements);
|
const clearMeasurements = useModelStore((s) => s.clearMeasurements);
|
||||||
|
|
||||||
|
const activeId = useModelStore((s) => s.activeModelId);
|
||||||
|
const models = useModelStore((s) => s.models);
|
||||||
|
const activeModel = models.find((m) => m.id === activeId);
|
||||||
|
const setFineTuning = useModelStore((s) => s.setFineTuning);
|
||||||
|
|
||||||
|
const alignToFace = (face: string) => {
|
||||||
|
let rotX = 0, rotY = 0, rotZ = 0;
|
||||||
|
if (face === 'FRENTE') { rotX = 90; }
|
||||||
|
else if (face === 'LADO') { rotX = 90; rotY = 90; }
|
||||||
|
setFineTuning({ rotX, rotY, rotZ });
|
||||||
|
};
|
||||||
|
|
||||||
|
const snapTo90 = () => {
|
||||||
|
if (!activeModel) return;
|
||||||
|
const ft = activeModel.fineTuning;
|
||||||
|
setFineTuning({
|
||||||
|
rotX: Math.round(ft.rotX / 90) * 90,
|
||||||
|
rotY: Math.round(ft.rotY / 90) * 90,
|
||||||
|
rotZ: Math.round(ft.rotZ / 90) * 90,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetRot = () => {
|
||||||
|
setFineTuning({ rotX: 0, rotY: 0, rotZ: 0 });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Render</Text>
|
{/* Fileira 1: Render */}
|
||||||
<XR3DButton position={[-0.16, 0.085, 0]} size={[0.075, 0.022]} label="Sólido"
|
<Text position={[-0.24, 0.125, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Render</Text>
|
||||||
|
<XR3DButton position={[-0.16, 0.105, 0]} size={[0.075, 0.022]} label="Sólido"
|
||||||
active={renderMode === 'solid'} onClick={() => setRenderMode('solid')} />
|
active={renderMode === 'solid'} onClick={() => setRenderMode('solid')} />
|
||||||
<XR3DButton position={[-0.08, 0.085, 0]} size={[0.075, 0.022]} label="Bordas"
|
<XR3DButton position={[-0.08, 0.105, 0]} size={[0.075, 0.022]} label="Bordas"
|
||||||
active={renderMode === 'edges'} onClick={() => setRenderMode('edges')} />
|
active={renderMode === 'edges'} onClick={() => setRenderMode('edges')} />
|
||||||
|
|
||||||
<Text position={[-0.24, 0.05, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Toggles</Text>
|
{/* Fileira 2: Toggles */}
|
||||||
<XR3DButton position={[-0.16, 0.025, 0]} size={[0.075, 0.022]} label={`Grid ${showGrid ? 'ON' : 'OFF'}`}
|
<Text position={[-0.24, 0.075, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Toggles</Text>
|
||||||
|
<XR3DButton position={[-0.16, 0.055, 0]} size={[0.075, 0.022]} label={`Grid ${showGrid ? 'ON' : 'OFF'}`}
|
||||||
active={showGrid} onClick={() => setShowGrid(!showGrid)} />
|
active={showGrid} onClick={() => setShowGrid(!showGrid)} />
|
||||||
<XR3DButton position={[-0.08, 0.025, 0]} size={[0.075, 0.022]} label={`Medir ${measureMode ? 'ON' : 'OFF'}`}
|
<XR3DButton position={[-0.08, 0.055, 0]} size={[0.075, 0.022]} label={`Medir ${measureMode ? 'ON' : 'OFF'}`}
|
||||||
active={measureMode} onClick={() => setMeasureMode(!measureMode)} />
|
active={measureMode} onClick={() => setMeasureMode(!measureMode)} />
|
||||||
<XR3DButton position={[0.0, 0.025, 0]} size={[0.075, 0.022]} label={`Snap ${p.snapToPlanes ? 'ON' : 'OFF'}`}
|
<XR3DButton position={[0.0, 0.055, 0]} size={[0.075, 0.022]} label={`Snap ${p.snapToPlanes ? 'ON' : 'OFF'}`}
|
||||||
active={p.snapToPlanes} onClick={p.onToggleSnap} />
|
active={p.snapToPlanes} onClick={p.onToggleSnap} />
|
||||||
<XR3DButton position={[0.08, 0.025, 0]} size={[0.075, 0.022]}
|
<XR3DButton position={[0.08, 0.055, 0]} size={[0.075, 0.022]}
|
||||||
label={p.placementMode ? 'Posic…' : 'Reposic.'}
|
label={p.placementMode ? 'Posic…' : 'Reposic.'}
|
||||||
active={p.placementMode} onClick={p.onTogglePlacement} />
|
active={p.placementMode} onClick={p.onTogglePlacement} />
|
||||||
<XR3DButton position={[0.16, 0.025, 0]} size={[0.075, 0.022]}
|
<XR3DButton position={[0.16, 0.055, 0]} size={[0.075, 0.022]}
|
||||||
label={`Zoom ${p.allowScale ? 'ON' : 'OFF'}`}
|
label={`Zoom ${p.allowScale ? 'ON' : 'OFF'}`}
|
||||||
active={p.allowScale} onClick={p.onToggleAllowScale} />
|
active={p.allowScale} onClick={p.onToggleAllowScale} />
|
||||||
|
|
||||||
{/* Selecionar — paridade com a tela inicial */}
|
{/* Fileira 3: Selecionar */}
|
||||||
<Text position={[-0.24, -0.005, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Selecionar</Text>
|
<Text position={[-0.24, 0.025, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Selecionar</Text>
|
||||||
<XR3DButton position={[-0.16, -0.03, 0]} size={[0.075, 0.022]}
|
<XR3DButton position={[-0.16, 0.005, 0]} size={[0.075, 0.022]}
|
||||||
label={selectionMode ? (selectedCount > 0 ? `${selectedCount} sel.` : 'Selec…') : 'Selec'}
|
label={selectionMode ? (selectedCount > 0 ? `${selectedCount} sel.` : 'Selec…') : 'Selec'}
|
||||||
active={selectionMode} onClick={() => setSelectionMode(!selectionMode)} />
|
active={selectionMode} onClick={() => setSelectionMode(!selectionMode)} />
|
||||||
<XR3DButton position={[-0.08, -0.03, 0]} size={[0.075, 0.022]} label="Esconder"
|
<XR3DButton position={[-0.08, 0.005, 0]} size={[0.075, 0.022]} label="Esconder"
|
||||||
onClick={hideSelectedElements} />
|
onClick={hideSelectedElements} />
|
||||||
<XR3DButton position={[0.0, -0.03, 0]} size={[0.075, 0.022]} label="Isolar"
|
<XR3DButton position={[0.0, 0.005, 0]} size={[0.075, 0.022]} label="Isolar"
|
||||||
onClick={isolateSelectedElements} />
|
onClick={isolateSelectedElements} />
|
||||||
<XR3DButton position={[0.08, -0.03, 0]} size={[0.075, 0.022]} label="Mostrar"
|
<XR3DButton position={[0.08, 0.005, 0]} size={[0.075, 0.022]} label="Mostrar"
|
||||||
active={hasHidden} onClick={showAllElements} />
|
active={hasHidden} onClick={showAllElements} />
|
||||||
|
|
||||||
<Text position={[-0.24, -0.06, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
{/* Fileira 4: Opacidade */}
|
||||||
|
<Text position={[-0.24, -0.025, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
Opacidade {Math.round(opacity * 100)}%
|
Opacidade {Math.round(opacity * 100)}%
|
||||||
</Text>
|
</Text>
|
||||||
<XR3DButton position={[-0.16, -0.085, 0]} size={[0.05, 0.022]} label="−25%"
|
<XR3DButton position={[-0.16, -0.045, 0]} size={[0.05, 0.022]} label="−25%"
|
||||||
onClick={() => setOpacity(Math.max(0, opacity - 0.25))} />
|
onClick={() => setOpacity(Math.max(0, opacity - 0.25))} />
|
||||||
<XR3DButton position={[-0.105, -0.085, 0]} size={[0.05, 0.022]} label="−5%"
|
<XR3DButton position={[-0.105, -0.045, 0]} size={[0.05, 0.022]} label="−5%"
|
||||||
onClick={() => setOpacity(Math.max(0, opacity - 0.05))} />
|
onClick={() => setOpacity(Math.max(0, opacity - 0.05))} />
|
||||||
<XR3DButton position={[-0.05, -0.085, 0]} size={[0.05, 0.022]} label="+5%"
|
<XR3DButton position={[-0.05, -0.045, 0]} size={[0.05, 0.022]} label="+5%"
|
||||||
onClick={() => setOpacity(Math.min(1, opacity + 0.05))} />
|
onClick={() => setOpacity(Math.min(1, opacity + 0.05))} />
|
||||||
<XR3DButton position={[0.005, -0.085, 0]} size={[0.05, 0.022]} label="+25%"
|
<XR3DButton position={[0.005, -0.045, 0]} size={[0.05, 0.022]} label="+25%"
|
||||||
onClick={() => setOpacity(Math.min(1, opacity + 0.25))} />
|
onClick={() => setOpacity(Math.min(1, opacity + 0.25))} />
|
||||||
<XR3DButton position={[0.06, -0.085, 0]} size={[0.05, 0.022]} label="100%"
|
<XR3DButton position={[0.06, -0.045, 0]} size={[0.05, 0.022]} label="100%"
|
||||||
onClick={() => setOpacity(1)} />
|
onClick={() => setOpacity(1)} />
|
||||||
|
|
||||||
<Text position={[-0.24, -0.115, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
{/* Fileira 5: Escala */}
|
||||||
|
<Text position={[-0.24, -0.075, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
Escala {scaleRatio.label}
|
Escala {scaleRatio.label}
|
||||||
</Text>
|
</Text>
|
||||||
{SCALE_LABELS.map((lbl, i) => {
|
{SCALE_LABELS.map((lbl, i) => {
|
||||||
const preset = SCALE_PRESETS.find((sp) => sp.label === lbl)!;
|
const preset = SCALE_PRESETS.find((sp) => sp.label === lbl)!;
|
||||||
return (
|
return (
|
||||||
<XR3DButton key={lbl}
|
<XR3DButton key={lbl}
|
||||||
position={[-0.16 + i * 0.06, -0.14, 0]} size={[0.055, 0.022]} label={lbl}
|
position={[-0.16 + i * 0.06, -0.095, 0]} size={[0.055, 0.022]} label={lbl}
|
||||||
active={scaleRatio.label === lbl}
|
active={scaleRatio.label === lbl}
|
||||||
onClick={() => setScaleRatio(preset)} />
|
onClick={() => setScaleRatio(preset)} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<XR3DButton position={[0.10, -0.14, 0]} size={[0.062, 0.022]}
|
<XR3DButton position={[0.10, -0.095, 0]} size={[0.062, 0.022]}
|
||||||
label="↺ Reset" color="#dc2626"
|
label="↺ Reset" color="#dc2626"
|
||||||
onClick={resetScale} fontSize={0.0085} />
|
onClick={resetScale} fontSize={0.0085} />
|
||||||
<XR3DButton position={[0.17, -0.14, 0]} size={[0.062, 0.022]}
|
<XR3DButton position={[0.17, -0.095, 0]} size={[0.062, 0.022]}
|
||||||
label="✕ Medidas" color="#dc2626"
|
label="✕ Medidas" color="#dc2626"
|
||||||
onClick={clearMeasurements} fontSize={0.0085} />
|
onClick={clearMeasurements} fontSize={0.0085} />
|
||||||
|
|
||||||
|
{/* Fileira 6: Rotação / Alinhamento Ortogonal */}
|
||||||
|
<Text position={[-0.24, -0.125, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
|
Alinhar Rotação
|
||||||
|
</Text>
|
||||||
|
<XR3DButton position={[-0.16, -0.145, 0]} size={[0.075, 0.022]}
|
||||||
|
label="Snap 90°" active={false}
|
||||||
|
onClick={snapTo90} fontSize={0.0075} />
|
||||||
|
<XR3DButton position={[-0.08, -0.145, 0]} size={[0.05, 0.022]}
|
||||||
|
label="Topo" active={false}
|
||||||
|
onClick={() => alignToFace('TOPO')} fontSize={0.0075} />
|
||||||
|
<XR3DButton position={[-0.025, -0.145, 0]} size={[0.05, 0.022]}
|
||||||
|
label="Frente" active={false}
|
||||||
|
onClick={() => alignToFace('FRENTE')} fontSize={0.0075} />
|
||||||
|
<XR3DButton position={[0.03, -0.145, 0]} size={[0.05, 0.022]}
|
||||||
|
label="Lado" active={false}
|
||||||
|
onClick={() => alignToFace('LADO')} fontSize={0.0075} />
|
||||||
|
<XR3DButton position={[0.12, -0.145, 0]} size={[0.09, 0.022]}
|
||||||
|
label="Zerar Rot." color="#dc2626"
|
||||||
|
onClick={resetRot} fontSize={0.0075} />
|
||||||
|
|
||||||
{measureMode && (
|
{measureMode && (
|
||||||
<Text position={[-0.24, -0.17, 0]} fontSize={0.0065} color="#a3e635" anchorX="left" maxWidth={0.5}>
|
<Text position={[-0.24, -0.175, 0]} fontSize={0.0055} color="#a3e635" anchorX="left" maxWidth={0.5}>
|
||||||
Gatilho D: marcar · A: desfazer · B: limpar · Gatilho E: alternar snap
|
Gatilho D: marcar · A: desfazer · B: limpar · Gatilho E: alternar snap
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { ArrowLeft, Glasses, Box, Ruler, FileText, Upload, Loader2 } from "lucid
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useModelStore } from "@/stores/useModelStore";
|
import { useModelStore } from "@/stores/useModelStore";
|
||||||
import { ModelViewerCanvas } from "@/components/three/ModelViewer";
|
import { ModelViewerCanvas } from "@/components/three/ModelViewer";
|
||||||
import { xrStore } from "@/lib/xrStore";
|
|
||||||
import { ViewerControls } from "@/components/ViewerControls";
|
import { ViewerControls } from "@/components/ViewerControls";
|
||||||
|
import { ViewCube } from "@/components/ViewCube";
|
||||||
import { InspectionChecklist } from "@/components/InspectionChecklist";
|
import { InspectionChecklist } from "@/components/InspectionChecklist";
|
||||||
import { FineTuningControls } from "@/components/FineTuningControls";
|
import { FineTuningControls } from "@/components/FineTuningControls";
|
||||||
import { MeasurementsList } from "@/components/MeasurementsList";
|
import { MeasurementsList } from "@/components/MeasurementsList";
|
||||||
@@ -143,6 +143,7 @@ const Viewer = () => {
|
|||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<ModelViewerCanvas />
|
<ModelViewerCanvas />
|
||||||
<ViewerControls />
|
<ViewerControls />
|
||||||
|
<ViewCube />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Side panel */}
|
{/* Side panel */}
|
||||||
|
|||||||
Reference in New Issue
Block a user