Fine tuning controls added
Implemented the Fine Tuning system with X/Y/Z position steps of 1mm and Y rotation step of 0.1°, integrated into the 3D viewer. Added FineTuningControls component, wired to model store, updated ModelViewer to apply offsets and rotation, and inserted controls into the Viewer sidebar. X-Lovable-Edit-ID: edt-9f467b59-61f9-45df-9093-6790b21f6d85
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
import { Move, RotateCw, RefreshCw } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
|
|
||||||
|
const POSITION_STEP = 0.001; // 1mm
|
||||||
|
const ROTATION_STEP = 0.1; // 0.1°
|
||||||
|
|
||||||
|
function StepButton({ label, onClick }: { label: string; onClick: () => void }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 font-mono text-xs"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AxisControl({
|
||||||
|
axis,
|
||||||
|
value,
|
||||||
|
unit,
|
||||||
|
onIncrement,
|
||||||
|
onDecrement,
|
||||||
|
displayValue,
|
||||||
|
}: {
|
||||||
|
axis: string;
|
||||||
|
value: number;
|
||||||
|
unit: string;
|
||||||
|
onIncrement: () => void;
|
||||||
|
onDecrement: () => void;
|
||||||
|
displayValue: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="font-mono text-xs text-muted-foreground w-6">{axis}</span>
|
||||||
|
<StepButton label="−" onClick={onDecrement} />
|
||||||
|
<span className="font-mono text-xs text-foreground w-20 text-center">{displayValue}{unit}</span>
|
||||||
|
<StepButton label="+" onClick={onIncrement} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FineTuningControls() {
|
||||||
|
const { fineTuning, setFineTuning, resetFineTuning } = useModelStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-mono text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
||||||
|
Ajuste Fino
|
||||||
|
</h3>
|
||||||
|
<Button variant="ghost" size="sm" className="h-6 text-[10px] gap-1" onClick={resetFineTuning}>
|
||||||
|
<RefreshCw className="h-3 w-3" />
|
||||||
|
Resetar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Position */}
|
||||||
|
<div className="rounded-lg border bg-muted/30 p-3 space-y-2">
|
||||||
|
<div className="flex items-center gap-1.5 mb-2">
|
||||||
|
<Move className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono text-[10px] text-muted-foreground">Posição (±1mm)</span>
|
||||||
|
</div>
|
||||||
|
<AxisControl
|
||||||
|
axis="X"
|
||||||
|
value={fineTuning.posX}
|
||||||
|
unit="mm"
|
||||||
|
displayValue={(fineTuning.posX * 1000).toFixed(1)}
|
||||||
|
onIncrement={() => setFineTuning({ posX: fineTuning.posX + POSITION_STEP })}
|
||||||
|
onDecrement={() => setFineTuning({ posX: fineTuning.posX - POSITION_STEP })}
|
||||||
|
/>
|
||||||
|
<AxisControl
|
||||||
|
axis="Y"
|
||||||
|
value={fineTuning.posY}
|
||||||
|
unit="mm"
|
||||||
|
displayValue={(fineTuning.posY * 1000).toFixed(1)}
|
||||||
|
onIncrement={() => setFineTuning({ posY: fineTuning.posY + POSITION_STEP })}
|
||||||
|
onDecrement={() => setFineTuning({ posY: fineTuning.posY - POSITION_STEP })}
|
||||||
|
/>
|
||||||
|
<AxisControl
|
||||||
|
axis="Z"
|
||||||
|
value={fineTuning.posZ}
|
||||||
|
unit="mm"
|
||||||
|
displayValue={(fineTuning.posZ * 1000).toFixed(1)}
|
||||||
|
onIncrement={() => setFineTuning({ posZ: fineTuning.posZ + POSITION_STEP })}
|
||||||
|
onDecrement={() => setFineTuning({ posZ: fineTuning.posZ - POSITION_STEP })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rotation */}
|
||||||
|
<div className="rounded-lg border bg-muted/30 p-3 space-y-2">
|
||||||
|
<div className="flex items-center gap-1.5 mb-2">
|
||||||
|
<RotateCw className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono text-[10px] text-muted-foreground">Rotação Y (±0.1°)</span>
|
||||||
|
</div>
|
||||||
|
<AxisControl
|
||||||
|
axis="Y°"
|
||||||
|
value={fineTuning.rotY}
|
||||||
|
unit="°"
|
||||||
|
displayValue={fineTuning.rotY.toFixed(1)}
|
||||||
|
onIncrement={() => setFineTuning({ rotY: fineTuning.rotY + ROTATION_STEP })}
|
||||||
|
onDecrement={() => setFineTuning({ rotY: fineTuning.rotY - ROTATION_STEP })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border bg-muted/50 p-2">
|
||||||
|
<p className="font-mono text-[10px] text-muted-foreground">
|
||||||
|
No Quest 3: Grip + Joystick esq. = X/Y, Joystick dir. = Z/Rotação
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ function GLBModel({ url }: { url: string }) {
|
|||||||
const opacity = useModelStore((s) => s.opacity);
|
const opacity = useModelStore((s) => s.opacity);
|
||||||
const renderMode = useModelStore((s) => s.renderMode);
|
const renderMode = useModelStore((s) => s.renderMode);
|
||||||
const checklist = useModelStore((s) => s.checklist);
|
const checklist = useModelStore((s) => s.checklist);
|
||||||
|
const fineTuning = useModelStore((s) => s.fineTuning);
|
||||||
|
|
||||||
const modelInfo = useMemo(() => {
|
const modelInfo = useMemo(() => {
|
||||||
const box = new THREE.Box3().setFromObject(scene);
|
const box = new THREE.Box3().setFromObject(scene);
|
||||||
@@ -71,8 +72,18 @@ function GLBModel({ url }: { url: string }) {
|
|||||||
});
|
});
|
||||||
}, [scene, opacity, renderMode, checklist]);
|
}, [scene, opacity, renderMode, checklist]);
|
||||||
|
|
||||||
|
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={ref} position={[-modelInfo.center.x, -modelInfo.center.y, -modelInfo.center.z]}>
|
<group
|
||||||
|
ref={ref}
|
||||||
|
position={[
|
||||||
|
-modelInfo.center.x + fineTuning.posX,
|
||||||
|
-modelInfo.center.y + fineTuning.posY,
|
||||||
|
-modelInfo.center.z + fineTuning.posZ,
|
||||||
|
]}
|
||||||
|
rotation={[0, rotYRad, 0]}
|
||||||
|
>
|
||||||
<primitive object={scene} />
|
<primitive object={scene} />
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useModelStore } from '@/stores/useModelStore';
|
|||||||
import { ModelViewerCanvas } from '@/components/three/ModelViewer';
|
import { ModelViewerCanvas } from '@/components/three/ModelViewer';
|
||||||
import { ViewerControls } from '@/components/ViewerControls';
|
import { ViewerControls } from '@/components/ViewerControls';
|
||||||
import { InspectionChecklist } from '@/components/InspectionChecklist';
|
import { InspectionChecklist } from '@/components/InspectionChecklist';
|
||||||
|
import { FineTuningControls } from '@/components/FineTuningControls';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
@@ -84,6 +85,10 @@ const Viewer = () => {
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
|
<FineTuningControls />
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<InspectionChecklist />
|
<InspectionChecklist />
|
||||||
|
|
||||||
<div className="rounded-lg border bg-muted/50 p-3">
|
<div className="rounded-lg border bg-muted/50 p-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user