🚀 Auto-deploy: melhoria no snap e medição AR em 01/06/2026 01:00:37
This commit is contained in:
@@ -731,6 +731,7 @@ function SmartSnapHandler() {
|
|||||||
function HoverDetector() {
|
function HoverDetector() {
|
||||||
const { camera, scene, gl } = useThree();
|
const { camera, scene, gl } = useThree();
|
||||||
const setHoverInfo = useModelStore((s) => s.setHoverInfo);
|
const setHoverInfo = useModelStore((s) => s.setHoverInfo);
|
||||||
|
const setHoverIfcProps = useModelStore((s) => s.setHoverIfcProps);
|
||||||
const raycaster = useMemo(() => new THREE.Raycaster(), []);
|
const raycaster = useMemo(() => new THREE.Raycaster(), []);
|
||||||
const mouse = useMemo(() => new THREE.Vector2(), []);
|
const mouse = useMemo(() => new THREE.Vector2(), []);
|
||||||
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
@@ -764,8 +765,8 @@ function HoverDetector() {
|
|||||||
lastHitKey.current = '';
|
lastHitKey.current = '';
|
||||||
lastHitExpressId.current = 0;
|
lastHitExpressId.current = 0;
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
|
setHoverIfcProps(null);
|
||||||
clearTimers();
|
clearTimers();
|
||||||
toast.dismiss('ifc-hover-info');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,40 +779,9 @@ function HoverDetector() {
|
|||||||
const props = elementRoot?.userData?.properties;
|
const props = elementRoot?.userData?.properties;
|
||||||
|
|
||||||
if (props && (props.name || props.material || props.tag)) {
|
if (props && (props.name || props.material || props.tag)) {
|
||||||
const infoLines: string[] = [];
|
setHoverIfcProps(props);
|
||||||
if (props.tag || props.name) {
|
|
||||||
infoLines.push(`Peça: ${props.tag || props.name}`);
|
|
||||||
}
|
|
||||||
if (props.material) {
|
|
||||||
infoLines.push(`Material: ${props.material}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const lengthKey = Object.keys(props).find(k => {
|
|
||||||
const kl = k.toLowerCase();
|
|
||||||
return kl.includes('length') || kl.includes('comprimento') || kl.includes('lengthvalue');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (lengthKey && props[lengthKey]) {
|
|
||||||
infoLines.push(`Comprimento: ${props[lengthKey]}`);
|
|
||||||
} else {
|
} else {
|
||||||
const commonLengthKeys = ['Length', 'Comprimento', 'LengthValue', 'height', 'comprimento'];
|
setHoverIfcProps(null);
|
||||||
for (const key of commonLengthKeys) {
|
|
||||||
if (props[key]) {
|
|
||||||
infoLines.push(`Comprimento: ${props[key]}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoLines.length > 0) {
|
|
||||||
toast(infoLines.join(' | '), {
|
|
||||||
id: 'ifc-hover-info',
|
|
||||||
duration: 8000,
|
|
||||||
position: 'bottom-center',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.dismiss('ifc-hover-info');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,8 +849,8 @@ function HoverDetector() {
|
|||||||
lastHitKey.current = '';
|
lastHitKey.current = '';
|
||||||
lastHitExpressId.current = 0;
|
lastHitExpressId.current = 0;
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
|
setHoverIfcProps(null);
|
||||||
clearTimers();
|
clearTimers();
|
||||||
toast.dismiss('ifc-hover-info');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
gl.domElement.addEventListener('pointermove', onMove);
|
gl.domElement.addEventListener('pointermove', onMove);
|
||||||
@@ -889,9 +859,9 @@ function HoverDetector() {
|
|||||||
gl.domElement.removeEventListener('pointermove', onMove);
|
gl.domElement.removeEventListener('pointermove', onMove);
|
||||||
gl.domElement.removeEventListener('pointerleave', onLeave);
|
gl.domElement.removeEventListener('pointerleave', onLeave);
|
||||||
clearTimers();
|
clearTimers();
|
||||||
toast.dismiss('ifc-hover-info');
|
setHoverIfcProps(null);
|
||||||
};
|
};
|
||||||
}, [camera, scene, gl, raycaster, mouse, setHoverInfo]);
|
}, [camera, scene, gl, raycaster, mouse, setHoverInfo, setHoverIfcProps]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-1
@@ -35,7 +35,8 @@ const Viewer = () => {
|
|||||||
fineTuning,
|
fineTuning,
|
||||||
sectionEnabled,
|
sectionEnabled,
|
||||||
sectionInvert,
|
sectionInvert,
|
||||||
sectionLevel
|
sectionLevel,
|
||||||
|
hoverIfcProps
|
||||||
} = useModelStore();
|
} = useModelStore();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [converting, setConverting] = useState(false);
|
const [converting, setConverting] = useState(false);
|
||||||
@@ -366,6 +367,37 @@ const Viewer = () => {
|
|||||||
{/* 3D Canvas with floating controls */}
|
{/* 3D Canvas with floating controls */}
|
||||||
<div className="relative flex-1 min-w-0" id="canvas-container">
|
<div className="relative flex-1 min-w-0" id="canvas-container">
|
||||||
<ModelViewerCanvas />
|
<ModelViewerCanvas />
|
||||||
|
{hoverIfcProps && (
|
||||||
|
<div
|
||||||
|
className={`absolute left-4 z-40 w-72 rounded-xl border border-primary/20 bg-background/80 p-3.5 shadow-2xl backdrop-blur-md transition-all duration-300 pointer-events-none select-none font-mono text-[11px] ${
|
||||||
|
isFullscreen ? 'top-16' : 'top-4'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-2.5">
|
||||||
|
<div className="flex items-center gap-1.5 border-b border-primary/10 pb-1.5">
|
||||||
|
<span className="h-1.5 w-1.5 rounded-full bg-primary animate-pulse" />
|
||||||
|
<span className="font-semibold text-primary uppercase text-[9px] tracking-wider font-sans">Propriedades IFC</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{Object.entries(hoverIfcProps).map(([key, value]) => {
|
||||||
|
if (!value) return null;
|
||||||
|
let displayKey = key;
|
||||||
|
if (key === 'name') displayKey = 'Nome';
|
||||||
|
else if (key === 'tag') displayKey = 'Marca';
|
||||||
|
else if (key === 'material') displayKey = 'Material';
|
||||||
|
else if (key === 'objectType') displayKey = 'Tipo';
|
||||||
|
else if (key === 'description') displayKey = 'Descrição';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={key} className="flex flex-col gap-0.5">
|
||||||
|
<span className="text-[8px] text-muted-foreground uppercase tracking-widest">{displayKey}</span>
|
||||||
|
<span className="text-foreground font-semibold truncate text-[11px] max-w-[250px]" title={value}>{value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{isFullscreen ? (
|
{isFullscreen ? (
|
||||||
<div className="absolute top-4 left-4 z-50 flex gap-2">
|
<div className="absolute top-4 left-4 z-50 flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -357,6 +357,9 @@ interface ModelStore {
|
|||||||
hoverInfo: HoverInfo | null;
|
hoverInfo: HoverInfo | null;
|
||||||
setHoverInfo: (info: HoverInfo | null) => void;
|
setHoverInfo: (info: HoverInfo | null) => void;
|
||||||
|
|
||||||
|
hoverIfcProps: Record<string, string> | null;
|
||||||
|
setHoverIfcProps: (props: Record<string, string> | null) => void;
|
||||||
|
|
||||||
screenshots: string[];
|
screenshots: string[];
|
||||||
addScreenshot: (dataUrl: string) => void;
|
addScreenshot: (dataUrl: string) => void;
|
||||||
removeScreenshot: (index: number) => void;
|
removeScreenshot: (index: number) => void;
|
||||||
@@ -782,6 +785,9 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
hoverInfo: null,
|
hoverInfo: null,
|
||||||
setHoverInfo: (hoverInfo) => set({ hoverInfo }),
|
setHoverInfo: (hoverInfo) => set({ hoverInfo }),
|
||||||
|
|
||||||
|
hoverIfcProps: null,
|
||||||
|
setHoverIfcProps: (hoverIfcProps) => set({ hoverIfcProps }),
|
||||||
|
|
||||||
screenshots: [],
|
screenshots: [],
|
||||||
addScreenshot: (dataUrl) => set((state) => ({ screenshots: [...state.screenshots, dataUrl] })),
|
addScreenshot: (dataUrl) => set((state) => ({ screenshots: [...state.screenshots, dataUrl] })),
|
||||||
removeScreenshot: (index) => set((state) => ({
|
removeScreenshot: (index) => set((state) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user