🚀 Auto-deploy: melhoria no snap e medição AR em 24/05/2026 02:10:48
This commit is contained in:
@@ -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 (
|
||||
<Html
|
||||
position={position}
|
||||
center
|
||||
distanceFactor={dfDynamic}
|
||||
zIndexRange={[100, 0]}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
display: visible ? 'block' : 'none',
|
||||
opacity: visible ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div className={`rounded bg-card/95 border ${borderClass} px-2 py-0.5 shadow-lg backdrop-blur-sm`}>
|
||||
<span className={`font-mono text-[11px] font-bold ${textClass} whitespace-nowrap`}>
|
||||
{text}
|
||||
</span>
|
||||
</div>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
|
||||
/** 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 && (
|
||||
<MeasurementLabel
|
||||
position={hoverInfo.position}
|
||||
text={hoverInfo.type === 'hole'
|
||||
? `Ø ${hoverInfo.value.toFixed(1)}`
|
||||
: `${hoverInfo.value.toFixed(1)} mm`}
|
||||
variant={hoverInfo.type === 'hole' ? 'accent' : 'primary'}
|
||||
fixed
|
||||
/>
|
||||
)}
|
||||
<HoverLabelOverlay hoverInfo={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<string | null>(null);
|
||||
const [liveViewers, setLiveViewers] = useState(0);
|
||||
|
||||
const rigRef = useRef<THREE.Group>(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,47 +1340,25 @@ function SceneInsideXR() {
|
||||
<XRSnapHandler />
|
||||
<XRGrid />
|
||||
<XRGridAutoFollower />
|
||||
|
||||
{/* Floating HUD overlay - visível apenas fora do headset passthrough */}
|
||||
<Html fullscreen style={{ pointerEvents: 'none', display: isPresenting ? 'none' : 'contents' }}>
|
||||
<div style={{ pointerEvents: 'auto' }}>
|
||||
<XRHud
|
||||
freeMove={freeMove}
|
||||
onToggleFreeMove={() => setFreeMove(!freeMove)}
|
||||
placementMode={placementMode}
|
||||
onTogglePlacement={() => setPlacementMode(!placementMode)}
|
||||
snapToPlanes={snapToPlanes}
|
||||
onToggleSnap={() => setSnapToPlanes(!snapToPlanes)}
|
||||
allowScale={allowScale}
|
||||
onToggleAllowScale={() => setAllowScale(!allowScale)}
|
||||
/>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
{/* Compartilhar botões ocultos para WebRTC e HUD de fora */}
|
||||
<Html style={{ display: 'none' }}>
|
||||
<div>
|
||||
<ShareButton
|
||||
onHandleChange={(h) => setLiveCode(h?.code ?? null)}
|
||||
onViewerCountChange={setLiveViewers}
|
||||
/>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
{/* DevPanel se devkit habilitado */}
|
||||
{devkit && (
|
||||
<Html fullscreen style={{ pointerEvents: 'none' }}>
|
||||
<div style={{ pointerEvents: 'auto' }}>
|
||||
<DevPanel simXR={simXR} onToggleSimXR={() => setSimXR((v) => !v)} />
|
||||
</div>
|
||||
</Html>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
const [placementMode, setPlacementMode] = useState(true);
|
||||
const [snapToPlanes, setSnapToPlanes] = useState(true);
|
||||
const [allowScale, setAllowScale] = useState(true);
|
||||
const [liveCode, setLiveCode] = useState<string | null>(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 (
|
||||
<div className="relative w-full h-full">
|
||||
<Canvas
|
||||
camera={{ position: [2, 2, 2], fov: 50, near: 0.001, far: 1000 }}
|
||||
gl={{ antialias: true, alpha: true, powerPreference: 'high-performance', preserveDrawingBuffer: true }}
|
||||
@@ -1334,8 +1376,60 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
}}
|
||||
>
|
||||
<XR store={xrStore}>
|
||||
<SceneInsideXR />
|
||||
<SceneInsideXR
|
||||
placementMode={placementMode}
|
||||
setPlacementMode={setPlacementMode}
|
||||
snapToPlanes={snapToPlanes}
|
||||
setSnapToPlanes={setSnapToPlanes}
|
||||
allowScale={allowScale}
|
||||
setAllowScale={setAllowScale}
|
||||
liveCode={liveCode}
|
||||
setLiveCode={setLiveCode}
|
||||
liveViewers={liveViewers}
|
||||
setLiveViewers={setLiveViewers}
|
||||
freeMove={freeMove}
|
||||
setFreeMove={setFreeMove}
|
||||
simXR={simXR}
|
||||
setSimXR={setSimXR}
|
||||
onPresentationChange={setIsPresenting}
|
||||
/>
|
||||
</XR>
|
||||
</Canvas>
|
||||
|
||||
{/* Floating HUD overlay - visível apenas fora do headset passthrough */}
|
||||
{effectiveInXR && !isPresenting && (
|
||||
<div className="absolute inset-0 pointer-events-none z-30">
|
||||
<div className="pointer-events-auto">
|
||||
<XRHud
|
||||
freeMove={freeMove}
|
||||
onToggleFreeMove={() => setFreeMove(!freeMove)}
|
||||
placementMode={placementMode}
|
||||
onTogglePlacement={() => setPlacementMode(!placementMode)}
|
||||
snapToPlanes={snapToPlanes}
|
||||
onToggleSnap={() => setSnapToPlanes(!snapToPlanes)}
|
||||
allowScale={allowScale}
|
||||
onToggleAllowScale={() => setAllowScale(!allowScale)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Compartilhar botões ocultos para WebRTC e HUD de fora */}
|
||||
<div style={{ display: 'none' }}>
|
||||
<ShareButton
|
||||
onHandleChange={(h) => setLiveCode(h?.code ?? null)}
|
||||
onViewerCountChange={setLiveViewers}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* DevPanel se devkit habilitado */}
|
||||
{devkit && (
|
||||
<div className="absolute inset-0 pointer-events-none z-40">
|
||||
<div className="pointer-events-auto">
|
||||
<DevPanel simXR={simXR} onToggleSimXR={() => setSimXR((v) => !v)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user