🚀 Auto-deploy: melhoria no snap e medição AR em 01/06/2026 01:00:37

This commit is contained in:
2026-06-01 01:00:37 +00:00
parent b47fb872b5
commit 0126d8d78c
3 changed files with 46 additions and 38 deletions
+7 -37
View File
@@ -731,6 +731,7 @@ function SmartSnapHandler() {
function HoverDetector() {
const { camera, scene, gl } = useThree();
const setHoverInfo = useModelStore((s) => s.setHoverInfo);
const setHoverIfcProps = useModelStore((s) => s.setHoverIfcProps);
const raycaster = useMemo(() => new THREE.Raycaster(), []);
const mouse = useMemo(() => new THREE.Vector2(), []);
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -764,8 +765,8 @@ function HoverDetector() {
lastHitKey.current = '';
lastHitExpressId.current = 0;
setHoverInfo(null);
setHoverIfcProps(null);
clearTimers();
toast.dismiss('ifc-hover-info');
return;
}
@@ -778,40 +779,9 @@ function HoverDetector() {
const props = elementRoot?.userData?.properties;
if (props && (props.name || props.material || props.tag)) {
const infoLines: string[] = [];
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]}`);
setHoverIfcProps(props);
} else {
const commonLengthKeys = ['Length', 'Comprimento', 'LengthValue', 'height', 'comprimento'];
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');
setHoverIfcProps(null);
}
}
@@ -879,8 +849,8 @@ function HoverDetector() {
lastHitKey.current = '';
lastHitExpressId.current = 0;
setHoverInfo(null);
setHoverIfcProps(null);
clearTimers();
toast.dismiss('ifc-hover-info');
};
gl.domElement.addEventListener('pointermove', onMove);
@@ -889,9 +859,9 @@ function HoverDetector() {
gl.domElement.removeEventListener('pointermove', onMove);
gl.domElement.removeEventListener('pointerleave', onLeave);
clearTimers();
toast.dismiss('ifc-hover-info');
setHoverIfcProps(null);
};
}, [camera, scene, gl, raycaster, mouse, setHoverInfo]);
}, [camera, scene, gl, raycaster, mouse, setHoverInfo, setHoverIfcProps]);
return null;
}
+33 -1
View File
@@ -35,7 +35,8 @@ const Viewer = () => {
fineTuning,
sectionEnabled,
sectionInvert,
sectionLevel
sectionLevel,
hoverIfcProps
} = useModelStore();
const fileInputRef = useRef<HTMLInputElement>(null);
const [converting, setConverting] = useState(false);
@@ -366,6 +367,37 @@ const Viewer = () => {
{/* 3D Canvas with floating controls */}
<div className="relative flex-1 min-w-0" id="canvas-container">
<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 ? (
<div className="absolute top-4 left-4 z-50 flex gap-2">
<Button
+6
View File
@@ -357,6 +357,9 @@ interface ModelStore {
hoverInfo: HoverInfo | null;
setHoverInfo: (info: HoverInfo | null) => void;
hoverIfcProps: Record<string, string> | null;
setHoverIfcProps: (props: Record<string, string> | null) => void;
screenshots: string[];
addScreenshot: (dataUrl: string) => void;
removeScreenshot: (index: number) => void;
@@ -782,6 +785,9 @@ export const useModelStore = create<ModelStore>((set, get) => ({
hoverInfo: null,
setHoverInfo: (hoverInfo) => set({ hoverInfo }),
hoverIfcProps: null,
setHoverIfcProps: (hoverIfcProps) => set({ hoverIfcProps }),
screenshots: [],
addScreenshot: (dataUrl) => set((state) => ({ screenshots: [...state.screenshots, dataUrl] })),
removeScreenshot: (index) => set((state) => ({