diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 8af14cd..1ad921e 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -469,6 +469,52 @@ function ModelMeasurements({ modelId }: { modelId: string }) { ); } +/** Renders snap indicator, hover tooltip, pending point and any *legacy* + * world-frame measurements that aren't attached to a specific model. */ +function HoverLabelOverlay({ hoverInfo }: { hoverInfo: any }) { + const visible = !!hoverInfo; + const position = hoverInfo?.position ?? [0, 0, 0]; + const text = hoverInfo + ? hoverInfo.type === 'hole' + ? `Ø ${hoverInfo.value.toFixed(1)}` + : `${hoverInfo.value.toFixed(1)} mm` + : ''; + const variant = hoverInfo?.type === 'hole' ? 'accent' : 'primary'; + + const dfDynamic = useLabelDistanceFactor(position); + + const borderClass = variant === 'success' + ? 'border-success/60' + : variant === 'accent' + ? 'border-amber-400/70' + : 'border-primary/60'; + const textClass = variant === 'success' + ? 'text-success' + : variant === 'accent' + ? 'text-amber-400' + : 'text-primary'; + + return ( + +
+ + {text} + +
+ + ); +} + /** Renders snap indicator, hover tooltip, pending point and any *legacy* * world-frame measurements that aren't attached to a specific model. */ function MeasurementOverlay() { @@ -486,16 +532,7 @@ function MeasurementOverlay() { )} {/* Hover auto-detect tooltip (works both inside and outside measure mode) */} - {hoverInfo && ( - - )} + {/* Pending first point (always in world coords) */} {measurePoints.length === 1 && ( @@ -1129,7 +1166,41 @@ function PositionDragHandler() { -function SceneInsideXR() { +interface SceneInsideXRProps { + placementMode: boolean; + setPlacementMode: (v: boolean) => void; + snapToPlanes: boolean; + setSnapToPlanes: (v: boolean) => void; + allowScale: boolean; + setAllowScale: (v: boolean) => void; + liveCode: string | null; + setLiveCode: (v: string | null) => void; + liveViewers: number; + setLiveViewers: (v: number) => void; + freeMove: boolean; + setFreeMove: (v: boolean) => void; + simXR: boolean; + setSimXR: (v: boolean) => void; + onPresentationChange: (v: boolean) => void; +} + +function SceneInsideXR({ + placementMode, + setPlacementMode, + snapToPlanes, + setSnapToPlanes, + allowScale, + setAllowScale, + liveCode, + setLiveCode, + liveViewers, + setLiveViewers, + freeMove, + setFreeMove, + simXR, + setSimXR, + onPresentationChange, +}: SceneInsideXRProps) { const isPresenting = useXR((state) => state.isPresenting); const positionMode = useModelStore((s) => s.positionMode); const activeId = useModelStore((s) => s.activeModelId); @@ -1137,21 +1208,14 @@ function SceneInsideXR() { const activeModel = models.find(m => m.id === activeId); const isActiveLocked = !!activeModel?.locked; - // Estados locais para a sessão de XR - const [freeMove, setFreeMove] = useState(true); - const [placementMode, setPlacementMode] = useState(true); - const [snapToPlanes, setSnapToPlanes] = useState(true); - const [allowScale, setAllowScale] = useState(true); - const [liveCode, setLiveCode] = useState(null); - const [liveViewers, setLiveViewers] = useState(0); - const rigRef = useRef(null); - - // DEVKIT: simXR forces in-XR rendering on desktop without a real WebXR session const devkit = useDevKit(); - const [simXR, setSimXR] = useState(false); const effectiveInXR = isPresenting || simXR; + useEffect(() => { + onPresentationChange(isPresenting); + }, [isPresenting, onPresentationChange]); + // Monitorar início e fim da sessão useEffect(() => { if (isPresenting) { @@ -1276,66 +1340,96 @@ function SceneInsideXR() { - - {/* Floating HUD overlay - visível apenas fora do headset passthrough */} - -
- setFreeMove(!freeMove)} - placementMode={placementMode} - onTogglePlacement={() => setPlacementMode(!placementMode)} - snapToPlanes={snapToPlanes} - onToggleSnap={() => setSnapToPlanes(!snapToPlanes)} - allowScale={allowScale} - onToggleAllowScale={() => setAllowScale(!allowScale)} - /> -
- - - {/* Compartilhar botões ocultos para WebRTC e HUD de fora */} - -
- setLiveCode(h?.code ?? null)} - onViewerCountChange={setLiveViewers} - /> -
- - - {/* DevPanel se devkit habilitado */} - {devkit && ( - -
- setSimXR((v) => !v)} /> -
- - )} ); } export function ModelViewerCanvas({ url }: ModelViewerProps) { + const [placementMode, setPlacementMode] = useState(true); + const [snapToPlanes, setSnapToPlanes] = useState(true); + const [allowScale, setAllowScale] = useState(true); + const [liveCode, setLiveCode] = useState(null); + const [liveViewers, setLiveViewers] = useState(0); + const [freeMove, setFreeMove] = useState(true); + const [simXR, setSimXR] = useState(false); + const [isPresenting, setIsPresenting] = useState(false); + + const devkit = useDevKit(); + const effectiveInXR = isPresenting || simXR; + return ( - { - if (useModelStore.getState().selectionMode) { - (e.target as HTMLCanvasElement).dispatchEvent(new CustomEvent('tsxr-selection-miss', { detail: e })); - } - }} - onCreated={({ gl }) => { - const isApple = /Mac|iPhone|iPad/.test(navigator.platform); - const maxRatio = isApple ? 2 : 1.5; - gl.setPixelRatio(Math.min(window.devicePixelRatio, maxRatio)); - }} - > - - - - +
+ { + if (useModelStore.getState().selectionMode) { + (e.target as HTMLCanvasElement).dispatchEvent(new CustomEvent('tsxr-selection-miss', { detail: e })); + } + }} + onCreated={({ gl }) => { + const isApple = /Mac|iPhone|iPad/.test(navigator.platform); + const maxRatio = isApple ? 2 : 1.5; + gl.setPixelRatio(Math.min(window.devicePixelRatio, maxRatio)); + }} + > + + + + + + {/* Floating HUD overlay - visível apenas fora do headset passthrough */} + {effectiveInXR && !isPresenting && ( +
+
+ setFreeMove(!freeMove)} + placementMode={placementMode} + onTogglePlacement={() => setPlacementMode(!placementMode)} + snapToPlanes={snapToPlanes} + onToggleSnap={() => setSnapToPlanes(!snapToPlanes)} + allowScale={allowScale} + onToggleAllowScale={() => setAllowScale(!allowScale)} + /> +
+
+ )} + + {/* Compartilhar botões ocultos para WebRTC e HUD de fora */} +
+ setLiveCode(h?.code ?? null)} + onViewerCountChange={setLiveViewers} + /> +
+ + {/* DevPanel se devkit habilitado */} + {devkit && ( +
+
+ setSimXR((v) => !v)} /> +
+
+ )} +
); }