Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-21 13:44:59 +00:00
parent f7ad9e0f21
commit c63aa72d99
+49 -27
View File
@@ -393,22 +393,24 @@ function SmartSnapHandler() {
return null;
}
/** Hover detector for auto-detect hole diameter and edge length */
/** Hover detector for auto-detect hole diameter and edge length.
* Runs in BOTH view mode and measure mode. Outside measure mode the label
* auto-hides after 4s; in measure mode it persists until the user clicks
* (registering it as a saved measurement) or moves the mouse significantly. */
function HoverDetector() {
const { camera, scene, gl } = useThree();
const measureMode = useModelStore((s) => s.measureMode);
const setHoverInfo = useModelStore((s) => s.setHoverInfo);
const raycaster = useMemo(() => new THREE.Raycaster(), []);
const mouse = useMemo(() => new THREE.Vector2(), []);
const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastHitKey = useRef('');
useEffect(() => {
if (measureMode) {
setHoverInfo(null);
if (hoverTimer.current) clearTimeout(hoverTimer.current);
return;
}
const clearTimers = () => {
if (hoverTimer.current) { clearTimeout(hoverTimer.current); hoverTimer.current = null; }
if (hideTimer.current) { clearTimeout(hideTimer.current); hideTimer.current = null; }
};
const onMove = (e: MouseEvent) => {
const rect = gl.domElement.getBoundingClientRect();
@@ -429,24 +431,31 @@ function HoverDetector() {
if (!hit || !(hit.object instanceof THREE.Mesh)) {
lastHitKey.current = '';
setHoverInfo(null);
if (hoverTimer.current) clearTimeout(hoverTimer.current);
clearTimers();
return;
}
// Stability check same approximate position for debounce
// Stability check same approximate position for debounce (~3 mm)
const key = `${hit.point.x.toFixed(3)},${hit.point.y.toFixed(3)},${hit.point.z.toFixed(3)}`;
if (key === lastHitKey.current) return; // timer already running
lastHitKey.current = key;
setHoverInfo(null);
clearTimers();
if (hoverTimer.current) clearTimeout(hoverTimer.current);
hoverTimer.current = setTimeout(() => {
if (!(hit.object instanceof THREE.Mesh)) return;
const canvas = gl.domElement;
const canvasSize = { width: canvas.clientWidth, height: canvas.clientHeight };
// Try hole detection first
if (hit.faceIndex !== undefined) {
// 1) Try circle fit from edge vertices around the hit (catches holes on flat faces)
const circ = detectCircularEdgeAtPoint(hit.object, hit.point, camera, canvasSize, 50);
if (circ) {
setHoverInfo({
type: 'hole',
position: [circ.center.x, circ.center.y, circ.center.z],
value: circ.diameterMM,
});
} else if (hit.faceIndex !== undefined && (() => {
const hole = detectHoleAtFace(hit.object, hit.faceIndex);
if (hole) {
setHoverInfo({
@@ -454,29 +463,42 @@ function HoverDetector() {
position: [hole.center.x, hole.center.y, hole.center.z],
value: hole.diameterMM,
});
return true;
}
return false;
})()) {
// handled inside IIFE
} else {
// 3) Edge length
const edge = findNearestEdgeSegment(hit.object, hit.point, camera, canvasSize, 15);
if (edge) {
setHoverInfo({
type: 'edge',
position: [edge.midpoint.x, edge.midpoint.y, edge.midpoint.z],
value: edge.lengthMM,
endpoints: {
a: { x: edge.a.x, y: edge.a.y, z: edge.a.z },
b: { x: edge.b.x, y: edge.b.y, z: edge.b.z },
},
});
} else {
setHoverInfo(null);
return;
}
}
// Try edge detection
const edge = findNearestEdgeSegment(hit.object, hit.point, camera, canvasSize, 15);
if (edge) {
setHoverInfo({
type: 'edge',
position: [edge.midpoint.x, edge.midpoint.y, edge.midpoint.z],
value: edge.lengthMM,
});
return;
// Outside measure mode, auto-hide after 4 s
const inMeasure = useModelStore.getState().measureMode;
if (!inMeasure) {
hideTimer.current = setTimeout(() => setHoverInfo(null), 4000);
}
setHoverInfo(null);
}, 1000);
}, 2000);
};
const onLeave = () => {
lastHitKey.current = '';
setHoverInfo(null);
if (hoverTimer.current) clearTimeout(hoverTimer.current);
clearTimers();
};
gl.domElement.addEventListener('pointermove', onMove);
@@ -484,9 +506,9 @@ function HoverDetector() {
return () => {
gl.domElement.removeEventListener('pointermove', onMove);
gl.domElement.removeEventListener('pointerleave', onLeave);
if (hoverTimer.current) clearTimeout(hoverTimer.current);
clearTimers();
};
}, [measureMode, camera, scene, gl, raycaster, mouse, setHoverInfo]);
}, [camera, scene, gl, raycaster, mouse, setHoverInfo]);
return null;
}