Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -393,22 +393,24 @@ function SmartSnapHandler() {
|
|||||||
return null;
|
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() {
|
function HoverDetector() {
|
||||||
const { camera, scene, gl } = useThree();
|
const { camera, scene, gl } = useThree();
|
||||||
const measureMode = useModelStore((s) => s.measureMode);
|
|
||||||
const setHoverInfo = useModelStore((s) => s.setHoverInfo);
|
const setHoverInfo = useModelStore((s) => s.setHoverInfo);
|
||||||
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);
|
||||||
|
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const lastHitKey = useRef('');
|
const lastHitKey = useRef('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (measureMode) {
|
const clearTimers = () => {
|
||||||
setHoverInfo(null);
|
if (hoverTimer.current) { clearTimeout(hoverTimer.current); hoverTimer.current = null; }
|
||||||
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
if (hideTimer.current) { clearTimeout(hideTimer.current); hideTimer.current = null; }
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const onMove = (e: MouseEvent) => {
|
const onMove = (e: MouseEvent) => {
|
||||||
const rect = gl.domElement.getBoundingClientRect();
|
const rect = gl.domElement.getBoundingClientRect();
|
||||||
@@ -429,24 +431,31 @@ function HoverDetector() {
|
|||||||
if (!hit || !(hit.object instanceof THREE.Mesh)) {
|
if (!hit || !(hit.object instanceof THREE.Mesh)) {
|
||||||
lastHitKey.current = '';
|
lastHitKey.current = '';
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
clearTimers();
|
||||||
return;
|
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)}`;
|
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
|
if (key === lastHitKey.current) return; // timer already running
|
||||||
lastHitKey.current = key;
|
lastHitKey.current = key;
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
|
clearTimers();
|
||||||
|
|
||||||
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
|
||||||
hoverTimer.current = setTimeout(() => {
|
hoverTimer.current = setTimeout(() => {
|
||||||
if (!(hit.object instanceof THREE.Mesh)) return;
|
if (!(hit.object instanceof THREE.Mesh)) return;
|
||||||
const canvas = gl.domElement;
|
const canvas = gl.domElement;
|
||||||
const canvasSize = { width: canvas.clientWidth, height: canvas.clientHeight };
|
const canvasSize = { width: canvas.clientWidth, height: canvas.clientHeight };
|
||||||
|
|
||||||
// Try hole detection first
|
// 1) Try circle fit from edge vertices around the hit (catches holes on flat faces)
|
||||||
if (hit.faceIndex !== undefined) {
|
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);
|
const hole = detectHoleAtFace(hit.object, hit.faceIndex);
|
||||||
if (hole) {
|
if (hole) {
|
||||||
setHoverInfo({
|
setHoverInfo({
|
||||||
@@ -454,29 +463,42 @@ function HoverDetector() {
|
|||||||
position: [hole.center.x, hole.center.y, hole.center.z],
|
position: [hole.center.x, hole.center.y, hole.center.z],
|
||||||
value: hole.diameterMM,
|
value: hole.diameterMM,
|
||||||
});
|
});
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
|
})()) {
|
||||||
// Try edge detection
|
// handled inside IIFE
|
||||||
|
} else {
|
||||||
|
// 3) Edge length
|
||||||
const edge = findNearestEdgeSegment(hit.object, hit.point, camera, canvasSize, 15);
|
const edge = findNearestEdgeSegment(hit.object, hit.point, camera, canvasSize, 15);
|
||||||
if (edge) {
|
if (edge) {
|
||||||
setHoverInfo({
|
setHoverInfo({
|
||||||
type: 'edge',
|
type: 'edge',
|
||||||
position: [edge.midpoint.x, edge.midpoint.y, edge.midpoint.z],
|
position: [edge.midpoint.x, edge.midpoint.y, edge.midpoint.z],
|
||||||
value: edge.lengthMM,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setHoverInfo(null);
|
// Outside measure mode, auto-hide after 4 s
|
||||||
}, 1000);
|
const inMeasure = useModelStore.getState().measureMode;
|
||||||
|
if (!inMeasure) {
|
||||||
|
hideTimer.current = setTimeout(() => setHoverInfo(null), 4000);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLeave = () => {
|
const onLeave = () => {
|
||||||
lastHitKey.current = '';
|
lastHitKey.current = '';
|
||||||
setHoverInfo(null);
|
setHoverInfo(null);
|
||||||
if (hoverTimer.current) clearTimeout(hoverTimer.current);
|
clearTimers();
|
||||||
};
|
};
|
||||||
|
|
||||||
gl.domElement.addEventListener('pointermove', onMove);
|
gl.domElement.addEventListener('pointermove', onMove);
|
||||||
@@ -484,9 +506,9 @@ function HoverDetector() {
|
|||||||
return () => {
|
return () => {
|
||||||
gl.domElement.removeEventListener('pointermove', onMove);
|
gl.domElement.removeEventListener('pointermove', onMove);
|
||||||
gl.domElement.removeEventListener('pointerleave', onLeave);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user