🚀 Auto-deploy: melhoria no snap e medição AR em 24/05/2026 23:31:10
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useRef, useState, forwardRef } from 'react';
|
||||
import { useRef, useState, useEffect, forwardRef } from 'react';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useXRInputSourceState, useXR } from '@react-three/xr';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
useCanvasRecorder, startRecording, pauseRecording, resumeRecording,
|
||||
stopRecording, captureScreenshot, formatElapsed,
|
||||
} from '@/hooks/useCanvasRecorder';
|
||||
import { getAllModelLocalGroups } from '@/lib/modelTransforms';
|
||||
|
||||
type Tab = 'scene' | 'tools' | 'inspection' | 'share' | 'capture' | 'webxr';
|
||||
type Tab = 'scene' | 'tools' | 'cuts' | 'inspection' | 'share' | 'capture' | 'webxr';
|
||||
|
||||
interface XRHudInWorldProps {
|
||||
freeMove: boolean;
|
||||
@@ -224,12 +225,13 @@ function FloatingPanel({
|
||||
const tabs: { id: Tab; label: string; icon: string }[] = [
|
||||
{ id: 'scene', label: 'Cena', icon: '🧩' },
|
||||
{ id: 'tools', label: 'Ferram.', icon: '🛠' },
|
||||
{ id: 'cuts', label: 'Cortes', icon: '✂' },
|
||||
{ id: 'inspection', label: 'Inspeção', icon: '✓' },
|
||||
{ id: 'capture', label: 'Captura', icon: '🎥' },
|
||||
{ id: 'share', label: 'Compart.', icon: '📡' },
|
||||
{ id: 'webxr', label: 'WebXR', icon: '⚙' },
|
||||
];
|
||||
const W = 0.5, H = 0.36;
|
||||
const W = 0.54, H = 0.38;
|
||||
return (
|
||||
<XR3DPanel size={[W, H]}>
|
||||
<Text position={[-W / 2 + 0.012, H / 2 - 0.018, 0.002]} fontSize={0.011} color="#3b82f6"
|
||||
@@ -255,15 +257,16 @@ function FloatingPanel({
|
||||
<group position={[0, H / 2 - 0.045, 0.001]}>
|
||||
{tabs.map((t, i) => (
|
||||
<XR3DButton key={t.id}
|
||||
position={[-W / 2 + 0.04 + i * 0.078, 0, 0]} size={[0.072, 0.022]}
|
||||
position={[-W / 2 + 0.038 + i * 0.072, 0, 0]} size={[0.066, 0.022]}
|
||||
label={t.label} icon={t.icon} active={tab === t.id}
|
||||
onClick={() => setTab(t.id)} fontSize={0.0072} />
|
||||
onClick={() => setTab(t.id)} fontSize={0.0068} />
|
||||
))}
|
||||
</group>
|
||||
|
||||
<group position={[0, -0.012, 0.001]}>
|
||||
<group position={[0, -0.018, 0.001]}>
|
||||
{tab === 'scene' && <SceneTab />}
|
||||
{tab === 'tools' && <ToolsTab {...p} />}
|
||||
{tab === 'cuts' && <CutsTab />}
|
||||
{tab === 'inspection' && <InspectionTab />}
|
||||
{tab === 'capture' && <CaptureTab />}
|
||||
{tab === 'share' && <ShareTab {...p} />}
|
||||
@@ -273,6 +276,164 @@ function FloatingPanel({
|
||||
);
|
||||
}
|
||||
|
||||
interface AxisRange { min: number; max: number; }
|
||||
type Axis = 'x' | 'y' | 'z';
|
||||
|
||||
function computeBoundsByAxis(): Record<Axis, AxisRange> | null {
|
||||
const groups = getAllModelLocalGroups();
|
||||
if (groups.length === 0) return null;
|
||||
const box = new THREE.Box3();
|
||||
let has = false;
|
||||
for (const g of groups) {
|
||||
g.updateWorldMatrix(true, true);
|
||||
const b = new THREE.Box3().setFromObject(g);
|
||||
if (Number.isFinite(b.min.x) && Number.isFinite(b.max.x)) {
|
||||
if (!has) { box.copy(b); has = true; } else box.union(b);
|
||||
}
|
||||
}
|
||||
if (!has) return null;
|
||||
return {
|
||||
x: { min: box.min.x, max: box.max.x },
|
||||
y: { min: box.min.y, max: box.max.y },
|
||||
z: { min: box.min.z, max: box.max.z },
|
||||
};
|
||||
}
|
||||
|
||||
const AXIS_COLOR: Record<Axis, string> = {
|
||||
x: '#ef4444',
|
||||
y: '#10b981',
|
||||
z: '#0ea5e9',
|
||||
};
|
||||
|
||||
const AXIS_LABEL: Record<Axis, string> = { x: 'X', y: 'Y', z: 'Z' };
|
||||
|
||||
function CutsTab() {
|
||||
const enabled = useModelStore((s) => s.sectionEnabled);
|
||||
const invert = useModelStore((s) => s.sectionInvert);
|
||||
const level = useModelStore((s) => s.sectionLevel);
|
||||
const setSectionEnabled = useModelStore((s) => s.setSectionEnabled);
|
||||
const setSectionInvert = useModelStore((s) => s.setSectionInvert);
|
||||
const setSectionLevel = useModelStore((s) => s.setSectionLevel);
|
||||
const activeModelId = useModelStore((s) => s.activeModelId);
|
||||
|
||||
const [bounds, setBounds] = useState<Record<Axis, AxisRange> | null>(null);
|
||||
const [step, setStep] = useState<number>(0.01); // 10mm por padrão
|
||||
|
||||
useEffect(() => {
|
||||
setBounds(computeBoundsByAxis());
|
||||
}, [activeModelId]);
|
||||
|
||||
const handleStepChange = (sVal: number) => {
|
||||
setStep(sVal);
|
||||
};
|
||||
|
||||
const handleAdjust = (axis: Axis, dir: 1 | -1) => {
|
||||
const range = bounds?.[axis];
|
||||
if (!range) return;
|
||||
const currentVal = level[axis];
|
||||
const newVal = Math.max(range.min, Math.min(range.max, currentVal + dir * step));
|
||||
setSectionLevel(axis, newVal);
|
||||
};
|
||||
|
||||
const handleSetPreset = (axis: Axis, type: 'min' | 'center' | 'max') => {
|
||||
const range = bounds?.[axis];
|
||||
if (!range) return;
|
||||
if (type === 'min') {
|
||||
setSectionLevel(axis, range.min);
|
||||
} else if (type === 'center') {
|
||||
setSectionLevel(axis, (range.min + range.max) / 2);
|
||||
} else if (type === 'max') {
|
||||
setSectionLevel(axis, range.max);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecalculate = () => {
|
||||
setBounds(computeBoundsByAxis());
|
||||
};
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* Cabeçalho / Passo */}
|
||||
<Text position={[-0.24, 0.115, 0]} fontSize={0.0085} color="#94a3b8" anchorX="left" anchorY="middle">
|
||||
Passo do Ajuste:
|
||||
</Text>
|
||||
<XR3DButton position={[-0.09, 0.115, 0]} size={[0.048, 0.02]}
|
||||
label="1 mm" active={step === 0.001} onClick={() => handleStepChange(0.001)} fontSize={0.007} />
|
||||
<XR3DButton position={[-0.038, 0.115, 0]} size={[0.048, 0.02]}
|
||||
label="10 mm" active={step === 0.01} onClick={() => handleStepChange(0.01)} fontSize={0.007} />
|
||||
<XR3DButton position={[0.014, 0.115, 0]} size={[0.048, 0.02]}
|
||||
label="50 mm" active={step === 0.05} onClick={() => handleStepChange(0.05)} fontSize={0.007} />
|
||||
|
||||
<XR3DButton position={[0.165, 0.115, 0]} size={[0.12, 0.02]}
|
||||
label="↺ Recalcular Limites" active={false} onClick={handleRecalculate} fontSize={0.007} />
|
||||
|
||||
{/* Controles dos Eixos */}
|
||||
{(['x', 'y', 'z'] as Axis[]).map((axis, idx) => {
|
||||
const yOffset = 0.055 - idx * 0.055;
|
||||
const range = bounds?.[axis];
|
||||
const minMM = range ? range.min * 1000 : 0;
|
||||
const maxMM = range ? range.max * 1000 : 100;
|
||||
const valMM = level[axis] * 1000;
|
||||
const isAxisEnabled = enabled[axis];
|
||||
const isAxisInverted = invert[axis];
|
||||
const isAxisDisabled = !range;
|
||||
|
||||
return (
|
||||
<group key={axis} position={[0, yOffset, 0]}>
|
||||
{/* Eixo label */}
|
||||
<Text position={[-0.24, 0, 0]} fontSize={0.014} color={AXIS_COLOR[axis]} anchorX="left" anchorY="middle" fontStyle="bold">
|
||||
{AXIS_LABEL[axis]}
|
||||
</Text>
|
||||
|
||||
{/* Toggle Cortar */}
|
||||
<XR3DButton position={[-0.19, 0, 0]} size={[0.045, 0.022]}
|
||||
label={isAxisEnabled ? "ON" : "OFF"} active={isAxisEnabled}
|
||||
disabled={isAxisDisabled}
|
||||
onClick={() => setSectionEnabled(axis, !isAxisEnabled)}
|
||||
fontSize={0.0075} />
|
||||
|
||||
{/* Toggle Inverter */}
|
||||
<XR3DButton position={[-0.135, 0, 0]} size={[0.052, 0.022]}
|
||||
label={isAxisInverted ? "Invertido" : "Inverter"} active={isAxisInverted}
|
||||
disabled={isAxisDisabled || !isAxisEnabled}
|
||||
onClick={() => setSectionInvert(axis, !isAxisInverted)}
|
||||
fontSize={0.007} />
|
||||
|
||||
{/* Valor */}
|
||||
<Text position={[-0.09, 0, 0]} fontSize={0.0085} color={isAxisEnabled ? "#ffffff" : "#64748b"} anchorX="left" anchorY="middle">
|
||||
{isAxisDisabled ? "sem peça" : `${valMM.toFixed(1)} mm`}
|
||||
</Text>
|
||||
|
||||
{/* Menos/Mais */}
|
||||
<XR3DButton position={[0.01, 0, 0]} size={[0.024, 0.022]}
|
||||
label="−" disabled={isAxisDisabled || !isAxisEnabled}
|
||||
onClick={() => handleAdjust(axis, -1)}
|
||||
fontSize={0.01} />
|
||||
<XR3DButton position={[0.038, 0, 0]} size={[0.024, 0.022]}
|
||||
label="+" disabled={isAxisDisabled || !isAxisEnabled}
|
||||
onClick={() => handleAdjust(axis, 1)}
|
||||
fontSize={0.01} />
|
||||
|
||||
{/* Presets */}
|
||||
<XR3DButton position={[0.076, 0, 0]} size={[0.034, 0.022]}
|
||||
label="Min" disabled={isAxisDisabled || !isAxisEnabled}
|
||||
onClick={() => handleSetPreset(axis, 'min')}
|
||||
fontSize={0.007} />
|
||||
<XR3DButton position={[0.118, 0, 0]} size={[0.042, 0.022]}
|
||||
label="Centro" disabled={isAxisDisabled || !isAxisEnabled}
|
||||
onClick={() => handleSetPreset(axis, 'center')}
|
||||
fontSize={0.007} />
|
||||
<XR3DButton position={[0.162, 0, 0]} size={[0.036, 0.022]}
|
||||
label="Max" disabled={isAxisDisabled || !isAxisEnabled}
|
||||
onClick={() => handleSetPreset(axis, 'max')}
|
||||
fontSize={0.007} />
|
||||
</group>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function SceneTab() {
|
||||
const models = useModelStore((s) => s.models);
|
||||
const activeId = useModelStore((s) => s.activeModelId);
|
||||
|
||||
Reference in New Issue
Block a user