Ajustou cota e estabilizou snap
X-Lovable-Edit-ID: edt-c6d88e59-2709-4850-9df5-a42a0e7fce71 Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -270,7 +270,7 @@ export function findNearestEdgeSegment(
|
|||||||
const hitX = (hitScreen.x * 0.5 + 0.5) * canvasSize.width;
|
const hitX = (hitScreen.x * 0.5 + 0.5) * canvasSize.width;
|
||||||
const hitY = (-hitScreen.y * 0.5 + 0.5) * canvasSize.height;
|
const hitY = (-hitScreen.y * 0.5 + 0.5) * canvasSize.height;
|
||||||
|
|
||||||
const matrix = edgeLines ? edgeLines.matrixWorld.clone().premultiply(mesh.matrixWorld) : mesh.matrixWorld;
|
const matrix = edgeLines ? edgeLines.matrixWorld : mesh.matrixWorld;
|
||||||
|
|
||||||
let bestDist = Infinity;
|
let bestDist = Infinity;
|
||||||
let bestMid: THREE.Vector3 | null = null;
|
let bestMid: THREE.Vector3 | null = null;
|
||||||
@@ -340,7 +340,7 @@ export function detectCircularEdgeAtPoint(
|
|||||||
const posAttr = geo.attributes.position;
|
const posAttr = geo.attributes.position;
|
||||||
if (!posAttr) return null;
|
if (!posAttr) return null;
|
||||||
|
|
||||||
const matrix = edgeLines ? edgeLines.matrixWorld.clone().premultiply(mesh.matrixWorld) : mesh.matrixWorld;
|
const matrix = edgeLines ? edgeLines.matrixWorld : mesh.matrixWorld;
|
||||||
|
|
||||||
const hitScreen = worldPoint.clone().project(camera);
|
const hitScreen = worldPoint.clone().project(camera);
|
||||||
const hitX = (hitScreen.x * 0.5 + 0.5) * canvasSize.width;
|
const hitX = (hitScreen.x * 0.5 + 0.5) * canvasSize.width;
|
||||||
@@ -443,6 +443,16 @@ const _ab = new THREE.Vector3();
|
|||||||
const _ap = new THREE.Vector3();
|
const _ap = new THREE.Vector3();
|
||||||
const _closest = new THREE.Vector3();
|
const _closest = new THREE.Vector3();
|
||||||
|
|
||||||
|
function closestPointOnSegment3D(p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3, target: THREE.Vector3): THREE.Vector3 {
|
||||||
|
_ab.subVectors(b, a);
|
||||||
|
_ap.subVectors(p, a);
|
||||||
|
const abLenSq = _ab.lengthSq();
|
||||||
|
if (abLenSq < 1e-6) return target.copy(a);
|
||||||
|
|
||||||
|
const t = THREE.MathUtils.clamp(_ap.dot(_ab) / abLenSq, 0, 1);
|
||||||
|
return target.copy(a).addScaledVector(_ab, t);
|
||||||
|
}
|
||||||
|
|
||||||
function pointToSegmentDistSq3D(p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3): number {
|
function pointToSegmentDistSq3D(p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3): number {
|
||||||
_ab.subVectors(b, a);
|
_ab.subVectors(b, a);
|
||||||
_ap.subVectors(p, a);
|
_ap.subVectors(p, a);
|
||||||
@@ -503,7 +513,7 @@ export function findNearestEdgeSegment3D(
|
|||||||
mesh: THREE.Mesh,
|
mesh: THREE.Mesh,
|
||||||
worldPoint: THREE.Vector3,
|
worldPoint: THREE.Vector3,
|
||||||
thresholdMeters: number = 0.05
|
thresholdMeters: number = 0.05
|
||||||
): { midpoint: THREE.Vector3; lengthMM: number; a: THREE.Vector3; b: THREE.Vector3 } | null {
|
): { midpoint: THREE.Vector3; snapPoint: THREE.Vector3; lengthMM: number; a: THREE.Vector3; b: THREE.Vector3 } | null {
|
||||||
let edgeLines: THREE.LineSegments | null = null;
|
let edgeLines: THREE.LineSegments | null = null;
|
||||||
mesh.children.forEach(c => {
|
mesh.children.forEach(c => {
|
||||||
if (c.userData.__edgeLine && c instanceof THREE.LineSegments) {
|
if (c.userData.__edgeLine && c instanceof THREE.LineSegments) {
|
||||||
@@ -523,12 +533,13 @@ export function findNearestEdgeSegment3D(
|
|||||||
const posAttr = geo.attributes.position;
|
const posAttr = geo.attributes.position;
|
||||||
if (!posAttr) return null;
|
if (!posAttr) return null;
|
||||||
|
|
||||||
const matrix = edgeLines ? edgeLines.matrixWorld.clone().premultiply(mesh.matrixWorld) : mesh.matrixWorld;
|
const matrix = edgeLines ? edgeLines.matrixWorld : mesh.matrixWorld;
|
||||||
const invMatrix = new THREE.Matrix4().copy(matrix).invert();
|
const invMatrix = new THREE.Matrix4().copy(matrix).invert();
|
||||||
const localPoint = worldPoint.clone().applyMatrix4(invMatrix);
|
const localPoint = worldPoint.clone().applyMatrix4(invMatrix);
|
||||||
|
|
||||||
let bestDistSq = Infinity;
|
let bestDistSq = Infinity;
|
||||||
let bestIndex = -1;
|
let bestIndex = -1;
|
||||||
|
const bestClosestLocal = new THREE.Vector3();
|
||||||
const thresholdSq = thresholdMeters * thresholdMeters;
|
const thresholdSq = thresholdMeters * thresholdMeters;
|
||||||
|
|
||||||
const segCount = posAttr.count / 2;
|
const segCount = posAttr.count / 2;
|
||||||
@@ -539,11 +550,13 @@ export function findNearestEdgeSegment3D(
|
|||||||
aLocal.fromBufferAttribute(posAttr, i * 2);
|
aLocal.fromBufferAttribute(posAttr, i * 2);
|
||||||
bLocal.fromBufferAttribute(posAttr, i * 2 + 1);
|
bLocal.fromBufferAttribute(posAttr, i * 2 + 1);
|
||||||
|
|
||||||
const distSq = pointToSegmentDistSq3D(localPoint, aLocal, bLocal);
|
closestPointOnSegment3D(localPoint, aLocal, bLocal, _closest);
|
||||||
|
const distSq = localPoint.distanceToSquared(_closest);
|
||||||
|
|
||||||
if (distSq < bestDistSq) {
|
if (distSq < bestDistSq) {
|
||||||
bestDistSq = distSq;
|
bestDistSq = distSq;
|
||||||
bestIndex = i;
|
bestIndex = i;
|
||||||
|
bestClosestLocal.copy(_closest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,8 +564,9 @@ export function findNearestEdgeSegment3D(
|
|||||||
const a = new THREE.Vector3().fromBufferAttribute(posAttr, bestIndex * 2).applyMatrix4(matrix);
|
const a = new THREE.Vector3().fromBufferAttribute(posAttr, bestIndex * 2).applyMatrix4(matrix);
|
||||||
const b = new THREE.Vector3().fromBufferAttribute(posAttr, bestIndex * 2 + 1).applyMatrix4(matrix);
|
const b = new THREE.Vector3().fromBufferAttribute(posAttr, bestIndex * 2 + 1).applyMatrix4(matrix);
|
||||||
const midpoint = a.clone().add(b).multiplyScalar(0.5);
|
const midpoint = a.clone().add(b).multiplyScalar(0.5);
|
||||||
|
const snapPoint = bestClosestLocal.clone().applyMatrix4(matrix);
|
||||||
const lengthMM = a.distanceTo(b) * 1000;
|
const lengthMM = a.distanceTo(b) * 1000;
|
||||||
return { midpoint, lengthMM, a, b };
|
return { midpoint, snapPoint, lengthMM, a, b };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -579,7 +593,7 @@ export function detectCircularEdgeAtPoint3D(
|
|||||||
const posAttr = geo.attributes.position;
|
const posAttr = geo.attributes.position;
|
||||||
if (!posAttr) return null;
|
if (!posAttr) return null;
|
||||||
|
|
||||||
const matrix = edgeLines ? edgeLines.matrixWorld.clone().premultiply(mesh.matrixWorld) : mesh.matrixWorld;
|
const matrix = edgeLines ? edgeLines.matrixWorld : mesh.matrixWorld;
|
||||||
const invMatrix = new THREE.Matrix4().copy(matrix).invert();
|
const invMatrix = new THREE.Matrix4().copy(matrix).invert();
|
||||||
const localPoint = worldPoint.clone().applyMatrix4(invMatrix);
|
const localPoint = worldPoint.clone().applyMatrix4(invMatrix);
|
||||||
|
|
||||||
@@ -652,6 +666,6 @@ export function resolveSnap3D(
|
|||||||
const v = findNearestVertex3D(mesh, hitPoint, vertexThresholdMeters);
|
const v = findNearestVertex3D(mesh, hitPoint, vertexThresholdMeters);
|
||||||
if (v) return { point: v, type: 'vertex' };
|
if (v) return { point: v, type: 'vertex' };
|
||||||
const e = findNearestEdgeSegment3D(mesh, hitPoint, edgeThresholdMeters);
|
const e = findNearestEdgeSegment3D(mesh, hitPoint, edgeThresholdMeters);
|
||||||
if (e) return { point: e.midpoint, type: 'edge' };
|
if (e) return { point: e.snapPoint, type: 'edge' };
|
||||||
return { point: hitPoint.clone(), type: 'surface' };
|
return { point: hitPoint.clone(), type: 'surface' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ function isPickableModelMesh(o: THREE.Object3D): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getModelIdFromObject(o: THREE.Object3D): string | undefined {
|
||||||
|
let cur: THREE.Object3D | null = o;
|
||||||
|
while (cur) {
|
||||||
|
if (cur.userData?.modelId) return cur.userData.modelId as string;
|
||||||
|
cur = cur.parent;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Right-controller trigger driven measurement for AR.
|
* Right-controller trigger driven measurement for AR.
|
||||||
*
|
*
|
||||||
@@ -60,6 +69,7 @@ export function XRControllerMeasure() {
|
|||||||
);
|
);
|
||||||
const laserMat = useRef(new THREE.LineBasicMaterial({ color: '#22c55e', transparent: true, opacity: 0.7, depthTest: false }));
|
const laserMat = useRef(new THREE.LineBasicMaterial({ color: '#22c55e', transparent: true, opacity: 0.7, depthTest: false }));
|
||||||
const tipColor = useRef(new THREE.Color('#22c55e'));
|
const tipColor = useRef(new THREE.Color('#22c55e'));
|
||||||
|
const lockedSnap = useRef<{ point: THREE.Vector3; kind: 'vertex' | 'edge' | 'hole'; modelId?: string; lastSeen: number } | null>(null);
|
||||||
|
|
||||||
// Dwell detection for hover-based smart measurement (1 s)
|
// Dwell detection for hover-based smart measurement (1 s)
|
||||||
const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity));
|
const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity));
|
||||||
@@ -118,11 +128,14 @@ export function XRControllerMeasure() {
|
|||||||
|
|
||||||
let snappedPoint: THREE.Vector3 | null = null;
|
let snappedPoint: THREE.Vector3 | null = null;
|
||||||
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
|
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
|
||||||
let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
|
let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; modelId?: string; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
|
||||||
|
let hitModelId: string | undefined;
|
||||||
|
const nowT = performance.now();
|
||||||
|
|
||||||
const hits = raycaster.current.intersectObjects(scene.children, true);
|
const hits = raycaster.current.intersectObjects(scene.children, true);
|
||||||
const hit = hits.find((h) => {
|
const hit = hits.find((h) => {
|
||||||
const o = h.object;
|
const o = h.object;
|
||||||
|
if (!(o instanceof THREE.Mesh)) return false;
|
||||||
if (!isPickableModelMesh(o)) return false;
|
if (!isPickableModelMesh(o)) return false;
|
||||||
if (o.userData.__edgeLine) return false;
|
if (o.userData.__edgeLine) return false;
|
||||||
if (o.geometry instanceof THREE.SphereGeometry) return false;
|
if (o.geometry instanceof THREE.SphereGeometry) return false;
|
||||||
@@ -132,9 +145,10 @@ export function XRControllerMeasure() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (hit && hit.object instanceof THREE.Mesh) {
|
if (hit && hit.object instanceof THREE.Mesh) {
|
||||||
|
hitModelId = getModelIdFromObject(hit.object);
|
||||||
const dist = tmpOrigin.current.distanceTo(hit.point);
|
const dist = tmpOrigin.current.distanceTo(hit.point);
|
||||||
const vertexThreshold = Math.max(0.06, dist * 0.08);
|
const vertexThreshold = Math.max(0.08, dist * 0.12);
|
||||||
const edgeThreshold = Math.max(0.08, dist * 0.10);
|
const edgeThreshold = Math.max(0.10, dist * 0.14);
|
||||||
|
|
||||||
// Snap to existing registered hole centers first
|
// Snap to existing registered hole centers first
|
||||||
const existing = useModelStore.getState().measurements;
|
const existing = useModelStore.getState().measurements;
|
||||||
@@ -158,7 +172,7 @@ export function XRControllerMeasure() {
|
|||||||
if (circle) {
|
if (circle) {
|
||||||
snappedPoint = circle.center;
|
snappedPoint = circle.center;
|
||||||
snapKind = 'hole';
|
snapKind = 'hole';
|
||||||
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center };
|
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center, modelId: hitModelId };
|
||||||
} else {
|
} else {
|
||||||
const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold);
|
const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold);
|
||||||
snappedPoint = snap.point;
|
snappedPoint = snap.point;
|
||||||
@@ -167,7 +181,7 @@ export function XRControllerMeasure() {
|
|||||||
const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold);
|
const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold);
|
||||||
if (seg) {
|
if (seg) {
|
||||||
const lenMM = seg.a.distanceTo(seg.b) * 1000;
|
const lenMM = seg.a.distanceTo(seg.b) * 1000;
|
||||||
hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } };
|
hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, modelId: hitModelId, endpoints: { a: seg.a, b: seg.b } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,9 +191,27 @@ export function XRControllerMeasure() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const strongSnapKind = snapKind === 'vertex' || snapKind === 'edge' || snapKind === 'hole' ? snapKind : null;
|
||||||
|
if (snappedPoint && strongSnapKind) {
|
||||||
|
const locked = lockedSnap.current;
|
||||||
|
const sameTarget = locked && locked.kind === strongSnapKind && locked.modelId === hitModelId;
|
||||||
|
if (sameTarget && locked.point.distanceTo(snappedPoint) < (snapKind === 'edge' ? 0.08 : 0.12)) {
|
||||||
|
if (snapKind === 'edge') locked.point.lerp(snappedPoint, 0.25);
|
||||||
|
snappedPoint = locked.point.clone();
|
||||||
|
locked.lastSeen = nowT;
|
||||||
|
} else {
|
||||||
|
lockedSnap.current = { point: snappedPoint.clone(), kind: strongSnapKind, modelId: hitModelId, lastSeen: nowT };
|
||||||
|
}
|
||||||
|
} else if (snappedPoint && lockedSnap.current && lockedSnap.current.point.distanceTo(snappedPoint) < 0.12 && nowT - lockedSnap.current.lastSeen < 600) {
|
||||||
|
snapKind = lockedSnap.current.kind;
|
||||||
|
snappedPoint = lockedSnap.current.point.clone();
|
||||||
|
lockedSnap.current.lastSeen = nowT;
|
||||||
|
} else if (!snappedPoint || (lockedSnap.current && nowT - lockedSnap.current.lastSeen > 600)) {
|
||||||
|
lockedSnap.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
|
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
|
||||||
const measureModeNow = useModelStore.getState().measureMode;
|
const measureModeNow = useModelStore.getState().measureMode;
|
||||||
const nowT = performance.now();
|
|
||||||
if (snappedPoint && hoverDetected) {
|
if (snappedPoint && hoverDetected) {
|
||||||
const dist = dwellPos.current.distanceTo(snappedPoint);
|
const dist = dwellPos.current.distanceTo(snappedPoint);
|
||||||
if (dist > 0.005) {
|
if (dist > 0.005) {
|
||||||
@@ -197,6 +229,7 @@ export function XRControllerMeasure() {
|
|||||||
a: { x: hoverDetected.endpoints.a.x, y: hoverDetected.endpoints.a.y, z: hoverDetected.endpoints.a.z },
|
a: { x: hoverDetected.endpoints.a.x, y: hoverDetected.endpoints.a.y, z: hoverDetected.endpoints.a.z },
|
||||||
b: { x: hoverDetected.endpoints.b.x, y: hoverDetected.endpoints.b.y, z: hoverDetected.endpoints.b.z },
|
b: { x: hoverDetected.endpoints.b.x, y: hoverDetected.endpoints.b.y, z: hoverDetected.endpoints.b.z },
|
||||||
},
|
},
|
||||||
|
modelId: hoverDetected.modelId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +279,7 @@ export function XRControllerMeasure() {
|
|||||||
const triggerHits = raycaster.current.intersectObjects(scene.children, true);
|
const triggerHits = raycaster.current.intersectObjects(scene.children, true);
|
||||||
const triggerHit = triggerHits.find((h) => {
|
const triggerHit = triggerHits.find((h) => {
|
||||||
const o = h.object;
|
const o = h.object;
|
||||||
|
if (!(o instanceof THREE.Mesh)) return false;
|
||||||
if (!isPickableModelMesh(o)) return false;
|
if (!isPickableModelMesh(o)) return false;
|
||||||
if (o.userData.__edgeLine) return false;
|
if (o.userData.__edgeLine) return false;
|
||||||
if (o.geometry instanceof THREE.SphereGeometry) return false;
|
if (o.geometry instanceof THREE.SphereGeometry) return false;
|
||||||
@@ -273,6 +307,7 @@ export function XRControllerMeasure() {
|
|||||||
sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint);
|
sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint);
|
||||||
st.addMeasurePoint({
|
st.addMeasurePoint({
|
||||||
x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
|
x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
|
||||||
|
modelId: hitModelId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (trigState.current && trigVal < TRIG_OFF) {
|
} else if (trigState.current && trigVal < TRIG_OFF) {
|
||||||
|
|||||||
@@ -33,3 +33,25 @@ export function worldToModelLocal(
|
|||||||
g.worldToLocal(v);
|
g.worldToLocal(v);
|
||||||
return { x: v.x, y: v.y, z: v.z };
|
return { x: v.x, y: v.y, z: v.z };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function modelLocalToWorld(
|
||||||
|
modelId: string | null | undefined,
|
||||||
|
p: { x: number; y: number; z: number },
|
||||||
|
): { x: number; y: number; z: number } | null {
|
||||||
|
const g = getModelLocalGroup(modelId);
|
||||||
|
if (!g) return null;
|
||||||
|
g.updateWorldMatrix(true, false);
|
||||||
|
const v = new THREE.Vector3(p.x, p.y, p.z);
|
||||||
|
g.localToWorld(v);
|
||||||
|
return { x: v.x, y: v.y, z: v.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelWorldScaleFactor(modelId: string | null | undefined): number {
|
||||||
|
const g = getModelLocalGroup(modelId);
|
||||||
|
if (!g) return 1;
|
||||||
|
g.updateWorldMatrix(true, false);
|
||||||
|
const s = new THREE.Vector3();
|
||||||
|
g.getWorldScale(s);
|
||||||
|
const factor = (Math.abs(s.x) + Math.abs(s.y) + Math.abs(s.z)) / 3;
|
||||||
|
return Number.isFinite(factor) && factor > 1e-6 ? factor : 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -383,15 +383,13 @@ export function XRSafeLine({ a, b }: { a: [number, number, number]; b: [number,
|
|||||||
new THREE.Vector3(b[0], b[1], b[2])
|
new THREE.Vector3(b[0], b[1], b[2])
|
||||||
], [a, b]);
|
], [a, b]);
|
||||||
|
|
||||||
const geometry = useMemo(() => {
|
const line = useMemo(() => {
|
||||||
return new THREE.BufferGeometry().setFromPoints(points);
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||||
|
const material = new THREE.LineBasicMaterial({ color: '#22c55e' });
|
||||||
|
return new THREE.Line(geometry, material);
|
||||||
}, [points]);
|
}, [points]);
|
||||||
|
|
||||||
return (
|
return <primitive object={line} />;
|
||||||
<line geometry={geometry}>
|
|
||||||
<lineBasicMaterial color="#22c55e" linewidth={2} />
|
|
||||||
</line>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Componente para renderizar medições que pertencem a um modelo específico
|
// Componente para renderizar medições que pertencem a um modelo específico
|
||||||
|
|||||||
+28
-19
@@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { worldToModelLocal } from '@/lib/modelTransforms';
|
import { getModelWorldScaleFactor, worldToModelLocal } from '@/lib/modelTransforms';
|
||||||
import { sendRemoteLog } from '@/lib/remoteLogger';
|
import { sendRemoteLog } from '@/lib/remoteLogger';
|
||||||
|
|
||||||
// ── Persistência de placement (fineTuning + escala) e flags WebXR ─────
|
// ── Persistência de placement (fineTuning + escala) e flags WebXR ─────
|
||||||
@@ -133,6 +133,8 @@ export interface MeasurePoint {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
|
/** Model hit by this point, used so saved dimensions attach to the right piece. */
|
||||||
|
modelId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Measurement {
|
export interface Measurement {
|
||||||
@@ -153,6 +155,7 @@ export interface HoverInfo {
|
|||||||
type: 'hole' | 'edge';
|
type: 'hole' | 'edge';
|
||||||
position: [number, number, number];
|
position: [number, number, number];
|
||||||
value: number; // mm (diameter or length)
|
value: number; // mm (diameter or length)
|
||||||
|
modelId?: string;
|
||||||
/** For edges: the two endpoints in world coords. */
|
/** For edges: the two endpoints in world coords. */
|
||||||
endpoints?: { a: MeasurePoint; b: MeasurePoint };
|
endpoints?: { a: MeasurePoint; b: MeasurePoint };
|
||||||
}
|
}
|
||||||
@@ -558,27 +561,25 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
const points = [...state.measurePoints, p];
|
const points = [...state.measurePoints, p];
|
||||||
if (points.length === 2) {
|
if (points.length === 2) {
|
||||||
const [a, b] = points;
|
const [a, b] = points;
|
||||||
const dx = (a.x - b.x) * 1000;
|
const modelId = (a.modelId && a.modelId === b.modelId ? a.modelId : state.activeModelId) ?? undefined;
|
||||||
const dy = (a.y - b.y) * 1000;
|
|
||||||
const dz = (a.z - b.z) * 1000;
|
|
||||||
const distanceWorldMM = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
||||||
const factor = state.scaleRatio?.factor ?? 1;
|
|
||||||
const distanceMM = distanceWorldMM / factor;
|
|
||||||
const modelId = state.activeModelId ?? undefined;
|
|
||||||
const aConv = modelId ? worldToModelLocal(modelId, a) : null;
|
const aConv = modelId ? worldToModelLocal(modelId, a) : null;
|
||||||
const bConv = modelId ? worldToModelLocal(modelId, b) : null;
|
const bConv = modelId ? worldToModelLocal(modelId, b) : null;
|
||||||
const attached = !!(aConv && bConv);
|
const attached = !!(aConv && bConv);
|
||||||
|
const pa = attached ? aConv! : a;
|
||||||
|
const pb = attached ? bConv! : b;
|
||||||
|
const dx = (pa.x - pb.x) * 1000;
|
||||||
|
const dy = (pa.y - pb.y) * 1000;
|
||||||
|
const dz = (pa.z - pb.z) * 1000;
|
||||||
|
const distanceMM = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
sendRemoteLog('info', `addMeasurePoint: calculando distância`, {
|
sendRemoteLog('info', `addMeasurePoint: calculando distância`, {
|
||||||
distanceWorldMM,
|
|
||||||
distanceMM,
|
distanceMM,
|
||||||
factor,
|
|
||||||
modelId,
|
modelId,
|
||||||
attached
|
attached
|
||||||
});
|
});
|
||||||
const measurement: Measurement = {
|
const measurement: Measurement = {
|
||||||
id: `m_${Date.now()}`,
|
id: `m_${Date.now()}`,
|
||||||
pointA: attached ? aConv! : a,
|
pointA: pa,
|
||||||
pointB: attached ? bConv! : b,
|
pointB: pb,
|
||||||
distanceMM,
|
distanceMM,
|
||||||
modelId: attached ? modelId : undefined,
|
modelId: attached ? modelId : undefined,
|
||||||
};
|
};
|
||||||
@@ -602,7 +603,8 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
return { measurements: state.measurements.slice(0, -1) };
|
return { measurements: state.measurements.slice(0, -1) };
|
||||||
}),
|
}),
|
||||||
registerHoverMeasurement: (info) => set((state) => {
|
registerHoverMeasurement: (info) => set((state) => {
|
||||||
const modelId = state.activeModelId ?? undefined;
|
const modelId = info.modelId ?? state.activeModelId ?? undefined;
|
||||||
|
const modelScale = modelId ? getModelWorldScaleFactor(modelId) : 1;
|
||||||
const toLocal = (p: MeasurePoint): { point: MeasurePoint; attached: boolean } => {
|
const toLocal = (p: MeasurePoint): { point: MeasurePoint; attached: boolean } => {
|
||||||
const conv = modelId ? worldToModelLocal(modelId, p) : null;
|
const conv = modelId ? worldToModelLocal(modelId, p) : null;
|
||||||
return conv ? { point: conv, attached: true } : { point: p, attached: false };
|
return conv ? { point: conv, attached: true } : { point: p, attached: false };
|
||||||
@@ -613,13 +615,14 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
const cw: MeasurePoint = { x: info.position[0], y: info.position[1], z: info.position[2] };
|
const cw: MeasurePoint = { x: info.position[0], y: info.position[1], z: info.position[2] };
|
||||||
const c = toLocal(cw);
|
const c = toLocal(cw);
|
||||||
attached = c.attached;
|
attached = c.attached;
|
||||||
|
const valueMM = attached ? info.value / modelScale : info.value;
|
||||||
measurement = {
|
measurement = {
|
||||||
id: `m_${Date.now()}`,
|
id: `m_${Date.now()}`,
|
||||||
pointA: c.point,
|
pointA: c.point,
|
||||||
pointB: c.point,
|
pointB: c.point,
|
||||||
distanceMM: info.value,
|
distanceMM: valueMM,
|
||||||
kind: 'hole',
|
kind: 'hole',
|
||||||
label: `Ø ${info.value.toFixed(1)}`,
|
label: `Ø ${valueMM.toFixed(1)}`,
|
||||||
modelId: attached ? modelId : undefined,
|
modelId: attached ? modelId : undefined,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -628,13 +631,19 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
const a = toLocal(aw);
|
const a = toLocal(aw);
|
||||||
const b = toLocal(bw);
|
const b = toLocal(bw);
|
||||||
attached = a.attached && b.attached;
|
attached = a.attached && b.attached;
|
||||||
|
const pa = attached ? a.point : aw;
|
||||||
|
const pb = attached ? b.point : bw;
|
||||||
|
const dx = (pa.x - pb.x) * 1000;
|
||||||
|
const dy = (pa.y - pb.y) * 1000;
|
||||||
|
const dz = (pa.z - pb.z) * 1000;
|
||||||
|
const valueMM = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
measurement = {
|
measurement = {
|
||||||
id: `m_${Date.now()}`,
|
id: `m_${Date.now()}`,
|
||||||
pointA: attached ? a.point : aw,
|
pointA: pa,
|
||||||
pointB: attached ? b.point : bw,
|
pointB: pb,
|
||||||
distanceMM: info.value,
|
distanceMM: valueMM,
|
||||||
kind: 'edge',
|
kind: 'edge',
|
||||||
label: `${info.value.toFixed(1)} mm`,
|
label: `${valueMM.toFixed(1)} mm`,
|
||||||
modelId: attached ? modelId : undefined,
|
modelId: attached ? modelId : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user